2010-11-18 6 views
4

J'ai expérimenté ce phénomène en Python en premier, mais il s'est avéré que c'est la réponse commune, par exemple MS Excel donne ceci. Wolfram Alpha donne une réponse schizoïde intéressante, où il indique que l'approximation rationnelle de zéro est 1/5. (1.0 mod 0.1)Pourquoi fmod (1.0,0.1) == .1?

D'un autre côté, si j'implémente la définition à la main, cela me donne la bonne réponse (0).

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

Que se passe-t-il ici? Est-ce que je manque quelque chose?

+3

renvoie ici 0,09999999999999995 (OS X, Python 2.5.4). La réponse dépend de la bibliothèque C de votre plateforme. – geoffspear

+0

ok, désolé j'ai arrondi. de toute façon, la réponse devrait être 0 dans ma compréhension puisque 1.0/0.1 = 10 qui est un nombre entier. – beemtee

Répondre

1

Ce résultat est dû à la représentation en virgule flottante de la machine. Dans votre méthode, vous 'cast' (un peu) le float à un int et n'avez pas ce problème. La meilleure façon d'éviter de tels problèmes (surtout pour mod) est de multiplier par un entier suffisamment grand (seulement 10 sont nécessaires dans votre cas) et de refaire l'opération.

fmod (1.0,0.1)

fmod (10.0,1.0) = 0

+0

Où est-ce que je fais le "casting" "? si la méthode «mon» donne la bonne réponse, pourquoi les autres utilisent-ils d'autres méthodes? – beemtee

+0

il n'y a pas exactement 'casting' en python comme il y en a en C++ (d'où le kinda). Vous faites effectivement le virgule flottante devenir un int lorsque vous «plancher» votre numéro. Stephen Canon a une réponse plus détaillée qui vous montre que [i] ne donne pas la bonne réponse, pas celle que vous attendiez ... – g19fanatic

22

Parce 0.1 est pas 0,1; cette valeur n'est pas représentable en double précision, il s'arrondi au nombre double précision, ce qui est exactement:

0.1000000000000000055511151231257827021181583404541015625 

Lorsque vous appelez fmod, vous obtenez le reste de la division par la valeur indiquée ci-dessus, ce qui est exactement:

0.0999999999999999500399638918679556809365749359130859375 

qui arrondit à 0.1 (ou peut-être 0.09999999999999995) lors de l'impression.

En d'autres termes, fmod fonctionne parfaitement, mais vous ne lui donnez pas la contribution que vous pensez être.


Edit: votre propre implémentation vous donne la bonne réponse, car il est moins précis , croyez-le ou non. Tout d'abord, notez que fmod calcule le reste sans erreur d'arrondi; la seule source d'inexactitude est l'erreur de représentation introduite en utilisant la valeur 0.1. Passons maintenant à votre implémentation et voyons comment l'erreur d'arrondi qu'elle provoque corrige exactement l'erreur de représentation.

Évaluer a - floor(a/n) * n une étape à la fois, garder la trace des valeurs exactes calculées à chaque étape:

D'abord, nous évaluons 1.0/n, où n est le plus proche approximation double précision à 0.1 comme indiqué ci-dessus. Le résultat de cette division est d'environ:

9.999999999999999444888487687421760603063276150363492645647081359... 

Notez que cette valeur est pas un nombre double précision représentable - il obtient arrondi.Pour voir comment cela se passe-t arrondi, regardons le nombre en binaire au lieu de décimales:

1001.1111111111111111111111111111111111111111111111111 10110000000... 

L'espace indique où l'arrondi à double précision se produit. Étant donné que la partie après le point rond est plus grande que le point de mi-parcours exact, cette valeur arrondit exactement à 10.

floor(10.0) est, de façon prévisible, 10.0. Tout ce qui reste est de calculer 1.0 - 10.0*0.1.

En binaire, la valeur exacte de 10.0 * 0.1 est:

1.0000000000000000000000000000000000000000000000000000 0100 

à nouveau, cette valeur est représentable comme un double, et ainsi est arrondi à la position indiquée par un espace. Cette fois, il arrondit à exactement 1.0, et donc le calcul final est 1.0 - 1.0, qui est bien sûr 0.0.

Votre implémentation contient deux erreurs d'arrondi, qui annulent exactement l'erreur de représentation de la valeur 0.1 dans ce cas. fmod, en revanche, est toujours exacte (au moins sur les plates-formes avec une bonne bibliothèque numérique), et expose l'erreur de représentation de 0.1.

+0

Cela n'explique pas pourquoi '1.0 - floor (1.0/0.1) * 0.1' donne zéro cependant. – kennytm

+0

ouais, je comprends. 1.0 peut être exactement représenté par un double alors que 0.1 ne le peut pas. Puisque le nombre représentable le plus proche est supérieur à l'original, le résultat de la division sera quelque chose comme 9,99999999999, qui devient 9 après la troncature. – beemtee

+0

@KennyTM: ajouté une réponse à la question de suivi pour vous. –

0

fmod renvoie x-i * y, qui est inférieur à y et i est un nombre entier. 0.09 .... est dû à la précision du flottant. essayez fmod(0.3, 0.1) -> 0.09... mais fmod(0.4, 0.1) -> 0.0 parce que 0,3 est 0,2999999 ... comme un flotteur.

fmod(1/(2.**n), 1/(2.**m) ne produira jamais rien d'autre que 0.0 pour l'entier n> = m.

1

De man fmod:

La fonction fmod() calcule le reste en virgule flottante de la division de x par y . La valeur de retour est x - n * y, où n est le quotient de x/y, arrondi vers zéro à un entier.

Alors qu'est-ce qui se passe est:

  1. En fmod(1.0, 0.1), le 0.1 est en fait légèrement supérieure à 0,1 parce que la valeur ne peut pas être exactement représentée comme un flotteur.
  2. Donc n = x/y = 1,0/0.1000something = 9.9999something
  3. Lorsque arrondi vers 0, n devient réellement 9
  4. x - n * y = 1,0 - 9 * 0,1 = 0,1

Editer: En ce qui concerne pourquoi cela fonctionne avec floor(x/y), autant que je peux dire que cela semble être une anomalie FPU. Sur x86, fmod utilise l'instruction fprem, tandis que x/y utilisera fdiv.Chose curieuse 1.0/0.1 semble revenir exactement 10.0:

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

Je suppose que fdiv utilise un algorithme plus précis que fprem. Une discussion se trouve ici: http://www.rapideuphoria.com/cgi-bin/esearch.exu?thread=1&fromMonth=A&fromYear=8&toMonth=C&toYear=8&keywords=%22Remainder%22

+0

Merci! C'est la réponse claire et définitive. La seule question qui reste est pourquoi la fonction définie par la main ne souffre pas de ce problème? – beemtee

+0

Non, ce n'est pas une anomalie FPU, mais plutôt une conséquence du fait que l'algorithme écrit à la main implique l'arrondi, alors que la fonction 'fmod' délivre toujours une réponse exacte. Voir ma réponse pour plus de détails. Notez également que l'implémentation particulière de 'fmod' est spécifique à la bibliothèque mathématique de votre plateforme, et non au matériel (bien que de nombreuses bibliothèques mathématiques x86 utilisent l'instruction' fprem', pas toutes). –

+0

@Stephen Canon: Vous avez raison, bien sûr. J'ai raté d'une manière ou d'une autre que l'étape supplémentaire «fdiv» provoque encore plus d'arrondi. – adw

0

Cela donne la bonne réponse:

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 

Je pense que cela fonctionne parce qu'elle utilise les équivalents rationnels des deux nombres à virgule flottante qui fournit une réponse plus précise.

0

La fonction divmod de Python est instructive ici. Il vous indique à la fois le quotient et le reste d'une opération de division.

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

Lorsque vous tapez 0.1, l'ordinateur ne peut pas représenter la valeur exacte en arithmétique binaire à virgule flottante, il choisit le nombre le plus proche qu'il peut représenter, ,10000000000000001. Ensuite, lorsque vous effectuez l'opération de division, l'arithmétique en virgule flottante décide que le quotient doit être 9, car 0.10000000000000001 * 10 est supérieur à 1.0. Cela vous laisse avec un reste légèrement inférieur à 0,1.

Je voudrais utiliser le nouveau module Python fractions pour obtenir des réponses exactes.

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

IOW, (1/1) mod (1/10) = (0/1), ce qui équivaut à 1 mod 0.1 = 0.

Une autre option consiste à implémenter vous-même l'opérateur de module, ce qui vous permet de spécifier votre propre politique.

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

BTW, Wolfram Alpha obtient cette réponse aussi, si vous le demandez en termes de fractions exactes au lieu de nombres à virgule flottante: http://www.wolframalpha.com/input/?i=1+mod+1%2F10 –

Questions connexes