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?
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 para - 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é).
-1 % 256
donne -1
et non pas 255
qui est -1 & 0xFF
. Par conséquent, l'optimisation serait incorrecte.
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 quotienta/b
est représentable dans le type du résultat,(a/b)*b + a%b
est égal àa
[...].
Cependant, en utilisant unsigned
types, l'optimisation serait tout à fait correcte, répondant à la convention ci-dessus:
unsigned(-1)%256 == 0xFF
Voir aussi this .
Ceci est traité de manière très différente selon les langages de programmation, car vous pouvez rechercher sur Wikipedia .
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.
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.
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.