Soit i
un type entier signé. Considérer
i += (i&-i);
i -= (i&-i);
où initialement i>0
.
Source: code du créateur d'un casse-tête de codage en ligne (sans explication ni commentaire).
Si i
a un type non signé, les expressions sont entièrement portables et bien définies.
Si i
a le type signé, il n'est pas portable, puisque &
est défini en termes de représentations mais que les noms unaires -
, +=
et -=
sont définis en termes de valeurs. Si le la prochaine version de la norme C++ impose le complément à deux , il deviendra portable et fera la même chose que dans le cas non signé.
Dans le cas non signé (et le complément à deux), il est facile de confirmer que i&-i
est une puissance de deux (a seulement un bit différent de zéro) et a la même valeur que le bit le plus bas de i
(qui est aussi le -place bit de -i
). Donc:
i -= i&-i;
efface le bit le plus faible de i
.i += i&-i;
incrémente (efface, mais avec report sur les bits les plus élevés) le bit le plus bas de i
.Pour les types non signés, il n'y a jamais de dépassement de capacité pour l'une ou l'autre expression. Pour les types signés, i -= i&-i
déborde en prenant -i
lorsque i
a initialement la valeur minimale du type, et i += i&-i
déborde dans le +=
lorsque i
a initialement la valeur maximale du type.
L'expression i & -i
est basée sur le complément à deux étant utilisé pour représenter des entiers négatifs. En termes simples, il renvoie une valeur k
où chaque bit, à l'exception du bit le moins significatif, est défini sur 0
, mais ce bit le moins significatif conserve sa propre valeur. (i.e. 1
)
Tant que l'expression que vous avez fournie est exécutée dans un système où le complément de Two est utilisé pour représenter des entiers négatifs, elle sera portable. Donc, la réponse à votre deuxième question serait que l'expression est dépendante de la représentation des entiers négatifs.
Pour répondre à votre première question, comme les expressions arithmétiques dépendent des types de données et de leurs représentations, je ne pense pas qu'il existe une expression uniquement arithmétique qui serait équivalente à l'expression i & -i
. En substance, le code ci-dessous aurait une fonctionnalité équivalente à cette expression. (en supposant que i
est de type int
) Notez, cependant, que je devais utiliser une boucle pour produire la même fonctionnalité, et pas seulement en arithmétique.
int tmp = 0, k = 0;
while(tmp < 32)
{
if(i & (1 << tmp))
{
k = i & (1 << tmp);
break;
}
tmp++;
}
i += k;
Sur une architecture à deux, avec des entiers signés sur 4 bits:
| i | bin | comp | -i | i&-i | dec |
+----+------+------+----+------+-----+
| 0 | 0000 | 0000 | -0 | 0000 | 0 |
| 1 | 0001 | 1111 | -1 | 0001 | 1 |
| 2 | 0010 | 1110 | -2 | 0010 | 2 |
| 3 | 0011 | 1101 | -3 | 0001 | 1 |
| 4 | 0100 | 1100 | -4 | 0100 | 4 |
| 5 | 0101 | 1011 | -5 | 0001 | 1 |
| 6 | 0110 | 1010 | -6 | 0010 | 2 |
| 7 | 0111 | 1001 | -7 | 0001 | 1 |
| -8 | 1000 | 1000 | -8 | 1000 | 8 |
| -7 | 1001 | 0111 | 7 | 0001 | 1 |
| -6 | 1010 | 0110 | 6 | 0010 | 2 |
| -5 | 1011 | 0101 | 5 | 0001 | 1 |
| -4 | 1100 | 0100 | 4 | 0100 | 4 |
| -3 | 1101 | 0011 | 3 | 0001 | 1 |
| -2 | 1110 | 0010 | 2 | 0010 | 2 |
| -1 | 1111 | 0001 | 1 | 0001 | 1 |
Remarques:
i&-i
n'a qu'un seul jeu de bits (sa puissance est de 2) et qu'il correspond au jeu de bits le moins significatif de i
.i + (i&-i)
a la propriété intéressante d'être un peu plus près de la puissance suivante.i += (i&-i)
définit le bit non défini le moins significatif de i
.Donc, si vous utilisez i += (i&-i);
, vous passerez éventuellement à la puissance suivante:
| i | i&-i | sum | | i | i&-i | sum |
+---+------+-----+ +---+------+-----+
| 1 | 1 | 2 | | 5 | 1 | 6 |
| 2 | 2 | 4 | | 6 | 2 | -8 |
| 4 | 4 | -8 | |-8 | -8 | UB |
|-8 | -8 | UB |
| i | i&-i | sum | | i | i&-i | sum |
+---+------+-----+ +---+------+-----+
| 3 | 1 | 4 | | 7 | 1 | -8 |
| 4 | 4 | -8 | |-8 | -8 | UB |
|-8 | -8 | UB |
UB: le débordement de l'entier signé présente un comportement non défini.
Voici ce que j'ai recherché suite à d'autres réponses. Les morsures
i -= (i&-i); // strips off the LSB (least-significant bit)
i += (i&-i); // adds the LSB
sont principalement utilisés pour traverser un arbre Fenwick . En particulier, i&-i
donne le LSB si les entiers signés sont représentés par le complément de two . Comme l'a déjà souligné Peter Fenwick dans sa proposition initiale, cela n'est pas transférable vers d'autres représentations d'entiers signés. cependant,
i &= i-1; // strips off the LSB
is (cela fonctionne aussi avec one complément et signé amplitude représentations) et a une opération de moins.
Cependant, il ne semble pas exister de solution de rechange portable simple pour l’ajout du LSB.
i & -i
est le moyen le plus simple d’obtenir le bit le moins significatif (LSB) pour un entier i
.
Vous pouvez en lire plus ici .
A1: Vous pouvez en savoir plus sur les «équivalents mathématiques» ici .
A2: Si la représentation de l’entier négatif n’est pas la forme standard habituelle (c’est-à-dire des entiers trop gros), alors i & -i
pourrait ne pas être LSB.