Czy istnieje różnica między „double val = 1;” i „double val = 1D;”?

17

Czy istnieje różnica między następującymi dwoma częściami kodu?

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;
    }

}

Odkryłem, że nasza baza kodu używa drugiego sposobu pisania.

srnldai
źródło
Z czubka mojej głowy, pierwsza z nich wymaga długiej lub podwójnej promocji.
Tanveer Badar
2
po pierwsze dokonuje niejawnej konwersji do podwójnej, po drugie nie dokonuje żadnej konwersji.
Serkan Arslan
2
Twój tytuł pytania i pytanie w treści nie pasują do siebie, a dwa pytania mają różne odpowiedzi.
Eric Lippert,
1
@Eric Lippert W rzeczywistości treść pytania została edytowana przez innych użytkowników.
srnldai
1
@Brian: Nie, oryginalny plakat jest poprawny; edycja zmieniła znaczenie pytania, którego nie zauważyłem. Pierwotne pytanie brzmiało: „Czy jest jakaś różnica ...? Co więcej, czy jest jakaś różnica ...?”, Moje podkreślenie. Usunięty „Dalszy” wskazuje, że oryginalny plakat zdał sobie sprawę, że zadaje dwa pytania, które mogą mieć różne odpowiedzi. To zła praktyka; pytania powinny idealnie zadać jedno pytanie. Ale edycja sprawia wrażenie, jakby dwa pytania miały być tym samym pytaniem, a nie dwoma różnymi pytaniami.
Eric Lippert,

Odpowiedzi:

18

Są tutaj dwa pytania i należy zauważyć, że mają one różne odpowiedzi.

Czy jest jakaś różnica między double val = 1;i double val = 1D;?

Nie. Kompilator C # rozpoznaje, kiedy literał całkowity jest używany w kontekście, w którym spodziewane jest podwójne, i zmienia typ w czasie kompilacji, więc te dwa fragmenty wygenerują ten sam kod.

Czy istnieje różnica między następującymi dwoma częściami kodu?

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

Tak. Zasada, że ​​stałe liczb całkowitych są automatycznie zmieniane na liczby podwójne, dotyczy tylko stałych i src ? ...nie jest stała . Kompilator wygeneruje pierwszy tak, jakbyś napisał:

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

A drugi jak

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

Oznacza to, że w pierwszej kolejności wybieramy liczbę całkowitą, a następnie przekształcamy ją na podwojoną, a w drugiej wybieramy podwójną.

Do waszej informacji: kompilator C # lub jitter mogą rozpoznać, że pierwszy program można zoptymalizować do drugiego, ale nie wiem, czy faktycznie to robi. C # kompilator jest czasem przejść konwersje podniesionym arytmetycznych w organach warunkowych; Napisałem ten kod jakieś osiem lat temu, ale nie pamiętam wszystkich szczegółów.

Eric Lippert
źródło
6

Jest to różnica w wygenerowanego kodu IL.

Ta klasa:

class Test1
{
    public readonly double Val;

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

Tworzy ten kod IL dla konstruktora:

.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
    }
}

A ta klasa:

class Test2
{
    public readonly double Val;

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

Tworzy ten kod IL dla konstruktora:

.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
    }
}

Jak widać, w pierwszej wersji musi on wywoływać conv.r8konwersję int na double.

Jednak: (1) Wynik końcowy jest identyczny i (2) kompilator JIT może przetłumaczyć oba z nich na ten sam kod maszynowy.

Odpowiedź brzmi: tak, jest różnica - ale nie taka, o którą trzeba się martwić.

Osobiście wybrałbym drugą wersję, ponieważ lepiej wyraża ona zamiar programisty i może wygenerować bardzo, nieco bardziej wydajny kod (w zależności od tego, co porabia kompilator JIT).

Matthew Watson
źródło
4

Nie ma różnicy, kompilator jest wystarczająco inteligentny, aby pośrednio wykonać konwersję lub nie.
Jednak jeśli używasz var, musisz napisać, var val = 42D;aby upewnić się, że zmienna jest podwójna, a nie 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
źródło