2009-06-19 14 views
13

Istnieje dziwne zachowanie, którego nie mogę zrozumieć. Uzgodniono, że liczba zmiennoprzecinkowa to przybliżenia, więc nawet operacje, które w sposób oczywisty zwracają liczbę bez liczb dziesiętnych, mogą być przybliżone do wartości dziesiętnych.Problem konwersji z int na float

Robię to:

int num = (int)(195.95F * 100); 

a ponieważ jest to operacji zmiennoprzecinkowej otrzymuję 19594 zamiast 19595 .. ale jest to rodzaj poprawne.

Co mnie zastanawia to, że jeśli zrobię

float flo = 195.95F * 100; 
int num = (int) flo; 

uzyskać poprawny wynik 19595.

Każdy pomysł, dlaczego tak się dzieje?

+0

z ciekawości, co dzieje się podczas korzystania z Convert.ToInt32? – AvatarOfChronos

+0

Konwersja przy użyciu Convert.ToInt32 zwróci prawidłowy wynik zarówno w scenariuszu – CodeClimber

+0

Związany sekcji [różnice między IEEE 754 implementacjach] (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html # 3098), który pojawia się na połączonej stronie internetowej jako dodatek anonimowy autor do tekstu _Czy każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej_. –

Odpowiedz

11

spojrzałem, aby zobaczyć, czy to kompilator robi matematyki, ale zachowuje się w ten sposób, nawet jeśli zmusić go:

static void Main() 
{ 
    int i = (int)(GetF() * GetI()); // 19594 
    float f = GetF() * GetI(); 
    int j = (int)f; // 19595 
} 
[MethodImpl(MethodImplOptions.NoInlining)] 
static int GetI() { return 100; } 
[MethodImpl(MethodImplOptions.NoInlining)] 
static float GetF() { return 195.95F; } 

to wygląda różnica jest to, czy pozostaje w rejestrach (szerszy od normalnego R4) lub jest zmuszony float zmiennej:

L_0001: call float32 Program::GetF() 
L_0006: call int32 Program::GetI() 
L_000b: conv.r4 
L_000c: mul 
L_000d: conv.i4 
L_000e: stloc.0 

vs

L_000f: call float32 Program::GetF() 
L_0014: call int32 Program::GetI() 
L_0019: conv.r4 
L_001a: mul 
L_001b: stloc.1 
L_001c: ldloc.1 
L_001d: conv.i4 
L_001e: stloc.2 

Jedyną różnicą jest stloc.1/ldloc.1.

Potwierdza to fakt, że jeśli nie zoptymalizowaną budowlanej (który usunie zmiennej lokalnej) uzyskać taką samą odpowiedź (19594) dla obu.

+0

Interesujące. Czy myślisz, że może się to zdarzyć w taki sam sposób na komputerach z procesorami x86 i 64-bitowymi? Czy rejestry różnią się szerokością? –

+0

Tak, ale nie mogłem przewidzieć, co się stanie na x64. Obawiam się, że * zgaduję * zachowa się prawie tak samo, ponieważ zmienna nadal jest r4, a rejestry są "co najmniej "r4 ... –

+0

dziękuję za pokazanie światła :) – CodeClimber

0

Po pomnożeniu przez 100, jest to liczba całkowita, więc wykonuje niejawną konwersję w tym kroku. Jeśli umieścisz "F" za 100, założę się, że będą takie same.

Zazwyczaj używam boksu/rozpakowywania z nawiasami, gdy jest to typ odniesienia. Kiedy jest to typ wartości, staram się używać metod statycznych Konwertuj.

Wypróbuj Convert.ToSingle (YourNumber); dla bardziej niezawodnej konwersji.

HTH

+0

Dziękuję, wiem, jak się konwertuję ... Zastanawiam się właśnie, dlaczego przypisanie zmiennej, a następnie rzutowanie, działa inaczej niż samo rzucanie – CodeClimber

+0

. Dlaczego miałoby to jakieś znaczenie? Problem polega na konwersji na float i kolejnym mnożeniu, a zapisanie go w większej liczbie słów niczego nie zmieni. –

0

nie mogę odpowiedzieć, dlaczego druga działa, a pierwszy z nich nie robi. Mogę jednak powiedzieć, że 195,95 to nie kończący się dziesiętny w systemie binarnym, a takie błędy zaokrąglania, jak ten, muszą się zdarzyć.

Spróbuj przekształcić w podwójną zamiast pływaka. Można również użyć typu pieniężnego lub dziesiętnego, a nie zmiennoprzecinkowego. Dzięki temu liczba będzie przechowywana inaczej i dokładniej.

Więcej informacji na temat liczb zmiennoprzecinkowych oraz IEEE reprezentację, przejdź tutaj:

http://en.wikipedia.org/wiki/IEEE_754

+0

Podwójny może działać przez przypadek, ale z powodu nieprecyzyjnej liczby nie ma powodu, dla którego dokładniejsza reprezentacja byłaby większa lub mniejsza niż rzeczywista wartość. W tym przypadku nie obchodzi nas, czy jest on poniżej poprawnej wartości przez float epsilon lub podwójny epsilon, tyle że poniżej. –

+0

Otrzymuję wartość jako float, muszę pomnożyć przez 100, a następnie zapisać jako Integer. Nie zamieniam niczego na float w moim kodzie. – CodeClimber

2

ten kod ...

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      float result = 195.95F*100; 
      int intresult = (int)(195.95F * 100); 
     } 
    } 
} 

dać tego źródła IL

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  14 (0xe) 
    .maxstack 1 
    .locals init ([0] float32 result, 
      [1] int32 intresult) 
    IL_0000: nop 
    IL_0001: ldc.r4  19595. 
    IL_0006: stloc.0 
    IL_0007: ldc.i4  0x4c8a 
    IL_000c: stloc.1 
    IL_000d: ret 
} // end of method Program::Main 

wygląd przy IL_00001 -> komparator wykonał kal .. W przeciwnym razie istnieje dziesiętny -> binarny problem konwersji

+0

to tylko podstawowy problem z konwersją z powodu innej pamięci (r4 vs i4) –

+1

Problem nadal występuje, jeśli podejmiesz decyzję od kompilatora - zobacz moją odpowiedź. –

+0

Po wyłączeniu optymalizacji uzyskuję takie same wyniki 95/94. diff jest w sposobie przechowywania vals (r4 vs i4) i powszechnym problemem binarnej rapresentacji danych dziesiętnych base10 w base2. –

1

Spróbuj konwersji pływaka podwoić w drugim przykładzie:

double flo = 195.95F * 100; 
int num = (int) flo; 

Zgaduję w pierwszym przykładzie kompilator jest za pomocą podwójnego trzymać wynik pośredni, a więc w przypadku szkła float tracisz precyzja.

+0

Twoje przypuszczenie jest na dobrej drodze. Ale to nie jest kompilator, to procesor dokonuje tej optymalizacji. I nie jest to podwójne; w niektórych procesorach może być jeszcze większa precyzja niż podwójna. –

2

odpowiedź Marka jest poprawna, ponieważ jest konwersja między nativefloat i float32/float64.

To jest określone w specyfikacji ECMA CLR ale David Notario wyjaśnia to o wiele lepiej niż ja.

+0

Wywołujemy to również w specyfikacji C#: "Operacje zmiennoprzecinkowe mogą być wykonywane z większą precyzją niż typ wyniku operacji.Na przykład niektóre architektury sprzętowe obsługują typ zmiennoprzecinkowy" rozszerzony "lub" długi podwójny " z większym zasięgiem i precyzją niż podwójny typ i niejawnie wykonuje wszystkie operacje zmiennoprzecinkowe przy użyciu tego typu wyższej precyzji. [...] C# pozwala na użycie wyższej precyzji dla wszystkich operacji zmiennoprzecinkowych. " –

+0

Ach tak, też o tym zapomniałem - myślę, że to jeden z tych dwóch kompilatorów i oboje mają coś do powiedzenia. – ShuggyCoUk

Powiązane problemy