关于c#:浮点运算是否稳定?

Is floating point arithmetic stable?

本问题已经有最佳答案,请猛点这里访问。

我知道浮点数有精度,精度后的数字不可靠。

但是如果用来计算数字的公式是一样的呢?我能假设结果也一样吗?

例如,我们有两个浮点数xy。我们可以假设机器1的结果x/y与机器2的结果完全相同吗?即,==比较将返回真值。


But what if the equation used to calculate the number is the same? can I assume the outcome would be the same too?

不,不一定。

特别是,在某些情况下,允许JIT使用更准确的中间表示法(例如,原始数据为64位时为80位),而在其他情况下则不允许使用。当以下任何一项为真时,这可能会导致看到不同的结果:

  • 代码稍有不同,例如使用局部变量而不是字段,这可以更改值是否存储在寄存器中。(这是一个相对明显的例子;还有一些更微妙的例子可以影响事物,比如方法中存在一个try块…)
  • 您在不同的处理器上执行(我曾经观察过AMD和Intel之间的差异;同一制造商的不同CPU之间也可能存在差异)
  • 您正在使用不同的优化级别执行(例如,是否在调试器下执行)

根据C 5规范第4.1.6节:

Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an"extended" or"long double" floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type. Only at excessive cost in performance can such hardware architectures be made to perform floating-point operations with less precision, and rather than require an implementation to forfeit both performance and precision, C# allows a higher precision type to be used for all floating-point operations. Other than delivering more precise results, this rarely has any measurable effects. However, in expressions of the form x * y / z, where the multiplication produces a result that is outside the double range, but the subsequent division brings the temporary result back into the double range, the fact that the expression is evaluated in a higher range format may cause a finite result to be produced instead of an infinity.


乔恩的回答当然是正确的。然而,没有一个答案说明了如何确保浮点运算在规范保证的精度范围内完成,而不是更多。

c在以下情况下自动截断任何浮点,使其回到标准的32或64位表示:

  • 您放入了一个冗余的显式转换:x + y可能有x和y作为随后添加的更高精度数字。但是,(double)((double)x+(double)y)确保在数学发生之前和之后,所有内容都被截断为64位精度。
  • 类、静态字段、数组元素或取消引用的指针的实例字段的任何存储都将截断。(存储到本地、参数和临时存储不能保证截断;它们可以注册。结构的字段可能位于短期池中,也可以注册。)

这些保证不是由语言规范做出的,但是实现应该遵守这些规则。C和clr的微软实现是这样的。

编写代码以确保浮点运算在C中是可预测的,这是一件痛苦的事情,但它是可以做到的。注意这样做可能会减慢你的算术速度。

对这种糟糕情况的抱怨应该针对英特尔,而不是微软;他们设计的芯片使可预测的运算速度变慢。

另外,请注意这是一个常见问题。您可以考虑将此关闭为以下内容的副本:

为什么C中的浮点精度在用偏执词分隔和用语句分隔时不同?

为什么这个浮点计算在不同的机器上给出不同的结果?

在返回float changes结果的方法中将结果强制转换为float

(.1f+.2f=.3f)!=(.1f+.2f).equals(.3f)为什么?

强制浮点在.NET中具有确定性?

C XNA Visual Studio:"发布"和"调试"模式之间的区别?

C-32位和64位的数学运算结果不一致

C中的舍入误差:不同PC上的不同结果

具有浮点文字与浮点变量的奇怪编译器行为


坦率地说,我不希望同一代码库中的两个位置在同一台机器上的同一进程中返回相同的x/yxy;这取决于xy如何被编译器/JIT优化-如果它们注册不同,它们可以有不同的中等精度。许多操作都是使用比预期更多的位(寄存器大小)来完成的;而当强制降到64位时,这会影响结果。"确切时间"的选择取决于周围代码中还发生了什么。


不,不是。每个CPU的计算结果可能不同,因为浮点运算的实现可能因每个CPU制造商或CPU的不同而不同。设计。我甚至还记得一些英特尔处理器的浮点运算中的一个错误,它把我们的计算搞砸了。

然后,在如何使用JIT编译器对代码进行评估方面存在差异。


理论上,在符合IEEE754标准的系统上,相同输入值的相同操作应该产生相同的结果。

正如维基百科总结的那样:

The IEEE 754-1985 allowed many variations in implementations (such as the encoding of some values and the detection of certain exceptions). IEEE 754-2008 has strengthened up many of these, but a few variations still remain (especially for binary formats). The reproducibility clause recommends that language standards should provide a means to write reproducible programs (i.e., programs that will produce the same result in all implementations of a language), and describes what needs to be done to achieve reproducible results.

然而,和往常一样,理论不同于实践。大多数常用的编程语言(包括C语言)并不严格符合IEEE754,也不一定提供编写可复制程序的方法。

此外,现代CPU/FPU使得确保严格遵守IEEE754有些尴尬。默认情况下,它们将以"扩展精度"操作,在内部存储的值比双精度值多。如果您想要严格的语义,您需要将值从FPU中提取到CPU寄存器中,检查并处理各种FPU异常,然后在每个FPU操作之间将值压回。由于这种尴尬,即使在硬件级别,严格的一致性也会有性能损失。C标准选择了一个更"草率"的要求,以避免对较小变化不是问题的更常见情况施加性能惩罚。

在实践中,这些都不是经常出现的问题,因为大多数程序员已经将浮点数学是不精确的(不正确的,或者至少是误导性的)思想内化了。此外,我们在这里讨论的错误都非常小,足以使它们与从十进制转换而导致的更常见的精度损失相形见绌。


除其他答案外,即使在同一台机器上,也有可能是x/y != x/y

x86中的浮点计算是使用80位寄存器完成的,但存储时被截断为64位。所以可以计算第一个除法,截断,然后重新加载到内存中,并与未截断的除法进行比较。

在这里查看更多信息(它是C++链接,但推理是相同的)