浮点运算总是偏离零吗?

Does floating-point arithmetic always err away from zero?

在 C#/.NET 中,表达式 String.Format("{0:R}", 0.1 * 199) 产生 19.900000000000002.

因为它是一个浮点数,我显然从没想过会得到一个精确的“19.9”结果。然而,从我的测试来看,错误似乎总是积极的,而不是消极的。也就是说,我的结果总是比应有的大一点点,绝不会小一点点。

我可以一直指望这种行为吗?还是我只是做错了测试?

(我认为这是与语言无关的原则,并非 C#/.NET 独有)

任何单一的操作都是为了最接近 floating-point 表示的实际结果;尝试 0.1 + 0.7.

Floating-point 算术有多个舍入行为。最常见的默认行为是将精确的数学结果舍入到最接近的可表示值,如果出现平局,则舍入到具有更低数字的值。

IEEE-754 floating-point 标准定义的其他舍入行为是:

  • 向零舍入。
  • 四舍五入(向+∞)。
  • 向下舍入(向-无穷大)。
  • 四舍五入到最接近的可表示值,但如果出现平局,则从 0 舍入。

如您所见,其中 none 是“从零舍入”,因此它不是标准化的舍入行为,您不太可能在常见的硬件或软件中找到它。但是,the Wikipedia page on rounding 讨论了它和其他行为。

虽然常见的默认设置是 round-to-nearest ties-to-even,但这与语言无关。每种编程语言 and/or 每个计算平台都可以选择它提供的舍入行为以及默认的舍入行为。

它不是绝对的语言不可知论,但仍然是一种实际的解释。在 C# 中,运行时支持的唯一舍入是 roundToNearestTiesEven(IEEE754 术语)。任何其他模式都需要丑陋的黑客作为处理器模式或专用库的不安全切换。这并不孤单。 Java、Python 和许多其他人也是如此。 (对于 Decimal 来说,这不一定是真的,它有自己的细节。)实际上,与 "unaware" 语言相比,明确定义舍入控制支持(如 C、C++)的语言要少。

有关其他详细信息,我会回复@Ry 和@EricPostpischil。 0.1 + 0.7 的示例适用于 double 值的舍入,即相同的 roundToNearestTiesToEven 确实向零舍入。

谈到 C#,人们还会注意到 32 位 x86 C# 有其自身与内部 FPU 处理相关的细节,可以对中间值使用更高精度。 Here is example that comparing of 0.1+0.2 converted to Single isn't equal to the same value remaining Double in FPU, and here 是它在 C 中的再现。