JavaScript中小數(shù)點(diǎn)精度丟失的原因以及解決方法
一、為什么會(huì)出現(xiàn)計(jì)算精度丟失的問(wèn)題?
JavaScript中存在小數(shù)點(diǎn)精度丟失的問(wèn)題是由于其使用的浮點(diǎn)數(shù)表示方式。JavaScript采用的是雙精度浮點(diǎn)數(shù)表示法,也稱為IEEE 754標(biāo)準(zhǔn),它使用64位來(lái)表示一個(gè)數(shù)字,其中52位用于表示有效數(shù)字,而其他位用于表示符號(hào)、指數(shù)和特殊情況。
由于使用有限的位數(shù)來(lái)表示無(wú)限的小數(shù),JavaScript無(wú)法準(zhǔn)確地表示某些小數(shù)。其中一個(gè)典型的示例是0.1,它在二進(jìn)制中是一個(gè)無(wú)限循環(huán)的小數(shù)。當(dāng)我們將0.1這樣的小數(shù)轉(zhuǎn)換為二進(jìn)制進(jìn)行存儲(chǔ)時(shí),存在近似表示的誤差,因此在進(jìn)行計(jì)算時(shí)會(huì)出現(xiàn)精度丟失的問(wèn)題。
例如,執(zhí)行以下計(jì)算:
0.1 + 0.2
預(yù)期的結(jié)果應(yīng)該是0.3,但在JavaScript中實(shí)際得到的結(jié)果是0.30000000000000004。這是因?yàn)?.1和0.2無(wú)法以精確的二進(jìn)制形式表示,導(dǎo)致小數(shù)點(diǎn)精度丟失。
這種精度丟失是浮點(diǎn)數(shù)表示法的固有特性,并不僅限于JavaScript,其他編程語(yǔ)言也可能面臨類似的問(wèn)題。因此,在進(jìn)行精確計(jì)算時(shí),特別是涉及到貨幣、金融等方面的計(jì)算,應(yīng)使用其它精確計(jì)算的方法,例如使用整數(shù)進(jìn)行運(yùn)算或使用專門的精確計(jì)算庫(kù)。
總而言之,小數(shù)點(diǎn)精度丟失的問(wèn)題是由于JavaScript使用的浮點(diǎn)數(shù)表示法的特性所致,需要在開(kāi)發(fā)中注意,并考慮使用其他方法或工具來(lái)解決精度問(wèn)題。
二、代碼示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script> var floatObj = function () { // 判斷傳入的值-是否為整數(shù) function isInteger(obj) { return Math.floor(obj) === obj } // 將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)成整數(shù),返回整數(shù)和倍數(shù)。如 3.14 >> 314,倍數(shù)是 100 // @param floatNum { number } 小數(shù) // @return { object } // { times: 100, num: 314 } // 用于返回整數(shù)和倍數(shù) function toInteger(floatNum) { // 聲明一個(gè)對(duì)象用來(lái)保存倍數(shù)和整數(shù) var ret = { times: 1, num: 0 } // 第一種情況:是整數(shù) if (isInteger(floatNum)) { // 把整數(shù)給 ret中的 num ret.num = floatNum return ret // 最后返回 ret } // 第二種情況-不是整數(shù), var strfi = floatNum + '' // 轉(zhuǎn)為字符串 "0.1" var dotPos = strfi.indexOf('.') // 查詢小數(shù)點(diǎn) var len = strfi.substr(dotPos + 1).length; // 獲取小數(shù)點(diǎn)后的長(zhǎng)度 var times = Math.pow(10, len) // 放大多少倍 var intNum = Number(floatNum.toString().replace('.', '')) // 返回 轉(zhuǎn)為字符串 截取掉小數(shù)點(diǎn) 最后轉(zhuǎn)為數(shù)字(整數(shù)) // 把獲取到的倍數(shù)和整數(shù)存入對(duì)象中 ret.times = times ret.num = intNum return ret } // 核心方法,實(shí)現(xiàn)加減乘除運(yùn)算,確保不丟失精度 // 思路:把小數(shù)放大為整數(shù)(乘),進(jìn)行算術(shù)運(yùn)算,再縮小為小數(shù)(除) // @param a { number } 運(yùn)算數(shù)1 // @param b { number } 運(yùn)算數(shù)2 // @param digits { number } 精度,保留的小數(shù)點(diǎn)數(shù),比如 2, 即保留為兩位小數(shù) // @param op { string } 運(yùn)算類型,有加減乘除(add / subtract / multiply / divide) function operation(a, b, digits, op) { // 獲取倍數(shù)和整數(shù)的對(duì)象 var o1 = toInteger(a) var o2 = toInteger(b) // 提取整數(shù) var n1 = o1.num var n2 = o2.num // 提取倍數(shù) var t1 = o1.times var t2 = o2.times // 獲取最大倍數(shù) var max = t1 > t2 ? t1 : t2 var result = null // switch (op) { case 'add': if (t1 === t2) { // 兩個(gè)小數(shù)位數(shù)相同 result = n1 + n2 // } else if (t1 > t2) { // o1 小數(shù)位 大于 o2 result = n1 + n2 * (t1 / t2) } else { // o1 小數(shù)位 小于 o2 result = n1 * (t2 / t1) + n2 } return result / max case 'subtract': if (t1 === t2) { result = n1 - n2 } else if (t1 > t2) { result = n1 - n2 * (t1 / t2) } else { result = n1 * (t2 / t1) - n2 } return result / max case 'multiply': result = (n1 * n2) / (t1 * t2) return result case 'divide': result = (n1 / n2) * (t2 / t1) return result } } // 加減乘除的四個(gè)接口 function add(a, b, digits) { return operation(a, b, digits, 'add') } function subtract(a, b, digits) { return operation(a, b, digits, 'subtract') } function multiply(a, b, digits) { return operation(a, b, digits, 'multiply') } function divide(a, b, digits) { return operation(a, b, digits, 'divide') } return { add: add, subtract: subtract, multiply: multiply, divide: divide } }(); // console.log(floatObj.add(0.5, 0.2)) console.log(floatObj.add(0.12, 0.3)) </script> </head> <body> </body> </html>
三、解決思路
解決方法:
1.首先封裝一個(gè)函數(shù),這個(gè)函數(shù)是用來(lái)判斷單價(jià)是否為一個(gè)整數(shù)。
2.然后在封裝第二個(gè)函數(shù),這個(gè)函數(shù)的作用是返回整數(shù)和倍數(shù)的。
同時(shí)聲明一個(gè)對(duì)象ret來(lái)保存這個(gè)倍數(shù)和整數(shù),
第一種情況就是單價(jià)傳過(guò)來(lái)時(shí)就是整數(shù),那么他的倍數(shù)也就為1, 所以我們可以直接存儲(chǔ)到聲明的對(duì)象中
第二種情況就是當(dāng)單價(jià)傳過(guò)來(lái)時(shí)是浮點(diǎn)數(shù),這時(shí)候我們就要做處理,
(1)、將傳遞過(guò)來(lái)的參數(shù)轉(zhuǎn)為字符串(隱式轉(zhuǎn)換),
(2)、使用indexOf找到小數(shù)點(diǎn)'.',
(3)、使用substr字符串方法截取小數(shù)點(diǎn)后的長(zhǎng)度length,
(4)、聲明一個(gè)為倍數(shù)的變量Time,同時(shí)使用Math.pow(10, 上一步截取的長(zhǎng)度), 這個(gè)time就是倍數(shù),小數(shù)點(diǎn)后每多一位就會(huì)為10×長(zhǎng)度的次方,再將浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)(tostring轉(zhuǎn)為字符, 使用replace將小數(shù)點(diǎn)替換為空,再用Number將字符串轉(zhuǎn)為數(shù)字)
最后將獲取到的倍數(shù)和整數(shù)存入對(duì)象中即可
3.最后實(shí)現(xiàn)加減乘除運(yùn)算,確保不丟失精度
主要思路:把小數(shù)放大為整數(shù)(乘),進(jìn)行算術(shù)運(yùn)算,再縮小為小數(shù)(除)
再次封裝一個(gè)為實(shí)現(xiàn)運(yùn)算的函數(shù)這個(gè)函數(shù)接受三個(gè)參數(shù)(運(yùn)算數(shù)1,運(yùn)算數(shù)2, '加減乘除四個(gè)方法的函數(shù)'),
聲明n1, n2, 分別為單價(jià)1, 單價(jià)2,
聲明t1, t2.為單價(jià)1的倍數(shù)和單價(jià)2的倍數(shù)
再獲取最大倍數(shù)max(使用三元運(yùn)算符),
使用switch判斷條件就是('加減乘除四個(gè)方法的函數(shù)')搭配Case判斷
加法: 三種狀況
1.t1和t2相同的情況下,就是兩個(gè)小數(shù)的位數(shù)相同情況下直接返回n1 + n2 就是整數(shù)相加
2.t1大于t2的情況下 第一個(gè)小數(shù)位數(shù)大于第二位返回, n1 + n2 * (t1 / t2):
3.t1小于t2的情況下 第一個(gè)小數(shù)位數(shù)小于第二位返回, n1 * (t2 / t1) + n2
最后無(wú)論走得是哪一種情況結(jié)果都要除以max(最大倍數(shù))
減法: 三種狀況
1.t1和t2相同的情況下,就是兩個(gè)小數(shù)的位數(shù)相同情況下直接返回n1 - n2 就是整數(shù)相減
2.t1大于t2的情況下 第一個(gè)小數(shù)位數(shù)大于第二位返回, n1 - n2 * (t1 / t2)
3.t1小于t2的情況下 第一個(gè)小數(shù)位數(shù)小于第二位返回, n1 * (t2 / t1) - n2
最后無(wú)論走得是哪一種情況結(jié)果都要除以max(最大倍數(shù))
乘法: 判斷狀態(tài)時(shí)候結(jié)果的是(n1 × n2)/ (t1 × t2)
除法: 判斷狀態(tài)時(shí)候結(jié)果的是(n1 × n2)/ (t1 × t2)
--- 最后進(jìn)行加減乘除進(jìn)行封裝,return這四個(gè)函數(shù)-- -
四、解決的問(wèn)題
在 JavaScript 中處理小數(shù)點(diǎn)精度可以帶來(lái)以下幾個(gè)好處:
避免精度丟失:JavaScript 使用 IEEE 754 浮點(diǎn)數(shù)標(biāo)準(zhǔn)來(lái)表示數(shù)字,但由于浮點(diǎn)數(shù)存儲(chǔ)的是二進(jìn)制近似值,會(huì)導(dǎo)致一些小數(shù)無(wú)法被準(zhǔn)確表示,從而造成精度丟失。通過(guò)處理小數(shù)點(diǎn)精度,可以減少這種精度丟失的情況,確保計(jì)算結(jié)果的準(zhǔn)確性。
精確計(jì)算金融數(shù)據(jù):在金融領(lǐng)域,小數(shù)點(diǎn)精度非常重要。處理貨幣金額、計(jì)算利率和利息等都需要保持精確的小數(shù)點(diǎn)計(jì)算,以避免數(shù)據(jù)錯(cuò)誤和損失。
避免舍入誤差:當(dāng)進(jìn)行多個(gè)浮點(diǎn)數(shù)計(jì)算時(shí),由于每次計(jì)算都可能存在一定的舍入誤差,累積計(jì)算結(jié)果可能會(huì)與預(yù)期的結(jié)果有所偏差。通過(guò)處理小數(shù)點(diǎn)精度,可以減少舍入誤差的影響,提高計(jì)算的準(zhǔn)確性。
增強(qiáng)數(shù)據(jù)可視化:在圖表和可視化數(shù)據(jù)中,小數(shù)點(diǎn)精度可以提供更精確的數(shù)據(jù)展示。例如,在繪制股票價(jià)格曲線或者科學(xué)實(shí)驗(yàn)數(shù)據(jù)時(shí),小數(shù)點(diǎn)精度可以使得數(shù)據(jù)更加準(zhǔn)確和可信。
總的來(lái)說(shuō),處理小數(shù)點(diǎn)精度可以確保計(jì)算結(jié)果的準(zhǔn)確性,尤其在對(duì)于金融數(shù)據(jù)和需要高精度計(jì)算的場(chǎng)景下,這種處理是非常重要的。
五、二進(jìn)制存儲(chǔ)原理
在JS中不區(qū)分整數(shù)和小數(shù),因?yàn)镴S中天生浮點(diǎn)數(shù)(雙精度),在計(jì)算機(jī)存儲(chǔ)中,雙精度的實(shí)際存儲(chǔ)位數(shù)是52位,由于二進(jìn)制中只有 0 和 1,但52位有時(shí)并不能準(zhǔn)確的表達(dá)小數(shù)點(diǎn)后面的數(shù)字,在十進(jìn)制中有四舍五入,在二進(jìn)制中存在0舍1入,所以當(dāng)52位無(wú)法準(zhǔn)確的表達(dá)出一個(gè)小數(shù)時(shí),就會(huì)產(chǎn)生補(bǔ)位動(dòng)作,數(shù)值偏差就在這時(shí)產(chǎn)生了,這是造成計(jì)算精度問(wèn)題的原因。
總結(jié)
到此這篇關(guān)于JavaScript中小數(shù)點(diǎn)精度丟失的原因以及解決方法的文章就介紹到這了,更多相關(guān)JS小數(shù)點(diǎn)精度丟失內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用js寫了一個(gè)類似php的print_r輸出換行功能
因?yàn)閜hp的print_r比較好用同時(shí)js卻沒(méi)有這個(gè)功能于是自己就寫了一個(gè),感興趣的你可不要錯(cuò)過(guò)了哈,希望本文對(duì)你提高知識(shí)有所幫助2013-02-02JavaScript中的一些實(shí)用小技巧總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于JavaScript中的一些實(shí)用小技巧,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04JavaScript 動(dòng)態(tài)添加表格行 使用模板、標(biāo)記
在客戶端使用JavaScript動(dòng)態(tài)添加表格行,先到網(wǎng)上找了相關(guān)的資料,發(fā)現(xiàn)有現(xiàn)成做好的組件,發(fā)現(xiàn)它只能夠滿足比較簡(jiǎn)單的要求。2009-10-10JS防抖節(jié)流函數(shù)的實(shí)現(xiàn)與使用場(chǎng)景
在行走江湖的過(guò)程中,會(huì)出現(xiàn)很多性能優(yōu)化的問(wèn)題來(lái)讓你手足無(wú)措,那么這篇文章主要給大家介紹了關(guān)于JS防抖節(jié)流函數(shù)的實(shí)現(xiàn)與使用場(chǎng)景,針對(duì)這兩個(gè)問(wèn)題來(lái)為你答疑解惑,需要的朋友可以參考下2021-07-07Javascript模擬實(shí)現(xiàn)new原理解析
這篇文章主要介紹了Javascript模擬實(shí)現(xiàn)new原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03JavaScript 生成隨機(jī)數(shù)并自動(dòng)大小排序
JavaScript按規(guī)定生成隨機(jī)數(shù),并按指定順序自動(dòng)排序,本例中將生成1——100以內(nèi)的隨機(jī)數(shù),并按照由小到大的順序排列起來(lái)。2009-12-12