Casting a result to float in method returning float changes result
为什么此代码在.NET 4中打印
我想要一个超越"浮点不准确"或"不要这样做"的答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | float a(float x, float y) { return ( x * y ); } float b(float x, float y) { return (float)( x * y ); } void Main() { Console.WriteLine( a( 10f, 1f/10f ) == b( 10f, 1f/10f ) ); } |
PS:这个代码来自于单元测试,而不是发布代码。代码是故意这样写的。我怀疑它最终会失败,但我想知道确切的时间和原因。答案证明了这种技术的有效性,因为它提供了一种超越了通常对浮点决定论理解的理解。这就是以这种方式编写这段代码的要点;深思熟虑的探索。
PPS:单元测试通过了.NET 3.5,但是在升级到.NET 4之后失败了。
大卫的评论是正确的,但不够有力。不能保证在同一程序中进行两次计算会产生相同的结果。
C规范在这一点上非常明确:
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编译器、抖动和运行时都具有广泛的特点,可以提供比规范要求的更精确的结果,无论何时,都是一时兴起的——它们不需要选择一致的结果,事实上它们不需要。
如果您不喜欢,那么不要使用二进制浮点数;要么使用小数,要么使用任意精度的定量。
I don't understand why casting to float in a method that returns float makes the difference it does
非常好。
您的示例程序演示了小的更改如何导致大的影响。您注意到,在运行时的某些版本中,强制转换为float显式给出了与不这样做不同的结果。当您显式地强制转换为float时,C编译器会提示运行时说"如果您碰巧使用这种优化,请将这件事情从超高精度模式中去掉"。正如规范所指出的,这有潜在的性能成本。
这样做刚好转到"正确答案"仅仅是一个愉快的意外;正确的答案是获得的,因为在这种情况下,失去精确性的结果是在正确的方向上丢失的。
How is .net 4 different?
您会问3.5和4.0运行时之间的区别是什么;区别很明显,在4.0中,抖动选择在特定情况下达到更高的精度,而3.5抖动选择不达到。这并不意味着这种情况在3.5中是不可能的;在运行时的每个版本和C编译器的每个版本中都是可能的。您刚刚遇到一个案例,在您的机器上,它们的细节有所不同。但这种抖动一直被允许进行这种优化,而且总是在其一时兴起时进行的。
C编译器也完全有权在编译时计算常量浮动时选择进行类似的优化。根据编译器运行时状态的详细信息,常量中两个看似相同的计算可能有不同的结果。
更一般地说,您对浮点数应该具有实数代数性质的期望完全与实际不符;它们不具有这些代数性质。浮点运算甚至都不具有关联性;它们当然不会像您期望的那样遵守乘法逆的规律。浮点数只是实数的一种近似值;一种非常接近的近似值,可以用来模拟物理系统、计算汇总统计数据或类似的事情。
我现在没有微软编译器,Mono也没有这样的效果。据我所知,GCC4.3+使用gmp和mpfr在编译时计算一些东西。C编译器可以对同一程序集中的非虚拟、静态或私有方法执行相同的操作。显式强制转换可能会干扰这种优化(但我看不出它不能具有相同行为的原因)。也就是说,它可能与计算某个级别的常量表达式(对于
GCC还有一个优化,如果这有意义的话,它可以将操作提升到更高的精度。
所以我认为这两种优化都是潜在的原因。但对于这两种情况,我看不出任何理由,为什么对结果进行显式的投射可能有一些额外的含义,比如"更接近标准"。