Si je veux arrondir un entier non signé au plus petit entier égal ou inférieur, puis-je diviser par 2 puis multiplier par 2?

Par exemple :

f(8)=8 f(9)=8 

Puis-je faire x = x/2*2; ? Le compilateur risque-t-il d’optimiser cette expression?

Le compilateur est autorisé à faire toutes les optimisations qu’il souhaite tant qu’il n’introduit aucun effet secondaire dans le programme. Dans votre cas, il ne peut pas annuler les 2 car alors l’expression aura une valeur différente pour les nombres impairs.

x / 2 * 2 est évalué ssortingctement comme (x / 2) * 2 , et x / 2 est effectué en arithmétique entière si x est un type intégral.

Ceci est en fait une technique d’arrondi idiomatique.

Puisque vous avez spécifié que les entiers ne sont pas signés, vous pouvez le faire avec un masque simple:

 x & (~1u) 

Ce qui mettra le LSB à zéro, produisant ainsi le nombre pair immédiat qui n’est pas plus grand que x . C’est-à-dire que si x a un type qui n’est pas plus large qu’un unsigned int .

Vous pouvez bien sûr forcer le 1 à être du même type qu’un x plus large, comme ceci:

 x & ~((x & 1u) | 1u) 

Mais à ce stade, vous devriez vraiment considérer cette approche comme un exercice de dissimulation et accepter la réponse de Bathsheba.


J’ai bien sûr oublié la bibliothèque standard. Si vous incluez stdint.h (ou cstdint , comme vous le devriez dans le code C ++). Vous pouvez laisser l’implémentation s’occuper des détails:

 uintmax_t const lsb = 1; x & ~lsb; 

ou

 x & ~UINTMAX_C(1) 

C et C ++ utilisent généralement la règle “comme si” dans l’optimisation. Le résultat du calcul doit être comme si le compilateur n’optimisait pas votre code.

Dans ce cas, 9/2*2=8 . Le compilateur peut utiliser n’importe quelle méthode pour obtenir le résultat 8. Cela inclut les masques de bits, les décalages de bits, ou tout piratage spécifique au processeur qui produit les mêmes résultats (x86 comporte quelques astuces qui ne font pas la différence entre les pointeurs). et entiers, contrairement à C et C ++).

Vous pouvez écrire x / 2 * 2 et le compilateur produira un code très efficace pour effacer le bit le moins significatif si x a un type non signé.

Inversement, vous pourriez écrire:

 x = x & ~1; 

Ou probablement moins lisiblement:

 x = x & -2; 

Ou même

 x = (x >> 1) << 1; 

Ou cela aussi:

 x = x - (x & 1); 

Ou ce dernier, proposé par supercat, qui fonctionne pour les valeurs positives de tous les types entiers et représentations:

 x = (x | 1) ^ 1; 

Toutes les propositions ci-dessus fonctionnent correctement pour tous les types d'entiers non signés sur les architectures de complément à 2. Que le compilateur produise un code optimal est une question de configuration et de qualité de mise en œuvre.

Notez cependant que x & (~1u) ne fonctionne pas si le type de x est plus grand que unsigned int . C'est un piège contre-intuitif. Si vous insistez pour utiliser une constante non signée, vous devez écrire x & ~(uintmax_t)1 car même x & ~1ULL échoueraient si x avait un type plus grand que unsigned long long . Pour aggraver les choses, de nombreuses plates-formes ont maintenant des types entiers plus grands que uintmax_t , tels que __uint128_t .

Voici un petit repère:

 typedef unsigned int T; T test1(T x) { return x / 2 * 2; } T test2(T x) { return x & ~1; } T test3(T x) { return x & -2; } T test4(T x) { return (x >> 1) << 1; } T test5(T x) { return x - (x & 1); } T test6(T x) { // suggested by supercat return (x | 1) ^ 1; } T test7(T x) { // suggested by Mehrdad return ~(~x | 1); } T test1u(T x) { return x & ~1u; } 

Comme suggéré par Ruslan, les tests sur le compilateur de Godbolt montrent que pour toutes les alternatives ci-dessus, gcc -O1 produit le même code exact pour unsigned int , mais changer le type T en unsigned long long montre un code différent pour test1u .

Si vos valeurs sont de type non signé, comme vous le dites, le plus simple est

 x & -2; 

Les merveilles de l’arithmétique non signée font que -2 est converti dans le type de x , et a un modèle de bit qui a tout, mais pour le bit le moins significatif qui est 0 .

Contrairement à d’autres solutions proposées, cela devrait fonctionner avec tout type d’entier non signé au moins aussi large que unsigned . (Et vous ne devriez pas faire de calcul avec des types plus étroits, de toute façon.)

Bonus supplémentaire, comme l’a fait remarquer Supercat, cela n’utilise que la conversion d’un type signé en un type non signé. Ceci est bien défini par la norme comme étant une arithmétique modulo. Donc, le résultat est toujours UTYPE_MAX-1 pour UTYPE le type non signé de x . En particulier, il est indépendant de la représentation des signes de la plate-forme pour les types signés.

Une option qui m’a surpris n’a pas encore été mentionnée est d’utiliser l’opérateur modulo. Je dirais que cela représente votre intention au moins aussi bien que votre extrait original, et peut-être même mieux.

 x = x - x % 2 

Comme d’autres l’ont dit, l’optimiseur du compilateur traitera toute expression raisonnable de manière équivalente. Toutes les réponses qui apportent des modifications sont intéressantes, mais vous ne devriez certainement pas en utiliser à la place des opérateurs arithmétiques (en supposant que la motivation est plutôt une arithmétique que des ajustements de bits).

il suffit d’utiliser ce qui suit:

 template inline T f(T v) { return v & (~static_cast(1)); } 

N’ayez pas peur que ce soit la fonction, le compilateur devrait finalement optimiser ceci en juste v & (~ 1) avec le type approprié de 1.