2015-11-04 14 views
15
(int)((float)10.9 * 10) 

wird zu 108 ausgewertet. Warum?Die angegebene Berechnung verstehen (Besetzung + Multiplikation)

IMO die (int) -cast sollte nach die Multiplikation ausgewertet werden.

+5

Sie treten in Fließkommaberechnungspräzisionsproblem auf. – MarcinJuraszek

+0

Könnten Sie bitte erklären, was Sie bisher über Fließkomma gelernt haben, damit wir wissen, wo wir anfangen sollen zu erklären (Sie haben offensichtlich nach etwas gesucht, wie "C# welcher Programmierer über den Punkt lernen sollte" - zB http: //blog.reverberate) .org/2014/09/was-jeder-Computer-Programmierer-sollte.html - so zögern Sie nicht, Ergebnisse Ihrer Forschung zu der Post hinzuzufügen). –

+2

@Joey sollten Sie Ihre Antwort rückgängig machen, die Antwort auf Sirams Kommentar zu Ihrer Antwort lautet: "Es ist, weil der Compiler, wenn Sie eine einzelne Anweisung verwenden, es tatsächlich als" double f = ((float) 10.9 * 10); int i = (int) f; 'es wird ein Doppel für den Zwischenwert verwendet." –

Antwort

10

Das ist genau der Fall. Es ist nur, dass 10.9 ist leicht weniger als 10.9 aufgrund der Art, wie Fließkommazahlen dargestellt werden (10.9 kann nicht genau dargestellt werden, so erhalten Sie eine Annäherung, die in diesem Fall etwa 10.89999999 ist ...). Die Besetzung schneidet dann alle Ziffern ab, die dem Dezimalpunkt folgen. So erhalten Sie 108

Genaue Werte sind wie folgt hier (erhalten mit Jon Skeet DoubleConverter Klasse):

10.9   -> 10.9000000000000003552713678800500929355621337890625 
(float) 10.9 -> 10.8999996185302734375 

von 10, dass die Schwimmer Multipliziert und dann alle Dezimalstellen Abschneiden führt offensichtlich in 108

+3

Umm ich verstehe nicht, warum es in zwei Anweisungen funktioniert zu arbeiten erwartet. 'var f = ((float) 10.9 * 10); int i = (int) f;' gibt 109 und 109 –

+0

Aber warum bekomme ich '109' anstelle von' 108.9999 ... ', wenn ich folgendes mache: '((float) 10.9 * 10) .ToString()' – thomai

+0

Weil 'ToString' rundet, während casting nach int abgeschnitten wird. –

11

Lustig genug, das Problem hier ist, dass der Ausdruck zur Kompilierzeit berechnet wird, scheinbar mit einfacher Genauigkeit Mathematik wie erwartet. Dies geschieht in beiden Debug- und Release-Builds:

// this replaces the whole operation 
    IL_0001: ldc.i4.s 108 
    IL_0003: stloc.0 
    IL_0004: ldloc.0 
    IL_0005: call void [mscorlib]System.Console::WriteLine(int32) 

Während der Fall var f =((float)10.9 * 10);int i = (int)f; noch für die Multiplikation optimiert ist, aber mit doppelter Genauigkeit. Da die Besetzung in einem separaten Schritt getan wird Ich denke, es ist den Compiler (??) verwechselt:

IL_000b: ldc.r4 109  // 109 result for the first part 
    IL_0010: stloc.1 
    IL_0011: ldloc.1 
    IL_0012: conv.i4   // separate conversion to int 
    IL_0013: stloc.2 
    // rest is printing it 
    IL_0014: ldloc.1 
    IL_0015: box [mscorlib]System.Single 
    IL_001a: ldstr " " 
    IL_001f: ldloc.2 
    IL_0020: box [mscorlib]System.Int32 
    IL_0025: call string [mscorlib]System.String::Concat(object, object, object) 
    IL_002a: call void [mscorlib]System.Console::WriteLine(string) 

Ehrlich gesagt würde ich sagen, dass die Compiler falschen Code im zweiten Fall erzeugt, was wahrscheinlich ist, warum vermeidet C++ Optimierung Fließkomma-Code wie die Pest. Glücklicherweise ist dies nur ein Problem für diese Art von Testcode, der vollständig zur Kompilierungszeit durchgeführt werden kann, die tatsächliche Multiplikation der Variablen wäre in Ordnung.

ich tatsächlich versucht dies zu:

Console.WriteLine((int)(float.Parse(Console.ReadLine()) * int.Parse(Console.ReadLine()))); 

und gab es 10,9 und 10, und das Ergebnis war 108 wie erwartet.

+0

* ... und das Ergebnis war 108 wie erwartet *. Du musst Fließkomma lieben. – Groo

+0

Ich glaube nicht, dass das jemals in Frage stand, es ist die Tatsache, dass, wenn es in Schritten zerbrochen wird, das komische Ding ist. Der Compiler berechnet intern den falschen (besseren, aber immer noch falschen) Typ. – Blindy

+0

Jedoch ist 'var z = 10; var f = (10,9 f * z); int i = (int) f; 'macht die Multiplikation (in IL), berechnet aber immer noch 109. Ich habe das Gefühl, dass der Jitter etwas Spaß machen muss. – zinglon

2

Ihr float Ergebnis, vor dem Gießen zu int, ist 108.9999, da 109.0000 can't be represented in floating point. (Wie in den Kommentaren erwähnt, das ist falsch; 109 ganz gut dargestellt werden kann, aber irgendwie die Expression zu 108,9999 - das gilt nach wie vor)

Der int Wert 108 sein Warum? Das Umwandeln von Gleitkomma (double oder float) zu int schneidet den Dezimalteil ab. Es wird nicht abgerundet. So schneidet es die .9999 und Sie erhalten 108.

Zum runden, ersetzen Sie den Guss durch Math.Round() oder Convert.ToInt32().

Convert.ToInt32((float) 10.9 * 10); 
+0

http://blog.reverberate.org/2014/09/what-every-computer-programmer-should.html sais, dass "Mathematische Operationen wie diese geben Ihnen genaue Ergebnisse, solange alle Werte ganze Zahlen kleiner als sind 2^24 ". 109 ist viel kleiner als 2^24 ... – thomai

+2

Ja, Kay ist falsch, 109 kann gut dargestellt werden. Es ist die Multiplikation selbst, die nicht präzise genug ist. – Blindy

+1

Obwohl @Blindy korrekt ist, +1 für die Erwähnung von 'Convert.ToInt32'. Ich wusste es nicht Runden, das ist eine gute Sache. – Groo