C#中的某些内容可以在运行时更改浮点比较行为吗? [64]

时间:2014-01-17 07:45:00

标签: c# .net

我在Windows 7 64位的商业产品中使用我们的测试代码遇到了一个非常奇怪的问题,VS 2012 .net 4.5编译为64位。

以下测试代码在单独项目中执行时表现如预期(使用NUnit测试运行器):

[Test]
public void Test()
{
    float x = 0.0f;
    float y = 0.0f;
    float z = 0.0f;

    if ((x * x + y * y + z * z) < (float.Epsilon))
    {
        return;
    }
    throw new Exception("This is totally bad");
}

测试作为与&lt;的比较返回。 float.Epsilon对于x,y和z始终为true,为0.0f。

现在这是奇怪的部分。当我在商业产品的上下文中运行此代码时,此测试失败。我知道这听起来多么愚蠢,但我得到了异常抛出。我调试了这个问题,当我评估条件时它总是正确但编译后的可执行文件仍然不会进入条件的真正分支并抛出异常。

在商业产品中,此测试用例仅在我的测试用例执行额外的设置代码(专为集成测试而设计)时失败,其中初始化非常大的系统(C#,CLI和非常大的C ++部分)。我不能在这个设置调用中进一步挖掘,因为它几乎可以引导一切。

我不知道C#中有任何会影响评估的内容。

奖金古怪: 当我与float.Epsilon:

的较小或相等时
if ((x * x + y * y + z * z) <= (float.Epsilon)) // this works!
然后测试成功。我试过比较只有less-than和float.Epsilon * 10,但这不起作用:

if ((x * x + y * y + z * z) < (float.Epsilon*10)) // this doesn't!

我一直在谷歌上搜索这个问题,尽管Eric Lippert等人发表了帖子。往往会浮动.Epsilon我不完全明白对我的代码应用了什么效果。 它是一些C#设置,大规模本机到管理,反之亦然影响系统。 CLI中的东西?

编辑:还有更多要发现的事情: 我已使用此MSDN页面http://msdn.microsoft.com/en-us/library/system.single.epsilon%28v=vs.110%29.aspx中的GetComponentParts来查看我的mantiassa结束指数,结果如下:

测试代码:

 float x = 0.0f;
 float y = 0.0f;
 float z = 0.0f;
 var res = (x*x + y*y + z*z);
 Console.WriteLine(GetComponentParts(res));
 Console.WriteLine();
 Console.WriteLine(GetComponentParts(float.Epsilon));

没有我获得的整个boostrap链(测试通过)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

1.401298E-45: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

完全自举链我得到(测试失败)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

需要注意的事项:float.Epsilon在其尾数中丢失了最后一位。

我无法看到C ++中的/ fp编译器标志如何影响float.Epsilon表示。


编辑和最终裁决 虽然可以使用单独的线程来获取float.Epsilon,但它在FPU字减少的线程上的行为将与预期的不同。

在缩小的FPU字线程上,这是“thread-foreign”float的输出.Epsilon

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

请注意,最后一个尾数位是预期的1,但是这个浮点值仍将被解释为0.这当然有意义,因为我们使用的浮点精度比FPU字集大但可能是某人的陷阱。

我决定转移到计算机fps,计算一次如下所述:https://stackoverflow.com/a/9393079/2416394(移植到浮动当然)

3 个答案:

答案 0 :(得分:11)

已知DirectX可以修改FPU设置。请参阅此相关问题:Can floating-point precision be thread-dependent?

您可以通过在调用D3DCREATE_FPU_PRESERVE时指定CreateDevice标志或在新线程上执行浮点代码来告诉DirectX保留FPU设置。

答案 1 :(得分:3)

如果您在调试时和在发布模式下运行时遇到差异,则可能会违反以下规定:

(来自MS Partition I,12.1.3):

  

浮点数(静态,数组元素和类的字段)的存储位置具有固定大小......其他地方(在评估堆栈上,作为参数,如   返回类型,并作为局部变量)使用内部浮点表示浮点数   类型。 ... 它的   值可以在内部用额外的范围和/或精度表示

  

当浮点值的内部表示具有比其标称值更大的范围和/或精度时   type被放入存储位置,它会自动强制转换为存储位置的类型。这可能涉及   精度损失或创建超出范围的值

和最后的说明:

  

[注意:使用宽于float32float64的内部表示可能导致   当开发人员对他们的代码进行看似无关的修改时的计算结果   这可能是一个值从内部表示(例如,在寄存器中)溢出到一个位置   堆。 结束记录]

调试通常会导致很多修改 - 您倾向于使用不同的优化,而且更有可能导致此类溢出。

答案 2 :(得分:2)

当我创建包含测试的测试应用程序时,不会抛出异常。这意味着它不会直截了当。一些进一步调查的想法:

  • 如果您在应用程序启动时运行此测试(例如在Main / entry例程中),那么它是否会失败?
  • 如果以上情况属实,请使用相同的目标框架启动新项目&amp;运行相同测试的架构。如果它通过,则开始逐渐添加主应用程序的位,并查看是否可以找到使其失败的位。