JS 四舍五入方法 和 toFixed()最全总结(包括toFixed()的坑以及四舍五入实现方法)

Bululublue 2024-08-07 10:35:02 阅读 88

问题描述

最近在针对前端四舍五入做优化,前人是使用toFixed()来解决的,但客户发现有些数据并不是我们理解意义的四舍五入法。

查阅资料得知,toFixed()实现的是银行家算法:即四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一

更新: 网上很多资料都说toFixed()遵循的是银行家算法,但查看ECMA官网的说明(下带有红框图),觉得toFixed()并不严格遵循银行家算法,根因分析是IEEE754标准的限制,因此实现“四舍六入五成双”的银行家舍入,就不能通过toFixed()来简单实现了,具体解决方案代码请移步点击跳转

举个例子:

<code>// toFixed()

// 银行家算法即:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一

console.log((1.450001).toFixed(1)); // 1.5 // 五后非零就进一

console.log((1.45000).toFixed(1)); // 1.4 // 五后为零看奇偶, 五前为偶应舍去

console.log((1.55000).toFixed(1)); // 1.6 // 五前为奇要进一

本以为这样就是toFixed的规则了,但是当我测试下面数据的时候,发现了奇怪的事情:

console.log((1.885450001).toFixed(4)); // 五后非零就进一

console.log((1.88545000).toFixed(4)); // 五后为零看奇偶, 五前为偶应舍去

console.log((1.88555000).toFixed(4)); // 五前为奇要进一

结果如下:

在这里插入图片描述

可以发现,(1.88545000).toFixed(4)应该是符合”五前为偶应舍去“的规则,那结果应该为1.8854, 但结果却是1.8855,我不理解,但大受震撼。。。查询ECMA官网看到这样一句话:

在这里插入图片描述

解释:n为整数,对于一个精确的值,则有n/10^f - x尽可能的接近于0,如果有两个这样的n,得到的n/10^f - x的值一样,那么取最大的那个。

(啥呀这是)初看确实不太好理解,跟着我继续分析。


原因分析:

<code>我们从结果来看:(1.88545000).toFixed(4)应该是符合”五前为偶应舍去“的规则,那结果应该为1.8854, 但实际是1.8855。那咱们就带入这个官方公式,看看是个怎么情况!

定义以下变量,n1=18855, n2=18854,来看看那个n带入公式最接近0

let n1 = 18855;

let n2 = 18854;

let f = 4;

let x = 1.88545000;

// 为方便查看那个更接近0,使用绝对值判断

console.log(Math.abs(n1 / Math.pow(10, f) - x));

console.log(Math.abs(n2 / Math.pow(10, f) - x));

打印结果如下:

在这里插入图片描述

一看吓一跳,原来n1为18855时带入公式最接近0,也就是说18855/10^4 最接近 x=1.88545000,到此对于toFixed()的分析就完整了。(toFixed()还是慎用啊)

解决方案:

那如何达到我们从小到大所理解的“四舍五入”,使用Math.round()即可解决:

<code>roundUp(data, size){

let a = Math.pow(10, size);

let res = Math.round(data * a) / a;

return res;

}

以为这样就可以了吗,试试roundUp(-2.5, 0),得到结果却是-2而不是-3,这是因为round在刚好舍入的为5时,则舍入到相邻的在正方向的整数。解决方法如下:

先记录数值正负,保证round操作的是正数,在最后返回时,再进行修改正负

roundUp (data, size) {

let abs = 1;

if (data < 0) abs = -1;

const _data = Math.abs(data);

let factor = Math.pow(10, size);

let result = Math.round(_data * factor) / factor;

// 这里使用toFixed的原因是为了防止舍入后round自动去除多余0的情况,

//这样达不到要求的小数位数,例如Math.round(1.4101, 3)为1.41而不是1.410,

//因此使用toFixed的作用仅仅是补充缺失的0

return (Number(result) * abs).toFixed(size).toString();

}

总结: toFixed()由于浮点数规则的限制,不能够准确的达到四舍五入的效果,IEEE754标准是计算机浮点数的表示方法,这也恰恰解释了0.1 + 0.2 != 0.3的问题,所以涉及到前端四舍五入的问题时,尽量不要使用toFixed(),使用Math.round(),但要注意Math.round()对于负数浮点数的四舍五入问题(在解决方案里有相应解决方法)

已更新至公司知识库



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。