web-dev-qa-db-fra.com

Pourquoi C ++ génère-t-il des nombres négatifs lors de l'utilisation de modulo?

Mathématiques :

Si vous avez une équation comme celle-ci:

x = 3 mod 7

x pourrait être ... -4, 3, 10, 17, ..., ou plus généralement:

x = 3 + k * 7

où k peut être n'importe quel entier. Je ne sais pas si une opération modulo est définie pour les mathématiques, mais l'anneau factoriel l'est certainement.

Python :

En Python, vous obtiendrez toujours des valeurs non négatives lorsque vous utilisez % avec un m positif:

#!/usr/bin/python
# -*- coding: utf-8 -*-

m = 7

for i in xrange(-8, 10 + 1):
    print(i % 7)

Résulte en:

6    0    1    2    3    4    5    6    0    1    2    3    4    5    6    0    1    2    3

C++:

#include <iostream>

using namespace std;

int main(){
    int m = 7;

    for(int i=-8; i <= 10; i++) {
        cout << (i % m) << endl;
    }

    return 0;
}

Sortira:

-1    0    -6    -5    -4    -3    -2    -1    0    1    2    3    4    5    6    0    1    2    3    

ISO/CEI 14882: 2003 (E) - 5.6 Opérateurs multiplicatifs:

L'opérateur binaire/donne le quotient, et l'opérateur binaire% donne le reste de la division de la première expression par la seconde. Si le deuxième opérande de/ou% est nul, le comportement n'est pas défini; sinon (a/b) * b + a% b est égal à a. Si les deux opérandes sont non négatifs, le reste est non négatif; sinon, le signe du reste est défini par l'implémentation 74) .

et

74) Selon les travaux en cours en vue de la révision de l'ISO C, l'algorithme préféré pour la division entière suit les règles définies dans la norme ISO Fortran, ISO/IEC 1539: 1991, dans laquelle le quotient est toujours arrondi vers zéro.

Source: ISO/CEI 14882: 2003 (E)

(Je n'ai pas trouvé de version gratuite de ISO/IEC 1539:1991. Est-ce que quelqu'un sait d'où l'obtenir?)

L'opération semble être définie comme ceci:

enter image description here

Question :

Est-il sensé de le définir comme ça?

Quels sont les arguments pour cette spécification? Y a-t-il un endroit où les gens qui créent de telles normes en discutent? Où puis-je lire quelque chose sur les raisons pour lesquelles ils ont décidé de procéder de cette façon?

La plupart du temps, lorsque j'utilise modulo, je souhaite accéder aux éléments d'une structure de données. Dans ce cas, je dois m'assurer que le mod renvoie une valeur non négative. Donc, dans ce cas, il serait bon que mod renvoie toujours une valeur non négative. (Une autre utilisation est le algorithme euclidien . Comme vous pourriez rendre les deux nombres positifs avant d'utiliser cet algorithme, le signe de modulo importerait.)

Matériel supplémentaire :

Voir Wikipedia pour une longue liste de ce que fait modulo dans différentes langues.

47
Martin Thoma

Sur x86 (et d'autres architectures de processeur), la division entière et le modulo sont effectués par une seule opération, idiv (div pour les valeurs non signées), qui produit à la fois le quotient et le reste (pour la taille Word) , dans AX et DX respectivement). Ceci est utilisé dans la fonction de bibliothèque C divmod, qui peut être optimisée par le compilateur en une seule instruction!

La division entière respecte deux règles:

  • Les quotients non entiers sont arrondis vers zéro; et
  • l'équation dividend = quotient*divisor + remainder est satisfait des résultats.

Par conséquent, lors de la division d'un nombre négatif par un nombre positif, le quotient sera négatif (ou nul).

Ce comportement peut donc être considéré comme le résultat d'une chaîne de décisions locales:

  • La conception du jeu d'instructions du processeur est optimisée pour le cas commun (division) par rapport au cas moins courant (modulo);
  • La cohérence (arrondi vers zéro et respectant l'équation de division) est préférable à l'exactitude mathématique;
  • C préfère l'efficacité et la simplicité (surtout compte tenu de la tendance à considérer C comme un "assembleur de haut niveau"); et
  • C++ préfère la compatibilité avec C.
26
ecatmur

Quels sont les arguments pour cette spécification?

L'un des objectifs de conception de C++ est de mapper efficacement le matériel. Si le matériel sous-jacent implémente la division de manière à produire des restes négatifs, c'est ce que vous obtiendrez si vous utilisez % en C++. C'est tout ce qu'il y a vraiment.

Y a-t-il un endroit où les gens qui créent ces normes en discutent?

Vous trouverez des discussions intéressantes sur comp.lang.c ++. Modéré et, dans une moindre mesure, comp.lang.c ++

10
fredoverflow

À l'époque, quelqu'un qui concevait le jeu d'instructions x86 a décidé qu'il était juste et bon d'arrondir la division entière vers zéro plutôt que vers le bas. (Que les puces de mille chameaux se nichent dans la barbe de sa mère.) Pour garder un semblant de correction mathématique, l'opérateur REM, qui se prononce "reste", a dû se comporter en conséquence. NE LISEZ PAS ceci: https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzatk/REM.htm

Je t'avais prévenu. Plus tard, quelqu'un faisant la spécification C a décidé qu'il serait conforme pour un compilateur de le faire de la bonne façon ou de la manière x86. Ensuite, un comité faisant la spécification C++ a décidé de le faire de la manière C. Plus tard encore, après la publication de cette question, un comité C++ a décidé de normaliser dans le mauvais sens. Maintenant, nous sommes coincés avec ça. Beaucoup de programmeurs ont écrit la fonction suivante ou quelque chose comme ça. Je l'ai probablement fait au moins une douzaine de fois.

 inline int mod(int a, int b) {int ret = a%b; return ret>=0? ret: ret+b; }

Voilà votre efficacité.

Ces jours-ci, j'utilise essentiellement les éléments suivants, avec des trucs de type_traits. (Merci à Clearer pour un commentaire qui m'a donné une idée d'une amélioration en utilisant le C++ dernier jour. Voir ci-dessous.)

<strike>template<class T>
inline T mod(T a, T b) {
    assert(b > 0);
    T ret = a%b;
    return (ret>=0)?(ret):(ret+b);
}</strike>

template<>
inline unsigned mod(unsigned a, unsigned b) {
    assert(b > 0);
    return a % b;
}

Vrai fait: j'ai fait pression sur le comité des normes Pascal pour qu'il modifie la bonne façon jusqu'à ce qu'il cède. À mon horreur, ils ont fait la division entière dans le mauvais sens. Donc, ils ne correspondent même pas.

EDIT: Clearer m'a donné une idée. Je travaille sur un nouveau.

#include <type_traits>

template<class T1, class T2>
inline T1 mod(T1 a, T2 b) {
    assert(b > 0);
    T1 ret = a % b;
    if constexpr  ( std::is_unsigned_v<T1>)
    {
        return ret;
    } else {
        return (ret >= 0) ? (ret) : (ret + b);
    }
}
8
Jive Dadson