.net在编译时和运行时浮动舍入错误

时间:2016-01-26 17:18:22

标签: c# .net casting type-conversion

我最近为测试用例设置了一些数据,用于检查浮点数据类型的舍入错误,并遇到了一些意外的结果。我预计情况t2和t3会产生与t1相同的结果,但在我的机器上并非如此。谁能告诉我为什么?

我怀疑差异的原因是在编译时评估了t2和t3,但我很惊讶编译器完全忽略了我在评估期间强制它使用中间浮点数据类型的尝试。 c#标准中是否有一部分要求使用最大可用数据类型来评估常量,而不管指定的数据类型是什么?

这是在运行.net 4.5.2的win7 64位英特尔机器上。

  float temp_t1 = 1/(3.0f);
  double t1 = (double)temp_t1;

  const float temp_t2 = 1/(3.0f);
  double t2 = (double)temp_t2;

  double t3 = (double)(float)(1/(3.0f));

  System.Console.WriteLine( t1 ); //prints 0.333333343267441
  System.Console.WriteLine( t2 ); //prints 0.333333333333333
  System.Console.WriteLine( t3 ); //prints 0.333333333333333

2 个答案:

答案 0 :(得分:2)

人们经常对浮点计算的一致性有疑问。在这一点上,.NET Framework几乎没有任何保证。 To quote Eric Lippert

  

C#编译器,抖动和运行时都具有广泛的优势,可以随时随地为您提供比规范要求的更准确的结果 - 他们不需要选择一致地执行此操作事实上他们没有。

在这种特殊情况下,答案是直截了当的。发布版本的原始IL:

IL_0000: ldc.r4 0.333333343
IL_0005: conv.r8
IL_0006: ldc.r8 0.33333333333333331
IL_000f: stloc.0
IL_0010: ldc.r8 0.33333333333333331
IL_0019: stloc.1
IL_001a: call void [mscorlib]System.Console::WriteLine(float64)
IL_001f: ldloc.0
IL_0020: call void [mscorlib]System.Console::WriteLine(float64)
IL_0025: ldloc.1
IL_0026: call void [mscorlib]System.Console::WriteLine(float64)
IL_002b: ret

这里的所有算术都是由编译器完成的。在Roslyn编译器中,temp_t1是一个变量这一事实导致编译器发出IL,它加载一个4字节的float,然后将其转换为double。我相信这与以前的版本一致。在另外两种情况下,编译器以双精度执行所有算术并存储这些结果。因为编译器不在IL中保留局部常量,所以第二种和第三种情况没有区别也就不足为奇了。

答案 1 :(得分:-1)

C#浮点行为基于底层CPU,使用IEEE 754格式。如果你想真正看到发生了什么,你需要通过将它们转换为字节来查看二进制格式的数字。当你打印它们时,它们会从基数2转换为基数10,并且你正在进行大量的处理。

这是我怀疑正在发生的事情。您的第一个计算(temp_t1)使用单精度浮点,尾数为23位。我怀疑,但没有确认,temp_t2和t2已被编译器中的优化组件转换,因此temp_t2不是使用单精度浮点计算的,而是双精度计算,t2拾取该值。

有关浮点行为的更多信息:https://msdn.microsoft.com/en-us/library/aa691146(v=vs.71).aspx