一、場景
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()降低小數精度
其他:
