4

単純な数式が ( and を使用checkedしてcatch(OverflowException)) オーバーフローするかどうかを確認したいのですが、毎回 try-catch ブロックを使用する必要はありません。したがって、式 (結果ではありません!) を functioncheckOverflowに渡す必要があります。関数は、オーバーフローの場合に応じて動作します。

これは私が試みたものですが、ラムダ式の字句スコープがないように見えるため、機能しません。

static void Main(string[] args)
{
    double g, h;

    // Check if the expression g+h would overflow, *without* the need to use
    // try/catch around the expression

    // Both of the following attempts fail because there's no lexical scoping
    checkOverflow((FloatReturningExpression) delegate() {return g+h;});
    checkOverflow(() => g+h);

    Console.ReadKey();
}

private static void checkOverflow(FloatReturningExpression exp)
{
    try
    {
        checked { double f = exp(); }
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

private delegate double FloatReturningExpression();

そのための解決策はありますか?(.NET 2 で作業しますが、必ずしもそうとは限りません。)

4

1 に答える 1

7

.Net の浮動小数点数は、整数演算のようにオーバーフローしません。

それらは、Double.PositiveIfinity、Double.NegativeIfinity、または (数学演算が無効な Double.Nan になる場合に固有) に役立ちます。

これは、精度が大きく異なる 2 つの数値に直面した場合の浮動小数点の動作のために、やや複雑になることに注意してください。

Console.WriteLine(double.MaxValue);
Console.WriteLine(double.MaxValue * 2);
Console.WriteLine(double.MaxValue + 1);
Console.WriteLine(double.MaxValue + double.MaxValue);

与えます:

1.79769313486232E+308
Infinity
1.79769313486232E+308
Infinity

また、 checkOverflow 関数に何をさせたいのか明確ではありません。それが起こったと書いてください。

それだけの場合、このアプローチは機能します (私はあなたのために int に変換しました)

void Main()
{
    int a, b;
    a = int.MaxValue;
    b = 1;

    // Check if the expression a+b would overflow, *without* the need to use
    // try/catch around the expression
    checkOverflow(() => {checked { return a+b; }});    
}       

private static void checkOverflow(Func<int> exp)
{
    try
    {
        exp();
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

これが機能する理由を追加する必要
があります。checked は、変数に影響を与えるという意味でレキシカル スコープではありません。これはコンパイラによって解釈される領域であり、整数演算を行うこの内部のすべてのコードは、オーバーフロー トラップ命令を生成する必要があります。変数がどこから来るかは問題ではなく、どのコードがどこで定義されているかだけです。

あなたのメンタルモデルは次のようなものだと思います:

checked  // enter a 'checked' state where all operations 
{        // (say on the current thread) are checked

code, function calls, etc. etc

}  // leave the checked mode, all operations are now unchecked

これはチェックがどのように機能するかではなく、チェックはコンパイル時に発行される命令を定義します (一部の命令はオーバーフローをトラップし、一部の命令はトラップしません)

チェックされたブロックは、その外側で定義されたコードには影響しません。たとえば、関数を使用するだけです:

int Times2(int a)
{
    return a * 2;
}

void TheresNoDifferenceHere()
{
    checked { Times2(int.MaxValue); }
    Times2(int.MaxValue);
}

Times2 関数呼び出しは、次のように解決されます

IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  ldc.i4.2    
IL_0003:  mul         
IL_0004:  stloc.0     
IL_0005:  br.s        IL_0007
IL_0007:  ldloc.0     
IL_0008:  ret   

もし使っていたら

int Times2(int a)
{
    checked { return a * 2; }
}

IL_0000:  nop         
IL_0001:  nop         
IL_0002:  ldarg.1     
IL_0003:  ldc.i4.2    
IL_0004:  mul.ovf     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

mul と mul.ovf の使用の違いに注意してください。したがって、それを 2 回呼び出しても、後でチェックするかどうかを変更することはできません。上記の例の最初の呼び出しの周りのチェックされたブロックは、結果の IL に実際には影響しません。その内部で定義されている、重要な操作はありません。

したがって、元のアイデアは、ある場所で算術演算を定義し (チェックせずに)、別のポイントで実行することでした (関数呼び出しのように) が、「チェックされた」命令は、コンパイル時に囲まれなかったコードに影響を与えることはできません。

ラムダは、式ツリーまたは匿名デリゲートのいずれかに解決されます (おそらく、クロージャー関連の変数を保持および維持するために必要な合成クラスによってサポートされます)。どちらの場合も、それらの任意の部分のチェックされた側面は、それらが呼び出された場所ではなく、定義された場所で完全に定義されます。

于 2010-02-18T10:42:59.573 に答える