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?
;After.
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?
;After.
Antworten:
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.
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 requiredSo 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 )
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;
}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;
}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.
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.
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=
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.
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
autoist also nicht der einzige Fall.
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.
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.
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.
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;
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.
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.
doubleund floatist anders, es antwortet nicht, ob Sie ein floataus einem Doppelliteral initialisieren können
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'
3.0für Sie in einen Float. Das Endergebnis ist nicht zu unterscheiden vonfloat a = 3.0f.