Когда вызывается оператор Double ==?

Все началось с трюка, который кто-то мне задал.. (Он упоминается в книге - С# в двух словах) Вот суть этого.

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)
  • для удвоений, вызов оператора == преобразуется в код ceq IL
  • где как для строк, он переводится в System.String:: op_Equality (строка, строка).

Разумеется, документация для ceq указывает, что она предназначена для чисел с плавающей запятой и NaN. Этим объясняются наблюдения.

Вопросы:

  • Почему op_Equality определен в Double? (И реализация не влияет на специфическое поведение NaN)
  • Когда он вызывается?

Ответ 1

Ошибочная интерпретация рефлектора

Декомпиляция, которую вы видите из Reflector, на самом деле является ошибкой в ​​Reflector. Отражатель должен иметь возможность декомпилировать функцию, в которой сравниваются два удвоения; в этих функциях вы найдете ceq, испускаемый прямо в код. В результате Reflector интерпретирует инструкцию ceq как == между двумя удвоениями, чтобы помочь декомпилировать функцию, в которой сравниваются два удвоения.

По умолчанию типы значений не связаны с реализацией ==. (Не определяемые пользователем структуры наследуют перегруженный == оператор?) Однако все встроенные скалярные типы имеют явно перегруженный оператор, что компилятор переводит в соответствующий CIL. Перегрузка также содержит простое сравнение ceq, так что вызовы с динамической/поздней привязкой/рефлексией при перегрузке оператора == не будут терпеть неудачу.


Подробнее

Для предопределенных типов значений оператор равенства (==) возвращает true, если значения его операндов равны, в противном случае - false. Для справки типы, отличные от string, == возвращает true, если его два операнда ссылаются на тот же объект. Для типа строки == сравнивает значения строки.

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

То, что вы сказали, подразумевает, что == использует семантику ссылочного типа для сравнения double. Однако, поскольку double - тип значения, он использует семантику значений. Вот почему 3 == 3 истинно, даже если это разные объекты стека.

Вы можете почти думать об этом преобразовании компилятора как о том, как объект LINQ Queryable содержит методы расширения с кодом в них, но компилятор переводит эти вызовы в деревья выражений, которые затем передаются поставщику LINQ. В обоих случаях основная функция никогда не вызвана.


Двойная сравнительная семантика

Документация для Double ссылается на то, как работает инструкция ceq CIL:

Если два значения Double.NaN проверяются на равенство, вызывая метод Equals, метод возвращает true. Однако, если два значения NaN проверяются на равенство с помощью оператора равенства, оператор возвращает false. Если вы хотите определить, является ли значение Double не числом (NaN), альтернативой является вызов метода IsNaN.

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


Исходный источник компилятора

Если вы посмотрите в декомпилированном источнике компилятора С#, вы найдете следующий код для обработки прямого перевода двойных сравнений в 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(...), и я добавил "...", чтобы сделать код более читаемым для этой цели.

Ответ 2

В msdn указано, что:

Если два значения Double.NaN проверяются на равенство, вызывая равенство метод возвращает true. Однако, если проверены два значения NaN для равенства с помощью оператора равенства оператор возвращает ложный. Если вы хотите определить, не является ли значение Double число (NaN), альтернативой является вызов метода IsNaN.

Это делается delibaretly, чтобы соответствовать IEC 60559: 1989, поскольку он утверждает, что два значения NaN не равны, поскольку они не рассматриваются как числа, поэтому соответствие op_Equal соответствует этому стандарту;

Согласно IEC 60559: 1989, два числа с плавающей запятой со значениями NaN никогда не равны. Однако, согласно спецификации для Метод System.Object:: Equals, желательно переопределить этот метод для обеспечения семантики равенства ценности. Поскольку System.ValueType предоставляет эта функциональность с использованием Reflection, описание для Object.Equals указывает, что типы значений должны учитывать переопределение реализации ValueType по умолчанию для достижения производительности увеличение. Фактически, глядя на источник System.ValueType:: Equals (строка 36 из clr\src\BCL\System\ValueType.cs в SSCLI), есть даже комментарий команды CLR Perf к Эффект System.ValueType:: Равно не быстрый.

обратитесь к: http://blogs.msdn.com/b/shawnfa/archive/2004/07/19/187792.aspx

Ответ 3

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

Если два значения Double.NaN проверяются на равенство, вызывая равенство метод возвращает true. Однако, если проверены два значения NaN для равенства с помощью оператора равенства оператор возвращает ложный. Если вы хотите определить, не является ли значение Double число (NaN), альтернативой является вызов метода IsNaN.