2013-07-21 8 views
7

kiedy zrobię 5.2 - 2.3 w ghci Zdobędę 2.9000000000000004 zamiast 2.9. Również takie brzydkie (i dla ludzi NIEPRAWIDŁOWE) wyniki pojawiają się w innych miejscach podczas pracy z Double lub Float.brzydkie duble - dlaczego 2.9000000000000004 zamiast 2.9?

Dlaczego tak się dzieje? (to jest tylko dla ciekawości, nie moje prawdziwe pytanie)

Moje prawdziwe pytanie: Jak powiedzieć ghci, aby tego nie robił i pokazać wyniki operacji na podwójnych tak, jak każdy inny język programowania (i kalkulator) i tak jak każdy 15-latek je zapisał?

Jest to bardzo denerwujące, gdy używam ghci jako miłego kalkulatora i pracuję na listach, na których wykonuję takie operacje.

map ((-)2.3) [4.0, 3.8, 5.2, 6.4, 1.3, 8.3, 13.7, 9.0, 7.5, 2.4] 
[-1.7000000000000002,-1.5,-2.9000000000000004,-4.1000000000000005,0.9999999999999998,-6.000000000000001,-11.399999999999999,-6.7,-5.2,-0.10000000000000009] 

To po prostu nie pomóc podczas korzystania z numerów na kawałku papieru w późniejszym czasie

góry dziękuję :)

+3

tak jak komentarz boczny, jest to prawdopodobnie najczęściej zadawane pytanie w historii wszech czasów. –

Odpowiedz

8

Jak mogę powiedzieć ghci aby nie robić, i pokazują wyniki operacji na Doubles tak jak każdy inny język programowania (i kalkulatora) będzie i tak jak każdy 15-letni będzie je pisać?

Ponieważ te wyniki są wyniki rzeczywiste GHCI (a standardowy kalkulator *) oblicza nie można zmienić wewnętrzną reprezentację wyniku (see TNI's answer). Ponieważ chcesz wyświetlać tylko określoną liczbę miejsc po przecinku, bardziej zależy to od prezentacji (porównaj z printf("%f.2",...) w C).

Rozwiązanie tego problemu można znaleźć pod adresem https://stackoverflow.com/a/2327801/1139697. Można go zastosować w następujący sposób:

import Numeric 
fixedN :: (RealFloat b) => Int -> b -> String 
fixedN a b = showFFloat (Just a) b "" 

map (fixedN 2 . (-)2.3) [4.0, 3.8, 5.2, 6.4, 1.3, 8.3, 13.7, 9.0, 7.5, 2.4] 
-- result: ["-1.70","-1.50","-2.90","-4.10","1.00","-6.00",...] 

Pamiętaj, że nie będzie to możliwe, jeśli chcesz kontynuować obliczenia. Jeśli chcesz uzyskać arytmetykę dokładną, w każdym razie lepiej z użyciem Rationals. Nie zapominaj, że twoje dane wejściowe powinny być w tym przypadku racjonalne.

* tak, nawet standardowy kalkulator robi to samo, jedynym powodem, dla którego nie widzisz, jest stała prezentacja, nie może pokazywać więcej niż ustalona liczba miejsc po przecinku.

+1

W rzeczywistości wiele z prostszych kalkulatorów kieszonkowych używa [BCD] (http://en.wikipedia.org/wiki/Binary-coded_decimal), które nie cierpią z powodu ten problem, ale całkowicie nie nadaje się do robienia czegoś bardziej zaawansowanego niż pierwiastek kwadratowy wydajnie. Można powiedzieć, że omija błąd w sposobie liczenia ludzi ... – leftaroundabout

18

Dlaczego tak się dzieje?

Ponieważ niektóre liczby zmiennoprzecinkowe nie mogą być reprezentowane przez skończoną liczbę bitów bez zaokrąglenia. Floating-point numbers mają ograniczoną liczbę cyfr, nie mogą dokładnie przedstawiać wszystkich real numbers: gdy jest więcej cyfr niż dopuszcza format, pozostałe są pomijane - liczba jest zaokrąglana.

Powinieneś prawdopodobnie przeczytać What Every Computer Scientist Should Know About Floating-Point Arithmetic i this answer.

+2

w rzeczywistości nie mogą nawet reprezentować wszystkich racjonalnych rozwiązań, jak widzimy tutaj. –

3

Jest to charakter liczb zmiennoprzecinkowych, które nie mogą dokładnie reprezentować rzeczywistych (ani wymiernych) liczb. Domyślna konwersja Haskella na ciąg zapewnia, że ​​po ponownym odczytaniu liczby otrzymasz dokładnie taką samą reprezentację.Jeśli chcesz inny sposób drukowania numerów, możesz utworzyć własny typ, który pokazuje numery inaczej.

Coś (niesprawdzone):

newtype MyDouble = MyDouble {getMyDouble :: Double} 
      deriving (Eq, Ord, Num, Real, RealFrac, Fractional, Floating) 
instance Show MyDouble where show = printf "%g" . getMyDouble 
default (MyDouble) 

ta tworzy kopię typu Double, ale z innym Show przykład, że po prostu drukuje kilka dziesiętnych. Deklaracja default powoduje, że kompilator wybiera ten typ w przypadku niejasności. Aha, i żeby to działało, potrzebujesz kilku rozszerzeń językowych.

Można również wypróbować typ CReal z pakietu numbers.

3

Jak mogę powiedzieć ghci aby nie robić, i pokazują wyniki operacji na Doubles tak jak każdy inny język programowania (i kalkulatora) byłoby

Czy rzeczywiście spróbować „każdy inny język programowania"? A może po prostu się znęcacie?

FWIW, tutaj jest wyjście z tłumacza na język, który używa JVM:

frege> 5.2 - 2.3 
2.9000000000000004 

Wygląda mi się, że masz ten sam wynik ze wszystkich języków JVM. A ponieważ JVM jest napisane w C++, istnieje szansa, że ​​wynik jest taki sam. A ponieważ większość języków uruchomionych w języku C/C++ jest napisanych w języku C/C++, istnieje szansa, że ​​uzyskasz taki sam wynik także w tych językach. Chyba że są one tak "przyjazne dla użytkownika" i wykonują zaokrąglenia, o które nie prosiłeś.

Powiązane problemy