2010-11-18 11 views
4

Ich habe dieses Phänomen zuerst in Python erlebt, aber es stellte sich heraus, dass es die übliche Antwort ist, zum Beispiel gibt MS Excel dies. Wolfram Alpha gibt eine interessante schizoide Antwort, wo es besagt, dass die rationale Näherung von Null 1/5 ist. (1.0 mod 0.1)Warum fmod (1.0.0.1) == .1?

Auf der anderen Seite, wenn ich die Definition von Hand implementieren, gibt es mir die "richtige" Antwort (0).

def myFmod(a,n): 
    return a - floor(a/n) * n 

Was ist hier los? Vermisse ich etwas?

+3

gibt hier 0.09999999999999995 zurück (OS X, Python 2.5.4). Die Antwort hängt von der C-Bibliothek Ihrer Plattform ab. – geoffspear

+0

ok, tut mir leid, ich habe die Rundung gemacht. Wie auch immer, die Antwort sollte 0 in meinem Verständnis sein, seit 1.0/0.1 = 10, was eine ganze Zahl ist. – beemtee

Antwort

1

Dieses Ergebnis ist auf die Fließkomma-Darstellung der Maschine zurückzuführen. In Ihrer Methode "gießen" Sie den Float zu einem int und haben dieses Problem nicht. Der 'beste' Weg, um solche Probleme zu vermeiden (besonders für mod), besteht darin, mit einem ausreichend großen int zu multiplizieren (in diesem Fall werden nur 10 benötigt) und die Operation erneut durchzuführen.

fmod (1.0,0.1)

fmod (10.0,1.0) = 0

+0

wo mache ich das "ein bisschen Casting"? wenn "meine" Methode die richtige Antwort gibt, warum benutzen andere andere Methoden? – beemtee

+0

gibt es nicht genau 'Casting' in Python, wie es in C++ (daher die Art) ist. Sie machen den Fließkomma effektiv zu einem int, wenn Sie Ihre Nummer "floor". Stephen Canon hat eine ausführlichere Antwort, die Ihnen zeigt, dass es die richtige Antwort gibt, nicht die, die Sie erwartet haben ... – g19fanatic

22

Da 0.1 nicht 0,1; dieser Wert nicht in doppelter Genauigkeit darstellbare, es wird auf die nächste Zahl mit doppelter Genauigkeit, die genau so gerundet:

0.1000000000000000055511151231257827021181583404541015625 

Wenn Sie fmod nennen, erhalten Sie den Rest der Division durch den Wert oben aufgeführten, das ist genau das:

0.0999999999999999500399638918679556809365749359130859375 

, die 0.1 runden (oder vielleicht 0.09999999999999995), wenn Sie es drucken.

Mit anderen Worten, funktioniert fmod perfekt, aber Sie geben es nicht die Eingabe, die Sie denken, Sie sind.


Edit: Ihre eigene Implementierung gibt Ihnen die richtige Antwort, weil es ungenaue, es glaubt oder nicht. Zunächst einmal, beachte, dass fmod den Rest ohne Rundungsfehler berechnet; Die einzige Ungenauigkeitsquelle ist der Darstellungsfehler, der durch Verwendung des Werts 0.1 eingeführt wurde. Sehen wir uns nun Ihre Implementierung an und sehen Sie, wie der Rundungsfehler, den sie verursacht, den Darstellungsfehler genau aufhebt.

Evaluieren a - floor(a/n) * n ein Schritt zu einem Zeitpunkt, an jeder Stufe berechnet die genauen Werte zu verfolgen:

Zuerst wir 1.0/n bewerten, wo n zu 0.1 die Nähe doppelter Genauigkeit Näherung ist wie oben gezeigt. Das Ergebnis dieser Division ist ungefähr:

9.999999999999999444888487687421760603063276150363492645647081359... 

Beachten Sie, dass dieser Wert nicht darstellbare Zahl mit doppelter Genauigkeit ist - so wird es abgerundet.Um zu sehen, wie diese Rundung geschieht, schauen wir uns die Zahl in binärer aussehen statt dezimal:

1001.1111111111111111111111111111111111111111111111111 10110000000... 

Der Raum zeigt an, wo die Rundung mit doppelter Genauigkeit erfolgt. Da der Teil nach dem runden Punkt größer ist als der exakte halbe Punkt, wird dieser Wert auf genau 10 gerundet.

floor(10.0) ist, vorhersagbar, 10.0. Alles, was übrig bleibt, ist 1.0 - 10.0*0.1 zu berechnen.

im Binär-, der genaue Wert von 10.0 * 0.1 ist:

1.0000000000000000000000000000000000000000000000000000 0100 

wieder, dieser Wert nicht darstellbaren als Doppel, und so wird an der Position durch einen Raum angegeben gerundet. Diesmal rundet es auf genau 1.0, und so ist die endgültige Berechnung 1.0 - 1.0, die natürlich 0.0 ist.

Ihre Implementierung enthält zwei Rundungsfehler, die in diesem Fall den Darstellungsfehler des Wertes 0.1 exakt aufheben. fmod dagegen ist immer genau (zumindest auf Plattformen mit einer guten Numerikbibliothek) und macht den Darstellungsfehler von 0.1.

+0

Dies erklärt nicht warum '1.0 - Boden (1.0/0.1) * 0.1 gibt jedoch null. – kennytm

+0

Ja, ich verstehe es. 1,0 kann genau durch ein Doppel dargestellt werden, während 0,1 nicht kann. Da die nächste darstellbare Zahl größer als das Original ist, wird das Ergebnis der Division etwa 9.99999999999 sein, was nach dem Abschneiden zu 9 wird. – beemtee

+0

@KennyTM: eine Antwort auf die Follow-up-Frage für Sie hinzugefügt. –

0

fmod gibt x-i * y zurück, was kleiner als y ist, und i ist eine ganze Zahl. 0,09 .... ist wegen der Gleitkomma-Präzision. versuchen Sie fmod(0.3, 0.1) -> 0.09... aber fmod(0.4, 0.1) -> 0.0 weil 0,3 0,2999999 ist ... als float.

fmod(1/(2.**n), 1/(2.**m) wird nie etwas anderes als 0.0 für Integer n> = m produzieren.

1

Von man fmod:

Die fmod() Funktion berechnet den Gleitkommawert Rest x Dividieren durch y. Der Rückgabewert ist x - n * y, wobei n der Quotient von x/y, auf Null zu einer ganzen Zahl gerundet ist.

Also, was passiert ist:

  1. In fmod(1.0, 0.1), die 0,1 tatsächlich etwas größer als 0,1 ist, weil der Wert nicht genau als Schwimmer vertreten sein.
  2. So n = x/y = 1,0/0.1000something = 9.9999something
  3. Wenn auf 0 abgerundet, n wird tatsächlich 9
  4. x - y n * = 1,0 - 9 * 0,1 = 0,1

Bearbeiten: Warum es mit floor(x/y) funktioniert, so weit ich das sagen kann, scheint dies eine FPU Eigenart zu sein. Auf x86 verwendet fmod die Anweisung fprem, während x/yfdiv verwendet.Merkwürdiger 1.0/0.1 scheint genau zurückzukehren 10.0:

>>> struct.pack('d', 1.0/0.1) == struct.pack('d', 10.0) 
True 

Ich nehme an fdiv verwendet einen genaueren Algorithmus als fprem. Einige Diskussionen sind hier zu finden: http://www.rapideuphoria.com/cgi-bin/esearch.exu?thread=1&fromMonth=A&fromYear=8&toMonth=C&toYear=8&keywords=%22Remainder%22

+0

Danke! Dies ist die klare und endgültige Antwort. Die einzige Frage ist, warum die handdefinierte Funktion nicht unter diesem Problem leidet. – beemtee

+0

Nein, es ist keine FPU-Eigenart, sondern eine Folge der Tatsache, dass der handgeschriebene Algorithmus eine Rundung beinhaltet, während die 'fmod'-Funktion immer eine exakte Antwort liefert. Siehe meine Antwort für weitere Details. Beachten Sie auch, dass die spezielle Implementierung von 'fmod' spezifisch für die Math-Bibliothek Ihrer Plattform ist, nicht für die Hardware (obwohl viele x86-Math-Bibliotheken die' fprem'-Anweisung verwenden, nicht alle). –

+0

@ Stephen Canon: Du hast natürlich Recht. Irgendwie habe ich übersehen, dass der zusätzliche 'fdiv'-Schritt noch mehr Rundung verursacht. – adw

0

Dies gibt die richtige Antwort:

a = 1.0 
b = 0.1 

a1,a2 = a.as_integer_ratio() 
b1,b2 = b.as_integer_ratio() 
div = float(a1*b2)/float(a2*b1) 
mod = a - b*div 
print mod 
# 0.0 

Ich denke, es funktioniert, weil durch sie rational Äquivalente der beiden Gleitkommazahlen verwendet, die eine genauere Antwort liefert.

0

Die Python-Divmod-Funktion ist hier lehrreich. Es zeigt Ihnen sowohl den Quotienten als auch den Rest einer Divisionsoperation an.

$ python 
>>> 0.1 
0.10000000000000001 
>>> divmod(1.0, 0.1) 
(9.0, 0.09999999999999995) 

Wenn Sie 0,1 eingeben, kann der Computer nicht, dass genauen Wert in binärer Gleitpunktarithmetik darstellen, so dass es die nächste Nummer wählt, die sie darstellen können, 0,10000000000000001. Wenn Sie dann die Divisionsoperation ausführen, entscheidet die Fließkomma-Arithmetik, dass der Quotient 9 sein muss, da 0,1000000000000000001 * 10 größer als 1,0 ist. Dies ergibt einen Rest von etwas weniger als 0,1.

Ich möchte das neue Python fractions Modul verwenden, um genaue Antworten zu erhalten.

>>> from fractions import Fraction 
>>> Fraction(1, 1) % Fraction(1, 10) 
Fraction(0, 1) 

IOW, (1/1) mod (1/10) = (0/1), die 1 mod 0.1 = 0 entspricht.

Eine andere Möglichkeit ist, den Modulo-Operator selbst zu implementieren, damit Sie Ihre eigene Richtlinie festlegen können.

>>> x = 1.0 
>>> y = 0.1 
>>> x/y - math.floor(x/y) 
0.0 
+0

BTW, Wolfram Alpha bekommt diese Antwort auch, wenn Sie es in Bezug auf exakte Fraktionen anstelle von Fließkommazahlen fragen: http://www.wolframalpha.com/input/?i=1+mod+1%2F10 –