這是我參與8月更文挑戰的第1天,活動詳情查看:8月更文挑戰。html
這個問題有意義嗎?我以爲可能不大,這不就是個浮點數精度問題嗎。嗯,確實。以前筆試遇到過這個題,問0.8-0.6===0.2和0.2-0.1 === 0.1 true 仍是false,一個小公司,叫中xx龍。我成功記住了他,哈哈哈。如今,來盲目分析一波。segmentfault
先來看看問題。markdown
0.1+0.2 ===0.3;
//false
0.2-0.1 === 0.1; // 式1
//true
0.8 - 0.6 === 0.2; // 式2
//false
複製代碼
0.1+0.2的問題太經典了,直接來看看下面2個浮點數減法。0.2減去0.1的時候沒有精度問題。oop
再多整幾個例子看看,有沒有規律可言。post
0.4-0.2===0.2; // 式3
//true
0.6-0.3 === 0.3;// 式4
//true
0.8 - 0.7 === 0.1; // 式5
//false
0.8-0.3 === 0.5; // 式6
//true
複製代碼
分別對比式1和式5,式2和式3,能夠看出來結果爲0.1和0.2的時候有true也有false,說明不是結果的數值決定精度。編碼
對比結果爲true的式1,式3,式4,發現式1+式3能夠獲得式4,兩個等式相加仍然獲得一個等式,很是合理。同時,當被減數是減數的2倍時,不會有精度問題。spa
仔細看:式2 + 式4 ==式6?,一個不等式與等式相加,結果竟然是一個等式。。。。。。code
問題大了。orm
整數轉二進制是除二取餘法htm
13 .toString(2);
//"1101"
0.25.toString(2);
//"0.01"
複製代碼
小數轉二進制則是乘二取整法
0.25=0*2-1 + 1 * 2-2
0.25*2=>0.5;//整數部分爲0
0.5*2=>1.0;//整數部分爲1,減去1剩餘0,結束
複製代碼
關於計算機浮點數存儲問題,已經有不少人分析過了。
能夠查看JavaScript是怎樣編碼數字的,瞭解詳細內容
浮點數 64位中 : 1位符號位 s,+ 11位指數位 e, 52位小數位f
value = (-1)s(1.f) * 2e
注意:s表示正負,小數部分f是二進制形式,因爲f前面自帶一個1.,因此value的精度實際上有53位。e表示小數點偏移的位數,有正負,最大可偏移1024
好吧,這個理解後文的基礎,不是本文的主題。
對0.1採用乘二取整法,會產生無限循環。
因爲小數位f只有精度53位,超出部分0舍1入,因此存在精度問題。
0.1.toString(2);
"0.0001100110011001100110011001100110011001100110011001101"
0.2.toString(2);
"0.001100110011001100110011001100110011001100110011001101"
0.3.toString(2);
"0.010011001100110011001100110011001100110011001100110011"
複製代碼
這時候可能就有聰明的金針菇要問了:‘’上面不是說有指數部分還有符號位,這個咋沒有呢?‘’
由於上面的String只是表達的經過乘二取整法獲得的二進制形式,離計算機存儲的格式還有點差異。
固然也不可能用字符串來存儲。。。。這裏只是表達了一個精度的取捨。
可能又有嚴謹的小夥伴要問了:「這個小數點後面的位數也不必定是53位啊」
0.1.toString(2).length;
//57
0.2.toString(2).length;
//56
複製代碼
嗯,就算把小數點和前置0減去也獲得不53.那麼換個思路,既然是精度,就得數小數點後第一個不爲0的位到最後一位的長度,對於0.1的二進制形式,把小數點後面的3個0去掉。
"0.0001100110011001100110011001100110011001100110011001101" =>
1100110011001100110011001100110011001100110011001101=>
("1100") * 12 + "1101" => 52位
複製代碼
額,難道又錯了?
並無。還記得前文說了,有一個0舍1入的近似。
已知0.1轉換會產生無限循環,循環部分爲1100,那麼在近似舍入以前,有
("1100") * 12+ "11001100"+ "1100".......
複製代碼
保留53位,看第54位,爲1,根據0舍1入規則,先捨去後面部分獲得
("1100") * 12 + "11001"
複製代碼
再向前進1位,巧了53位是1,1+1爲2,再向52位進1,獲得
("1100") * 12 + "11010"
複製代碼
很顯然,最末尾得0位沒有顯示,,就像0.25轉換爲‘‘0.01’’同樣,末尾的0被省去了,不顯示了。
因此「0.000"+("1100") * 12 + "1101"就是0.1,精確到53位的0.1。
順帶一提,計算機中存儲0.1的形式是符號位s=0,指數位-4(10進制值,表明小數點左移4位)
小數位100+("1100") * 11 + ‘11010’ ,
也即(-1)0*2-4 *(1.1001100110011001100110011001100110011001100110011010)
如今,咱們知道tostring轉化後顯示的精度正是浮點數的精度,那麼開始作加減法吧。
"0.0001100110011001100110011001100110011001100110011001101"//0.1
+
"0.001100110011001100110011001100110011001100110011001101"//0.2
=
// 直接作有點費眼睛,換個形式
0.0(0011)01 //0.1
+
0.0(0110)1 //0.2
=
0.0(1001)11 // 括號裏是52位,加上末尾2個1就是54位了,進位再忽視末0
=> 0.0(1001)101
複製代碼
0.0(1001)100 //而0.3是這樣的
(0.1+0.2).toString(2);
// "0.0100110011001100110011001100110011001100110011001101"
// 0.0(1001)101
複製代碼
0.1+0.2不等與0.3的緣由找到了。
在來作減法
0.0(0110)1 //0.2
-
0.0(0011)01 //0.1
=
0.0(0011)01 //巧了不是,正好53位精度不存在舍入,完美等於0.1
複製代碼
大膽推論:當被減數是減數的2倍時,不會有偏差,嗯
實際並非。。。。。。
式6中0.8-0.3等於0.5的過程當中,其實是存在進位過程的。因此並不是嚴格意義上的等式。
有興趣能夠本身證實。
只要存在進位,就再也不準確了。
引用郭冬臨的經典語錄:你用謊話去驗證謊話,獲得的也只能是謊話。
還有就是出這種題是真的沒意思,難道要人去算到小數點後54位?
如發現有錯,歡迎指正。 參考 搞懂js中小數運算精度問題緣由及解決辦法