22

それはすべて、誰かが私に提起したひっかけ問題から始まりました.. (本で言及されています - 一言で言えばC#) これがその要点です。

Double a = Double.NaN;
Console.WriteLine(a == a); // => false
Console.WriteLine(a.Equals(a)); // => true

上記は正しくないようです。a は常にそれ自体に対して == である必要があり (参照の等価性)、両方が一貫している必要があります。

Double は == 演算子をオーバーロードしているようです。次のようにリフレクターによって確認されました。

[__DynamicallyInvokable]
public static bool operator ==(double left, double right)
{
    return (left == right);
}

再帰的に見え、NaN 固有の動作について言及されていないのは奇妙です。では、なぜ false を返すのでしょうか?

だから私は区別するためにいくつかのコードを追加します

var x = "abc";
var y = "xyz";
Console.WriteLine(x == y); // => false

わかりました

    L_0001: ldc.r8 NaN
    L_000a: stloc.0 
    L_000b: ldloc.0 
    L_000c: ldloc.0 
    L_000d: ceq 
    L_000f: call void [mscorlib]System.Console::WriteLine(bool)
    L_0014: nop 
    L_0015: ldloca.s a
    L_0017: ldloc.0 
    L_0018: call instance bool [mscorlib]System.Double::Equals(float64)
    L_001d: call void [mscorlib]System.Console::WriteLine(bool)
    L_0022: nop 
    L_0023: ldstr "abc"
    L_0028: stloc.1 
    L_0029: ldstr "xyz"
    L_002e: stloc.2 
    L_002f: ldloc.1 
    L_0030: ldloc.2 
    L_0031: call bool [mscorlib]System.String::op_Equality(string, string)
    L_0036: call void [mscorlib]System.Console::WriteLine(bool)
  • double の場合、== 演算子呼び出しはceqIL オペコードに変換されます
  • 文字列については、System.String::op_Equality(string, string) に変換されます。

確かに、のドキュメントでceqは、浮動小数点数と NaN の特殊なケースであると指定されています。これは観察を説明します。

質問:

  • op_Equality が Double で定義されているのはなぜですか? (そして、実装は NaN 固有の動作を考慮していません)
  • いつ呼び出されますか?
4

3 に答える 3

11

リフレクターの誤った解釈

Reflector から見ている逆コンパイルは、実際には Reflector のバグです。Reflector は、2 つの double が比較される関数を逆コンパイルできる必要があります。これらの関数では、コードに直接出力されていることがわかりceqます。その結果、Reflector はceq命令を 2 つの double 間の == として解釈し、2 つの double が比較される関数の逆コンパイルを支援します。

デフォルトでは、値型には == 実装が付属していません。(ユーザー定義の構造体は、オーバーロードされた == 演算子を継承しませんか? ) ただし、すべての組み込みスカラー型には、コンパイラが適切な CIL に変換する明示的にオーバーロードされた演算子があります。オーバーロードには単純なceqベースの比較も含まれているため、== 演算子のオーバーロードの動的/遅延バインド/リフレクション ベースの呼び出しは失敗しません。


詳細

定義済みの値の型の場合、等価演算子 (==) は、オペランドの値が等しい場合は true を返し、そうでない場合は false を返します。文字列以外の参照型の場合、2 つのオペランドが同じオブジェクトを参照している場合、== は true を返します。文字列型の場合、== は文字列の値を比較します。

-- http://msdn.microsoft.com/en-us/library/53k8ybth.aspx

あなたが言ったことは、 == が a の比較に参照型セマンティクスを使用することを意味しますdouble。ただし、doubleは値型であるため、値のセマンティクスを使用します。3 == 3これが、異なるスタック オブジェクトであっても が true である理由です。

このコンパイラの変換は、LINQ の Queryable オブジェクトにコードを含む拡張メソッドが含まれていると考えることができます、コンパイラはこれらの呼び出しを式ツリーに変換し、代わりに LINQ プロバイダーに渡します。どちらの場合も、基になる関数が実際に呼び出されることはありません。


Double の比較セマンティクス

ceqDouble のドキュメントは、 CIL 命令がどのように機能するかをほのめかしています。

Equals メソッドを呼び出して 2 つの Double.NaN 値が等しいかどうかをテストすると、メソッドは true を返します。ただし、等値演算子を使用して 2 つの NaN 値が等しいかどうかをテストすると、演算子は false を返します。Double の値が数値 (NaN) ではないかどうかを判断する場合は、代わりに IsNaN メソッドを呼び出します。

-- http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx


生のコンパイラ ソース

逆コンパイルされた C# コンパイラ ソースを見ると、二重比較を に直接変換する次のコードが見つかりますceq

private void EmitBinaryCondOperator(BoundBinaryOperator binOp, bool sense)
{
    int num;
    ConstantValue constantValue;
    bool flag = sense;
    BinaryOperatorKind kind = binOp.OperatorKind.OperatorWithLogical();
    if (kind <= BinaryOperatorKind.GreaterThanOrEqual)
    {
        switch (kind)
        {
            ...

            case BinaryOperatorKind.Equal:
                goto Label_0127;

            ...
        }
    }
...
Label_0127:
    constantValue = binOp.Left.ConstantValue;
    if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating)
    {
        ...
        return;
    }
    constantValue = binOp.Right.ConstantValue;
    if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating)
    {
        ...
        return;
    }
    this.EmitBinaryCondOperatorHelper(ILOpCode.Ceq, binOp.Left, binOp.Right, sense);
    return;
}

上記のコードは からRoslyn.Compilers.CSharp.CodeGen.CodeGenerator.EmitBinaryCondOperator(...)のもので、この目的のためにコードを読みやすくするために「...」を追加しました。

于 2013-02-19T13:01:42.070 に答える
2

msdnには次のように記載されています。

Equals メソッドを呼び出して 2 つの Double.NaN 値が等しいかどうかをテストすると、メソッドは true を返します。ただし、等値演算子を使用して 2 つの NaN 値が等しいかどうかをテストすると、演算子は false を返します。Double の値が数値 (NaN) ではないかどうかを判断する場合は、代わりに IsNaN メソッドを呼び出します。

これは、IEC 60559:1989 に準拠するために意図的に行われます。これは、2 つの NaN 値は数値として扱われないため等しくないと述べているため、op_Equal定義はこの標準に準拠しています。

IEC 60559:1989 によると、NaN の値を持つ 2 つの浮動小数点数が等しくなることはありません。ただし、System.Object::Equals メソッドの仕様によると、このメソッドをオーバーライドして、値が等しいセマンティクスを提供することが望ましいとされています。System.ValueType は Reflection を使用してこの機能を提供するため、Object.Equals の説明では、パフォーマンスを向上させるために、値の型はデフォルトの ValueType 実装をオーバーライドすることを検討する必要があると具体的に述べています。実際、System.ValueType::Equals のソース (SSCLI の clr\src\BCL\System\ValueType.cs の 36 行目) を見ると、System.ValueType の効果に対する CLR Perf チームからのコメントさえあります。 ::高速ではないことに等しい。

参照: http://blogs.msdn.com/b/shawnfa/archive/2004/07/19/187792.aspx

于 2013-02-19T13:10:46.480 に答える
1

msdn から: http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx

Equals メソッドを呼び出して 2 つの Double.NaN 値が等しいかどうかをテストすると、メソッドは true を返します。ただし、等値演算子を使用して 2 つの NaN 値が等しいかどうかをテストすると、演算子は false を返します。Double の値が数値 (NaN) ではないかどうかを判断する場合は、代わりに IsNaN メソッドを呼び出します。

于 2013-02-19T13:03:43.027 に答える