为什么此代码在.NET 4中打印False
?似乎一些意想不到的行为是由显式演员引起的。
我想要一个超出“浮动点不准确”或“不要那样做”的答案。
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后失败。
答案 0 :(得分:80)
大卫的评论是正确的,但不够强大。无法保证在同一程序中进行两次计算将产生相同的结果。
C#规范在这一点上非常明确:
可以以比操作的结果类型更高的精度执行浮点运算。例如,某些硬件体系结构支持“扩展”或“长双”浮点类型,其范围和精度比double类型更大,并使用此更高精度类型隐式执行所有浮点运算。只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现失去性能和精度,C#允许更高精度的类型用于所有浮点运算。除了提供更精确的结果外,这几乎没有任何可衡量的影响。但是,在
x * y / z
形式的表达式中,乘法产生的结果超出双范围,但随后的除法将临时结果带回双范围,表达式的计算结果更高范围格式可能会导致生成有限结果而不是无穷大。
C#编译器,抖动和运行时都具有广泛的优势,可以在任何时候,随心所欲地为您提供更准确的结果 - 它们不是必需的选择这样做是一贯的,事实上他们没有。
如果您不喜欢,请不要使用二进制浮点数;要么使用小数,要么使用任意精度。
我不明白为什么在返回float的方法中使用float进行浮动会产生差异
非常好。
您的示例程序演示了较小的更改如何导致较大的效果。您注意到在某些版本的运行时中,显式转换为float会产生与不这样做不同的结果。 当您显式转换为float时,C#编译器会向运行时提供提示“如果您正好使用此优化,请将此事件从超高精度模式中取出”。正如规范所指出的那样,这有潜在的性能成本。
这样做恰好围绕“正确答案”只是一个幸福的事故;得到了正确的答案,因为在这种情况下失去精确度恰好在正确的方向上失去。
.net 4有何不同?
你问3.5到4.0运行时间有什么区别;差别很明显,在4.0中,抖动选择在特定情况下达到更高的精度,3.5抖动选择不。这并不意味着这种情况在3.5中是不可能的;它可以在每个版本的运行时和每个版本的C#编译器中使用。你碰巧碰到的情况是,在你的机器上,他们的细节不同。但是抖动已经总是被允许进行这种优化,而且总是一直这样做。
在编译时计算常量浮点数时,C#编译器完全有权选择进行类似的优化。根据编译器的运行时状态的详细信息,常量中两个看似相同的计算可能会有不同的结果。
更一般地说,你期望浮点数应具有实数的代数性质,这与现实完全不符;他们没有那些代数属性。浮点运算甚至不是关联;他们当然不会像你期望的那样遵守乘法逆的定律。浮点数只是实数算术的近似值;一个足够接近的近似值,比如模拟物理系统,或计算汇总统计数据,或某些此类事物。
答案 1 :(得分:0)
我现在没有Microsoft编译器,Mono没有这样的效果。
据我所知GCC 4.3+ uses gmp and mpfr to calculate some stuff in compile time。 C#编译器可以对同一程序集中的非虚拟,静态或私有方法执行相同的操作。显式强制转换可能会干扰此类优化(但我认为没有理由说它不能具有相同的行为)。即它可以内联计算常量表达到某个级别(对于b()
它可能是例如直到演员表。)
GCC也有优化,如果有意义的话,可以将操作提升到更高的精度。
所以我认为两种优化都是潜在的原因。但是对于他们两个人,我认为没有理由为什么显式结果可能会有一些额外的含义,比如“更接近标准”。