一、場景
0.1 + 0.2 = 0.30000000000000004
1.5 - 1.2 = 0.30000000000000004
19.9 * 100 = 1989.9999999999998
0.3 / 0.1 = 2.9999999999999996
二、原因
js中的數字只有 Number這種類型,其存采用的64位雙精度浮點數(1位符號位、11位指數位,52位小數位),如下:

做運算操作時會將10進制小數轉換為2進制小數,整數部分采用除2取余法,如下:

小數部分采用的“乘2取整,順序排位法”,以0.1為例,如下:

問題是不可能乘以2后恰好為整數,此時就會導致無限循環,而存儲結構中的尾數部分最多只能表示 52 位,超出的會被舍棄掉,所以機器中存儲的就是一個近似值,這就是導致小數精度的問題所在
三、解決辦法
思路1:縮放法
整數不會有精度問題,因此先將小數放大為整數進行運算,運算完再將結果縮為小數。
1.先找出小數位數最多的數字作為x(例:a為0.1,b為0.22,那么x就是2),然后每個數字都乘以10的x次方變為為整數,方法如下
Number(`${n}e${x}`) //n為需要變換的數字,e在計算機中表示底數為10,x為小數位數。 轉換也可以寫成n*Math.pow(10,x)
計算完成后,加減法用結果除以10的x次方,將結果變為小數。
result / Math.pow(10,x)
乘法:
result/Math.pow(10,x*2) //如果是3個數,則將2換為3,以此類推
除法
假設a和b同時都放大了10的x次方,那么商相當於沒有放大(也就是放大了10^0 = 1),由於商可能為小數,因此可以利用除以一個數,等於乘以該數的導數這個公式,調用乘法再次進行轉換。
/** * 計算小數位數 */ function digitLength(num) { const arr = num.toString().split('.'); return arr[1]!==undefined ? arr[1].length : 0; } /** * 乘法 */ function times(a,b) { const x = digitLength(a),y = digitLength(b); const max = Math.max(x,y); const int_a = Number(`${a}e${max}`); const int_b = Number(`${b}e${max}`); const result = int_a * int_b; return result / Math.pow(10,x*2); } /** * 除法 */ function divide(a,b) { const x = digitLength(a),y = digitLength(b); const max = Math.max(x,y); const int_a = Number(`${a}e${max}`); const int_b = Number(`${b}e${max}`); const result = int_a / int_b; return times(result, 1); //result可能為小數,因此使用乘法,1的倒數還是1 }
思路2:
使用toFixed()降低小數精度
其他: