3

私のアルゴリズムは、単精度浮動小数点演算のイプシロンを計算しています。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値で実行されると思います。もちろん、この場合の結果は間違っています!

また、これは、プロジェクトオプションでX86リリースをターゲットにしている場合にのみ発生します。繰り返しますが、最適化のオン/オフは重要ではありません。私は64ビットWIN7、VS 2010 Ultimateを使用しており、.NET4.0をターゲットにしています。

何がその振る舞いを引き起こす可能性がありますか?いくつかのWOWの問題?信頼できる方法でそれを回避する方法は?単精度計算の代わりに倍精度を使用するコードをCLRが生成しないようにするにはどうすればよいですか?

注:ここで問題が発生しなくても、プラットフォームターゲットとして「AnyCPU」または「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 に答える