一、js浮點數(shù)計算精度丟失的一些例子
1、四則運算精度丟失:
0.1+0.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
10.22*100 = 1022.0000000000001
2.4/0.8 = 2.9999999999999996
32.2*100 = 3220.0000000000005
32.2*1000 = 32200.000000000004
(32.2*100 + 3.14*100) / 100 = 35.34 // 這里的精度怎么又不丟失了?
32.3*100 = 3229.9999999999995
32.3*1000 = 32299.999999999996
...
2、toFixed() 四舍五入精度丟失:
(1.335).toFixed(2); // '1.33'
(6.265).toFixed(2); // '6.26'
二、浮點數(shù)計算精度丟失的原因
js采用64位浮點數(shù)表示法(幾乎所有現(xiàn)代編程語言所采用),這是一種二進制表示法。二進制浮點數(shù)表示法并不能精確表示類似 0.1 這樣簡單的數(shù)字。
這個問題不只在js中才會出現(xiàn),在任何使用二進制浮點數(shù)的編程語言中都會出現(xiàn)。
JavaScript的未來版本或許會支持十進制數(shù)字類型以避免精度丟失的問題。
三、解決辦法
1、使用 big.js(如果有大量連續(xù)的計算推薦使用)
- 既解決了浮點數(shù)計算精度丟失問題,又解決了 toFixed() 四舍五入精度丟失問題。
big.js
是big.js
,bignumber.js
,decimal.js
三姐妹中功能最少的,但也是體積最小的,壓縮版只有3k
,對于處理js精度丟失已經(jīng)足夠用了。
import Big from 'big.js'
// 運算
const plus = Big(0.1).plus(0.2); // 加
const minus = Big(0.3).minus(0.1); // 減
const mul = Big(10.22).times(100); // 乘
const div = Big(2.4).div(0.8); // 除
// toFixed
const fixed = new Big(6.265).toFixed(2); // 6.27
console.log(
plus.toNumber(),
minus.toNumber(),
mul.toNumber(),
div.toNumber()
)
// 0.3 0.2 1022 3
2、解決四則運算精度丟失問題
方法1:沒有具體要求保留幾位小數(shù)的,最簡單的方法是直接用 toFixed()
從上面四則運算精度丟失的例子可以看到,四則運算的精度丟失主要會出現(xiàn)很多位 0 或很多位 9。
function precision(val) {
return +val.toFixed(8);
}
precision(0.1 + 0.2)
方法2:有具體要求精確到第幾位,用科學計數(shù)法
對運算結(jié)果進行四舍五入
MDN 已經(jīng)給出了具體代碼(也是利用“科學計數(shù)法”擴大 10 的 n 次不會出現(xiàn)精度丟失的特性):
function round(number, precision) {
return Math.round(+number + 'e' + precision) / Math.pow(10, precision);
}
round(1.005, 2); //1.01
round(1.002, 2); //1
或者:
/**
* Decimal adjustment of a number.
*
* @param {String} type The type of adjustment.
* @param {Number} value The number.
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* @returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? +value[1] - exp : -exp)));
// Shift back
value = value.toString().split('e');
value = +(value[0] + 'e' + (value[1] ? +value[1] + exp : exp));
return value;
}
export default {
round: (value, exp) => {
return decimalAdjust('round', value, exp);
},
floor: (value, exp) => {
return decimalAdjust('floor', value, exp);
},
ceil: (value, exp) => {
return decimalAdjust('ceil', value, exp);
}
};
實現(xiàn)原理:
比如 1022.0000000000001 要保留2位小數(shù),先用 e2 把這個數(shù)擴大 100 倍,
再用 Math.round(), Math.floor(), Math.ceil() 取整,然后再用 e-2 縮小回來。
使用方法:
Decimal.round(val, precision)
console.log(Decimal.round(1.13265, -3)) //1.133
console.log(Decimal.round(3.17, -3)) //3.17
console.log(Decimal.round(0.1+0.2, -3)) //0.3
console.log(Decimal.round(3.17)) //3
console.log(Decimal.round(3.17, 0)) //3
console.log(Decimal.round(31216, 1)) //31220
console.log(Decimal.round(31213, 2)) //31200
precision 可選值:不傳,0,負數(shù),正數(shù)。
- 不傳、0: 精確到整數(shù)。
- 正數(shù): 1就是個位為0,十位是個位四舍五入的值。
- 負數(shù): 精確到小數(shù)點后幾位
3、解決 toFixed() 精度丟失問題:重寫 toFixed 方法(重點?。。。。?/h4>
function toFixed(number, precision = 2) {
number = Math.round(+number + 'e' + precision) / Math.pow(10, precision) + '';
let s = number.split('.');
if ((s[1] || '').length < precision) {
s[1] = s[1] || '';
s[1] += new Array(precision - s[1].length).fill('0').join('');
}
return s.join('.');
}
toFixed(6) // '6.00'
四、判斷小數(shù)是否相等
function epsEqu(x,y) {
return Math.abs(x - y) < Math.pow(2, -52);
// 因為 Number.EPSILON === Math.pow(2, -52),所以也可以這么寫:
// return Math.abs(x - y) < Number.EPSILON;
}
// 舉例
0.1 + 0.2 === 0.3 // false
epsEqu(0.1 + 0.2, 0.3) // true
小數(shù)比較時,要給它一個誤差范圍,在誤差范圍內(nèi)的都算相等。
五、其他由浮點數(shù)引起的問題
parseInt(0.0000008) // -> 8
六、項目內(nèi)實際應(yīng)用
function toFixed(number, precision = 2) {
number = Math.round(+number + 'e' + precision) / Math.pow(10, precision) + '';
let s = number.split('.');
if ((s[1] || '').length < precision) {
s[1] = s[1] || '';
s[1] += new Array(precision - s[1].length).fill('0').join('');
}
return s.join('.');
}
toFixed(6) // '6.00'
function epsEqu(x,y) {
return Math.abs(x - y) < Math.pow(2, -52);
// 因為 Number.EPSILON === Math.pow(2, -52),所以也可以這么寫:
// return Math.abs(x - y) < Number.EPSILON;
}
// 舉例
0.1 + 0.2 === 0.3 // false
epsEqu(0.1 + 0.2, 0.3) // true
小數(shù)比較時,要給它一個誤差范圍,在誤差范圍內(nèi)的都算相等。
parseInt(0.0000008) // -> 8
在列表上勾選5個涉案金額為0.055
萬元的案件,進行批量結(jié)案操作,在批量結(jié)案中,有一個減損值的計算,通過計算勾選案件涉案金額平均值-用戶所填寫的結(jié)案支付金額得出減損值
,那么問題出現(xiàn)了,我在結(jié)案支付金額填寫為0.055
,正常計算的話結(jié)果應(yīng)改為0
,可是截圖卻如圖所示:
這里的業(yè)務(wù)代碼為:文章來源:http://www.zghlxwxcb.cn/news/detail-753643.html
// 批量結(jié)案涉案金額取勾選數(shù)據(jù)的平均值
const amountInvolved = selectedRows.reduce((c, R) => c + (R.amountInvolved - 0), 0) / selectedRows.length
//計算減損值
OnchangeMoney(value) {
this.mdl.impairmentValue = this.req.amountInvolved - value
},
打斷點發(fā)現(xiàn)是計算平均值amountInvolved
時出現(xiàn)了浮點數(shù),那么封裝一個方法:文章來源地址http://www.zghlxwxcb.cn/news/detail-753643.html
function strip(num, precision = 12) {
return +parseFloat(num.toPrecision(precision));
}
為什么選擇 12 做為默認精度?
這是一個經(jīng)驗的選擇,一般選12就能解決掉大部分0001和0009問題,
而且大部分情況下也夠用了,如果你需要更精確可以調(diào)高。
- 處理平均數(shù)計算:
// 批量結(jié)案涉案金額取勾選數(shù)據(jù)的平均值
const amountInvolved = this.strip(
selectedRows.reduce((c, R) => c + (R.amountInvolved - 0), 0) / selectedRows.length
)
- 運行之后發(fā)現(xiàn)還是有浮點數(shù),打斷點是計算差值是也出現(xiàn)了浮點數(shù),解決:
//計算金額
OnchangeMoney(value) {
this.mdl.impairmentValue = this.strip(this.req.amountInvolved - value)
},
參考(JS 計算最小值,最大值,平均值,標準差,中位數(shù)):
// @numbers 包含所有數(shù)字的一維數(shù)組
// @digit 保留數(shù)值精度小數(shù)位數(shù),默認兩位小數(shù)
function getBebeQ(numbers, digit = 2) {
// 修復(fù)js浮點數(shù)精度誤差問題
const formulaCalc = function formulaCalc(formula, digit) {
let pow = Math.pow(10, digit);
return parseInt(formula * pow, 10) / pow;
};
let len = numbers.length;
let sum = (a, b) => formulaCalc(a + b, digit);
let max = Math.max.apply(null, numbers);
let min = Math.min.apply(null, numbers);
// 平均值
let avg = numbers.reduce(sum) / len;
// 計算中位數(shù)
// 將數(shù)值從大到小順序排列好,賦值給新數(shù)組用于計算中位數(shù)
let sequence = [].concat(numbers).sort((a,b) => b-a);
let mid = len & 1 === 0 ?
(sequence[len/2] + sequence[len/2+1]) / 2 :
sequence[(len+1)/2];
// 計算標準差
// 所有數(shù)減去其平均值的平方和,再除以數(shù)組個數(shù)(或個數(shù)減一,即變異數(shù))再把所得值開根號
let stdDev = Math.sqrt(numbers.map(n=> (n-avg) * (n-avg)).reduce(sum) / len);
return {
max,
min,
avg: avg.toFixed(digit),
mid: parseFloat(mid).toFixed(digit),
stdDev : stdDev.toFixed(digit)
}
}
到了這里,關(guān)于js浮點數(shù)四則運算精度丟失以及toFixed()精度丟失解決方法的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!