有人说toFixed不是四舍五入而是采用了银行家舍入法?我有点不信
起因
但是我总感觉有点不对劲。有兴趣的朋友可以先点开上文看下,下文会解释toFixed到底用的什么。
四舍五入就不说了,先来看看这个银行家舍入法
原文的解释是:
-
情况1:保留位数的后一位如果小于5,则舍去。
-
情况2:保留位数的后一位如果大于5,则进。
-
情况3:保留位数的后一位如果是5
- 情况a:如果5后面仍有非0数,则直接进1。
- 情况b:若5后面不再有非0数,要根据尾数“5”的前一位决定:
- 情况x:如果前一位是奇数则进。
- 情况y:如果是偶数则舍去。
例子:
5.214.toFixed(2)
'5.21' // 情况1
5.216.toFixed(2)
'5.22' // 情况2
5.2254.toFixed(2)
'5.23' // 情况a
5.215.toFixed(2)
'5.21' // 情况x,但结果不对,5前一位是1但并未进1
5.225.toFixed(2)
'5.22' // 情况y
对此原文的解释为
5.215.toPrecision(17)
'5.2149999999999999' //计算机浮点数精度问题
计算机浮点数存在精度问题,很多文章讲过这个IEEE 754-2019。不多说了。 原文上5.215底层竟然是5.214999……,那么此时按照奇进偶舍的规则,第三位4小于5直接舍弃,就成了5.21。实则匹配到情况1.
看似挺合理的,直到1.125的出现,发现不对劲了。
1.125.toPrecision(17)
'1.1250000000000000'
1.125.toFixed(2)
'1.13' // 情况y,5后面没有数,5前面是偶数,但是并没有舍去,而是进1.
0.125这种小数,2 的-3次方 ,是可以通过二进制完美表示的,不存在精度问题。 符合银行家舍入法情况y,但是结果却不对,这是不是说明toFixed并没有采用符合银行家舍入法?。
到底toFixed怎么操作的呢?
翻开es的最新规范(ECMAScript® 2024 Language Specification (tc39.es))的伪代码看一下就知道了。
翻译一下关键步骤操作: step1-2: x 是原数字,f为参数,即要保留的位数
step2-6: 处理边界和错误
step7:将x转为R(x),其实就是以计算机内浮点数表示来看待x,这时候就会有精度问题了。这一步很关键,需要用于后面的step11的计算。
step8-10:不说了
step11:找到一个整数n,使得n / 10f - x 的结果尽可能的接近0.如果有2个n满足条件,选择更大的数作为n.最关键的一步就是找这个整数n,后面的就是把小数点放在n里面了,不多解释了。
拿1.125 .toFixed(2)来走一下上面的流程:
- x=1.125,f=2;
- 经过step7,可以认为x = 1.12500000000000...;
- 寻找整数n,使n/100-1.1250000000 最小,尽可能的接近0,有两个n会满足条件,一个是112,一个是113。取大的那个数,所以n为113,小数点放进去,最后返回1.13字符串
完美!1.125 .toFixed(2) === '1.13'
再看5.215,经过step7,x= 5.2149999999999999,再到step11,n只能找到521。所以5.215.toFixed(2) === '5.21'
总结
根据最新的规范,toFixed 采用的方法既不是四舍五入也不是银行家舍入。而是一个最近原则的的算法规则(同近取大)。
当然,也不能说原文错误,因为可能在老版本的规范或者浏览器里面他可能是采用银行家舍入法后者四舍五入法的。
所以前提条件很重要。
转载自:https://juejin.cn/post/7221434573829357623