您是对的:IEEE754 binary64 无法表示 1.20515,因此十进制 -> 二进制转换将四舍五入到最接近的值,即 1.2051499999999999435118525070720352232456207275390625。
IEEE754 标准实际上并没有说明将二进制值舍入为非整数小数(舍入到最接近的整数不会遇到此问题),因此任何此类功能都符合语言标准(如果它选择来定义它)。JavaScripttoFixed
清楚地将其定义为精确的数学值(即 1.2051)。
(更新:实际上,IEEE754 标准确实指定了应该如何执行 FP -> 字符串转换,请参阅下面的 Stephen Canon 的评论)。
但是,如果您想对整个管道进行正确的舍入,您可以改为
function roundeven(x) {
return Math.sign(x)*((Math.abs(x) + 4.503599627370496e15) - 4.503599627370496e15);
}
roundeven(Math.round(parseFloat(s)*1e6)/1e2)/1e4;
只要s
少于 16 位(即绝对值小于 10 9),它就可以工作。
为什么会这样?
Math.round(parseFloat(s)*1e6)
是准确的:这是因为 binary64 可以正确地往返最多 15 个十进制数字,并且通过缩放到整数值基本上是做同样的事情。
- 除法
1e2
将涉及一些舍入(因为并非所有值都可以精确表示),但重要的是,它可以 (i) 精确地表示带有小数一半的值,并且 (ii) 不会将任何其他值四舍五入为小数一半(因为我们仍然少于 16 位小数)。
roundeven
实现对偶数舍入到最接近的整数。此实现对上述范围内的任何值都有效。
- 最后的除法将再次涉及一些舍入,但这些值将最接近正确的十进制值,因此转换回字符串(如果需要,例如 via
_.toFixed(2)
)将给出正确的结果。
(感谢 bill.cn 和 Mark Dickinson 的更正)