68 votes

Si je veux arrondir un entier non signé à l'entier pair plus petit ou égal le plus proche, 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 ne risque-t-il pas d'optimiser une telle expression ?

0 votes

Les optimisations ne sont pas autorisées à modifier les résultats d'une opération bien définie.

2 votes

Il est fortement conseillé de l'utiliser : x >>= 1; x <<= 1;

5 votes

@user3629249 Pourquoi le suggérer fortement ? Il exprime moins clairement l'intention que le code déjà dans la question, il se compile en code machine identique donc il n'est pas plus rapide, et il n'y a pas d'analogue si vous voulez arrondir au multiple le plus proche d'un nombre différent de 2 (à moins que ce nombre soit aussi une puissance de 2).

86voto

Bathsheba Points 23209

Le compilateur est autorisé à effectuer toutes les optimisations qu'il souhaite tant qu'il n'introduit pas d'effets secondaires dans le programme. Dans votre cas, il ne peut pas annuler les '2' car l'expression aurait alors une valeur différente pour les nombres impairs.

x / 2 * 2 est évalué strictement como (x / 2) * 2 et x / 2 est effectuée en arithmétique entière si x est un type intégral.

Il s'agit en fait d'une technique d'arrondi idiomatique.

4 votes

J'aime la remarque sur le fait que c'est idiomatique. C'est aussi un peu plus intuitif qu'un masque, selon moi.

29 votes

Bien sûr, le compilateur es permis de l'optimiser correctement, par exemple en le remplaçant par (x>>1)<<1 . Il existe des compilateurs qui le font même si les optimisations sont techniquement désactivées.

2 votes

@MSalters : Oui, c'était une chose stupide à dire. Je l'ai modifié.

53voto

StoryTeller Points 6139

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

x & (~1u)

Ce qui mettra le LSB à zéro, produisant ainsi le nombre pair immédiat qui n'est pas supérieur à 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 pour être du même type qu'un plus grand x comme ceci :

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

Mais à ce stade, vous devriez vraiment considérer cette approche comme un exercice d'obscurcissement, 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 il se doit 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)

3 votes

N'avez-vous pas besoin x & (~static_cast<decltype(x)>(1)) ou ai-je besoin d'un café ?

0 votes

Même ou plus large .

0 votes

@Bathsheba - Eh bien, les promotions intégrales s'en chargeront, à moins que I J'ai besoin d'un café.

36voto

MSalters Points 74024

Le C et le C++ utilisent généralement la règle du "comme si" dans l'optimisation. Le résultat du calcul doit être comme si le compilateur n'a pas optimisé 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 bitmasks, les décalages de bits, ou toute autre astuce spécifique au processeur qui produit les mêmes résultats (x86 a un certain nombre d'astuces qui reposent sur le fait qu'il ne fait pas de différence entre les pointeurs et les entiers, contrairement à C et C++).

11 votes

Un exemple concret . Voici ce que produit GCC avec -O1 . Toutes les approches sont distillées dans le même code machine.

0 votes

Et comme je le soupçonnais, pour les 3 variantes, GCC utilisera l'attribut and RAX, -2 même sur -O0 .

0 votes

... et GCC sur ARM sait qu'il faut utiliser l'option bic (Bit Clear). En vérifiant plus avant, MSVC et ICC génèrent la même and <Reg>, -2 comme GCC.

21voto

chqrlie Points 17105

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é.

À l'inverse, vous pourriez écrire :

x = x & ~1;

Ou probablement de façon moins lisible :

x = x & -2;

Ou même

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

Ou ça aussi :

x = x - (x & 1);

Ou cette dernière, suggérée par supercat, qui fonctionne pour les valeurs positives de tous les types et représentations d'entiers :

x = (x | 1) ^ 1;

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

Notez toutefois que x & (~1u) ne fonctionne pas si le type de x est plus grande que unsigned int . Il s'agit d'un piège contre-intuitif. Si vous insistez pour utiliser une constante non signée, vous devez écrire x & ~(uintmax_t)1 comme même x & ~1ULL échouerait si x a un caractère plus grand que unsigned long long . Pour aggraver les choses, de nombreuses plateformes ont maintenant des types d'entiers plus grands que uintmax_t comme __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 L'explorateur de compilateur de Godbolt montre que pour toutes les alternatives ci-dessus gcc -O1 produit le même code exact pour unsigned int mais en changeant le type T à unsigned long long montre un code différent pour test1u .

0 votes

Voici un exemple de ce que fait GCC avec x/2*2 pour les non signés x : godbolt.org/g/Nee7cJ

0 votes

@Ruslan : Merci pour votre commentaire, gcc produit effectivement le même code pour toutes les alternatives proposées.

0 votes

@M.M : Je crois que cela fonctionne pour tous les types et représentations d'entiers, mais seulement pour les valeurs positives de x . Pouvez-vous démontrer pour quelle valeur cela ne fonctionnerait pas ?

7voto

Jens Gustedt Points 40410

Si vos valeurs sont de n'importe quel type non signé comme vous le dites, le plus facile est

x & -2;

Les merveilles de l'arithmétique non signée font que -2 est converti au type de x et a une configuration binaire qui contient tous les uns, sauf le bit le moins significatif qui est 0 .

Contrairement à certaines des autres solutions proposées, cela devrait fonctionner avec n'importe quel type d'entier non signé qui est au moins aussi large que unsigned . (Et vous ne devriez pas faire d'arithmétique 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 de l'arithmétique modulo. Le résultat est donc toujours UTYPE_MAX-1 para UTYPE le type non signé de x . En particulier, elle est indépendante de la représentation des signes de la plate-forme pour les types signés.

4 votes

Il peut être utile de noter, pour éviter toute confusion, que la conversion de -2 en "non signé" donnera la "valeur non signée" qui, ajoutée à 2, donnera 0, indépendamment du fait qu'un système utilise des calculs signés à deux compléments. . En revanche, si l'on avait un système à complémentation par un, ~1 serait égal à -1, ce qui, converti en non signé, donnerait une valeur dont tous les bits sont activés.

0 votes

@supercat, merci, oui exactement. J'ai essayé d'intégrer ces idées dans ma réponse.

0 votes

Pour rendre cette opération sûre pour les types non signés inférieurs à unsigned int, vous pouvez utiliser (1u * x) & -2.

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