为什么不同语言对 99.1*1.05 的四舍五入结果不一样

293 天前
 unspring

今天开发的时候碰到一个问题

nodejs 在计算

(99.1 *1.05).toFixed(2)

时的输出是 104.05

而 Ruby 计算

(99.1*1.05).round(2)

时的输出是 104.06

我还试了下其他语言 Python 和 nodejs 是一样的

Excel 和 Ruby 的输出是一样的

99.1*1.05 的结果是 104.05499999999999 但不同语言对这个数字的舍入处理却不同

感觉 nodejs 这么流行的语言不太会出现这种问题

发个贴来问下大家的看法

3992 次点击
所在节点    程序员
38 条回复
codehz
293 天前
https://bugs.ruby-lang.org/issues/14635
看了一下关联的 issue ,这个可能和浮点数通常的问题有些不一样
yesterdaysun
293 天前
https://en.wikipedia.org/wiki/Rounding
可选的舍入方式有 6 种, 常说的四舍五入对应 infinity 这种, 在 c#里面也叫 AwayFromZero, 但是这个会有统计学误差, 所以另一种常见的舍入方式是 even, c#里叫 ToEven, Java 里叫 HalfEven, 也就是上面有人提到的银行家舍入

不同的语言, 不同的函数使用的舍入规则都是不一样, 比如 toFixed 和 Math.round 用的就是不一样的, MySQL 的 decimal 和 float 规则不一样, 如果追求 100%精确的话就得去看文档他们用的到底是哪一种方案, 或者 Java/c#这种可以有选项让你控制使用哪一种舍入规则
mxT52CRuqR6o5
293 天前
按照四舍五入规则的话最终结果应该是 104.05 ,但受限于浮点数精度问题计算结果不准导致舍入问题
如果是和钱有关的场景就不能直接用浮点数去算(所有语言都是),比如 js 里可以用 dicimal.js
https://mikemcl.github.io/decimal.js/
在控制台中运行
Decimal('99.1').mul('1.05').toFixed(2)
可以得到 104.06
54xavier
293 天前
不就是浮点运算精度的问题吗?
vituralfuture
293 天前
私以为不是浮点数精度问题而是输出时的截断策略问题,各种语言应当遵守 IEEE 754 ,也就是浮点数的二进制表示方法是相同的,同一架构下浮点数的计算方法也应该是相同的,只是一般输出时自动截断小数点后多少位,截断的过程包括了舍入,而不同语言截断的策略不同,输出自然不同

如何验证?
使用各种语言计算这个值,将得到的浮点数的二进制表示输出,注意输出的应该是 32 位的二进制。然后逐字节比较,应当是完全相同的

另外楼上提到的浮点数精度问题,在无法容忍浮点数带来的误差的场景下,应该使用十进制数,这个在许多语言都有提供,只是性能低很多
tool2d
293 天前
我程序是自研算法,对于 104.05499999999999 这类的数字,在最后一位有效位,进行四舍五入处理,这样就变成了 104.05500000000, 然后把尾巴 0 去掉。

这样付出的代价,是浮点精度少一位,换来的是大部分情况下,整整齐齐的小数。
charlie21
293 天前
js 四舍五入用 Intl.NumberFormat (大约 2021 年开始被广泛使用)

https://developer.aliyun.com/article/1377609 像这种提都没提过 Intl 的呢这是老文章了

https://juejin.cn/post/6979515365227233294

https://zhuanlan.zhihu.com/p/356991916?
v21984
293 天前
toFixed 四舍六入五留双
hcwhan
293 天前
因为 toFixed 不是四舍五入 使用的是银行家舍入 需要四舍五入用 Math.round

1 , 四舍五入
当舍去位的数值大于等于 5 时,在舍去该位的同时向前位进一;当舍去位的数值小于 5 时,则直接舍去该位。
2 , 银行家舍入
所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。其规则是:当舍去位的数值小于 5 时,直接舍去该位;当舍去位的数值大于等于 6 时,在舍去该位的同时向前位进一;当舍去位的数值等于 5 时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。
liuhuihao
293 天前
@hcwhan @v21984 toFixed 既不是银行家舍入也不是标准四舍五入,而是有一套自己的规则。

测试 (2.335).toFixed(2) => '2.33'
按照银行家的舍入规则应该是 '2.34'

测试 (2.55).toFixed(1) => '2.5'
按照标准四舍五入应该是 '2.6'
orange2023
293 天前
@liuhuihao 尝试(2.55)%1 得到小数部分
orange2023
293 天前
我认为不能认为一个 float 比如 1.15 它真的就是 1.15 啊,可能实际是 0.1499999999999999
jsjhlk
293 天前
unspring
293 天前
确实按照四舍六入五成双的算法来算,结果应该是 104.05

但是手写竖式计算结果是 104.055
四舍五入后显然是 104.06

这意味着包括 toFixed ,mathjs.round 在内的方法对这个数字的 rounding 都是不正确的

这意味着 js 的浮点运算实际上并不准确,会出现小数点后两位之内的误差
unspring
293 天前
至少是和大家通常使用的普遍意义上的舍入是不同的
lscho
293 天前
稍微了解点 js 就知道 toFixed 和 round 不是一回事
CRVV
293 天前
这两个浮点数的精确值,实际上
99.1*1.05 是 104.0549999999999926103555480949580669403076171875
104.055 是 104.05500000000000682121026329696178436279296875
这两个数字之间没有其它的 float64 了

这两个数字都不是刚好一半的情况,所以和舍入规则没关系
不论用不用 银行家舍入,round(99.1*1.05, 2) 都是 104.05 ,round(104.055, 2) 都是 104.06

Excel 可能是把 99.1*1.05 的结果直接算成了后面那个 104.05500000000000682121026329696178436279296875 ,然后再 round 当然就得到了 104.06
Excel 应该也能正确处理各种 .1+.2 == .3 的情况

这个问题在 Python 文档里面说得很清楚,https://docs.python.org/3/library/functions.html#round

> Note: The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.


Ruby 好像是额外处理了这种情况,Ruby 的 2.675.round(2) 是 2.68

irb(main):001:0> 104.054999999999978399500832892954349517822265625.round(2)
=> 104.05
irb(main):002:0> 104.0549999999999926103555480949580669403076171875.round(2)
=> 104.06
这个行为对我来说很 surprising ,还没写在文档里面。
https://ruby-doc.org/core-2.5.1/Float.html#method-i-round
CRVV
293 天前
顺便一说

十进制
99.1*1.005 = 99.5955
round(99.5955, 3) = 99.596
我猜 Excel 能得到这个结果

Ruby
irb(main):005:0> (99.1*1.005).round(3)
=> 99.595

其它语言当然也是 99.595

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1020406

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX