将结果转换为float方法返回float更改结果

Rz *_* Mk 53 .net c# floating-point casting .net-4.0

为什么此代码False在.NET 4中打印?似乎一些意外的行为是由显式演员引起的.

我想要一个超越"浮动点不准确"或"不要那样做"的答案.

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 ) );
}
Run Code Online (Sandbox Code Playgroud)

PS:此代码来自单元测试,而不是发布代码.代码是故意以这种方式编写的.我怀疑它最终会失败,但我想知道确切的时间和原因.答案证明了这种技术的有效性,因为它提供的理解超出了对浮点确定性的通常理解.这就是用这种方式编写代码的重点; 刻意探索.

PPS:单元测试在.NET 3.5中传递,但现在在升级到.NET 4后失败了.

Eri*_*ert 82

大卫的评论是正确的,但不够强大.无法保证在同一程序中进行两次计算会产生相同的结果.

在这一点上,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#编译器完全有权选择进行类似的优化.根据编译器的运行时状态的详细信息,常量中两个看似相同的计算可能会有不同的结果.

更一般地说,你期望浮点数应该具有实数的代数性质,这与现实完全不符; 他们没有那些代数属性.浮点运算甚至不是关联的 ; 他们当然不会像你期望的那样遵守乘法逆的定律.浮点数只是实数算术的近似值; 一个足够接近的近似值,比如模拟物理系统,或计算汇总统计数据,或某些此类事物.

  • 不幸的是我已经知道浮点不准确了.我不知道的是关于铸造和优化的一切.如果我知道答案,那么提出正确的问题会更容易. (7认同)
  • @CodeInChaos:这个功能大大超过了我在这里的时间.我发现它也令人惊讶.使用强制转换与在CLR中实现该功能的方式一致,即发出conv.r4或conv.r8指令. (4认同)
  • 我不喜欢将"不准确"这个术语应用于浮点数.`(float)1`是100%准确的.添加小整数并乘以具有正常结果的2的幂也是100%准确的.这个例子中的差异不是'不准确'计算的结果,而是(正如Eric详细解释的那样)语言和运行时选择某些实现细节的自由. (2认同)
  • 我从没想过铸造浮子本身有什么影响.您有没有理由选择这样一个令人惊讶的功能,而不是使用像Math.ForceToSingle这样的内置方法? (2认同)