web-dev-qa-db-fra.com

La division entière est-elle toujours égale au plancher de la division régulière?

Pour les grands quotients, la division entière (//) Ne semble pas nécessairement égale au plancher de la division régulière (math.floor(a/b)).

Selon Python docs ( https://docs.python.org/3/reference/expressions.html - 6.7),

la division au sol des entiers donne un entier; le résultat est celui de la division mathématique avec la fonction "plancher" appliquée au résultat.

Cependant,

math.floor(648705536316023400 / 7) = 92672219473717632

648705536316023400 // 7 = 92672219473717628

'{0:.10f}'.format(648705536316023400 / 7) renvoie '92672219473717632.0000000000', mais les deux derniers chiffres de la partie décimale doivent être 28 et non 32.

29
Aditya Chanana

La raison pour laquelle les quotients dans votre cas de test ne sont pas égaux est que dans le cas math.floor(a/b), le résultat est calculé avec une arithmétique à virgule flottante (IEEE-754 64 bits), ce qui signifie qu'il existe une précision maximale. Le quotient que vous avez là est supérieur au 253 limite au-dessus de laquelle la virgule flottante n'est plus précise jusqu'à l'unité.

Avec la division entière cependant, Python utilise sa plage entière illimitée, et donc ce résultat est correct.

Voir aussi "Sémantique de la vraie division" dans PEP 238 :

Notez que pour les arguments int et long, la vraie division peut perdre des informations; c'est dans la nature de la vraie division (tant que les rationnels ne sont pas dans le langage). Les algorithmes qui utilisent consciemment les longs devraient envisager d'utiliser //, car la véritable division des longs ne conserve pas plus de 53 bits de précision (sur la plupart des plates-formes).

30
trincot

Vous pouvez avoir affaire à des valeurs intégrales qui sont trop grandes pour être exprimées exactement comme des flottants. Votre nombre est nettement supérieur à 2 ^ 53, ce qui est où les écarts entre les doubles adjacents à virgule flottante commencent à devenir supérieurs à 1 . Vous perdez donc une certaine précision lors de la division en virgule flottante.

La division entière, en revanche, est calculée exactement.

18
interfect

Votre problème est que, malgré le fait que "/" est parfois appelé le "véritable opérateur de division" et son nom de méthode est __truediv__, son comportement sur les entiers n'est pas une "vraie division mathématique". Au lieu de cela, il produit un résultat en virgule flottante qui a inévitablement une précision limitée.

Pour des nombres suffisamment grands, même la partie intégrante d'un nombre peut souffrir d'erreurs d'arrondi en virgule flottante. Lorsque 648705536316023400 est converti en un Python float (IEEE double), il est arrondi à 6487055363160234241.

Je n'arrive pas à trouver de documentation faisant autorité sur le comportement exact des opérateurs sur les types intégrés dans Python actuel. Le PEP d'origine qui a introduit la fonctionnalité indique que "/" équivaut à convertir les entiers en virgule flottante, puis à effectuer une division en virgule flottante. Cependant, un test rapide en Python 3.5 montre que ce n'est pas le cas. Si c'était le cas, le code suivant ne produirait aucune sortie.

for i in range(648705536316023400,648705536316123400):
    if math.floor(i/7) != math.floor(float(i)/7):
        print(i)

Mais au moins pour moi, cela produit une sortie.

Au lieu de cela, il me semble que Python effectue la division sur les nombres tels que présentés et arrondit le résultat pour tenir dans un nombre à virgule flottante. Prenons un exemple de la sortie de ce programme.

648705536316123383 // 7                   == 92672219473731911
math.floor(648705536316123383 / 7)        == 92672219473731904
math.floor(float(648705536316123383) / 7) == 92672219473731920
int(float(92672219473731911))             == 92672219473731904

La bibliothèque standard Python fournit un type de Fraction et l'opérateur de division pour une Fraction divisée par un int effectue la "vraie division mathématique".

math.floor(Fraction(648705536316023400) / 7) == 92672219473717628
math.floor(Fraction(648705536316123383) / 7) == 92672219473731911

Cependant, vous devez être conscient des performances et des implications mémoire potentiellement graves de l'utilisation du type Fraction. N'oubliez pas que les fractions peuvent augmenter les besoins de stockage sans augmenter en ampleur.


Pour tester davantage ma théorie de "un arrondi contre deux", j'ai fait un test avec le code suivant.

#!/usr/bin/python3
from fractions import Fraction
edt = 0
eft = 0
base = 1000000000010000000000
top = base + 1000000
for i in range(base,top):
    ex = (Fraction(i)/7)
    di = (i/7)
    fl = (float(i)/7)
    ed = abs(ex-Fraction(di))
    ef = abs(ex-Fraction(fl))
    edt += ed
    eft += ef
print(edt/10000000000)
print(eft/10000000000)

Et la magnitude moyenne de l'erreur était considérablement plus faible pour effectuer la division directement que pour la conversion en flottant en premier, soutenant la théorie d'un arrondi contre deux.

1Notez que l'impression directe d'un flottant n'affiche pas sa valeur exacte, mais plutôt le nombre décimal le plus court qui arrondira à cette valeur (permettant une conversion aller-retour sans perte de flottant en chaîne et de retour en flottant).

9
plugwash