Skip to content
Retour au blog
Tutoriels

Opérations bit à bit : AND, OR, XOR, décalages et masques expliqués

Maîtrisez les opérations bit à bit : AND, OR, XOR, décalages, complément à deux, masques et drapeaux de fonctionnalité — avec exemples en JS, Python, Go et C.

17 min de lecture

Opérations bit à bit : AND, OR, XOR, décalages et masques expliqués

Vous ouvrez une ancienne migration PostgreSQL et tombez sur permissions & 0b100. Un collègue livre un système de feature flags qui empaquette 32 booléens dans un seul entier. Un calcul de sous-réseau Kubernetes affiche 192.168.1.0/24 et il faut extraire l’adresse réseau en code. Trois situations, une même compétence sous-jacente : les opérations bit à bit.

La plupart des développeurs applicatifs n’ont jamais besoin de toucher à & ou ^ dans une webapp, jusqu’au jour où si. Ce guide couvre les six opérateurs bit à bit, le complément à deux, neuf motifs à retenir, et les pièges propres à chaque langage (surtout en JavaScript). Le code est en JS, Python, Go et C ; chaque exemple est exécutable.

Ouvrez notre Convertisseur de base dans un autre onglet : plusieurs sections vous invitent à saisir un nombre et regarder le motif binaire changer.

Pourquoi les opérations bit à bit comptent encore en 2026

Les langages de haut niveau n’ont pas rendu les opérations bit à bit obsolètes. Ils les ont juste cachées. Vous en dépendez aujourd’hui sans forcément le savoir :

  • Le Row-Level Security de PostgreSQL stocke les privilèges ACL (SELECT, INSERT, UPDATE, DELETE…) dans un bitmap entier.
  • Les capabilities Linux remplacent le vieux modèle tout-ou-rien par 40+ bits de permission combinés avec |.
  • Les en-têtes d’algorithme JWT encodent le hash dans un petit champ où la comparaison au niveau du bit est courante côté bibliothèque.
  • Snowflake, ULID et UUIDv7 empaquètent horodatage, ID machine et numéro de séquence dans un entier 64 ou 128 bits via des décalages à gauche.
  • BITCOUNT et BITOP de Redis exposent les primitives bit à bit directement à l’application, pour l’estimation de cardinalité et le bucketing A/B.
  • Le traitement d’image lit les pixels RGBA 32 bits et extrait les canaux avec & et >>.

Les opérations bit à bit restent du O(1) au niveau instruction CPU. Empaqueter 32 booléens dans un entier économise 31 octets de mémoire et, surtout, permet de tester « l’un de ces 32 flags est-il activé » en une seule comparaison != 0.

Les fondations binaires à maîtriser

Ce guide suppose que vous connaissez déjà le binaire. Si besoin, relisez d’abord notre Guide de conversion de bases numériques puis revenez.

Petit vocabulaire à fixer :

  • Un bit vaut 0 ou 1.
  • Un nibble fait 4 bits (un chiffre hexadécimal).
  • Un octet fait 8 bits.
  • Un mot fait typiquement 32 ou 64 bits selon le CPU.

Dans la plupart des langages, les entiers ont une largeur fixe : 8, 16, 32 ou 64. La largeur compte beaucoup pour les opérations bit à bit, parce que les décalages éjectent les bits qui débordent et que le bit de signe occupe la position la plus à gauche des entiers signés.

À essayer tout de suite : ouvrez le Convertisseur de base, saisissez 170 en décimal et regardez la sortie binaire. Vous obtenez 10101010, un motif alterné qu’on recroisera plusieurs fois.

Les six opérateurs bit à bit

Tous les langages mainstream fournissent les six mêmes opérateurs, avec quelques variations syntaxiques. &, |, ^, ~, << et >> fonctionnent à l’identique en JavaScript, Python, Go, Rust, C, C++, Java et C#. JavaScript ajoute >>>, le décalage à droite non signé.

AND (&) : filtre de bits

Le bit résultant vaut 1 uniquement si les deux bits d’entrée valent 1.

ABA & B
000
010
100
111

Voyez AND comme une porte : seuls les bits activés des deux côtés passent. L’usage le plus fréquent est le masquage : garder certains bits, annuler les autres.

// Extraire les 4 bits bas (nibble de droite)
const value = 0b11010110;   // 214
const low4  = value & 0x0F; // 0b00000110 = 6

// Tester la parité
const isOdd = (n) => (n & 1) === 1;
isOdd(7);  // true
isOdd(42); // false
# Identique en Python
value = 0b11010110
low4 = value & 0x0F  # 6

def is_odd(n):
    return (n & 1) == 1

OR (|) : activateur de bits

Le bit résultant vaut 1 si au moins un des bits d’entrée vaut 1.

ABA | B
000
011
101
111

OR combine les drapeaux. Si READ = 1, WRITE = 2 et EXECUTE = 4, alors READ | WRITE vaut 3 : les deux permissions activées.

const READ  = 0b001;
const WRITE = 0b010;
const EXEC  = 0b100;

const rw = READ | WRITE;  // 0b011 = 3
READ, WRITE, EXEC = 0b001, 0b010, 0b100
rw = READ | WRITE  # 3

XOR (^) : basculeur de bits

Le bit résultant vaut 1 si les bits d’entrée diffèrent.

ABA ^ B
000
011
101
110

XOR a trois propriétés algébriques derrière la plupart de ses astuces :

  • a ^ a = 0 : tout XOR avec lui-même s’annule.
  • a ^ 0 = a : XOR avec zéro est l’identité.
  • a ^ b ^ a = b : XOR est son propre inverse.

La dernière propriété explique pourquoi on retrouve XOR un peu partout, du contrôle de parité aux chiffrements de flux, en passant par le classique problème d’entretien « trouver le nombre unique dans un tableau où tous les autres apparaissent en double ».

// Trouver l'unique dans un tableau où tous les autres apparaissent deux fois
const findUnique = (arr) => arr.reduce((a, b) => a ^ b, 0);
findUnique([4, 1, 2, 1, 2]);  // 4
from functools import reduce
from operator import xor
find_unique = lambda arr: reduce(xor, arr, 0)
find_unique([4, 1, 2, 1, 2])  # 4

NOT (~) : inverseur de bits

L’unaire ~ inverse chaque bit : 0 devient 1, 1 devient 0.

~0b00001111  // -16 (JavaScript force 32 bits signés)
~5           // -6
~5  # -6
// Attention : en Go, ^ sert aussi de NOT bit à bit unaire
var x int8 = 5
fmt.Println(^x)  // -6

Le résultat ~5 = -6 surprend les débutants dans presque tous les langages. La raison est le complément à deux, traité juste après. Pour l’instant, retenez ceci : ~x vaut toujours -(x + 1) dans tout langage utilisant le complément à deux pour les négatifs, c’est-à-dire tous.

Décalage à gauche (<<) : multiplication par puissance de 2

x << n décale tous les bits de x de n positions vers la gauche, en remplissant de zéros à droite. Mathématiquement, cela multiplie par 2ⁿ.

1 << 0   // 1    (2^0)
1 << 1   // 2    (2^1)
1 << 3   // 8    (2^3)
1 << 10  // 1024 (2^10 = 1 KiB)

// Construire des flags
const FLAG_ADMIN    = 1 << 0;
const FLAG_EDITOR   = 1 << 1;
const FLAG_REVIEWER = 1 << 2;

L’intérêt de 1 << n : produire un nombre avec un seul bit activé en position n. Ce bit devient un drapeau.

Attention au dépassement. En JavaScript, 1 << 31 vaut -2147483648 (pas 2147483648), parce que les opérations bit à bit JS travaillent sur des entiers 32 bits signés.

Décalage à droite (>> vs >>>) : signé ou rempli de zéros ?

Le décalage à droite pousse les bits vers la droite. Reste à savoir ce qu’on met à la place à gauche.

  • >> (décalage arithmétique à droite) : préserve le bit de signe. Les négatifs restent négatifs.
  • >>> (décalage logique, non signé) : remplit de zéros. JavaScript est le seul langage courant à avoir cet opérateur dédié.
-8 >> 1   // -4   (signe préservé)
-8 >>> 1  // 2147483644  (signe traité comme bit de donnée)

8 >> 1    // 4
8 >> 2    // 2

En C, pour les types signés, le comportement de >> (arithmétique ou logique) dépend de l’implémentation. La plupart des compilateurs font de l’arithmétique, mais ne vous appuyez pas dessus sans vérifier. Go exige que le décalage soit un entier non signé et distingue strictement signé et non signé. Python n’a pas de >>> parce qu’il n’a pas d’entiers de largeur fixe.

Complément à deux : comment les ordinateurs stockent les négatifs

Si un bit vaut 0 ou 1, comment encode-t-on -5 ? La réponse retenue dans les années 1960 est le complément à deux, et tout CPU moderne l’utilise.

L’approche naïve, qui consiste à réserver un bit pour le signe, a deux défauts. D’abord, on se retrouve avec +0 et -0, pénible. Ensuite, les circuits d’addition et de soustraction doivent vérifier le signe, ce qui complique le matériel. Le complément à deux règle les deux.

La règle tient en trois lignes :

  1. Prendre la représentation binaire positive.
  2. Inverser chaque bit (c’est le « complément à un »).
  3. Ajouter 1.

Exemple détaillé : encoder -5 en complément à deux sur 8 bits.

 5 en binaire :       0000 0101
 inverser les bits :  1111 1010   (c'est -6 en complément à deux !)
 ajouter 1 :          1111 1011   ← voilà -5

Vérifiez avec notre convertisseur : entrez 251 (décimal) dans le Convertisseur de base, la sortie binaire donne 11111011. En contexte signé 8 bits, c’est -5 ; en non signé, c’est 251. Mêmes bits, interprétation différente.

Cela explique la surprise ~5 = -6. Le NOT bit à bit produit le complément à un. Le complément à deux est le complément à un plus 1. Donc :

~x    = -(x + 1)    // vrai dans tout langage en complément à deux
~5    = -6
~(-3) = 2

Pour un entier signé sur n bits, la plage va de -2ⁿ⁻¹ à 2ⁿ⁻¹ − 1. 8 bits signés couvrent -128 à 127. 32 bits signés couvrent environ -2,1 milliards à +2,1 milliards.

Neuf motifs courants de manipulation de bits

Ces neuf motifs couvrent à peu près 95 % de la manipulation de bits que vous écrirez. Mémorisez-les, vous les reconnaîtrez partout dans le code système.

Activer un bit : x | (1 << n)

Allumer le bit n, laisser les autres intacts.

let flags = 0b0100;
flags = flags | (1 << 0);  // 0b0101

Effacer un bit : x & ~(1 << n)

Éteindre le bit n, laisser les autres. ~(1 << n) est un masque avec tous les bits activés sauf n.

let flags = 0b0111;
flags = flags & ~(1 << 1);  // 0b0101

Basculer un bit : x ^ (1 << n)

Retourner le bit n, quel que soit son état.

let flags = 0b0100;
flags = flags ^ (1 << 2);  // 0b0000
flags = flags ^ (1 << 2);  // 0b0100 de nouveau

Tester un bit : (x >> n) & 1

Renvoie 1 si le bit n est à 1, 0 sinon. Forme équivalente : (x & (1 << n)) !== 0.

const flags = 0b0101;
const isBit2Set = (flags >> 2) & 1;  // 1

Isoler le bit le plus bas : x & -x

Ne garde que le 1 le plus à droite de x. L’astuce marche parce qu’en complément à deux, -x vaut ~x + 1, ce qui retourne tous les bits jusqu’au plus bas inclus.

const x = 0b10110100;
const lowest = x & -x;  // 0b00000100 = 4

C’est la base des arbres de Fenwick (BIT) pour des sommes préfixes en O(log n).

Compter les bits à 1 (popcount)

Compter combien de 1 dans un entier. La plupart des langages ont une fonction native :

// JavaScript (version manuelle)
const popcount = (n) => {
  let count = 0;
  while (n) { count += n & 1; n >>>= 1; }
  return count;
};
popcount(0b10110100);  // 4
# Python 3.10+
(0b10110100).bit_count()  # 4
// Go
import "math/bits"
bits.OnesCount(0b10110100)  // 4

Échange XOR sans variable temporaire

Tour de passe-passe classique : échanger deux entiers sans variable tierce. À ne jamais utiliser en prod (plus lent qu’une temporaire et cassé si a et b pointent la même mémoire), mais ça vaut le coup d’être compris.

let a = 5, b = 9;
a = a ^ b;  // a = 5 ^ 9
b = a ^ b;  // b = (5 ^ 9) ^ 9 = 5
a = a ^ b;  // a = (5 ^ 9) ^ 5 = 9
// a = 9, b = 5

Détecter une puissance de 2 : (x & (x - 1)) === 0

Une puissance de 2 a exactement un bit activé. Soustraire 1 éteint ce bit et active tous ceux d’en dessous. Le AND donne zéro uniquement pour les puissances de 2 (et 0, d’où le garde x > 0).

const isPow2 = (x) => x > 0 && (x & (x - 1)) === 0;
isPow2(16);  // true
isPow2(17);  // false

Test d’imparité rapide : x & 1

Plus rapide que x % 2 dans certains langages, équivalent dans d’autres après optimisation. Utile dans une boucle chaude ou quand la lisibilité passe au second plan.

const isOdd = (x) => (x & 1) === 1;

Masques de bits dans du vrai code

C’est ici que les opérations bit à bit passent du tour de magie à la pratique quotidienne.

32 booléens dans un feature flag

Au lieu d’une struct avec 32 champs booléens, empaquetez le tout :

const FLAGS = {
  DARK_MODE:      1 << 0,
  NEW_NAV:        1 << 1,
  AI_SUGGESTIONS: 1 << 2,
  BETA_EDITOR:    1 << 3,
  // ... jusqu'à 1 << 31
};

let userFlags = 0;
userFlags |= FLAGS.DARK_MODE | FLAGS.AI_SUGGESTIONS;  // activer

if (userFlags & FLAGS.AI_SUGGESTIONS) {
  showSuggestions();
}

userFlags &= ~FLAGS.DARK_MODE;  // désactiver

32 booléens stockés dans 4 octets, tout sous-ensemble testable en un AND. Les bases de données adorent : une colonne au lieu de 32.

Permissions de fichiers Unix

chmod 755 est du bit à bit. Les trois chiffres octaux correspondent à trois triplets de bits :

7 = 111  (propriétaire : rwx)
5 = 101  (groupe :       r-x)
5 = 101  (autres :       r-x)

À essayer : dans le Convertisseur de base, source en octal, entrée 755, sortie binaire 111101101. Le système de fichiers stocke littéralement les permissions sous cette forme.

Activer uniquement « écriture de groupe » :

const perms = 0o755;
const withGroupWrite = perms | 0o020;  // 0o775

Masquage de sous-réseau IP

Avec 192.168.1.10/24, extraire l’adresse réseau par AND avec le masque :

const ip      = 0xC0A8010A;  // 192.168.1.10
const mask    = 0xFFFFFF00;  // 255.255.255.0 (/24)
const network = ip & mask;   // 0xC0A80100 = 192.168.1.0

Même logique pour un VLAN d’entreprise : sur un /22 fourni par un hébergeur comme OVH ou Scaleway, le masque 0xFFFFFC00 isole les dix bits de préfixe, et les six bits restants adressent jusqu’à 1 022 hôtes utiles. Les équipements réseau (Cisco, MikroTik, pfSense) effectuent exactement ce AND à chaque paquet, des millions de fois par seconde.

IDs empaquetés : Snowflake

Le Snowflake de Twitter empaquète horodatage, ID machine et séquence dans un entier 64 bits :

┌─ 1 bit ─┬─── 41 bits ───┬─ 10 bits ─┬─ 12 bits ─┐
│  signe  │  horodatage   │  machine  │   seq     │
└─────────┴───────────────┴───────────┴───────────┘

Encoder un ID tient en deux décalages et deux OR :

const id = (BigInt(timestamp) << 22n) |
           (BigInt(machineId) << 12n) |
            BigInt(sequence);

Décoder fait l’inverse : décalage à droite et masque. Pour choisir entre Snowflake, ULID et UUIDv7, voyez notre comparatif d’IDs distribués.

Pièges propres à chaque langage

JavaScript : piège de la coercition 32 bits

JavaScript convertit les opérandes en entiers 32 bits signés avant chaque opération bit à bit, puis reconvertit en Number. Toute valeur au-delà de 2³¹ − 1 = 2147483647 déborde.

2147483647 | 0   // 2147483647  (ok)
2147483648 | 0   // -2147483648 (débordement !)
4294967295 | 0   // -1          (tous bits à 1, interprété signé)

Pour du 64 bits, utilisez BigInt, qui dispose de ses propres opérateurs sans limite de largeur :

(2n ** 40n) | 1n  // 1099511627777n

Bugs de priorité d’opérateurs

Un des bugs bit à bit les plus fréquents :

// Bug : lu comme (x & (1 == 0)) car == lie plus fort que &
if (x & 1 == 0) { /* ... */ }

// Correct : parenthèses
if ((x & 1) == 0) { /* ... */ }

En C, JavaScript, Python, Go et descendants, les opérateurs de comparaison ont priorité supérieure à AND/OR/XOR bit à bit. Dans le doute, parenthésez.

Tableau comparatif des langages

LangageCoercition de largeur>> négatifSupport BigInt
JavaScriptForce 32 bits signés ; >>> non signéarithmétiqueBigInt a ses propres opérateurs
PythonPrécision arbitraire, pas de largeur fixearithmétiqueNatif
GoStrict ; décalage doit être non signéarithmétique pour signésmath/big
C/C++Suit le type ; int, unsigned, etc.défini par l’implémentation pour signésRien en natif
RustStrict ; panic au dépassement en debugarithmétique pour signésu128 / crates externes

La bizarrerie infinie de Python

Les entiers Python n’ont pas de largeur fixe, donc la logique du complément à deux s’étend « à l’infini » vers la gauche. D’où ~5 = -6 (ni 250 ni 65530) : Python traite le résultat comme un entier négatif, pas comme un motif de bits à largeur fixe. Pour une sémantique de bouclage, masquez explicitement :

# Simuler NOT 8 bits
(~5) & 0xFF  # 250

La performance en 2026, pour de vrai

La croyance répandue dit que le bit à bit est « toujours plus rapide ». En 2026, c’est à moitié vrai.

Les compilateurs font déjà les réécritures évidentes. Les optimiseurs modernes transforment x * 2 en x << 1 automatiquement. Écrire x << 1 dans du code applicatif « pour la vitesse » relève du culte du cargo : ça n’aide pas, et ça dégrade la lisibilité.

Là où le bit à bit gagne vraiment :

  • Boucles numériques chaudes : popcount, comptage de zéros initiaux ou finaux, moteurs d’échecs à bitboards.
  • Structures compactes : filtres de Bloom, roaring bitmaps, arbres de Fenwick.
  • Registres matériels et E/S mappées en mémoire : embarqué, noyau, firmware.
  • Primitives cryptographiques : AES, ChaCha20, SHA, tous bâtis sur XOR, rotations et décalages.
  • Compression et décompression : codage de Huffman, RLE, entiers empaquetés.
  • Moteurs de bases de données : index bitmap, formats colonnes empaquetés comme Parquet.

Là où ça n’aide pas : remplacer x % 2 par x & 1 dans une fonction métier exécutée deux fois par requête. Accélération imperceptible, coût de lisibilité bien réel.

Le cas où la manipulation de bits gagne toujours, c’est l’empreinte mémoire. 32 flags dans un int, c’est 31 octets de moins que 32 booléens. À grande échelle, sur des millions d’enregistrements ou des milliards d’événements, c’est la différence entre un layout cache-friendly et un cache-miss storm.

Aide-mémoire rapide

OpérationOpérateurExempleRésultatUsage typique
AND&0b1100 & 0b10100b1000Masque/extraction
OR|0b1100 | 0b10100b1110Combiner flags
XOR^0b1100 ^ 0b10100b0110Bascule/diff
NOT~~0b1100...11110011Masque inverse
Décalage à gauche<<1 << 38Multiplier par 2ⁿ
Décalage à droite>>16 >> 24Diviser par 2ⁿ (signé)
Décalage à droite non signé (JS)>>>-1 >>> 04294967295Traiter en non signé
Activer bit n|x | (1 << n)Allumer
Effacer bit n& ~x & ~(1 << n)Éteindre
Basculer bit n^x ^ (1 << n)Retourner
Tester bit n&(x >> n) & 10 ou 1Interroger
Bit bas isolé& -x & -xIsoler
Puissance de 2&x > 0 && (x & (x-1)) == 0booléenDétecter

FAQ

Quelle différence entre AND logique (&&) et bit à bit (&) ?

Le AND logique opère sur des booléens entiers et court-circuite : false && expr n’évalue jamais expr. Le AND bit à bit opère sur les bits individuels d’entiers et évalue toujours les deux côtés. Utilisez && pour les conditions, & pour manipuler les bits.

Pourquoi ~1 vaut-il -2 dans la plupart des langages ?

Le NOT bit à bit inverse chaque bit, produisant le complément à un. En représentation complément à deux, inverser tous les bits de x donne -(x + 1). D’où ~1 = -2, ~0 = -1, ~(-1) = 0.

x << 1 est-il vraiment plus rapide que x * 2 ?

Non, x << 1 n’est pas plus rapide que x * 2 en pratique. Tout compilateur moderne reconnaît x * 2 et émet la même instruction de décalage. Préférez x * 2 pour la clarté, et réservez << aux cas où vous pensez vraiment en bits, par exemple pour construire un masque.

JavaScript supporte-t-il les opérations bit à bit 64 bits ?

JavaScript ne supporte pas nativement le 64 bits avec les opérateurs standard &, |, ^, <<, >>, qui forcent les opérandes en 32 bits signés. Utilisez les littéraux BigInt (ex. 1n << 40n) pour des opérations bit à bit à précision arbitraire. Attention : on ne peut pas mélanger BigInt et Number dans la même expression sans conversion explicite.

Comment compter les bits à 1 efficacement ?

Pour compter les bits à 1 efficacement, utilisez la fonction native de votre langage : bits.OnesCount en Go, Integer.bitCount en Java, .bit_count() en Python 3.10+, intrinsèques popcount en C/C++. Toutes se traduisent par une seule instruction POPCNT sur x86 et ARM modernes. Évitez les boucles manuelles en code chaud : la version native est dix à vingt fois plus rapide.

Quand préférer un masque de bits à une struct de booléens ?

Masque quand vous devez stocker beaucoup de flags de façon compacte (bases de données, protocoles réseau, formats de fichier) ou tester rapidement une combinaison (flags & REQUIRED_MASK). Struct quand les champs ont des types différents ou quand la lisibilité prime sur la mémoire.

Que se passe-t-il si je décale au-delà de la largeur ?

Décaler au-delà de la largeur produit un comportement dépendant du langage : indéfini en C/C++, pris modulo 32 en JavaScript (donc 1 << 32 vaut 1, pas 0), et sans limite en Python car les entiers n’ont pas de largeur fixe (1 << 100 est juste un entier plus grand). Ne vous reposez jamais sur ce comportement ; masquez vous-même le décalage si nécessaire.

Pourquoi ~5 vaut -6 en Python et pas 2 ?

Les entiers Python n’ont pas de largeur fixe, donc le complément à deux s’étend conceptuellement à l’infini. ~5 vaut -(5 + 1) = -6, comme dans tout langage en complément à deux. Pour la valeur « inversée » 8 bits 250, masquez : (~5) & 0xFF.

Le chiffrement XOR est-il sûr ?

Un masque jetable avec une clé vraiment aléatoire aussi longue que le message est théoriquement incassable. Réutiliser la même clé sur plusieurs messages est catastrophique : le « chiffrement XOR » à clé courte répétée se casse trivialement. Les vrais algorithmes comme AES et ChaCha20 utilisent XOR en interne, mais comme une étape parmi beaucoup d’autres.

Comment écrire à la main un négatif en complément à deux ?

Écrivez la valeur positive en binaire à la largeur cible, inversez chaque bit, ajoutez 1. Exemple : -5 sur 8 bits = 00000101 → inverser 11111010 → ajouter 1 → 11111011. Vérifiez avec notre Convertisseur de base en convertissant 251 (interprétation non signée de 11111011) et en confirmant que la sortie binaire est 11111011.

Outils associés et lectures complémentaires

Articles connexes

Voir tous les articles