近一段時間有個剛剛入行開發的朋友問我一些計數問題,
他說,它命名使用 toFixed() 方法來截取小數的長度了,雖然大部分都正常,但是有部分會出現結果不准確的問題。
先看圖:
例如:
結果是:
但是:下面的就不是想要的結果了。
按道理結果應該是 82.1%的,但是實際上卻不是這樣。
先不說是什么原因導致的。
然后我讓他改一下:變成:
就改變了一下運算位置,但是結果大不同。
到底哪一個才是正確的呢?我們來借助一下計算器:
結果已經很明顯了,正確答案是 82.21%
我朋友他的想法是,先將5755 / 7000 然后得出的結果通過 toFixed() 方法 來截取3位小數,然后再乘以 100 來做一個百分百。
按道理來說,在平時的計算中這兩個寫法都是正確的才對啊,但是為什么會出現這樣的情況呢?
先不要急,我們先來看一個最簡單的例子:
看到這里的時候你是不是第一時間覺得答案是 0.3 ?
那看一下js打印的結果是怎么樣的。
what?
0.1 + 0.2 竟然不是等於 0.3 ?
沒錯,這個就是 js 的計算問題了。所以上面出現答案不正確的問題就是因為js的計算方式導致的。
要弄清這個問題,首先我們需要了解的是:在計算機中數字是如何存儲和運算的。
在計算機中,數字無論是整點數還是浮點數都是以多位二進制的方式進行存儲的。
在JS中數字采用的IEEE 754的雙精度標准進行存儲,我們可以無需知道他的存儲形式,
只需要簡單的理解成就是存儲一個數值所使用的二進制位數比較多而已,這樣得到的數會更加精確。
這里為了簡單直觀,我們使用定點數來說明問題。在定點數中,如果我們以8位二進制來存儲數字。
對於整數來說,十進制的35會被存儲為: 00100011 其代表 2^5 + 2^1 + 2^0。
對於純小數來說,十進制的0.375會被存儲為: 0.011 其代表 1/2^2 + 1/2^3 = 1/4 + 1/8 = 0.375
而對於像0.1這樣的數值用二進制表示你就會發現無法整除,最后算下來會是 0.000110011....由於存儲空間有限,最后計算機會舍棄后面的數值,所以我們最后就只能得到一個近似值。
在JS中采用的IEEE 754的雙精度標准也是一樣的道理,我們且不管這個標准下的存儲方式跟定點數存儲有何不同,單單在這一點上他們都是相同的,也就是存儲空間有限,
當出現這種無法整除的小數的時候就會取一個近似值,在js中如果這個近似值足夠近似,那么js就會認為他就是那個值。
所以我們現在應該可以理解,就是說由於0.1轉換成二進制時是無限循環的,所以在計算機中0.1只能存儲成一個近似值。除了那些能表示成 x/2^n 的數可以被精確表示以外,其余小數都是以近似值得方式存在的。
在0.1 + 0.2這個式子中,0.1和0.2都是近似表示的,在他們相加的時候,兩個近似值進行了計算,導致最后得到的值是0.30000000000000004,此時對於JS來說,
其不夠近似於0.3,於是就出現了0.1 + 0.2 != 0.3 這個現象。 當然,也並非所有的近似值相加都得不到正確的結果。
有時兩個近似值進行計算的時候,得到的值是在JS的近似范圍內的,於是就可以得到正確答案。至於哪些值計算后能得到正確結果,哪些不能,我們也不需要去記。
最好的方法就是我們想辦法規避掉這類小數計算時的精度問題就好了,那么最常用的方法就是將浮點數轉化成整數計算。因為整數都是可以精確表示的。
所以,要解決類似問題,方法很簡單,將浮點數轉化成整數來計算就可以解決精度丟失問題了。