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.

Jeśli chodzi o maszynę wirtualną, próbowałem z Mono / Win7, Mono / MacOS i. NET / Windows, z tymi samymi wynikami.
Author: Jignesh Joisar, 2012-01-18

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ą.

 170
Author: Raymond Chen,
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.

# Patrz Jon Skeets Dzięki tej klasie możesz naprawdę wizualizować wartość liczby zmiennoprzecinkowej jako ciąg znaków. 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

 11
Author: dknaack,
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...

 5
Author: Thomas Levesque,
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.

 1
Author: InBetween,
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 
 1
Author: Rodji,
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

 0
Author: Massimo Zerbini,
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!

 -4
Author: Neo,
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