3

我的算法正在计算单精度浮点运算的epsilon 。它应该在 1.1921e-007 左右。这是代码:

static void Main(string[] args) {
    // start with some small magic number
    float a = 0.000000000000000013877787807814457f;
    for (; ; ) {
        // add the small a to 1
        float temp = 1f + a;
        // break, if a + 1 really is > '1'
        if (temp - 1f != 0f) break;
        // otherwise a is too small -> increase it
        a *= 2f;
        Console.Out.WriteLine("current increment: " + a); 
    }
    Console.Out.WriteLine("Found epsilon: " + a); 
    Console.ReadKey(); 
}

在调试模式下,它给出以下合理的输出(缩写):

current increment: 2,775558E-17
current increment: 5,551115E-17
...
current increment: 2,980232E-08
current increment: 5,960464E-08
current increment: 1,192093E-07
Found epsilon: 1,192093E-07

但是,当切换到发布模式时(无论有/无优化!),代码给出以下结果:

current increment: 2,775558E-17
current increment: 5,551115E-17
current increment: 1,110223E-16
current increment: 2,220446E-16
Found epsilon: 2,220446E-16

这对应于double precision的值。所以我假设,一些优化会导致对双精度值进行计算。在这种情况下,结果当然是错误的!

另外:只有在项目选项中以X86 Release 为目标时才会发生这种情况。同样:优化开/关无关紧要。我使用的是 64 位 WIN7,VS 2010 Ultimate,目标是 .NET 4.0。

什么可能导致这种行为?一些WOW问题?如何以可靠的方式绕过它?如何防止 CLR 生成使用双精度而不是单精度计算的代码?

注意:切换到“Any CPU”甚至“X64”作为平台目标是没有选择的——即使这里没有出现问题。但是我们有一些本地库,有不同版本的 32/64 位。所以目标必须是具体的。

4

1 回答 1

3

正如评论中所讨论的,这是意料之中的。可以通过删除 JIT 将值保存在寄存器中的能力(这将比实际值更宽)来回避 - 通过将其强制到一个字段(具有明确定义的大小):

class WorkingContext
{ 
    public float Value; // you'll excuse me a public field here, I trust
    public override string ToString()
    {
        return Value.ToString();
    }
}
static void Main()
{
    // start with some small magic number
    WorkingContext a = new WorkingContext(), temp = new WorkingContext();
    a.Value = 0.000000000000000013877787807814457f;
    for (; ; )
    {
        // add the small a to 1
        temp.Value = 1f + a.Value;
        // break, if a + 1 really is > '1'
        if (temp.Value - 1f != 0f) break;
        // otherwise a is too small -> increase it
        a.Value *= 2f;
        Console.Out.WriteLine("current increment: " + a);
    }
    Console.Out.WriteLine("Found epsilon: " + a);
    Console.ReadKey();
}

有趣的是,我首先用一个结构体尝试了这个,但是 JIT 能够看穿我的作弊行为(大概是因为它都在堆栈上)。

于 2011-06-08T11:47:09.807 回答