最近在做一個計算器,發現0.1+0.2!=0.3,感覺計算機有時候也是有問題的。了解資料才看到小數點在計算機中是以二進制表示,而有些小數用二進制表示是無窮,所以才會出現上面這種精確度的問題。·
一些浮點數表示成二進制
十進制 二進制
0.1 0.0001 1001 1001 1001 ...
0.2 0.0011 0011 0011 0011 ...
0.3 0.0100 1100 1100 1100 ...
0.4 0.0110 0110 0110 0110 ...
0.5 0.1 0.6 0.1001 1001 1001 1001 ...
運行一下下面代碼
輸入 輸出 0.1+0.05==0.15 FALSE 1-0.1-0.1-0.1==0.7 FALSE 0.3/0.1 == 3 FALSE 1.0-0.6 == 0.4 True 1.0-0.5 == 0.5 True 1.0-0.4 == 0.6 True 1.0-0.3 == 0.7 True 1.0-0.2 == 0.8 True
出現這個問題的原因,其實是因為數值的表示在計算機內部是用二進制的。例如,十進制的0.625,換成二進制表示就是0.101(1*2-1+0*2-2+1*2-3)。0.625這個數倒還好,剛好可以准確表示出來。但如果是0.1的話呢,換成二進制就是0.00011(0011無限循環),也就是:0.000110011001100110011001100110011...,位數是無限的,只能取近似。對於這些不能准確表示的數就有可能會出現這個問題。為什么是可能呢?因為有些數的計算結果,例如0.1+0.3,它雖然也是不能精確地表示,但是它結果足夠接近0.4,那取了近似后就成了0.4了。·
解決方法
方法一:使用github上的庫:BigDecimal.js或bignumber.js
方法二:在這篇文章里面找到一個簡單的函數。
//小數點后面有6個或以上的0或9,就四舍五入 function fixFloatCalcRudely(num){ if(typeof num == 'number'){ var str=num.toString(), match=str.match(/\.(\d*?)(9|0)\2{5,}(\d{1,5})$/); if(match != null){ return num.toFixed(match[1].length)-0; } } return num; }
可以將每次的運算結果賦值給它,從而得到比較精確的結果。·
方法三:使用簡單點四舍五入方法,其實跟上面的方法差不多,只不過取了一個10位小數。
function numTofixed(num) {
if (typeof num == 'number') {
num = parseFloat(num.toFixed(10))
}
return num;
}
numTofixed(0.1 + 0.2);
//code from http://caibaojian.com/float-point-math.html
match=str.match(/\.(\d*?)(9|0)\2{5,}(\d{1,5})$/);
-
match()·
- 在字符串內檢索指定的值,找到一個或多個正則表達式的匹配
- stringObject.match(searchvalue)
- stringObject.match(regexp)
- 返回匹配結果的數組,數組內容依賴於regexp是否具有全局標志g。
例如下面這個
var d = '55 ff 33 hh 77 tt'.match(/\d+/g); // d = ["55", "33", "77"]
再看一下表達式里面有一個\2{5,},這個\2表示跟第二個表達式匹配,即跟(9|0)這個相同。涉及到子表達式和捕獲,反向引用的概念
- 一個子表達式是一個整體,可重復后面引用
- /(\d)(\d)\2\1/gi; 第二位和第三位相同,第一位和第四位相同
- /(\d)\1(\d)\2(\d)\3(\d)4/; aabbccdd形式的數字
- /(\d){5}-(\d)\2\2(\d)\3\3(\d)\4\4/gi; 12345-111222333
假如我們使用·
fixFloatCalcRudely(0.1+0.2) //[".30000000000000004", "3", "0", "4"]
由於沒有加全局,所以會輸出全部結果,第一個元素匹配整個字符串,第二個開始與圓括號內的子表達式相匹配的子串結果。由於我們輸出只有0.3后面都是0,進入無限循環。
