web-dev-qa-db-fra.com

Pourquoi (un% 256) est-il différent de (un & 0xFF)?

J'ai toujours supposé que lorsque (a % 256) était utilisé, l'optimiseur utiliserait naturellement une opération au niveau des bits efficace, comme si j'avais écrit (a & 0xFF).

Lors des tests sur le compilateur Explorer gcc-6.2 (-O3):

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

Et en essayant l'autre code:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

On dirait que je manque complètement quelque chose… .. Des idées?

142
Elad Weiss

Ce n'est pas la même chose. Essayez num = -79 et vous obtiendrez des résultats différents pour les deux opérations. (-79) % 256 = -79, alors que (-79) & 0xff est un nombre positif. 

En utilisant unsigned int, les opérations sont les mêmes et le code sera probablement le même. 

PS- Quelqu'un a commenté 

Ils ne devraient pas être les mêmes, a % b étant défini par a - b * floor (a / b).

Ce n'est pas comme cela qu'il est défini en C, C++, Objective-C (c'est-à-dire tous les langages où le code de la question serait compilé). 

228
gnasher729

Réponse courte

-1 % 256 donne -1 et non pas 255 qui est -1 & 0xFF. Par conséquent, l'optimisation serait incorrecte. 

Longue réponse

C++ a pour convention (a/b)*b + a%b == a, ce qui semble assez naturel. a/b renvoie toujours le résultat arithmétique sans la partie décimale (tronquée vers 0). Par conséquent, a%b a le même signe que a ou est 0. 

La division -1/256 donne 0 et par conséquent -1%256 doit être -1 pour satisfaire à la condition ci-dessus ((-1%256)*256 + -1%256 == -1). Ceci est évidemment différent de -1&0xFF qui est 0xFF. Par conséquent, le compilateur ne peut pas optimiser ce que vous voulez.

La section pertinente du norme C++ [expr.mul §4] à partir de N4606 indique:

Pour les opérandes intégraux, l'opérateur / renvoie le quotient algébrique avec toute partie fractionnaire rejetée; si le quotient a/b est représentable dans le type du résultat, (a/b)*b + a%b est égal à a [...].

Activer l'optimisation

Cependant, en utilisant unsigned types, l'optimisation serait tout à fait correcte, répondant à la convention ci-dessus:

unsigned(-1)%256 == 0xFF

Voir aussi this

Autres langues

Ceci est traité de manière très différente selon les langages de programmation, car vous pouvez rechercher sur Wikipedia

54
Ralph Tandetzky

Depuis C++ 11, num % 256 doit être non positif si num est négatif.

Le modèle de bits dépend donc de la mise en œuvre des types signés sur votre système: pour un premier argument négatif, le résultat n'est pas l'extraction des 8 bits les moins significatifs.

Ce serait une autre affaire si num dans votre cas était unsigned: ces jours-ci, je m'attendrais presque m'attendais à un compilateur pour optimiser l'optimisation que vous citez.

50
Bathsheba

Je n'ai pas de compréhension télépathique du raisonnement du compilateur, mais dans le cas de %, il est nécessaire de traiter les valeurs négatives (et les divisions autour de zéro), tandis qu'avec &, le résultat est toujours les 8 bits les plus bas.

L’instruction sar me semble être "décalage arithmétique correct", remplissant les bits vides avec la valeur du bit de signe.

11

Mathématiquement, modulo est défini comme suit:

a% b = a - b * étage (a/b)

Ce ici devrait éclaircir pour vous. Nous pouvons éliminer floor pour les entiers car la division entière est équivalente à floor (a/b). Cependant, si le compilateur utilisait une astuce générale comme vous le suggérez, il devrait fonctionner pour tous les a et tous les b. Malheureusement, ce n'est tout simplement pas le cas. Mathématiquement, votre astuce est correcte à 100% pour les entiers non signés (je vois une réponse indiquant que les entiers signés sont rompus, mais je peux confirmer ni infirmer ceci car -a% b devrait être positif). Cependant, pouvez-vous faire cette astuce pour tout b? Probablement pas. C'est pourquoi le compilateur ne le fait pas. Après tout, si modulo était facilement écrit comme une opération au niveau du bit, nous ajouterions simplement un circuit modulo comme pour l’addition et les autres opérations.

0
The Great Duck