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

我在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"); } 

测试返回,因为与<float.Epsilon的比较总是为x,y和z为0.0f。

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

在商业产品中,当我的测试用例执行额外的设置代码(专为集成测试而设计)时,这个测试用例只会失败,其中一个非常大的系统被初始化(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等人发表了post。 往往浮动.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字线程上,这是“线程外部”float.Epsilon的输出

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

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

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

已知DirectX可以修改FPU设置。 请参阅此相关问题: 浮点精度是否可以依赖于线程?

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

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

(来自MS Partition I ,12.1.3):

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

和,

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

最后的说明:

[ 注意:当开发人员对其代码进行看似无关的修改时,使用比float32float64更宽的内部表示会导致计算结果的差异,其结果可能是从内部表示中溢出值(例如, ,在寄存器中)到堆栈上的某个位置。 结束说明 ]

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

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

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