坑1:含有小数的数字相加
例如 0.1+0.2 !==0.3
0.1+0.2
0.30000000000000004
因为在计算过程中是转成二进制的,
0.1.toString(2)
"0.0001100110011001100110011001100110011001100110011001101"
0.2.toString(2)
"0.001100110011001100110011001100110011001100110011001101"
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004
网友提供的处理思路:乘以10的N次方,在截取出整数部分进行相加处理,再除以10的N次方。
于是实现代码如下:
/** 含有小数点的数字 加法计算 */
Number_Add(p_numberArray: number[], p_fixed: number = 3): number {
let m = Math.pow(10, p_fixed);
let result: number = 0;
p_numberArray.map(x => {
result += parseInt(String((x ?? 0) * m), 10);
})
return result / m;
}
坑2:再chrome浏览器下的四舍五入
0.15.toFixed(1)
"0.1"
0.25.toFixed(1)
"0.3"
0.05.toFixed(1)
"0.1"
0.015.toFixed(2)
"0.01"
在精度的下一位如果数字是5,则会出现诡异的一幕,据说是银行家舍入法。
银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。
简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。但是不论引入toFixed解决浮点数计算精度缺失的问题也好,它有没有使用银行家舍入法也罢,都是为了解决精度的问题,但是又离不开二进制浮点数的环境,但至少他帮助我们找到了问题所在,从而让我们有解决方法。
但我觉得不是。不过无所谓,能实现我们要的效果就行了。
/** 精确到小数点几位 */
NumberFixed(p_number: number, p_fixed: number = 2): number {
try {
if (typeof p_number === 'number') {
//toFixed 方法有bug 应该抛弃
let strNumber = p_number.toString();
let strNumberArray = strNumber.split('.');
if (strNumberArray.length == 1) {
return Number(p_number.toFixed(p_fixed));
}
//decimalNubmer:小数点后面的数
let decimalNubmer = strNumberArray[1];
if (decimalNubmer.length > p_fixed) {
let saveNumber = decimalNubmer.substring(0, p_fixed);
let checkNumber = decimalNubmer.substr(p_fixed, 1);
let result: number;
if (Number(checkNumber) >= 5)
result = Number(`${strNumberArray[0]}.${Number(saveNumber) + 1}`);
else
result = Number(`${strNumberArray[0]}.${saveNumber}`);
return result;
}
}
return p_number;
}
catch (ex) {
console.log(`NumberFixed_error:p_number:${p_number},${ex}`);
}
}
后来我又找到了一个好的方案,以下是在nestjs环境下的方法:
/** 四舍五入 */
export function NumberFixed(p_number: number, p_fixed: number = 2): number {
let tmpNumber = Math.pow(10, p_fixed)
return Math.round((p_number + Number.EPSILON) * tmpNumber) / tmpNumber;
}