Есть ли разница между «double val = 1;» и «двойной вал = 1D;»?

17

Есть ли разница между следующими двумя частями кода?

class Test {

    public readonly double Val;

    public Test(bool src) {
        this.Val = src ? 1 : 0;
    }

}

class Test {

    public readonly double Val;

    public Test(bool src) {
        this.Val = src ? 1D : 0D;
    }

}

Я обнаружил, что наша кодовая база использует второй способ написания.

srnldai
источник
Вдобавок ко всему, первое связано с продвижением в два раза.
Танвеер Бадар
2
первый делает неявное преобразование в double, второй не делает никакого преобразования.
Серкан Арслан
2
Ваше название вопроса и вопрос в теле не совпадают, и на два вопроса есть разные ответы.
Эрик Липперт
1
@Eric Lippert На самом деле, основная часть вопроса была отредактирована другими пользователями.
srnldai
1
@ Брайан: Нет, оригинальный постер правильный; редактирование изменило смысл вопроса, который я не заметил. Первоначальный вопрос был: «Есть ли разница ...? Далее, есть ли разница ...?», Мой акцент. Удаленное «Далее» указывает на то, что первоначальный автор понял, что они задают два вопроса, на которые могут быть разные ответы. Это плохая практика; вопросы в идеале должны задать один вопрос. Но редактирование создает впечатление, что два вопроса предназначены для одного и того же вопроса, а не для двух разных вопросов.
Эрик Липперт

Ответы:

18

Здесь есть два вопроса, и важно отметить, что у них разные ответы.

Есть ли разница между double val = 1;и double val = 1D;?

Нет. Компилятор C # распознает, когда целочисленный литерал используется в контексте, где ожидается double, и изменяет ли тип во время компиляции, поэтому эти два фрагмента будут генерировать один и тот же код.

Есть ли разница между следующими двумя частями кода?

double Val; 
...    
this.Val = src ? 1 : 0;
---
this.Val = src ? 1D : 0D;

Да. Правило, согласно которому целочисленные константы автоматически изменяются на двойные, применяется только к константам и src ? ...не является константой . Компилятор сгенерирует первый, как будто вы написали:

int t;
if (src)
  t = 1;
else
  t = 0;
this.Val = (double)t;

А второй как

double t;
if (src)
  t = 1D;
else
  t = 0D;
this.Val = t;

То есть, в первом мы выбираем целое число, а затем конвертируем его в двойное, а во втором мы выбираем двойное.

К вашему сведению: компилятору C # или джиттеру разрешено распознавать, что первую программу можно оптимизировать во вторую, но я не знаю, так ли это на самом деле. C # компилятор делает иногда двигаться преобразованиями для поднятого арифметики в органы условных; Я написал этот код около восьми лет назад, но я не помню все детали.

Эрик Липперт
источник
6

Там является разница в сгенерированном коде IL.

Этот класс:

class Test1
{
    public readonly double Val;

    public Test1(bool src)
    {
        this.Val = src ? 1 : 0;
    }
}

Создает этот код IL для конструктора:

.class private auto ansi beforefieldinit Demo.Test1
    extends [mscorlib]System.Object
{
    .field public initonly float64 Val

    .method public hidebysig specialname rtspecialname instance void .ctor (
            bool src
        ) cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ldarg.0
        IL_0007: ldarg.1
        IL_0008: brtrue.s IL_000d

        IL_000a: ldc.i4.0
        IL_000b: br.s IL_000e

        IL_000d: ldc.i4.1

        IL_000e: conv.r8
        IL_000f: stfld float64 Demo.Test1::Val
        IL_0014: ret
    }
}

И этот класс:

class Test2
{
    public readonly double Val;

    public Test2(bool src)
    {
        this.Val = src ? 1d : 0d;
    }
}

Создает этот код IL для конструктора:

.class private auto ansi beforefieldinit Demo.Test2
    extends [mscorlib]System.Object
{
    .field public initonly float64 Val

    .method public hidebysig specialname rtspecialname instance void .ctor (
            bool src
        ) cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ldarg.0
        IL_0007: ldarg.1
        IL_0008: brtrue.s IL_0015

        IL_000a: ldc.r8 0.0
        IL_0013: br.s IL_001e

        IL_0015: ldc.r8 1

        IL_001e: stfld float64 Demo.Test2::Val
        IL_0023: ret
    }
}

Как видите, в первой версии он должен вызывать conv.r8для преобразования int в double.

Однако: (1) конечный результат идентичен и (2) компилятор JIT вполне может перевести оба этих кода в один и тот же машинный код.

Так что ответ: Да, есть разница - но не один , что вам нужно беспокоиться о.

Лично я бы пошел на вторую версию, так как она лучше выражает намерение программиста и может привести к очень немного более эффективному коду (в зависимости от того, что делает JIT-компилятор).

Мэтью Уотсон
источник
4

Нет никакой разницы, компилятор достаточно умен, чтобы неявно делать преобразование или нет.
Однако, если вы используете var, вам нужно написать, var val = 42D;чтобы убедиться, что переменная является double, а не int.

double foo = 1;  // This is a double having the value 1
double bar = 1d; // This is a double having the value 1

var val = 42d;   // This is a double having the value 42
var val2 = 42;   // /!\ This is an int having the value 42 !! /!\
Cid
источник