C Basics · beginner · ~12 min
Odd-bit detection and single-step encoding.
Two classic bit tricks, each built on XOR. Parity asks whether a value has an odd number of set bits — computed not by counting them but by folding the word onto itself with XOR shifts until the answer collapses into bit 0, five steps for 32 bits. Gray code is a numbering in which consecutive values differ in exactly one bit; converting binary to Gray is the single expression x ^ (x >> 1). Both matter because XOR is associative and self-inverse, which is precisely what makes error detection and glitch-free encoding possible.
Parity is the simplest error-detection scheme there is — one bit that catches every single-bit corruption, used on serial links, in RAM, and inside more sophisticated codes. Gray code eliminates a whole class of hardware bug: when an ordinary binary counter steps from 0111 to 1000, four bits change and a reader that samples mid-transition can see a value that never existed. With Gray code exactly one bit changes per step, so an intermediate sample is always either the old value or the new one.
Parity by folding. XOR-ing a value with a shifted copy of itself combines the parity of two halves, because XOR is exactly addition modulo 2. x ^= x >> 16 merges the top and bottom halves; >> 8, >> 4, >> 2, >> 1 continue until the parity of the whole word sits in bit 0. Then x & 1u reads it. Five steps for 32 bits, versus 32 for a counting loop.
Why it works. Parity is the XOR of all bits. XOR is associative and commutative, so the bits can be combined in any grouping — the halving fold is just an efficient grouping.
Gray encoding. gray = x ^ (x >> 1). Adjacent values then differ in exactly one bit.
Gray decoding. The inverse is not symmetric — you must fold: x = g; x ^= x >> 1; x ^= x >> 2; x ^= x >> 4; x ^= x >> 8; x ^= x >> 16;. Forgetting that decode differs from encode is the usual mistake.
Unsigned only. Both rely on >> shifting in zeros; on a signed negative value it sign-extends and both results are wrong.
int parity(unsigned x) { // 1 if an odd number of bits are set
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
return (int)(x & 1u);
}
unsigned binary_to_gray(unsigned x) { return x ^ (x >> 1); }
unsigned gray_to_binary(unsigned g) { // NOT symmetric — must fold
g ^= g >> 1; g ^= g >> 2; g ^= g >> 4;
g ^= g >> 8; g ^= g >> 16;
return g;
}
Key points:
& 1u.unsigned; a signed right shift breaks both.Parity is popcount mod 2 — a fold of XOR-shifts computes it fast. Gray code (x ^ (x>>1)) reorders values so consecutive ones differ by exactly one bit, avoiding glitches in encoders and simplifying Karnaugh maps.
The demo prints parities and the 3-bit Gray sequence.
#include <stdio.h>
static int parity(unsigned x){ x^=x>>16; x^=x>>8; x^=x>>4; x^=x>>2; x^=x>>1; return (int)(x&1u); }
static unsigned binary_to_gray(unsigned x){ return x ^ (x>>1); }
int main(void){
printf("parity(0x7) = %d parity(0xF) = %d\n", parity(7u), parity(0xFu));
printf("Gray code 0..7:");
for(unsigned i=0;i<8;i++) printf(" %u", binary_to_gray(i));
putchar('\n');
return 0;
}
| Step | Line | What happens |
|---|---|---|
| 1 | parity(0b1011) |
Three set bits, so the answer should be 1 (odd). |
| 2 | x ^= x >> 16, >> 8, >> 4 |
No high bits are set here, so x is unchanged at this size. |
| 3 | x ^= x >> 2 |
1011 ^ 0010 → 1001; the parities of bit pairs are now combined. |
| 4 | x ^= x >> 1 |
1001 ^ 0100 → 1101; the parity of the whole word now sits in bit 0. |
| 5 | return x & 1u |
1101 & 1 → 1 — odd, as expected. The upper bits are meaningless leftovers. |
| 6 | binary_to_gray(6) |
0b110 ^ 0b011 → 0b101; and gray(7) is 0b100, differing from gray(6) in exactly one bit. |
Returning the popcount instead of its low bit; OR-ing instead of XOR-ing for Gray.
Compiler errors and warnings:
warning: right shift count >= width of type — you added a >> 32 fold step to a 32-bit value.Runtime symptoms:
& 1u; the fold leaves garbage above bit 0.>> 8 instead of >> 16, so the top half was never merged.g ^ (g >> 1) again. Decoding needs the full fold, not the inverse-looking single shift.Technique: verify gray_to_binary(binary_to_gray(x)) == x over random values, and check that popcount(gray(i) ^ gray(i+1)) == 1 for a range of i — that is the defining property of Gray code.
>> on a signed negative value sign-extends, so both the parity fold and Gray decoding produce wrong answers. Use unsigned/uint32_t.x ^= x >> 32; a 32-bit fold applied to a 64-bit value silently ignores the top half. Conversely >> 32 on a 32-bit value is undefined.Concrete uses: UART frames carry an optional parity bit; ECC memory builds on parity across groups of bits. RAID relies on XOR parity to rebuild a lost drive. Rotary encoders and absolute position sensors output Gray code so a sample taken during a transition is never a wild value. Clock-domain-crossing FIFOs in FPGAs pass their pointers as Gray code for the same reason. Karnaugh maps are ordered by Gray code.
Professional best practices:
Beginner:
& 1u.Intermediate:
sizeof.1. (Beginner) Parity two ways. Implement parity with the XOR fold and a second version that counts bits and takes the remainder mod 2; confirm they agree on 1,000 random values. Example: 0b1011 → 1. Concepts: XOR folding, oracle testing.
2. (Beginner) Encode Gray code. Implement binary_to_gray and print i and gray(i) in binary for i in 0..15. Concepts: x ^ (x>>1).
3. (Intermediate) Decode Gray code. Implement gray_to_binary with the full fold and assert the round trip over random values. Requirements: must not simply reapply the encode step. Concepts: asymmetric inverse.
4. (Intermediate) Prove the property. Write a test asserting popcount(gray(i) ^ gray(i+1)) == 1 for i in 0..1000. Concepts: the defining property of Gray code, reuse of popcount.
XOR underpins both tricks. Parity is the XOR of every bit, computed efficiently by folding the word in half repeatedly (>>16, >>8, >>4, >>2, >>1) until the answer lands in bit 0 — which you must then isolate with & 1u, because the fold leaves debris above it. Gray code, x ^ (x >> 1), numbers values so that neighbours differ in exactly one bit, eliminating the multi-bit transition glitch that plagues binary counters; decoding is not symmetric and needs a full fold. Keep everything unsigned, match the fold width to the type, and remember a parity bit detects simple corruption — it is not an integrity guarantee.