62 votes

Quelle est la raison mathématique pour laquelle Python a choisi d'arrondir la division des nombres entiers à l'infini négatif ?

Je connais Python // s'arrondit vers l'infini négatif et en C++ / est tronqué, arrondi vers 0.

Et voici ce que je sais jusqu'à présent :

               |remainder| 
-12 / 10  = -1,   - 2      // c++
-12 // 10 = -2,   + 8      # python

12 / -10  = -1,     2      // c++
12 // -10 = -2,   - 8      # python

12 / 10  = 1,      2       //both
12 // 10 = 1,      2 

-12 / -10 = 1,    - 2      //both
          = 2,    + 8

C++: 
1. m%(-n) == m%n
2. -m%n == -(m%n)
3. (m/n)*n + m%n == m

python:
1. m%(-n) == -8 == -(-m%n)
2. (m//n)*n + m%n == m

Mais pourquoi Python // choisir de tourner vers l'infini négatif ? Je n'ai pas trouvé de ressources expliquant cela, mais seulement trouvé et entendu des gens le dire vaguement : "pour des raisons mathématiques" .

Par exemple, dans Pourquoi -1/2 est évalué à 0 en C++, mais -1 en Python ? :

Les gens qui traitent de ces choses dans l'abstrait ont tendance à penser que qu'il est plus logique de se diriger vers l'infini négatif ( cela signifie que c'est compatible avec la fonction modulo telle qu'elle est définie en mathématiques, plutôt que de plutôt que % ayant une signification quelque peu étrange ).

Mais je ne vois pas que le C++'s / n'étant pas compatible avec la fonction modulo. En C++, (m/n)*n + m%n == m s'applique également.

Quelle est donc la raison (mathématique) pour laquelle Python choisit d'arrondir à l'infini négatif ?


Je pense que je devrais mettre ça Voici l'ancien article du blog de Guido van Rossum sur le sujet. dans le corps de la question au cas où des personnes manqueraient les commentaires.

75voto

Ilmari Karonen Points 20585

Mais pourquoi Python // choisir d'arrondir vers l'infini négatif ?

Je ne suis pas sûr que le site La raison pour laquelle ce choix a été fait à l'origine n'est documentée nulle part (bien que, pour ce que j'en sais, elle pourrait être expliquée en détail dans un PEP quelque part), mais nous pouvons certainement trouver diverses raisons pour lesquelles cela a du sens.

L'une des raisons est simplement que l'arrondi vers l'infini négatif (ou positif !) signifie que tous les nombres sont arrondis de la même manière, alors que l'arrondi vers zéro rend le zéro spécial. La façon mathématique de le dire est que l'arrondi vers le bas vers est invariant de translation c'est-à-dire qu'il satisfait à l'équation :

round_down(x + k) == round_down(x) + k

pour tous les nombres réels x et tous les entiers k . L'arrondi vers zéro ne le fait pas, puisque, par exemple :

round_to_zero(0.5 - 1) != round_to_zero(0.5) - 1

Bien sûr, d'autres arguments existent aussi, comme celui que vous citez et qui se base sur la compatibilité avec (comment nous voudrions) les % opérateur (pour se comporter) - plus d'informations à ce sujet ci-dessous.

En effet, je dirais que le réel La question qui se pose ici est de savoir pourquoi le programme Python int() La fonction est no défini pour arrondir les arguments à virgule flottante vers l'infini négatif, de sorte que m // n serait égal à int(m / n) . (Je soupçonne des "raisons historiques".) Encore une fois, ce n'est pas si grave, puisque Python dispose au moins de math.floor() qui satisfait m // n == math.floor(m / n) .


Mais je ne vois pas le C++ 's / n'étant pas compatible avec la fonction modulo. En C++, (m/n)*n + m%n == m s'applique également.

C'est vrai, mais garder cette identité tout en ayant / Pour arrondir vers zéro, il faut définir % de manière maladroite pour les nombres négatifs. En particulier, nous perdons les deux propriétés mathématiques utiles suivantes de Python % :

  1. 0 <= m % n < n pour tous m et tous les positifs n ; et
  2. (m + k * n) % n == m % n pour tous les entiers m , n y k .

Ces propriétés sont utiles car l'une des principales utilisations de la % c'est "envelopper" un nombre m à une gamme limitée de longueur n .


Par exemple, disons que nous essayons de calculer des directions : disons que heading est notre actuel cap compas en degrés (comptés dans le sens des aiguilles d'une montre à partir du plein nord, avec 0 <= heading < 360 ) et que nous voulons calculer notre nouveau cap après avoir tourné angle degrés (où angle > 0 si on tourne dans le sens des aiguilles d'une montre, ou angle < 0 si on tourne dans le sens inverse des aiguilles d'une montre). En utilisant l'outil Python % nous pouvons calculer notre nouvelle rubrique simplement comme suit :

heading = (heading + angle) % 360

et cela fonctionnera simplement dans tous les cas.

Cependant, si nous essayons d'utiliser cette formule en C++, avec ses différentes règles d'arrondi et ses différences correspondantes, nous ne pourrons pas utiliser cette formule. % opérateur, nous constaterons que l'enveloppement ne fonctionne pas toujours comme prévu ! Par exemple, si nous commençons par faire face au nord-ouest ( heading = 315 ) et tourner de 90° dans le sens des aiguilles d'une montre ( angle = 90 ), nous allons en effet nous retrouver face au nord-est ( heading = 45 ). Mais si alors essayer de faire demi-tour 90° dans le sens inverse des aiguilles d'une montre ( angle = -90 ), avec le C++ % opérateur, nous ne finirons pas par revenir à heading = 315 comme prévu, mais plutôt à heading = -45 !

Pour obtenir un comportement enveloppant correct en utilisant le langage C++. % nous devrons plutôt écrire la formule comme suit :

heading = (heading + angle) % 360;
if (heading < 0) heading += 360;

ou comme :

heading = ((heading + angle) % 360) + 360) % 360;

(La formule plus simple heading = (heading + angle + 360) % 360 ne fonctionnera que si nous pouvons toujours garantir que heading + angle >= -360 .)

C'est le prix à payer pour avoir une règle d'arrondi non invariante en translation pour la division et, par conséquent, une règle d'arrondi non invariante en translation pour la division. % opérateur.

8voto

supercat Points 25534

L'arithmétique des nombres entiers et celle des nombres réels définissent leurs opérateurs de division de telle sorte que les deux équivalences suivantes sont valables pour toutes les valeurs de n et d.

(n+d)/d = (n/d)+1
(-n)/d = -(n/d)

Malheureusement, l'arithmétique des nombres entiers ne peut pas être définie de manière à ce que les deux soient valables. Pour de nombreuses raisons, la première équivalence est plus utile que la seconde, mais dans la plupart des situations où le code divise deux valeurs, l'une des deux possibilités suivantes s'applique :

  1. Les deux valeurs sont positives, auquel cas la deuxième équivalence n'est pas pertinente.

  2. Le dividende est un multiple entier précis du diviseur, auquel cas les deux équivalences peuvent se vérifier simultanément.

Historiquement, la façon la plus simple de traiter la division impliquant des nombres négatifs était de vérifier si un seul opérande était négatif, d'enlever les signes, d'effectuer la division, puis de rendre le résultat négatif si un seul opérande l'était. Cette méthode se comporterait comme il se doit dans les deux situations courantes et serait moins coûteuse que l'utilisation d'une approche qui maintiendrait la première équivalence dans tous les cas, plutôt que seulement lorsque le diviseur est un multiple exact du dividende.

Python ne devrait pas être considéré comme utilisant une sémantique inférieure, mais plutôt comme décidant que la sémantique qui serait généralement supérieure dans les affaires qui ont compté vaudrait la peine de faire une division légèrement plus lente même dans les cas où la sémantique précise n'a pas d'importance. .

8voto

Daweo Points 10024

Mais pourquoi Python // choisir d'arrondir vers l'infini négatif ?

Selon python-history.blogspot.com Guido van Rossum élu un tel comportement pour // parce que

(...)il y a une bonne raison mathématique. L'opération de division d'un nombre entier (//) et sa sœur, l'opération modulo (%), vont ensemble et satisfont une belle relation mathématique (toutes les variables sont des entiers) :

a/b = q avec le reste r

de sorte que

b*q + r = a et 0 <= r < b

(en supposant que a et b sont >= 0).

Si vous voulez que la relation s'étende pour un a négatif (en gardant b positif), vous avez deux choix : si vous tronquez q vers zéro, r deviendra négatif, de sorte que l'invariant se transforme en 0 <= abs(r) < sinon, vous pouvez plancher q vers l'infini négatif, et l'invariant invariant reste 0 <= r < b(...) En théorie mathématique des nombres, les mathématiciens préfèrent toujours le dernier choix (...). Pour Python, j'ai fait le même choix car il y a applications intéressantes de l'opération modulo où le signe de a est sans intérêt. de a est inintéressant. Prenons l'exemple d'un timestamp POSIX (secondes depuis le début de l'année 1970) et de le transformer en heure du jour. Puisque Puisqu'il y a 24*3600 = 86400 secondes dans un jour, ce calcul est simplement t % 86400. Mais si nous devions exprimer les temps avant 1970 en utilisant des chiffres négatifs, la règle de la "troncature vers zéro" donnerait un résultat dénué de sens ! résultat dénué de sens ! En utilisant la règle du plancher, tout se passe bien. D'autres applications auxquelles j'ai pensé sont les calculs de la position des pixels en infographie. Je suis sûr qu'il y en a d'autres.

Donc, pour résumer // Le choix du comportement est dû au fait qu'il doit rester cohérent avec % Ce dernier a été choisi en raison de son utilité pour travailler avec des horodatages et des pixels négatifs (avant le début de 1970).

7voto

Adrian Mole Points 30581

Bien que je ne puisse pas fournir un officiel la définition de la raison pour laquelle les modes d'arrondi ont été choisis comme ils l'ont été, la citation concernant la compatibilité avec le système de gestion des données de l'UE. % opérateur, que vous avez inclus, fait sont logiques si l'on considère que % n'est pas tout à fait la même chose en C++ et en Python.

En C++, c'est le reste alors qu'en Python, c'est l'opérateur module et, quand les deux opérandes ont différents signes, ce n'est pas forcément la même chose. Il y a de bonnes explications de la différence entre ces opérateurs dans les réponses à : Quelle est la différence entre "mod" et "remainder" ?

Maintenant, compte tenu de cette différence, les modes d'arrondi (troncature) pour la division d'un nombre entier ont pour qu'ils soient tels qu'ils sont dans les deux langues, afin de garantir que la relation que vous avez citée, (m/n)*n + m%n == m reste valable.

Voici deux petits programmes qui en font la démonstration (veuillez pardonner mon code Python quelque peu naïf - je suis un débutant dans ce langage) :

C++ :

#include <iostream>

int main()
{
    int dividend, divisor, quotient, remainder, check;
    std::cout << "Enter Dividend: ";                        // -27
    std::cin >> dividend;
    std::cout << "Enter Divisor: ";                         // 4
    std::cin >> divisor;

    quotient = dividend / divisor;
    std::cout << "Quotient = " << quotient << std::endl;    // -6
    remainder = dividend % divisor;
    std::cout << "Remainder = " << remainder << std::endl;  // -3

    check = quotient * divisor + remainder;
    std::cout << "Check = " << check << std::endl;          // -27
    return 0;
}

Python :

print("Enter Dividend: ")             # -27
dividend = int(input())
print("Enter Divisor: ")              # 4
divisor = int(input())
quotient = dividend // divisor;
print("Quotient = " + str(quotient))  # -7
modulus = dividend % divisor;
print("Modulus = " + str(modulus))    # 1
check = quotient * divisor + modulus; # -27
print("Check = " + str(check))

Notez que, pour les entrées données de signes différents (-27 et 4), le quotient et le reste/module sont différents d'une langue à l'autre. mais aussi que le restauré check est correcte dans les deux cas .

6voto

Karl Knechtel Points 24349

"pour des raisons mathématiques"

Considérons le problème (assez courant dans les jeux vidéo) où vous avez une coordonnée X qui peut être négative, et vous voulez la traduire en une coordonnée de tuile (supposons des tuiles 16x16) et un décalage dans la tuile

Python % vous donne directement l'offset, et / vous donne directement la tuile :

>>> -35 // 16 # If we move 35 pixels left of the origin...
-3
>>> -35 % 16 # then we are 13 pixels from the left edge of a tile in row -3.
13

(Et divmod vous donne les deux à la fois).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X