日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后

發(fā)布時(shí)間:2025/4/16 javascript 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Brief

一天有個(gè)朋友問我“JS中計(jì)算0.7 * 180怎么會(huì)等于125.99999999998,坑也太多了吧!”那時(shí)我猜測(cè)是二進(jìn)制表示數(shù)值時(shí)發(fā)生round-off error所導(dǎo)致,但并不清楚具體是如何導(dǎo)致,并且有什么方法去規(guī)避。于是用了3周時(shí)間靜下心把這個(gè)問題搞懂,在學(xué)習(xí)的過程中還發(fā)現(xiàn)不僅0.7 * 180==125.99999999998,還有以下的坑

  • 著名的 0.1 + 0.2 === 0.30000000000000004

  • 1000000000000000128 === 1000000000000000129

  • IEEE 754 Floating-point

    眾所周知JS僅有Number這個(gè)數(shù)值類型,而Number采用的時(shí)IEEE 754 64位雙精度浮點(diǎn)數(shù)編碼。而浮點(diǎn)數(shù)表示方式具有以下特點(diǎn):

  • 浮點(diǎn)數(shù)可表示的值范圍比同等位數(shù)的整數(shù)表示方式的值范圍要大得多;

  • 浮點(diǎn)數(shù)無法精確表示其值范圍內(nèi)的所有數(shù)值,而有符號(hào)和無符號(hào)整數(shù)則是精確表示其值范圍內(nèi)的每個(gè)數(shù)值;

  • 浮點(diǎn)數(shù)只能精確表示m*2e的數(shù)值;

  • 當(dāng)biased-exponent為2e-1-1時(shí),浮點(diǎn)數(shù)能精確表示該范圍內(nèi)的各整數(shù)值;

  • 當(dāng)biased-exponent不為2e-1-1時(shí),浮點(diǎn)數(shù)不能精確表示該范圍內(nèi)的各整數(shù)值。

  • 由于部分?jǐn)?shù)值無法精確表示(存儲(chǔ)),于是在運(yùn)算統(tǒng)計(jì)后偏差會(huì)愈見明顯。

    想了解更多浮點(diǎn)數(shù)的知識(shí)可參考以下文章:

    • 《基礎(chǔ)野:細(xì)說原碼、反碼和補(bǔ)碼》

    • 《基礎(chǔ)野:細(xì)說無符號(hào)整數(shù)》

    • 《基礎(chǔ)野:細(xì)說有符號(hào)整數(shù)》

    • 《基礎(chǔ)野:細(xì)說浮點(diǎn)數(shù)》

    Why 0.1 + 0.2 === 0.30000000000000004?

    在浮點(diǎn)數(shù)運(yùn)算中產(chǎn)生誤差值的示例中,最出名應(yīng)該是0.1 + 0.2 === 0.30000000000000004了,到底有多有名?看看這個(gè)網(wǎng)站就知道了http://0.30000000000000004.com/。也就是說不僅是JavaScript會(huì)產(chǎn)生這種問題,只要是采用IEEE 754 Floating-point的浮點(diǎn)數(shù)編碼方式來表示浮點(diǎn)數(shù)時(shí),則會(huì)產(chǎn)生這類問題。下面我們來分析整個(gè)運(yùn)算過程。

  • 0.1 的二進(jìn)制表示為 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-4;

  • 當(dāng)64bit的存儲(chǔ)空間無法存儲(chǔ)完整的無限循環(huán)小數(shù),而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.1實(shí)際存儲(chǔ)時(shí)的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010;

  • 0.2 的二進(jìn)制表示為 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-3;

  • 當(dāng)64bit的存儲(chǔ)空間無法存儲(chǔ)完整的無限循環(huán)小數(shù),而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.2實(shí)際存儲(chǔ)時(shí)的位模式是0-01111111100-1001100110011001100110011001100110011001100110011010;

  • 實(shí)際存儲(chǔ)的位模式作為操作數(shù)進(jìn)行浮點(diǎn)數(shù)加法,得到 0-01111111101-0011001100110011001100110011001100110011001100110100。轉(zhuǎn)換為十進(jìn)制即為0.30000000000000004。

  • Why 0.7 * 180===125.99999999998?

  • 0.7實(shí)際存儲(chǔ)時(shí)的位模式是0-01111111110-0110011001100110011001100110011001100110011001100110;

  • 180實(shí)際存儲(chǔ)時(shí)的位模式是0-10000000110-0110100000000000000000000000000000000000000000000000;

  • 實(shí)際存儲(chǔ)的位模式作為操作數(shù)進(jìn)行浮點(diǎn)數(shù)乘法,得到0-10000000101-1111011111111111111111111111111111111111101010000001。轉(zhuǎn)換為十進(jìn)制即為125.99999999998。

  • Why 1000000000000000128 === 1000000000000000129?

  • 1000000000000000128實(shí)際存儲(chǔ)時(shí)的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

  • 1000000000000000129實(shí)際存儲(chǔ)時(shí)的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

  • 因此1000000000000000128和1000000000000000129的實(shí)際存儲(chǔ)的位模式是一樣的。

  • Solution

    到這里我們都理解只要采取IEEE 754 FP的浮點(diǎn)數(shù)編碼的語言均會(huì)出現(xiàn)上述問題,只是它們的標(biāo)準(zhǔn)類庫已經(jīng)為我們提供了解決方案而已。而JS呢?顯然沒有。壞處自然是掉坑了,而好處恰恰也是掉坑了:)

    針對(duì)不同的應(yīng)用需求,我們有不同的實(shí)現(xiàn)方式。

    Solution 0x00 - Simple implementation

    對(duì)于小數(shù)和小整數(shù)的簡(jiǎn)單運(yùn)算可用如下方式

    function numAdd(num1/*:String*/, num2/*:String*/) { var baseNum, baseNum1, baseNum2; try { baseNum1 = num1.split(".")[1].length; } catch (e) { baseNum1 = 0; } try { baseNum2 = num2.split(".")[1].length; } catch (e) { baseNum2 = 0;} baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); return (num1 * baseNum + num2 * baseNum) / baseNum; };

    Solution 0x01 - math.js

    若需要復(fù)雜且全面的運(yùn)算功能那必須上math.js,其內(nèi)部引用了decimal.js和fraction.js。功能異常強(qiáng)大,用于生產(chǎn)環(huán)境上妥妥的!

    Solution 0x02 - D.js

    D.js算是我的練手項(xiàng)目吧,截止本文發(fā)表時(shí)D.js版本為V0.2.0,僅實(shí)現(xiàn)了加、減、乘和整除運(yùn)算而已,bug是一堆堆的,但至少解決了0.1+0.2的問題了。

    var sum = D.add(0.1, 0.2) console.log(sum + '') // 0.3var product = D.mul("1e-2", "2e-4") console.log(product + '') // 0.000002var quotient = D.div(-3, 2) console.log(quotient + '') // -(1+1/2)

    解題思路:

  • 由于僅位于Number.MIN_SAFE_INTEGER和Number.MAX_SAFE_INTEGER間的整數(shù)才能被精準(zhǔn)地表示,也就是只要保證運(yùn)算過程的操作數(shù)和結(jié)果均落在這個(gè)閥值內(nèi),那么運(yùn)算結(jié)果就是精準(zhǔn)無誤的;

  • 問題的關(guān)鍵落在如何將小數(shù)和極大數(shù)轉(zhuǎn)換或拆分為Number.MIN_SAFE_INTEGER至Number.MAX_SAFE_INTEGER閥值間的數(shù)了;

  • 小數(shù)轉(zhuǎn)換為整數(shù),自然就是通過科學(xué)計(jì)數(shù)法表示,并通過右移小數(shù)點(diǎn),減小冪的方式處理;(如0.000123 等價(jià)于 123 * 10-6)

  • 而極大數(shù)則需要拆分,拆分的規(guī)則是多樣的。

  • 按因式拆分:假設(shè)對(duì)12345進(jìn)行拆分得到 5 * 2469;

  • 按位拆分:假設(shè)以3個(gè)數(shù)值為一組對(duì)12345進(jìn)行拆分得到345和12,而實(shí)際值為12*1000 + 345。
    就我而言,1 的拆分規(guī)則結(jié)構(gòu)不穩(wěn)定,而且不直觀;而 2 的規(guī)則直觀,且拆分和恢復(fù)的公式固定。

  • 余數(shù)由符號(hào)位、分子和分母組成,而符號(hào)與整數(shù)部分一致,因此只需考慮如何表示分子和分母即可。

  • 無限循環(huán)數(shù)則僅需考慮如何表示循環(huán)數(shù)段即可。(如10.2343434則分成10.23 和循環(huán)數(shù)34和34的權(quán)重即可)

  • 得到編碼規(guī)則后,那就剩下基于指定編碼如何實(shí)現(xiàn)各種運(yùn)算的問題了。

  • 基于上述的數(shù)值編碼規(guī)則如何實(shí)現(xiàn)加、減運(yùn)算呢?

  • 基于上述的數(shù)值編碼規(guī)則如何實(shí)現(xiàn)乘、除運(yùn)算呢?(其實(shí)只要加、減運(yùn)算解決了,乘除必然可解,就是效率問題而已)

  • 基于上述的數(shù)值編碼規(guī)則如何實(shí)現(xiàn)其它如sin、tan、%等數(shù)學(xué)運(yùn)算呢?

  • 另外由于涉及數(shù)學(xué)運(yùn)算,那么將作為add、sub、mul和div等入?yún)⒌淖兞勘3秩缤瑪?shù)學(xué)公式運(yùn)算數(shù)般純凈(Persistent/Immutable Data Structure)是必須的,那是否還要引入immutable.js呢?(D.js現(xiàn)在采用按需生成副本的方式,可預(yù)見隨著代碼量的增加,這種方式會(huì)導(dǎo)致整體代碼無法維護(hù))

    Conclusion

    依照我的尿性,D.js將采取不定期持續(xù)更新的策略(待我理解Persistent/Immutable Data Structure后吧:))。歡迎各位指教!

    尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明來自:http://www.cnblogs.com/fsjohnhuang/p/5115672.html ^_^肥子John

    Thanks

    http://es5.github.io
    https://github.com/MikeMcl/decimal.js/
    http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
    http://demon.tw/copy-paste/javascript-precision.html

    總結(jié)

    以上是生活随笔為你收集整理的JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。