Lost precision

H4
  • Rejestracja:ponad 13 lat
  • Ostatnio:ponad 4 lata
  • Postów:107
0

Ok, Ja wiem na czym polega strata precyzji, ale czuje się już zagubiony :(

[CPP] http://ideone.com/qzPQPf - C++ - 1.000000
[C#] http://ideone.com/1kQ6Sj - C# (mono-2.8) - 0.9999907
[C#] http://pastebin.com/dpQiVDRh - C# (mono-2.10) - 0.9999907
[C#] VisualStudio - 0.9999907
[C] http://ideone.com/Pr8C6N - 1.000000
[CPP] (jakiś starszy GCC, nie wiem jak sprawdzić) - 0.999991

Staram się zebrać informacje odnośnie unikania owych strat, ale jak widać zależy to od kompilera (swoją drogą sry za mix C#)

Prawidłowy wynik to oczywiście 1

Już nie wiem co się dzieje, czy żeby zacząc tłumaczyć na czym polega lost precision trzeba najpierw napisać klauze "Zależnie od kompilatora"?

Jak to w końcu jest? Skąd mam wiedzieć, czy mój program nie wykrzaczy się / nie zbuguje? Jak unikać utraty precyzji?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:minuta
1

GCC prawdopodobnie zamienia to na mnożenie. Spróbuj jakiegoś bardziej zaawansowanego algorytmu, np ortogonalizację Grama-Schmidta :P
Albo skompiluj z wyłączonymi optymalizacjami.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

o_O
Ale co to ma wspólnego z utratą precyzji? Przecież to co pokazałes to jest zwykłe ograniczenie reprezentacji zmiennoprzecinkowej i tyle, bo 0.1 to jest ułamek okresowy. Od czego zależy? Głównie od tego na ilu bitach trzyma się w danym języku floaty, double i long double (czy zgodnie z IEEE754 czy tez nie) i czy zezwala się na wykonywanie operacji w pamięci na liczbach wyższej precyzji jeśli są dostepne (np. java potrafi korzystać z obliczeń wyższej precyzji niż specyfikuje JVM jeśli ma taką możliwość). Poza tym kompilator nie jest głupi i potrafi sobie różne rzeczy optymalizować. Zobacz najpierw jaki kod ten kompilator w ogóle wygenerował...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
bogdans
Jesteś pewien tego co piszesz o Javie? W kodzie, który zamieściłem trzy posty dalej, Java daje takie same wyniki jak C++.
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:minuta
0

Jakbyś podał jeszcze kod, który daje takie wyniki... (może da się go poprawić)

Jak to w końcu jest?
Oprócz wewnętrznej reprezntacji liczby dochodzi kwestia formatowania liczby do wyświetlenia na ekranie. Różne języki mogą mieć różną domyślną liczbę cyfr po przecinku...

Skąd mam wiedzieć, czy mój program nie wykrzaczy się / nie zbuguje?
Dlaczego miałby się wykrzaczyć?

Jak unikać utraty precyzji?
Jeśli liczba nie będzie wyświetlana na ekranie (czyli nie musi ładnie wyglądać) to pogodzić się z tym, i olać. Jeśli wynik jest dla użytkownika, zazwyczaj wystarczą np. dwie cyfry po przecinku, a wtedy problem nie istnieje.

edytowany 1x, ostatnio: Azarien
H4
  • Rejestracja:ponad 13 lat
  • Ostatnio:ponad 4 lata
  • Postów:107
0

@Azarien Wykrzaczył by się przykładowo przy porównaniu do jedynki, np. deadlocki etc. etc.
Mówiąc o wygenerowanym kodzie przez kompilator macie na myśli kod ASM? Jestem tragiczny w jego interpretacji, ale mogę go zamieścić jeśli nalegacie, tymczasem:

Bawiąc się debuggerem doszedłem do tego etapu:
[code]
static void Main(string[] args)
{
float a = 0.05f;
a += 0.01f;

        double b = 0.05d;
        b += 0.01d;
    }

[/code]

a = 0.0600000024;
b = 0.060000000000000005;
Żadna matematyka nie tłumaczy mi tej 24ki na końcu

Ok, Ja rozumiem, w pewnym sensie tak mała różnica nie powinna robić wielkiego szumu, ale czy w takim razie takie narzędzia jak, np. MS Excel / wolfram też dzielą ten ból?

Natomiast:
[code]
float a = 0.06f;
a += 0.01f;
[/code]
Da już poprawne 0,07

Zaznaczenie optymalizacji kodu (domyślnie wyłączonej) niczego nie zmieniła

I jak tu teraz porównywać floaty do, np 2.0 :P

MarekR22
jak to żadna matematyka nie tłumaczy tej 24? A ile w zapisie dziesiętnym wyjdzie 0.01 + 1/30 jeśli możesz użyć określonej ilości cyfr? Ten sam problem jest przy 1/100, ale w zapisie binarnym. Float ma dokładność około 7.2 cyfr (dziesiętnych) wiodących, więc wszystko się zgadza (ty masz błąd przy 8 cyfrze). Double ma dokładność 15.9 cyfr wiodących, też się zgadza, masz błąd przy 17 cyfrze. http://en.wikipedia.org/wiki/Floating_point
bogdans
Moderator
  • Rejestracja:prawie 17 lat
  • Ostatnio:prawie 5 lat
0

Handryczysz się o jakieś drobiazgi na 10 miejscu po przecinku. Obliczanie pola trójkąta z wzoru Herona:

Kopiuj
#include <iostream>
#include <math.h>

int main()
{
    {
        float a=12345678.0;
        float b=12345679.0;
        float c=1.01233995;
        float p=(a+b+c)/2;
        float pole=sqrt(p*(p-a)*(p-b)*(p-c));
        std::cout << "(float)Pole = " << pole << "\n"; //(float)Pole = 0
    }
    {
        double a=12345678.0;
        double b=12345679.0;
        double c=1.01233995;
        double p=(a+b+c)/2;
        double pole=sqrt(p*(p-a)*(p-b)*(p-c));
        std::cout << "(double)Pole = " << pole << "\n"; //(double)Pole = 972730
    }
}

I to jest znacząca różnica. http://ideone.com/hY0B0x
Liczb zmiennoprzecinkowych nie należy porównywać tak: (x == y), ale tak: abs(x - y) < jakaś_mała_liczba.


To smutne, że głupcy są tak pewni siebie, a ludzie mądrzy - tak pełni wątpliwości. Bertrand Russell
edytowany 2x, ostatnio: bogdans
Zobacz pozostałe 2 komentarze
bogdans
Tak, ale nie przeze mnie. Ponoć te liczby zaproponowali W. Kahan i Joseph D. Darcy.
Azarien
wystarczy przekształcić na float pole = sqrt((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))/4; i będzie dobrze.
vpiotr
@bogdans, Ty to pewnie wiesz, ale może mylić innych - to dokument z 1998 roku, od tamtego czasu wprowadzono m.in. strictfp (w tym samym roku - 1.2).
vpiotr
Znalazłem też sposób na "unsigned" - http://code.google.com/p/guava-libraries/wiki/PrimitivesExplained#Unsigned_support Przydałby się jakiś artykuł krytykujący / aktualizujący "JAVAhurt" - może dopiszę do Wiki - jak mi pozwolą.
vpiotr
  • Rejestracja:prawie 14 lat
  • Ostatnio:prawie 3 lata
0

@Hell4Ge:

Jak chcesz unikać błędów precyzji to jest to osobny temat.
Jedno z rozwiązań to używanie int-ów: http://ideone.com/FVcaBP

Inne to używanie odpowiedniej biblioteki: (Arbitrary-precision arithmetic, multi precision, bigint):

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:minuta
0

@Azarien Wykrzaczył by się przykładowo przy porównaniu do jedynki
Zależy co rozumiesz przez „wykrzaczył”.
Jeśli spodziewasz się 1.0000 i porównujesz do 1.0000 to będzie problem. Ale patrz niżej...

Liczb zmiennoprzecinkowych nie należy porównywać tak: (x == y), ale tak: abs(x - y) < jakaś_mała_liczba.
Jeśli mamy dzielenie, i chcemy zabezpieczyć się przed zerem, robimy ==0.0 (albo !=0.0), bez żadnych abs(). Bo dzielenie przez 0.00000004 się uda, i przez -0.0000001 też się uda. Interesuje nas tylko przypadek 0.000000

KR
Przez 0.00000 też się uda. Dostaniesz +Inf, -Inf lub NaN. Przy dzieleniu przez coś bardzo małego bliskiego 0, też możesz dostać +Inf lub -Inf lub NaN. Nie widzę niczego specyficznego w 0.
Endrju
  • Rejestracja:około 22 lata
  • Ostatnio:prawie 2 lata
1
bogdans napisał(a):

Liczb zmiennoprzecinkowych nie należy porównywać tak: (x == y), ale tak: abs(x - y) < jakaś_mała_liczba.

To też nie jest całkiem dobra metoda. Dobry artykuł: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm


"(...) otherwise, the behavior is undefined".
H4
  • Rejestracja:ponad 13 lat
  • Ostatnio:ponad 4 lata
  • Postów:107
0

Dzięki wszystkim :)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.