Wyrażenie C# Float: dziwne zachowanie podczas odlewania wyniku float do int
Mam następujący prosty kod:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
i speed2
powinny mieć tę samą wartość, ale w rzeczywistości mam:
speed1 = 61
speed2 = 62
Wiem, że powinienem używać matematyki.Okrągłe zamiast rzucania, ale chciałbym zrozumieć, dlaczego wartości są różne.
Spojrzałem na wygenerowany bajt, ale poza store I load, opcodes są takie same.
Próbowałem również tego samego kodu w Javie i poprawnie uzyskałem 62 i 62.
Czy ktoś może wyjaśnić ?Edit: W kodzie rzeczywistym nie jest to bezpośrednio 6.2 f * 10, ale wywołanie funkcji * stała. Mam następujący bajt:
Dla 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
Dla 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
Widzimy, że operandy są pływakami i że jedyną różnicą jest stloc/ldloc
.
7 answers
Po pierwsze, zakładam, że wiesz, że 6.2f * 10
nie jest dokładnie 62 ze względu na zaokrąglanie zmiennoprzecinkowe (w rzeczywistości jest to wartość 61.99999809265137 wyrażona jako double
) i że twoje pytanie dotyczy tylko tego, dlaczego dwa pozornie identyczne obliczenia prowadzą do niewłaściwej wartości.
Odpowiedź jest taka, że w przypadku (int)(6.2f * 10)
, bierzesz double
wartość 61.99999809265137 i obcinasz ją do liczby całkowitej, co daje 61.
W przypadku float f = 6.2f * 10
, bierzesz podwójną wartość 61.99999809265137 i zaokrąglenie do najbliższego float
, czyli 62. Następnie skracasz float
do liczby całkowitej, a wynikiem jest 62.
Ćwiczenie: wyjaśnij wyniki następującej sekwencji operacji.
double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2
Update: jak wspomniano w komentarzach, wyrażenie {[1] } jest formalnie float
, ponieważ drugi parametr ma domyślną konwersję na float
, która jest lepsza niż domyślna konwersja na double
.
Faktyczny problem polega na tym, że kompilator może (ale nie jest wymagany)używać półproduktu, który jest bardziej precyzyjny niż typ formalny (sekcja 11.2.2) . Dlatego widzisz różne zachowania na różnych systemach: w wyrażeniu (int)(6.2f * 10)
, kompilator ma możliwość zachowania wartości 6.2f * 10
w formie pośredniej o wysokiej precyzji przed konwersją na int
. Jeśli tak, to wynik jest 61. Jeśli nie, to wynik jest 62.
W drugim przykładzie jawne przypisanie do float
wymusza zaokrąglenie przed konwersją na liczbę całkowitą.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-23 05:47:59
Opis
Liczby zmiennoprzecinkowe a rzadko dokładne. 6.2f
jest czymś w rodzaju 6.1999998...
.
Jeśli wrzucisz to do int to obetnie go i to * 10 daje 61.
Double
i float
są obie liczbami zmiennymi , dziesiętne nie są (jest to stała liczba punktowa).
Próbka
DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875
Więcej Informacji
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:23
Spójrz na IL:
IL_0000: ldc.i4.s 3D // speed1 = 61
IL_0002: stloc.0
IL_0003: ldc.r4 00 00 78 42 // tmp = 62.0f
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000A: conv.i4
IL_000B: stloc.2
Kompilator redukuje wyrażenia stałe w czasie kompilacji do ich stałej wartości i myślę, że w pewnym momencie dokonuje błędnego przybliżenia, kiedy konwertuje stałą do int
. W przypadku speed2
Konwersja ta jest dokonywana nie przez kompilator, ale przez CLR i wydają się stosować różne reguły...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 14:29:33
Domyślam się, że 6.2f
prawdziwa reprezentacja z precyzją float jest 6.1999999
, podczas gdy {[2] } jest prawdopodobnie czymś podobnym do 62.00000001
. (int)
casting zawsze obraca wartość dziesiętną , dlatego otrzymujesz takie zachowanie.
EDIT: zgodnie z komentarzami przeformułowałem zachowanie int
odlewania do znacznie bardziej precyzyjnej definicji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 14:30:59
Skompilowałem i zdemasowałem ten kod (na Win7/. NET 4.0). Domyślam się, że kompilator ocenia zmienne wyrażenie stałe jako podwójne.
int speed1 = (int)(6.2f * 10);
mov dword ptr [rbp+8],3Dh //result is precalculated (61)
float tmp = 6.2f * 10;
movss xmm0,dword ptr [000004E8h] //precalculated (float format, xmm0=0x42780000 (62.0))
movss dword ptr [rbp+0Ch],xmm0
int speed2 = (int)tmp;
cvttss2si eax,dword ptr [rbp+0Ch] //instrunction converts float to Int32 (eax=62)
mov dword ptr [rbp+10h],eax
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-04 19:09:05
Single
mankament zawiera tylko 7 cyfr, a przy zapisie do Int32
kompilator obcina wszystkie cyfry zmiennoprzecinkowe. Podczas konwersji jedna lub więcej znaczących cyfr może zostać utracona.
Int32 speed0 = (Int32)(6.2f * 100000000);
Daje wynik 619999980 więc (Int32)(6.2 f * 10) daje 61.
Inaczej jest, gdy dwa pojedyncze są mnożone, w tym przypadku nie ma operacji obcięcia, a tylko przybliżenie.
Zobacz http://msdn.microsoft.com/en-us/library/system.single.aspx
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 14:48:07
Czy Jest jakiś powód, dla którego zamiast parsować piszesz do int
?
int speed1 = (int)(6.2f * 10)
By następnie przeczytać
int speed1 = Int.Parse((6.2f * 10).ToString());
Różnica jest prawdopodobnie związana z zaokrągleniem: jeśli rzucisz do double
prawdopodobnie dostaniesz coś w rodzaju 61.78426.
Zwróć uwagę na następujące wyjście
int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514
Dlatego dostajesz różne wartości!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 14:24:45