【问题标题】:Can something in C# change float comparison behaviour at runtime? [x64]C# 中的某些内容可以在运行时更改浮点比较行为吗? [x64]
【发布时间】:2014-02-06 11:05:59
【问题描述】:

我在使用 VS 2012 .net 4.5 编译为 64 位的 Windows 7 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");
}

测试返回为与

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

在商业产品中,这个测试用例只有在我的测试用例执行额外的设置代码(为集成测试而设计)时才会失败,其中初始化了一个非常大的系统(C#、CLI 和一个非常大的 C++ 部分)。我无法在此设置调用中进一步挖掘,因为它实际上引导了所有内容。

我不知道 C# 中有什么会影响评估。

额外的怪异: 当我用 float.Epsilon 与小于或等于比较时:

if ((x * x + y * y + z * z) <= (float.Epsilon)) // this works!

然后测试成功。我尝试仅与小于和 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(当然,移植到浮点数)

【问题讨论】:

  • 听起来很像(编译器的)优化错误。您是否使用任何优化标志?
  • 您的设置代码是否使用 DirectX?这可能会影响 FPU 设置:stackoverflow.com/questions/2847906/…
  • 我看不出 C++ 中的 /fp 编译器标志如何影响 float.Epsilon 表示。 你问代码是否可以改变浮点数的行为。 /fp 文档链接到一个页面,该页面向您展示如何更改浮点控制字使程序能够根据平台更改浮点数学包中的精度、舍入和无穷大模式我>
  • 能否修改设置代码以设置 D3DCREATE_FPU_PRESERVE?或者,您可以在新线程上执行有问题的代码吗?

标签: c# .net


【解决方案1】:

当我创建一个包含您的测试的测试应用程序时,不会引发异常。这意味着它不会一帆风顺。需要进一步研究的一些想法:

  • 如果您在应用程序启动时(例如在 Main/entry 例程中)运行此测试,那么它会失败吗?
  • 如果上述情况属实,请使用运行相同测试的相同目标框架和架构开始一个新项目。如果通过了,请开始逐步添加主应用程序的位,看看是否能找到导致它失败的位。

【讨论】:

    【解决方案2】:

    如果您在调试时和在发布模式下运行时出现差异,您可能会遇到以下问题:

    (来自MS Partition I,12.1.3):

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

    和,

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

    最后一点:

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

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

    【讨论】:

    • 两次运行都在调试中,没有打开“优化代码”标志,我试图区分它的 fpu 设置是否在本机部分。 Direct x,您描述的调试问题或在 64 位上下文中在 64 位机器上使用 32 位浮点数。
    【解决方案3】:

    众所周知,DirectX 会修改 FPU 设置。请参阅此相关问题:Can floating-point precision be thread-dependent?

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

    【讨论】:

    • 鉴于这两种方法都不可行(保留 FPU 或在另一个线程中执行所有测试),我想知道一个正确的解决方案是像 stackoverflow.com/a/9393079/2416394 那样计算机器相对 epsilon 还是尝试获得一个浮点数。 Epsilon 来自从另一个线程获取它的 utils 函数(我已经测试过:))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 2018-07-05
    • 2015-04-25
    • 2021-11-18
    • 1970-01-01
    相关资源
    最近更新 更多