2

假设我1.20515使用默认的四舍五入规则将数字舍入到 IEEE 兼容语言(C、Java 等)中的小数点后 4 位,结果将是“1.2051”,它不是偶数。

1.20515我认为这是由于以二进制形式存储时略微偏向于这样一个事实1.2051,因此二进制空间中甚至没有平局。

但是,如果输入1.20515是精确的小数,这种舍入实际上是不是错误的?

编辑:

我真正想知道的是,如果我不想使用精确的十进制算术(例如 Java 的BigDecimal),这些二进制舍入规则是否会在工作流程中引入偏差:字符串中的精确十进制(最大 6 dp)-> 解析为 IEEE double- > 使用 IEEE 规则舍入到 4 dp

编辑2:

“精确十进制”输入由 Java 使用BigDecimalString直接来自数据库生成。不幸的是,格式化必须在 JavaScript 中完成,它缺乏对正确舍入的很多支持(我正在考虑实现一些)。

4

1 回答 1

2

您是对的: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 的更正)

于 2015-10-23T16:26:38.270 回答