Ist 'float a = 3.0;' eine korrekte Aussage?

86

Wenn ich folgende Erklärung habe:

float a = 3.0 ;

Ist das ein Fehler? Ich habe in einem Buch gelesen, 3.0das ein doubleWert ist und den ich als angeben muss float a = 3.0f. Ist es so?

TESLA____
quelle
2
Der Compiler konvertiert das Doppelliteral 3.0für Sie in einen Float. Das Endergebnis ist nicht zu unterscheiden von float a = 3.0f.
David Heffernan
6
@EdHeal: Das ist es, aber es ist nicht besonders relevant für diese Frage, bei der es um C ++ - Regeln geht.
Keith Thompson
20
Zumindest brauchst du ein ;After.
Hot Licks
3
10 Abstimmungen und nicht viel in den Kommentaren, um sie zu erklären, sehr entmutigend. Dies ist die erste Frage der OP, und wenn die Leute der Meinung sind, dass dies 10 Abstimmungen wert ist, sollte es einige Erklärungen geben. Dies ist eine gültige Frage mit nicht offensichtlichen Auswirkungen und vielen interessanten Dingen, die Sie aus den Antworten und Kommentaren lernen können.
Shafik Yaghmour
3
@HotLicks es geht nicht darum, sich schlecht oder gut zu fühlen, sicher mag es unfair erscheinen, aber das ist das Leben, sie sind schließlich Einhornpunkte. Dowvotes sind sicherlich nicht dazu gedacht, Upvotes, die Sie nicht mögen, aufzuheben, genauso wie Upvotes nicht dazu, Downvotes aufzuheben, die Sie nicht mögen. Wenn die Leute der Meinung sind, dass die Frage verbessert werden kann, sollte ein erstmaliger Fragesteller sicherlich ein Feedback erhalten. Ich sehe keinen Grund, abzustimmen, aber ich würde gerne wissen, warum andere dies tun, obwohl sie dies nicht sagen können.
Shafik Yaghmour

Antworten:

159

Es ist kein Fehler zu deklarieren float a = 3.0 : Wenn Sie dies tun, konvertiert der Compiler das Doppelliteral 3.0 für Sie in einen Float.


In bestimmten Szenarien sollten Sie jedoch die Float-Literal-Notation verwenden.

  1. Aus Leistungsgründen:

    Beachten Sie insbesondere:

    float foo(float x) { return x * 0.42; }

    Hier gibt der Compiler für jeden zurückgegebenen Wert eine Konvertierung aus (die Sie zur Laufzeit bezahlen). Um dies zu vermeiden, sollten Sie Folgendes deklarieren:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
  2. So vermeiden Sie Fehler beim Vergleich der Ergebnisse:

    zB der folgende Vergleich schlägt fehl:

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!

    Wir können es mit der Float-Literal-Notation beheben:

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!

    (Hinweis: Auf diese Weise sollten Sie Float- oder Double-Zahlen natürlich nicht vergleichen, um die Gleichheit im Allgemeinen zu gewährleisten )

  3. So rufen Sie die richtige überladene Funktion auf (aus demselben Grund):

    Beispiel:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    int main()
    {       
        foo(42.0);   // calls double overload
        foo(42.0f);  // calls float overload
        return 0;
    }
  4. Wie von Cyber ​​festgestellt , ist es in einem Typabzugskontext erforderlich, dem Compiler zu helfen, a abzuleitenfloat :

    Im Falle von auto:

    auto d = 3;      // int
    auto e = 3.0;    // double
    auto f = 3.0f;   // float

    Und in ähnlicher Weise im Falle des Abzugs des Vorlagentyps:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    template<typename T>
    void bar(T t)
    {
          foo(t);
    }
    
    int main()
    {   
        bar(42.0);   // Deduce double
        bar(42.0f);  // Deduce float
    
        return 0;
    }

Live-Demo

quantdev
quelle
2
In Punkt 1 42handelt es sich um eine Ganzzahl, die automatisch heraufgestuft wird float(und die zur Kompilierungszeit in jedem anständigen Compiler auftritt), sodass keine Leistungseinbußen auftreten. Wahrscheinlich hast du so etwas gemeint 42.0.
Matteo Italia
@ MattoItalia, ja ich meinte 42.0 ofc (bearbeitet, danke)
quantdev
2
@ChristianHackl Das Konvertieren 4.2in 4.2fkann FE_INEXACTje nach Compiler und System den Nebeneffekt haben, dass das Flag gesetzt wird. Einige (zugegebenermaßen wenige) Programme kümmern sich darum, welche Gleitkommaoperationen genau sind und welche nicht, und testen auf dieses Flag . Dies bedeutet, dass die einfache offensichtliche Transformation zur Kompilierungszeit das Verhalten des Programms ändert.
6
float foo(float x) { return x*42.0; }kann zu einer Multiplikation mit einfacher Genauigkeit kompiliert werden und wurde so von Clang kompiliert, als ich es das letzte Mal versuchte. Jedoch float foo(float x) { return x*0.1; }kann nicht auf eine einzige Single-Precision Multiplikation kompiliert werden. Vor diesem Patch war es vielleicht etwas zu optimistisch, aber nach dem Patch sollte die Konvertierung-double_precision_op-Konvertierung nur dann in single_precision_op kombiniert werden, wenn das Ergebnis immer das gleiche ist. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq
1
Wenn man einen Wert berechnen möchte, der ein Zehntel beträgt, liefert someFloatder Ausdruck someFloat * 0.1genauere Ergebnisse als someFloat * 0.1f, während er in vielen Fällen billiger als eine Gleitkommadivision ist. Zum Beispiel wird (float) (167772208.0f * 0.1) korrekt auf 16777220 anstatt auf 16777222 gerundet. Einige Compiler ersetzen doubleeine Gleitkommadivision möglicherweise durch eine Multiplikation, aber für diejenigen, die dies nicht tun (dies ist für viele, wenn auch nicht für alle Werte sicher) ) Die Multiplikation kann eine nützliche Optimierung sein, jedoch nur, wenn sie mit einem doubleKehrwert durchgeführt wird.
Supercat
22

Der Compiler wandelt eines der folgenden Literale in Floats um, da Sie die Variable als Float deklariert haben.

float a = 3;     // converted to float
float b = 3.0;   // converted to float
float c = 3.0f;  // float

Es wäre wichtig, wenn Sie auto(oder andere Typabzugsmethoden) verwenden, zum Beispiel:

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float
Cory Kramer
quelle
5
Typen werden auch bei der Verwendung von Vorlagen abgeleitet, dies autoist also nicht der einzige Fall.
Shafik Yaghmour
14

Gleitkomma-Literale ohne Suffix sind vom Typ double . Dies wird im Entwurf des C ++ - Standardabschnitts behandelt. 2.14.4 Gleitliterale :

[...] Der Typ eines schwebenden Literals ist doppelt, sofern nicht ausdrücklich durch ein Suffix angegeben. [...]

Ist es also ein Fehler , einem Float3.0 ein Doppelliteral zuzuweisen ?

float a = 3.0

Nein, es ist nicht so, es wird konvertiert, was im Abschnitt 4.8 Gleitkommakonvertierungen behandelt wird :

Ein Wert vom Gleitkommatyp kann in einen Wert eines anderen Gleitkommatyps konvertiert werden. Wenn der Quellwert im Zieltyp genau dargestellt werden kann, ist das Ergebnis der Konvertierung diese genaue Darstellung. Wenn der Quellwert zwischen zwei benachbarten Zielwerten liegt, ist das Ergebnis der Konvertierung eine implementierungsdefinierte Auswahl eines dieser Werte. Andernfalls ist das Verhalten undefiniert.

Weitere Details zu den Auswirkungen finden Sie in GotW # 67: double oder nichts, was besagt:

Dies bedeutet, dass eine Doppelkonstante implizit (dh stillschweigend) in eine Gleitkommakonstante konvertiert werden kann, selbst wenn dies an Genauigkeit verliert (dh Daten). Dies durfte aus Gründen der C-Kompatibilität und Benutzerfreundlichkeit bestehen bleiben, es ist jedoch zu beachten, wenn Sie Gleitkommaarbeiten ausführen.

Ein Qualitätscompiler warnt Sie, wenn Sie versuchen, etwas zu tun, das nicht definiert ist, nämlich eine doppelte Menge in einen Float einzufügen, die kleiner als der minimale oder größer als der maximale Wert ist, den ein Float darstellen kann. Ein wirklich guter Compiler gibt eine optionale Warnung aus, wenn Sie versuchen, etwas zu tun, das möglicherweise definiert ist, aber Informationen verlieren könnte, nämlich eine doppelte Menge in einen Float zu setzen, die zwischen den von einem Float darstellbaren Minimal- und Maximalwerten liegt, dies aber nicht kann genau als Float dargestellt werden.

Es gibt also Vorbehalte für den allgemeinen Fall, die Sie beachten sollten.

Aus praktischer Sicht sind die Ergebnisse in diesem Fall höchstwahrscheinlich dieselben, obwohl technisch eine Konvertierung erfolgt. Wir können dies sehen, indem wir den folgenden Code auf godbolt ausprobieren :

#include <iostream>

float func1()
{
  return 3.0; // a double literal
}


float func2()
{
  return 3.0f ; // a float literal
}

int main()
{  
  std::cout << func1() << ":" << func2() << std::endl ;
  return 0;
}

und wir sehen, dass die Ergebnisse für func1und func2identisch sind, wobei sowohl clangals auch gcc:

func1():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret
func2():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret

Wie Pascal in diesem Kommentar betont, können Sie sich nicht immer darauf verlassen. Mit 0.1und 0.1fjeweils bewirkt , dass die Anordnung erzeugt zu unterscheiden , da die Umwandlung nun explizit getan werden muss. Der folgende Code:

float func1(float x )
{
  return x*0.1; // a double literal
}

float func2(float x)
{
  return x*0.1f ; // a float literal
}

führt zu folgender Montage:

func1(float):  
    cvtss2sd    %xmm0, %xmm0    # x, D.31147    
    mulsd   .LC0(%rip), %xmm0   #, D.31147
    cvtsd2ss    %xmm0, %xmm0    # D.31147, D.31148
    ret
func2(float):
    mulss   .LC2(%rip), %xmm0   #, D.31155
    ret

Unabhängig davon, ob Sie feststellen können, ob die Konvertierung Auswirkungen auf die Leistung hat oder nicht, dokumentiert die Verwendung des richtigen Typs Ihre Absicht besser. Verwenden Sie beispielsweise explizite Konvertierungenstatic_cast hilft auch zu verdeutlichen, dass die Konvertierung nicht versehentlich, sondern beabsichtigt war, was auf einen Fehler oder einen potenziellen Fehler hinweisen kann.

Hinweis

Wie Supercat betont, ist die Multiplikation mit zB 0.1und 0.1fnicht gleichwertig. Ich werde den Kommentar nur zitieren, weil er ausgezeichnet war und eine Zusammenfassung ihm wahrscheinlich nicht gerecht werden würde:

Wenn beispielsweise f gleich 100000224 war (was genau als Float darstellbar ist), sollte das Multiplizieren mit einem Zehntel ein Ergebnis ergeben, das auf 10000022 abgerundet wird, aber das Multiplizieren mit 0,1f ergibt stattdessen ein Ergebnis, das fälschlicherweise auf 10000023 aufrundet Wenn die Absicht besteht, durch zehn zu teilen, ist die Multiplikation mit der Doppelkonstante 0,1 wahrscheinlich schneller als die Division durch 10f und genauer als die Multiplikation mit 0,1f.

Mein ursprünglicher Punkt war es, ein falsches Beispiel zu demonstrieren, das in einer anderen Frage gegeben wurde, aber dies zeigt genau, dass subtile Probleme in Spielzeugbeispielen existieren können.

Shafik Yaghmour
quelle
1
Es kann erwähnenswert sein, dass die Ausdrücke f = f * 0.1;und f = f * 0.1f; verschiedene Dinge tun . Wenn beispielsweise f100000224 gleich ist (was genau als a darstellbar ist float), sollte das Multiplizieren mit einem Zehntel ein Ergebnis ergeben, das auf 10000022 abgerundet wird, aber das Multiplizieren mit 0,1f ergibt stattdessen ein Ergebnis, das fälschlicherweise auf 10000023 aufrundet Die Absicht ist, durch zehn zu dividieren. Die Multiplikation mit der doubleKonstanten 0,1 ist wahrscheinlich schneller als die Division durch 10fund präziser als die Multiplikation mit 0.1f.
Supercat
@supercat danke für das schöne Beispiel, ich habe Sie direkt zitiert, bitte zögern Sie nicht zu bearbeiten, wie Sie es für richtig halten.
Shafik Yaghmour
4

Es ist kein Fehler in dem Sinne, dass der Compiler ihn ablehnt, aber es ist ein Fehler in dem Sinne, dass es möglicherweise nicht das ist, was Sie wollen.

Wie Ihr Buch richtig sagt, 3.0ist es ein Wert vom Typ double. Es gibt eine implizite Konvertierung von doublenach float, alsofloat a = 3.0; wie eine gültige Definition einer Variablen.

Zumindest konzeptionell führt dies jedoch eine unnötige Konvertierung durch. Je nach Compiler kann die Konvertierung zur Kompilierungszeit durchgeführt oder zur Laufzeit gespeichert werden. Ein gültiger Grund für das Speichern zur Laufzeit ist, dass Gleitkommakonvertierungen schwierig sind und unerwartete Nebenwirkungen haben können, wenn der Wert nicht genau dargestellt werden kann und es nicht immer einfach ist, zu überprüfen, ob der Wert genau dargestellt werden kann.

3.0f vermeidet dieses Problem: Obwohl der Compiler technisch immer noch die Konstante zur Laufzeit berechnen darf (es ist immer so), gibt es hier absolut keinen Grund, warum ein Compiler dies möglicherweise tun könnte.


quelle
In der Tat wäre es im Fall eines Cross-Compilers völlig falsch, wenn die Konvertierung zur Kompilierungszeit durchgeführt würde, da sie auf der falschen Plattform stattfinden würde.
Marquis von Lorne
2

Obwohl es an sich kein Fehler ist, ist es ein wenig schlampig. Sie wissen, dass Sie einen Float möchten, also initialisieren Sie ihn mit einem Float.
Ein anderer Programmierer kann mitkommen und nicht sicher sein, welcher Teil der Deklaration korrekt ist, der Typ oder der Initialisierer. Warum nicht beide richtig sein?
float Antwort = 42.0f;

Ingenieur
quelle
0

Wenn Sie eine Variable definieren, wird sie mit dem bereitgestellten Initialisierer initialisiert. Dies erfordert möglicherweise die Konvertierung des Werts des Initialisierers in den Typ der Variablen, die initialisiert wird. Das passiert, wenn Sie sagen float a = 3.0;: Der Wert des Initialisierers wird in konvertiert float, und das Ergebnis der Konvertierung wird zum Anfangswert vona .

Das ist im Allgemeinen in Ordnung, aber es tut nicht weh zu schreiben, um 3.0fzu zeigen, dass Sie wissen, was Sie tun, und insbesondere, wenn Sie schreiben möchten auto a = 3.0f.

Kerrek SB
quelle
0

Wenn Sie Folgendes ausprobieren:

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

Sie erhalten folgende Ausgabe:

4:8

Dies zeigt, dass die Größe von 3.2f auf einem 32-Bit-Computer als 4 Byte angenommen wird, während 3.2 als doppelter Wert interpretiert wird, der auf einem 32-Bit-Computer 8 Byte benötigt. Dies sollte die Antwort liefern, nach der Sie suchen.

Dr. Debasish Jana
quelle
Das zeigt das doubleund floatist anders, es antwortet nicht, ob Sie ein floataus einem Doppelliteral initialisieren können
Jonathan Wakely
Natürlich können Sie einen Float von einem doppelten Wert initialisieren, der gegebenenfalls
abgeschnitten wird
4
Ja, ich weiß, aber das war die Frage des OP, daher konnte Ihre Antwort sie nicht wirklich beantworten, obwohl Sie behaupteten, die Antwort zu geben!
Jonathan Wakely
0

Der Compiler leitet den am besten passenden Typ aus Literalen ab oder zumindest aus dem, was er für am besten geeignet hält. Das bedeutet eher Effizienzverlust gegenüber Präzision, dh Verwendung eines Double anstelle von Float. Verwenden Sie im Zweifelsfall Klammer-Intializer, um dies deutlich zu machen:

auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int

Die Geschichte wird interessanter, wenn Sie von einer anderen Variablen aus initialisieren, für die Typkonvertierungsregeln gelten: Während es legal ist, eine Doppelform als Literal zu konstruieren, kann sie nicht ohne mögliche Verengung aus einem int konstruiert werden:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
Truschival
quelle