2010-01-10 11 views
8

Ich habe dieses Verhalten vor kurzem durch eine Haskell Tutorial gegangen und dann bemerkt, wenn einige einfache Haskell Ausdrücke in der interaktiven ghci Shell versucht:Warum sagt Ghci, dass 1.1 + 1.1 + 1.1> 3.3 wahr ist?

Prelude> 1.1 + 1.1 == 2.2 
True 
Prelude> 1.1 + 1.1 + 1.1 == 3.3 
False 
Prelude> 1.1 + 1.1 + 1.1 > 3.3 
True 
Prelude> 1.1 + 1.1 + 1.1 
3.3000000000000003 

Weiß jemand, warum das so ist?

+0

Nizza Javascript Float - Dezimal - binäre Visualizer: http://babbage.cs.qc.edu/IEEE-754/Decimal.html – Seth

+4

Dies ist kein Haskell-spezifisches Problem: das gleiche wird in jeder Sprache passieren, dass verwendet Gleitkommazahlen. Die genauen Details hängen von der Fließkomma-Implementierung ab, aber die meisten Prozessoren verwenden IEEE-754, das eng spezifiziert ist, um sicherzustellen, dass Programme sich überall genau gleich benehmen. –

Antwort

36

Weil 1.1 und 3.3floating point numbers sind. Dezimalbrüche wie .1 oder .3 sind in einer binären Gleitkommazahl nicht exakt darstellbar. .1 bedeutet 1/10. Um das binär darzustellen, wo jede Bruchzahl 1/2 n (1/2, 1/4, 1/8, usw.) darstellt, würden Sie unendlich viele Ziffern benötigen, 0.000110011 ... unendlich wiederholend.

Dies ist genau das gleiche Problem wie etwa 1/3 in Basis 10. In der Basis 10 benötigen Sie eine unendliche Anzahl von Ziffern, .33333 ... für immer, um genau 1/3 darzustellen. In der Basis 10 arbeitest du normalerweise rund um etwas wie .33. Aber wenn Sie drei Kopien davon addieren, erhalten Sie .99, nicht 1.

Für weit mehr Informationen zu diesem Thema lesen Sie What Every Computer Scientist Should Know About Floating Point Arithmetic.

Um rationale Zahlen in Haskell genauer darzustellen, können Sie immer den rationalen Datentyp verwenden, Ratio; gekoppelt mit Bignums (beliebig große ganze Zahlen, Integer in Haskell, im Gegensatz zu Int, die feste Größe sind) als der Typ für Zähler und Nenner, können Sie beliebig genaue rationale Zahlen, aber mit einer deutlich niedrigeren Geschwindigkeit als Gleitkommazahlen, die sind in Hardware implementiert und auf Geschwindigkeit optimiert.

Fließkommazahlen sind eine Optimierung, für wissenschaftliche und numerische Berechnungen, die Präzision für hohe Geschwindigkeit ausgleichen, so dass Sie eine große Anzahl von Berechnungen in kurzer Zeit durchführen können, solange Sie sich der Rundung bewusst sind Es beeinflusst Ihre Berechnungen.

+0

Danke, dass Sie den Datentyp 'Ratio' angegeben haben! –

+0

Nur ein Quibble, aber 'Ratio' ist ein Typkonstruktor -' Rational' ist ein Typ, der Typ (Alias) von 'Ratio Integer'. – BMeph

+0

@BMeph: Ah ja, stimmt. Ich bin inzwischen mit Haskell vertrauter geworden (seit ich diese Frage gestellt habe) und Typkonstruktoren vs. Typen verwirren mich nicht mehr, wenn sie denselben Namen haben. –

5

Nur wenige Schwimmer genau 754 Darstellung mit IEEE ausgedrückt werden kann, so dass sie immer ein wenig aus sein.

13

Da Gleitkommazahlen sind nicht genau (wikipedia)

+8

-1 Irreführend, tut mir leid. IEEE Gleitkommazahlen * sind absolut genau *, man muss nur auf Rundungsfehler mit Dezimalzahlen achten. Sie haben die gleichen Probleme, Brüche wie 1/3 in Dezimal (0,3333 ...) darzustellen. – Seth

+5

Einverstanden. Es geht nicht um Genauigkeit, sondern um Repräsentation. Zahlen, die vollständig dargestellt werden können, sind absolut genau. Diejenigen, die zwischen diesen Zahlen liegen, können nur angenähert werden, und das ist das Problem. – codekaizen

+16

Also, was Sie zwei Nitpickers sagen, ist, Gleitkommazahlen sind nur ungenau, wenn sie ungenau sind? Wow, nichts kommt an euch vorbei. Wenn "1.1 * 3! = 3.3" in Ihrem Mathesystem ist, ist das * nicht * genau. – Chuck

6

Es hat mit der Art, wie IEEE Gleitkommazahlen funktionieren, zu tun.

1.1 wird als 1.1000000000000001 in Fließkommazahl dargestellt, 3.3 wird als 3.2999999999999998 dargestellt.

So 1,1 + 1,1 + 1,1 ist eigentlich

1.1000000000000001 + 1.1000000000000001 + 1.1000000000000001 = 3.3000000000000003

die, wie Sie tatsächlich größer als 3,2999999999999998 ist zu sehen.

Die übliche Problemumgehung besteht darin, entweder die Gleichheit nicht auszuwerten oder zu prüfen, ob eine Zahl innerhalb des Ziels liegt +/- ein kleines Epsilon (was die Genauigkeit definiert, die Sie benötigen).

Bsp .: Wenn beide wahr sind, ist die Summe gleich "3,3" (innerhalb des zulässigen Fehlers).

1.1 + 1.1 + 1.1 < 3.3 + 1e9 
1.1 + 1.1 + 1.1 > 3.3 - 1e9 
+9

Nein, 1.1 ist nicht als 1.1000000000000001 dargestellt. Es wird dargestellt als 1.0001100110011001100110011001100110011001100110011010 (Basis 2), die bei der Konvertierung in Dezimal, Runden auf 1.10000000000000000001 –

+0

Ahh, wahr genug. – Seth

13

Sie können Fließkommafehler in Haskell mit rationalen Typen vermeiden:

Prelude Data.Ratio> let a = (11 % 10) + (11 % 10) + (11 % 10) 
Prelude Data.Ratio> a > (33 % 10) 
False 
Prelude Data.Ratio> fromRational a 
3.3 

Natürlich zahlen Sie eine Leistungseinbuße für die erhöhte Genauigkeit.

+8

Sie können sogar "let a = 1.1 + 1.1 + 1.1 :: Rational" schreiben, ohne sich Gedanken über den Genauigkeitsverlust machen zu müssen (das Literal '1.1' ist eigentlich eine Abkürzung für' fromRational (11% 10) '). –

+0

In der Tat können Sie einfach '1.1 + 1.1 + 1.1 <(3.3 :: Rational) schreiben, um genau das zu tun, was das OP will. Es ist eher un-kanonisch, dass ghci diese Berechnung standardmäßig auf float setzt, wenn Sie nicht ausdrücklich '1.1 + 1.1 + 1.1 <(3.3 :: Float) sagen. – leftaroundabout

1

Im Allgemeinen sollten Sie floats nicht auf Gleichheit (aus den oben genannten Gründen) vergleichen. Der einzige Grund, an den ich denken kann, ist, wenn Sie sagen wollen: "Hat sich dieser Wert verändert?" Zum Beispiel "if (newskore/= oldscore)" dann etwas unternehmen. Das ist in Ordnung, solange Sie nicht das Ergebnis von zwei getrennten Berechnungen vergleichen, um zu überprüfen, ob sie gleich sind (denn dann könnten sie sogar mathematisch, wenn sie sind, sonst runden).