摘要:又如,對于,結果其實并不是,但是最接近真實結果的數,比其它任何浮點數都更接近。許多語言也就直接顯示結果為了,而不展示一個浮點數的真實結果了。小結本文主要介紹了浮點數計算問題,簡單回答了為什么以及怎么辦兩個問題為什么不等于。
原文地址:為什么0.1+0.2不等于0.3
先看兩個簡單但詭異的代碼:
0.1 + 0.2 > 0.3 // true 0.1 * 0.1 = 0.010000000000000002
0.1加0.2為什么就不等于0.3昵?要回答這個問題,得先了解計算機內部是如何表示數的。
計算機內部如何表示數我們都知道,計算機用位來儲存及處理數據。每一個二進制數(二進制串)都一一對應一個十進制數。
這里以十進制數13來展示“按位計數法”如何表示整數:
十進制值 | 進制 | 按位格式 | 描述 |
---|---|---|---|
13 | 10 | 13 | 1x10^1 + 3x10^0 = 10 + 3 |
13 | 2 | 1101 | 1x2^3 + 1x2^2 + 0x2^1 + 1x2^0 = 8 + 4 + 0 + 1 |
再看小數怎么用按位計數法表示,以十進制數0.625為例:
十進制值 | 進制 | 按位格式 | 描述 |
---|---|---|---|
0.625 | 10 | 0.625 | 6x10^-1 + 2x10^-2 + 5x10^-3 = 0.6 + 0.02 + 0.005 |
0.625 | 2 | 0.101 | 1x2^-1 + 0 x2^-2 + 1x2^-3 = 1/2 + 0 + 1/8 |
關于十進制與二進制間如何轉換,這里不細說,直接給出結論:
十進制整數轉二進制方法:除2取余;十進制小數轉二進制方法:乘2除整
十進制0.1轉換成二進制,乘2取整過程:
0.1 * 2 = 0.2 # 0 0.2 * 2 = 0.4 # 0 0.4 * 2 = 0.8 # 0 0.8 * 2 = 1.6 # 1 0.6 * 2 = 1.2 # 1 0.2 * 2 = 0.4 # 0 .....
從上面可以看出,0.1的二進制格式是:0.0001100011....。這是一個二進制無限循環小數,但計算機內存有限,我們不能用儲存所有的小數位數。那么在精度與內存間如何取舍呢?
答案是:在某個精度點直接舍棄。當然,代價就是,0.1在計算機內部根本就不是精確的0.1,而是一個有舍入誤差的0.1。當代碼被編譯或解釋后,0.1已經被四舍五入成一個與之很接近的計算機內部數字,以至于計算還沒開始,一個很小的舍入錯誤就已經產生了。這也就是 0.1 + 0.2 不等于0.3 的原因。
有誤差的兩個數,其計算的結果,當然就很可能與我們期望的不一樣了。注意前面的這句話中的“很可能”這三個字?為啥是很可能昵?
0.1 + 0.1 為什么等于0.2答案是:兩個有舍入誤差的值在求和時,相互抵消了,但這種“負負得正,相互抵消”不一定是可靠的,當這兩個數字是用不同長度數位來表示的浮點數時,舍入誤差可能不會相互抵消。
又如,對于 0.1 + 0.3 ,結果其實并不是0.4,但0.4是最接近真實結果的數,比其它任何浮點數都更接近。許多語言也就直接顯示結果為0.4了,而不展示一個浮點數的真實結果了。
另外要注意,二進制能精確地表示位數有限且分母是2的倍數的小數,比如0.5,0.5在計算機內部就沒有舍入誤差。所以0.5 + 0.5 === 1
計算機這樣胡亂舍入,能滿足所有的計算需求嗎我們看兩個現實的場景:
對于一個修建鐵路的工程師而言,10米寬,還是10.0001米寬并沒有什么不同。鐵路工程師就不需要這么高0.x這樣的精度
對于芯片設計師,0.0001米就會是一個巨大不同,他也永遠不用處理超過0.1米距離
不同行業,要求的精度不是線性的,我們允許(對結果無關緊要的)誤差存在。10.0001與10.001在鐵路工程師看來都是合格的。
雖然允許誤差存在,但程序員在使用浮點數進行計算或邏輯處理時,不注意,就可能出問題。記住,永遠不要直接比較兩個浮點的大小:
var a = 0.1 var b = 0.2 if (a + b === 0.3) { // doSomething }JS中如何進入浮點數運算
整數是完全精度的,不存在舍入誤差。例如,一些關于人民幣的運算,都會以分為基本單位,計算采用分,展示再轉換成元。當然,這樣也有一些問題,會帶來額外的工作量,如果那天人民幣新增了一個貨幣單位,對系統的擴展性也會有考驗。
使用bignumber進行運算bignumber.js會在一定精度內,讓浮點數計算結果符合我們的期望。
{ let x = new BigNumber(0.1); let y = new BigNumber(0.2) let z = new BigNumber(0.3) console.log(z.equals(x.add(y))) // 0.3 === 0.1 + 0.2, true console.log(z.minus(x).equals(y)) // true console.log(z.minus(y).equals(x)) // true }
{ let x = 0.2 console.log(x * x === 0.04) // false let y = new BigNumber(0.2) let r = y.mul(y) // 0.04 console.log(r.equals(new BigNumber(0.04))) // true }
更多例子,可以看bignumber.js官方示例。
小結本文主要介紹了浮點數計算問題,簡單回答了為什么以及怎么辦兩個問題:
為什么0.1 + 0.2 不等于0.3。因為計算機不能精確表示0.1, 0.2這樣的浮點數,計算時使用的是帶有舍入誤差的數
并不是所有的浮點數在計算機內部都存在舍入誤差,比如0.5就沒有舍入誤差
具有舍入誤差的運算結可能會符合我們的期望,原因可能是“負負得正”
怎么辦?1個辦法是使用整型代替浮點數計算;2是不要直接比較兩個浮點數,而應該使用bignumber.js這樣的浮點數運算庫
最后,本文只是簡單回答了為什么,如果讀者對更根本深入的原理感興趣,可以自行google之。限于水平有限,本文如果有錯誤,歡迎指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90020.html
摘要:本文通過介紹的二進制存儲標準來理解浮點數運算精度問題,和理解對象的等屬性值是如何取值的,最后介紹了一些常用的浮點數精度運算解決方案。浮點數精度運算解決方案關于浮點數運算精度丟失的問題,不同場景可以有不同的解決方案。 本文由云+社區發表 相信大家在平常的 JavaScript 開發中,都有遇到過浮點數運算精度誤差的問題,比如 console.log(0.1+0.2===0.3)// fa...
摘要:返回是,這是為什么呢我們知道浮點數計算是不精確的,上面的返回式實際上是這樣的在的新規范加入了一個新的東西是在對象上面,新增一個極小的常量。根據規格,它表示與大于的最小浮點數之間的差。上面的代碼為浮點數運算,部署了一個誤差檢查函數。 0.1+0.2 === 0.3 //返回是false, 這是為什么呢?? 我們知道浮點數計算是不精確的,上面的返回式實際上是這樣的:0.1 + 0.2 = ...
摘要:按照的數字格式,整數有的范圍是,而且只能表示有限個浮點數,能表示的個數為個。 0.1+0.2 等于0.3嗎?相信拿著這條題目隨便問一個高年級的小學生,他們都會毫不猶豫都回答:相等。是的,相等是正常的,這是常識。但是都說實踐是檢驗真理的唯一標準,拿這道簡單的算術題用javascript在chrome控制臺試驗一下: 結果令人大跌眼鏡,在控制臺輸入0.1+0.2 == 0.3返回的結果竟然...
摘要:因此利用以及語法樹在代碼構建過程中重寫等符號,開發時直接以這樣的形式編寫代碼,在構建過程中編譯成,從而在開發人員無感知的情況下解決計算失精的問題,提升代碼的可讀性。 前言 你了解過0.1+0.2到底等于多少嗎?那0.1+0.7,0.8-0.2呢? 類似于這種問題現在已經有了很多的解決方案,無論引入外部庫或者是自己定義計算函數最終的目的都是利用函數去代替計算。例如一個漲跌幅百分比的一個...
閱讀 3115·2021-11-24 09:39
閱讀 973·2021-09-07 10:20
閱讀 2393·2021-08-23 09:45
閱讀 2267·2021-08-05 10:00
閱讀 573·2019-08-29 16:36
閱讀 840·2019-08-29 11:12
閱讀 2821·2019-08-26 11:34
閱讀 1843·2019-08-26 10:56