为什么不同语言对 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 条回复
masterjoess
293 天前
因为 toFixed 不是四舍五入
Puteulanus
293 天前
104.06 感觉像奇进偶舍的结果
henix
293 天前
js 的四舍五入应该是:Math.round(99.1*1.05*100)/100 // => 104.06
codehz
293 天前
因为 ruby 的实现里
https://github.com/ruby/ruby/blob/master/numeric.c#L2536C13-L2536C25
发现数字的小数位数大于 14 位就会用另一个算法了
jhdxr
293 天前
因为这些不是一个问题/bug 而是一个 feature 。

Floating Point Precision ,属于我能想到的最常见的科班知道而非科班甚至不会意识到这东西存在的一个问题。
thinkershare
293 天前
浮点数常用的舍入有 6/7 种,每种舍入都有自己的具体使用场景。你先搞清楚,每个方法的具体舍入算法再看。
只要使用的是 IEEE64 ,则最终同一个算法的结果应该是相同的。
unspring
293 天前
@henix 这个做法感觉更像是 double 转成 long 再转回来,感觉不是通用解法

程序员在意识到这个问题之前不会这么处理,而这个问题也难以发现
unspring
293 天前
@codehz 震惊,居然能迅速翻到源码

js 对这种场景居然就没处理吗
unspring
293 天前
@masterjoess toFixed 会自动四舍五入,而且 mathjs 和 lodash 也是一样会算成 104.05
wildnode
293 天前
首先,Node.js 不是一门语言,它只是 JS 的一个运行时,所以本质上是 JS 浮点数精度问题

```js
let num = 99.1 *1.05;
let adjustedNum = num + (Number.EPSILON * Math.pow(10, 2));
console.log(adjustedNum.toFixed(2)); // 104.06
```

这样可以实现你想要的效果,但是有风险,不能用于生产。

生产推荐使用 decimal.js 或者 big.js
aloxaf
293 天前
@unspring #8 实际上大部分语言对浮点数运算都没有特殊处理,因为二进制就是没办法精确表示十进制小数,你压根处理不完特殊情况。0.1 + 0.2 都不等于 0.3 ,也没有哪个语言为浮点数重载一下等于号。
你要是追求精确,就不该用浮点数。
lyxxxh2
293 天前
某篇文章:
在 Python3 中 ,round()函数 并不是四舍五入,而是四舍六入五成双,遵循向偶数靠拢的原则,为奇数则进 1 ,为偶数则不进位。


所以我不用 round,自己封装 fixed
```
function toFixed($str, $precision)
{
return number_format((float) $str, $precision, '.', '');
}
```
nitmali
293 天前
toFixed(2)就是在第三位上四舍五入
codehz
293 天前
@unspring 那个方法效率很低啊(可能慢数十倍),而且也不是彻底的方案,因为其实一开始出现这个数字就表示前面计算的结果已经无法精确表达了,还有很多边界条件没处理到
nitmali
293 天前
而且其中还涉及到一个银行家算法,不是传统意义上的四舍五入。
masterjoess
293 天前
@unspring 如果你觉得 IEEE 754 银行家舍入也算(小学学的)四舍五入,那你确定说的对
sankooc
293 天前
sankooc
293 天前
(2.34).toFixed(1); // '2.3'
(2.35).toFixed(1); // '2.4'; it rounds up
(2.55).toFixed(1); // '2.5'
// it rounds down as it can't be represented exactly by a float and the closest representable float is lower
(2.449999999999999999).toFixed(1); // '2.5'
// it rounds up as it's less than Number.EPSILON away from 2.45.
// This literal actually encodes the same number value as 2.45
---
toFixed 方法很明显不是咱们理解的四舍五入
realJamespond
293 天前
二进制没法表示示例中的 10 进制小数,所以有误差,相当于 10 进制表示 1/3
min
293 天前
“感觉 nodejs 这么流行的语言不太会出现这种问题”

对 js 的严谨程度这么有信心吗?

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

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

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

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

© 2021 V2EX