Ich habe den folgenden einfachen Code:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
und speed2
sollte den gleichen Wert haben, aber in der Tat habe ich:
speed1 = 61
speed2 = 62
Ich weiß, ich sollte wahrscheinlich Math.Round anstelle von Casting verwenden, aber ich würde gerne verstehen, warum die Werte unterschiedlich sind.
Ich habe mir den generierten Bytecode angesehen, aber bis auf einen Speicher und eine Ladung sind die Opcodes dieselben.
Ich habe den gleichen Code auch in Java ausprobiert und erhalte 62 und 62 korrekt.
Kann das jemand erklären?
Bearbeiten: Im realen Code ist es nicht direkt 6.2f * 10, sondern ein Funktionsaufruf * eine Konstante. Ich habe folgenden Bytecode:
für speed1
:
IL_01b3: ldloc.s V_8
IL_01b5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ba: ldc.r4 10.
IL_01bf: mul
IL_01c0: conv.i4
IL_01c1: stloc.s V_9
für speed2
:
IL_01c3: ldloc.s V_8
IL_01c5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ca: ldc.r4 10.
IL_01cf: mul
IL_01d0: stloc.s V_10
IL_01d2: ldloc.s V_10
IL_01d4: conv.i4
IL_01d5: stloc.s V_11
wir können sehen, dass Operanden Floats sind und dass der einzige Unterschied der ist stloc/ldloc
.
Was die virtuelle Maschine betrifft, habe ich es mit Mono / Win7, Mono / MacOS und .NET / Windows versucht, mit den gleichen Ergebnissen.
quelle
Antworten:
Zunächst
6.2f * 10
gehe ich davon aus, dass Sie wissen, dass dies aufgrund der Gleitkomma-Rundung nicht genau 62 ist (es ist tatsächlich der Wert 61.99999809265137, ausgedrückt als adouble
), und dass Ihre Frage nur lautet, warum zwei scheinbar identische Berechnungen zu einem falschen Wert führen.Die Antwort ist, dass Sie im Fall von
(int)(6.2f * 10)
dendouble
Wert 61.99999809265137 nehmen und ihn auf eine Ganzzahl kürzen, die 61 ergibt.Im Fall von nehmen
float f = 6.2f * 10
Sie den doppelten Wert 61.99999809265137 und runden auf den nächsten Wertfloat
(62). Sie kürzen diesen Wert dannfloat
auf eine Ganzzahl, und das Ergebnis ist 62.Übung: Erläutern Sie die Ergebnisse der folgenden Abfolge von Operationen.
Update: Wie in den Kommentaren erwähnt, ist der Ausdruck
6.2f * 10
formal ein,float
da der zweite Parameter eine implizite Konvertierung aufweist, infloat
die besser ist als die implizite Konvertierung indouble
.Das eigentliche Problem ist, dass der Compiler ein Zwischenprodukt verwenden darf (aber nicht muss), das eine höhere Genauigkeit als der formale Typ aufweist (Abschnitt 11.2.2) . Aus diesem Grund sehen Sie auf verschiedenen Systemen ein unterschiedliches Verhalten: Im Ausdruck
(int)(6.2f * 10)
hat der Compiler die Möglichkeit, den Wert6.2f * 10
vor der Konvertierung in eine hochpräzise Zwischenform zu haltenint
. Wenn dies der Fall ist, ist das Ergebnis 61. Wenn dies nicht der Fall ist, ist das Ergebnis 62.Im zweiten Beispiel
float
erzwingt die explizite Zuweisung zu , dass die Rundung vor der Konvertierung in eine Ganzzahl erfolgt.quelle
(int)(6.2f * 10)
derdouble
Wert wief
angegeben verwendetfloat
? Ich denke, der Hauptpunkt (immer noch unbeantwortet) ist hier.6.2f * 10
ist eigentlichfloat
nichtdouble
. Ich denke, der Compiler optimiert das Intermediate, wie es der letzte Absatz von 11.1.6 erlaubt .float
zuerst eine Konvertierung durchgeführt.Beschreibung
Schwebende Zahlen sind selten genau.
6.2f
ist so etwas wie6.1999998...
. Wenn Sie dies in ein int umwandeln, wird es abgeschnitten und diese * 10 ergibt 61.Schauen Sie sich die Jon Skeets-
DoubleConverter
Klasse an. Mit dieser Klasse können Sie den Wert einer schwebenden Zahl wirklich als Zeichenfolge visualisieren.Double
undfloat
sind beide Gleitkommazahlen , Dezimalzahlen nicht (es ist eine Festkommazahl).Stichprobe
Mehr Informationen
quelle
Schauen Sie sich die IL an:
Der Compiler reduziert konstante Ausdrücke zur Kompilierungszeit auf ihren konstanten Wert, und ich denke, er macht irgendwann eine falsche Annäherung, wenn er die Konstante in konvertiert
int
. Im Fall vonspeed2
wird diese Konvertierung nicht vom Compiler, sondern von der CLR vorgenommen, und sie scheinen unterschiedliche Regeln anzuwenden ...quelle
Ich vermute, dass eine
6.2f
echte Darstellung mit Float-Präzision6.1999999
dabei62f
wahrscheinlich etwas Ähnliches ist62.00000001
.(int)
Beim Casting wird der Dezimalwert immer abgeschnitten, sodass Sie dieses Verhalten erhalten.EDIT : Laut Kommentaren habe ich das Verhalten des
int
Castings in eine viel genauere Definition umformuliert .quelle
int
der Dezimalwert abgeschnitten, nicht gerundet.float
->int
beinhaltet das Runden. = DIch habe diesen Code kompiliert und disassembliert (unter Win7 / .NET 4.0). Ich denke, dass der Compiler den schwebenden konstanten Ausdruck als doppelt bewertet.
quelle
Single
enthält nur 7 Ziffern und beim Umwandeln in einen schneidetInt32
der Compiler alle Gleitkommaziffern ab. Während der Konvertierung können eine oder mehrere signifikante Ziffern verloren gehen.ergibt das Ergebnis von 619999980, also ergibt (Int32) (6.2f * 10) 61.
Es ist anders, wenn zwei Single multipliziert werden. In diesem Fall gibt es keine abgeschnittene Operation, sondern nur eine Annäherung.
Siehe http://msdn.microsoft.com/en-us/library/system.single.aspx
quelle
Gibt es einen Grund, warum Sie Typ Casting
int
anstelle von Parsing verwenden?würde dann lesen
Der Unterschied liegt wahrscheinlich in der Rundung: Wenn Sie auf werfen, erhalten
double
Sie wahrscheinlich so etwas wie 61.78426.Bitte beachten Sie die folgende Ausgabe
Deshalb bekommen Sie unterschiedliche Werte!
quelle
Int.Parse
Nimmt einen String als Parameter.