為什麼 JavaScript 中 0.1+0.2 不等於 0.3 ?
本文首發於 vivo網際網路技術 微信公眾號
連結: https://mp.weixin.qq.com/s/2kea7-jACCJmSYBQAwXyIg
作者:劉洋
在 js 中進行數學的運算時,會出現0.1+0.2=0.300000000000000004的結果,一開始認為是浮點數的二進位制儲存導致的精度問題,但這似乎不能很好的解釋為什麼在同樣的儲存方式下0.3+0.4=0.7可以得到正確的結果。本文主要透過浮點數的二進位制儲存及運算,和IEEE754下的舍入規則,解釋為何會出現這種情況。
一、浮點數的二進位制儲存
JavaScript遵循IEEE754標準,在64位中儲存一個資料的有效數字形式。
其中,第0位為符號位,0表示正數1表示負數;第1到11位儲存指數部分;第12到63位存小數部分(尾數部分)(即有效數字)。由於二進位制的有效數字總是表示為
…的形式,尾數部分在規約形式下的第一位預設為1,故儲存時第一位省略不寫,尾數部分f儲存有效數字小數點後的xxx...,最長52位。因此,JavaScript提供的有效數字最長為53個二進位制位(尾數部分52位+被省略的1位)。
以0.1、0.2、0.3、0.4和0.7的二進位制形式為例:
0.1->0.0001100110011...(0011無限迴圈)->0-01111111011-(1 .)1001100110011001100110011001100110011001100110011010(入) 0.2->0.001100110011...(0011無限迴圈)->0-01111111100-(1 .)1001100110011001100110011001100110011001100110011010(入) 0.3->0.01001100110011...(0011無限迴圈)->0-01111111101-(1 .)0011001100110011001100110011001100110011001100110011(舍) 0.4->0.01100110011...(0011無限迴圈)->0-01111111101-(1 .)1001100110011001100110011001100110011001100110011010(入) 0.7->0.101100110011...(0011無限迴圈)->0-01111111110-(1 .)0110011001100110011001100110011001100110011001100110(舍)
對於52位之後進行舍入運算,此時可看作0舍1入(具體舍入規則在第三部分詳細說明),有精度損失。
二、對階運算
由於指數位數不同,運算時需要進行對階運算。對階過程略,0.1+0.2與0.3+0.4的尾數求和結果分別如下:
0.1+0.2->10.0110011001100110011001100110011001100110011001100111 0.3+0.4->10.1100110011001100110011001100110011001100110011001101
求和結果需規格化(有效數字表示),右規導致低位丟失,此時需對丟失的低位進行舍入操作:
0.1+0.2->1.00110011001100110011001100110011001100110011001100111->1.0011001100110011001100110011001100110011001100110100(入) 0.3+0.4->1.01100110011001100110011001100110011001100110011001101->1.0110011001100110011001100110011001100110011001100110(舍)
即:
00111->0100
01101->0110
此處同樣有精度損失。在這裡我們可以發現,0.3+0.4對階階運算且規格化後的運算結果與0.7在二進位制中的儲存尾數相同(可對照尾數後幾位),而0.1+0.2的運算結果與0.3的儲存尾數不同,且0.1+0.2轉化為十進位制時結果為0.300000000000000004。
此時,雖然0.1+0.2與0.3+0.4進行舍入操作的近似位都為1,但一入一舍導致計算結果與“標準答案”的異同。
三、IEEE754標準下的舍入規則
維基百科對最近偶數舍入原則的解釋如下:舍入到最接近,在一樣接近的情況下偶數優先(Ties To Even,這是預設的舍入方式),即會將結果舍入為最接近(精度損失最小)且可以表示的值,但是當存在兩個數一樣接近的時候,則取其中的偶數(在二進位制中是以0結尾的)。
首先要注意的是,保留小數不是隻看後面一位或者兩位,而是看保留位後面的所有位。
如圖,可以看到近似需要看三位,保留位(近似後的最低位)、近似位(保留位的後一位)、粘滯位(sticky bit 近似位後的所有位進行或運算後看作一位)。
當粘滯位為1時,舍入規則可以看作0舍1入,近似位為0舍,近似位為1入(即第一部分小數二進位制儲存為52位尾數時所進行的舍入操作)。
當粘滯位為0時,若近似位為0則捨去。
當粘滯位為0時,若近似位為1,無論舍入精度損失都相同,故需取捨入兩種結果中的偶數:保留位為1時入,保留位為0時舍(即第二部分對階運算規格化時的舍入操作)。
四、總結思考
由於IEEE754標準,這樣的“bug”不止在JavaScript中會出現,在所有采用該標準的語言中都會存在,實際程式設計中可以透過設定精度保留位數等方式解決。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912579/viewspace-2661073/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 如何解決JavaScript中0.1+0.2不等於0.3JavaScript
- 為什麼JS中0.1+0.2 != 0.3JS
- 0.1+0.2!==0.3,為什麼?
- 0.1 + 0.2不等於0.3?為什麼JavaScript有這種“騷”操作?JavaScript
- js小數計算的問題,為什麼0.1+0.2 != 0.3JS
- 0.1+0.2不等於0.3,微信小程式雲開發如何解決JavaScript小數計算精度失準的問題微信小程式JavaScript
- 都知道0.1+0.2 = 0.30000000000000004,那要怎麼讓它等於0.3
- Go 介面:nil介面為什麼不等於nil?Go
- JS 精度差異 0.1+0.2 != 0.3JS
- 組成原理|為什麼計算機中0.3 + 0.6 等於 0.899999999...?計算機
- 3 * 0.1 == 0.3將會返回什麼?為什麼?
- 0.1+0.2=?在前端裡,告訴你:≠0.3 !前端
- 一個函式讓你看懂 'Why 0.1+0.2!=0.3'函式
- 利用babel(AST)優雅地解決0.1+0.2!=0.3的問題BabelAST
- [譯] JavaScript 中為什麼會有 Symbol 型別?JavaScriptSymbol型別
- 自然語言不等於英語,為什麼NLPer應當認識到這個問題,以及該怎麼做?
- javascript中null是什麼JavaScriptNull
- 為什麼 JavaScript 的 this 要這麼用?JavaScript
- JavaScript 為什麼能活到現在?JavaScript
- 為什麼 JavaScript 會無處不在?JavaScript
- javascript中web worker是什麼JavaScriptWeb
- javascript中閉包是什麼JavaScript
- 在JavaScript中this到底指代什麼?JavaScript
- [譯] JavaScript中的“this”是什麼?JavaScript
- 動畫:面試官問我 0.1 + 0.2 __ 0.3 ? 為什麼?該如何正確回答?動畫面試
- 為什麼 JavaScript 是 TypeScript 的基礎JavaScriptTypeScript
- 幽默:Javascript為什麼算術沒算好?JavaScript
- 為什麼Web前端語言只有JavaScript?Web前端JavaScript
- 2020-12-26 JavaScript基本概念5:cookie,sessionStorage和localStorage,0.1+0.2!=0.3怎麼處理,陣列的常用方法,new一個物件的過程,繼承JavaScriptCookieSession陣列物件繼承
- javascript中window.$是什麼意思JavaScript
- javascript中promise有什麼侷限JavaScriptPromise
- mongodb條件查詢不等於MongoDB
- 為什麼 JavaScript 需要非同步程式設計JavaScript非同步程式設計
- 為什麼我喜歡JavaScript的Optional ChainingJavaScriptAI
- 為什麼 JavaScript 要設計原型模式JavaScript原型模式
- 為什麼JavaScript需要模組化開發?JavaScript
- 【譯】JavaScript中純函式是什麼JavaScript函式
- 你知道 0.1+0.2 !==0.3是進位制問題,但你講不出個所以然,是吧??