web-dev-qa-db-fra.com

Que fait i + = (i & -i)? Est-ce portable?

Soit i un type entier signé. Considérer

i += (i&-i);
i -= (i&-i);

où initialement i>0.

  1. Que font-ils? Existe-t-il un code équivalent utilisant uniquement l'arithmétique?
  2. Cela dépend-il d'une représentation binaire spécifique des nombres entiers négatifs?

Source: code du créateur d'un casse-tête de codage en ligne (sans explication ni commentaire).

16
Walter

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.

4
R..

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;
10
ilim

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:

  1. Vous pouvez supposer que 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.
  2. i + (i&-i) a la propriété intéressante d'être un peu plus près de la puissance suivante.
  3. 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.

9
YSC

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.

4
Walter

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.

3
S. Plum P.