Loading learning content...
When a Hamming codeword arrives at a receiver, it may have been corrupted during transmission or storage. The receiver's task is twofold: detect whether an error occurred and, if so, locate the error position so it can be corrected.
The mechanism that accomplishes both tasks simultaneously is the syndrome—a binary pattern computed from the received word that directly indicates where an error occurred (if any).
This page dissects syndrome calculation with mathematical precision, showing why it works and how to implement it efficiently.
By the end of this page, you will understand how to compute the syndrome from a received codeword, why the syndrome value equals the error position, the relationship between syndromes and parity check matrices, and complete decoding algorithms for error correction.
In coding theory, a syndrome is a pattern of parity check results that indicates whether and where an error has occurred. For Hamming codes, the syndrome is remarkably elegant: it directly encodes the position of a single-bit error in binary.
Formal Definition:
Given a received word r of length n, the syndrome S is an r-bit binary number where:
S = S_{r-1} S_{r-2} ... S_1 S_0 (binary representation)
Each syndrome bit S_k is the result of the parity check for check bit P_{2^k}:
S_k = ⊕{r_j : j satisfies (j & 2^k) ≠ 0}
In other words, S_k = 1 if the parity check for P_{2^k} fails (odd parity), and S_k = 0 if it passes (even parity).
The syndrome S, interpreted as a binary number, equals the position of the bit error. If S = 0, no error occurred. If S = 5 (binary 101), position 5 has an error. This direct correspondence is why Hamming codes are so elegant and efficient.
Why Does This Work?
Recall that each position j is covered by check bits whose indices correspond to the 1-bits in j's binary representation. When position j is corrupted:
P_{2^k} detects a parity violation if and only if bit k is set in j's binary representationj in binaryS = jThis is not coincidence—it's the fundamental design principle of Hamming codes. The power-of-two positioning of check bits directly enables this property.
| Error Position | Binary | P₄ Fails? | P₂ Fails? | P₁ Fails? | Syndrome (S₂S₁S₀) |
|---|---|---|---|---|---|
| None | No | No | No | 000 = 0 | |
| 1 | 001 | No | No | Yes | 001 = 1 |
| 2 | 010 | No | Yes | No | 010 = 2 |
| 3 | 011 | No | Yes | Yes | 011 = 3 |
| 4 | 100 | Yes | No | No | 100 = 4 |
| 5 | 101 | Yes | No | Yes | 101 = 5 |
| 6 | 110 | Yes | Yes | No | 110 = 6 |
| 7 | 111 | Yes | Yes | Yes | 111 = 7 |
Let's work through a complete example of syndrome calculation to solidify the concept.
Example: Detecting an Error in Hamming(7,4)
Original codeword: 0110011 (encoding data 1011)
Received word with error at position 5: 0110111
The bit at position 5 flipped from 0 to 1. Let's compute the syndrome to locate this error.
| Position | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Type | P₁ | P₂ | D₁ | P₄ | D₂ | D₃ | D₄ |
| Original | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
| Received | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| Error? | — | — | — | — | ✗ | — | — |
Step 1: Calculate S₀ (check positions covered by P₁)
P₁ covers positions 1, 3, 5, 7 (all positions with bit 0 set):
Step 2: Calculate S₁ (check positions covered by P₂)
P₂ covers positions 2, 3, 6, 7 (all positions with bit 1 set):
Step 3: Calculate S₂ (check positions covered by P₄)
P₄ covers positions 4, 5, 6, 7 (all positions with bit 2 set):
Syndrome S = S₂S₁S₀ = 101 (binary) = 5 (decimal). The error is at position 5! This matches exactly where we introduced the error. To correct: flip bit 5 from 1 back to 0.
Verification:
After correcting position 5 (flipping 1 → 0):
0110011The syndrome calculation has:
Syndrome calculation can be elegantly expressed using linear algebra over GF(2) (the binary field). The parity check matrix H encapsulates all the coverage relationships in matrix form.
Definition:
For an (n, m) Hamming code with r check bits, the parity check matrix H is an r × n matrix where:
For Hamming(7,4):
Position: 1 2 3 4 5 6 7
│ │ │ │ │ │ │
H = ┌─────────────────────────────┐
│ 1 0 1 0 1 0 1 │ ← bit 0 (S₀)
│ 0 1 1 0 0 1 1 │ ← bit 1 (S₁)
│ 0 0 0 1 1 1 1 │ ← bit 2 (S₂)
└─────────────────────────────┘
Each column is the binary representation of that position number, read top-to-bottom.
Syndrome Calculation via Matrix Multiplication:
S = H × rᵀ (mod 2)
Where r is the received word (as a column vector) and S is the syndrome (also a column vector).
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
import numpy as np def create_parity_check_matrix(n: int, r: int) -> np.ndarray: """ Create the parity check matrix H for an (n, n-r) Hamming code. Each column j contains the binary representation of j (1-indexed). Args: n: Total codeword length r: Number of check bits (rows in H) Returns: r x n parity check matrix """ H = np.zeros((r, n), dtype=int) for j in range(1, n + 1): # 1-indexed positions for k in range(r): H[k, j-1] = (j >> k) & 1 # Extract bit k of j return H def compute_syndrome_matrix(H: np.ndarray, received: np.ndarray) -> np.ndarray: """ Compute syndrome using matrix multiplication over GF(2). S = H × rᵀ (mod 2) """ return (H @ received) % 2 def syndrome_to_position(syndrome: np.ndarray) -> int: """ Convert syndrome bits to error position (little-endian). S₀ is LSB, so position = S₀ + 2*S₁ + 4*S₂ + ... """ position = 0 for k, bit in enumerate(syndrome): position += int(bit) * (1 << k) return position # Demonstration with Hamming(7,4)print("Syndrome Calculation via Parity Check Matrix")print("=" * 60) # Create H matrix for Hamming(7,4)H = create_parity_check_matrix(7, 3)print("Parity Check Matrix H:")print(H)print() # Original codeword (encoding 1011)original = np.array([0, 1, 1, 0, 0, 1, 1])print(f"Original codeword: {original}") # Verify original has zero syndromeS_original = compute_syndrome_matrix(H, original)print(f"Syndrome of original: {S_original} = {syndrome_to_position(S_original)}")print() # Introduce error at position 5received = original.copy()received[4] = 1 - received[4] # Flip bit at index 4 (position 5)print(f"Received with error at position 5: {received}") # Compute syndromeS = compute_syndrome_matrix(H, received)error_pos = syndrome_to_position(S)print(f"Syndrome: {S} = {error_pos}")print(f"Error detected at position: {error_pos}")print() # Correct the errorif error_pos > 0: corrected = received.copy() corrected[error_pos - 1] = 1 - corrected[error_pos - 1] print(f"Corrected codeword: {corrected}") print(f"Matches original? {np.array_equal(corrected, original)}")Why Columns Are Position Numbers:
Each column j of H represents which check bits cover position j. When we compute H × rᵀ:
If position j is corrupted, the syndrome equals column j of H—which is exactly the binary representation of j. This is the mathematical core of Hamming's insight.
For valid codewords: H × cᵀ = 0 (zero vector). The syndrome is non-zero if and only if an error has occurred. The columns of H are all distinct non-zero binary vectors, which is why every single-bit error produces a unique syndrome.
The syndrome provides a complete diagnostic of what happened during transmission. Let's examine all possible syndrome values and their meanings.
Case 1: Syndrome = 0 (All Zeros)
Interpretation: No single-bit error detected.
Possibilities:
The probability of Case 2 is much lower than Case 1 in typical channels, so we assume no error when S = 0.
Case 2: Syndrome = Power of Two (1, 2, 4, 8, ...)
Interpretation: Error in a check bit position.
Example: S = 4 means position 4 (a check bit) has an error.
Case 3: Syndrome = Non-Power-of-Two (3, 5, 6, 7, ...)
Interpretation: Error in a data bit position.
Example: S = 5 means position 5 (a data bit) has an error.
| Syndrome | Position Type | Action Required |
|---|---|---|
| 0 | None | No correction needed |
| 1 | Check bit (P₁) | Flip position 1 (or ignore—data unaffected) |
| 2 | Check bit (P₂) | Flip position 2 (or ignore) |
| 3 | Data bit (D₁) | Flip position 3 — data correction needed |
| 4 | Check bit (P₄) | Flip position 4 (or ignore) |
| 5 | Data bit (D₂) | Flip position 5 — data correction needed |
| 6 | Data bit (D₃) | Flip position 6 — data correction needed |
| 7 | Data bit (D₄) | Flip position 7 — data correction needed |
If two bits are corrupted, the syndrome will be the XOR of their positions (e.g., errors at positions 3 and 5 give syndrome 3⊕5=6). The decoder will 'correct' position 6, introducing a third error. Basic Hamming codes cannot handle 2-bit errors—they detect but miscorrect them.
Distinguishing 1-Bit vs. 2-Bit Errors:
Basic Hamming codes cannot distinguish single-bit errors from certain double-bit errors. The syndrome value 6 could mean:
This limitation motivates the SEC-DED (Single Error Correction, Double Error Detection) extension covered later, which adds an overall parity bit to detect (but not correct) double-bit errors.
Let's consolidate everything into a complete, production-ready decoding algorithm that computes the syndrome, locates any error, corrects it, and extracts the original data.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
def decode_hamming(received: list[int]) -> tuple[list[int], int, bool]: """ Complete Hamming code decoder with error correction. Args: received: Received codeword as list of 0s and 1s Returns: Tuple of: - Corrected data bits (list of 0s and 1s) - Syndrome value (0 if no error, otherwise error position) - Boolean indicating if correction was applied Example: data, syndrome, corrected = decode_hamming([0, 1, 1, 0, 1, 1, 1]) # Returns ([1, 0, 1, 1], 5, True) - error at position 5 was corrected """ n = len(received) r = 0 while (1 << r) < n + 1: r += 1 # Calculate syndrome syndrome = 0 for k in range(r): check_pos = 1 << k # 2^k parity = 0 for pos in range(1, n + 1): if pos & check_pos: parity ^= received[pos - 1] if parity == 1: syndrome |= check_pos # Set bit k of syndrome # Correct if necessary corrected = received.copy() error_corrected = False if syndrome != 0: if syndrome <= n: # Valid error position - correct it corrected[syndrome - 1] ^= 1 # Flip the error bit error_corrected = True else: # Syndrome points beyond codeword - uncorrectable raise ValueError(f"Syndrome {syndrome} exceeds codeword length {n}") # Extract data bits (non-power-of-two positions) data = [] for pos in range(1, n + 1): if pos & (pos - 1): # Not a power of two data.append(corrected[pos - 1]) return data, syndrome, error_corrected def verify_codeword(codeword: list[int]) -> bool: """ Verify that a codeword has zero syndrome (is valid). """ _, syndrome, _ = decode_hamming(codeword) return syndrome == 0 # Comprehensive demonstrationprint("Complete Hamming Decoder Demonstration")print("=" * 60) # Test casestest_cases = [ ([0, 1, 1, 0, 0, 1, 1], "No error (valid codeword)"), ([1, 1, 1, 0, 0, 1, 1], "Error at position 1 (P₁)"), ([0, 0, 1, 0, 0, 1, 1], "Error at position 2 (P₂)"), ([0, 1, 0, 0, 0, 1, 1], "Error at position 3 (D₁)"), ([0, 1, 1, 1, 0, 1, 1], "Error at position 4 (P₄)"), ([0, 1, 1, 0, 1, 1, 1], "Error at position 5 (D₂)"), ([0, 1, 1, 0, 0, 0, 1], "Error at position 6 (D₃)"), ([0, 1, 1, 0, 0, 1, 0], "Error at position 7 (D₄)"),] expected_data = [1, 0, 1, 1] # Original data for received, description in test_cases: data, syndrome, corrected = decode_hamming(received) status = "✓" if data == expected_data else "✗" correction_str = f" (corrected pos {syndrome})" if corrected else "" print(f"{status} {description}") print(f" Received: {received}") print(f" Syndrome: {syndrome}{correction_str}") print(f" Data out: {data}") print()In hardware implementations, all syndrome bits are computed in parallel using XOR trees. The syndrome-to-position conversion is implicit (syndrome bits directly address correction logic). Total decoding latency is O(log n) gate delays.
The parity check matrix H and the generator matrix G have a fundamental relationship that underlies all linear error-correcting codes, including Hamming codes.
The Generator Matrix G
G is an m × n matrix that maps m data bits to n codeword bits:
codeword = data × G (over GF(2))
For systematic codes (where data appears unchanged in the codeword), G has the form:
G = [I_m | P]
Where I_m is the m × m identity matrix and P is the m × r parity calculation matrix.
The Orthogonality Condition
H and G satisfy:
H × Gᵀ = 0 (the zero matrix)
This ensures that every valid codeword has zero syndrome:
| Matrix | Dimensions | Purpose |
|---|---|---|
| G (Generator) | 4 × 7 | Encode 4 data bits → 7 codeword bits |
| H (Parity Check) | 3 × 7 | Compute 3 syndrome bits from 7-bit word |
| Gᵀ (G transpose) | 7 × 4 | Used in orthogonality verification |
Constructing G for Systematic Hamming(7,4):
Since data bits go in positions 3, 5, 6, 7, the generator matrix maps:
P₁ P₂ D₁ P₄ D₂ D₃ D₄ (positions 1-7)
G = [ 1 1 1 0 0 0 0 ] d₁ affects P₁,P₂,D₁
[ 1 0 0 1 1 0 0 ] d₂ affects P₁,P₄,D₂
[ 0 1 0 1 0 1 0 ] d₃ affects P₂,P₄,D₃
[ 1 1 0 1 0 0 1 ] d₄ affects P₁,P₂,P₄,D₄
Each row shows which positions a data bit influences, with 1s in the data position and any check positions that cover it.
The rows of H form a basis for the dual code of the Hamming code. Any codeword is orthogonal (under GF(2) dot product) to every row of H. This duality is a deep property connecting coding theory to linear algebra.
Let's work through several complete examples to build fluency with syndrome calculation.
Example 1: No Error
Received: 1001010 (Hamming(7,4) for data 0010)
| Position | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Value | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
Let me recalculate the correct encoding for data 0010:
Correct codeword: 0100110
Now verify syndrome:
Actually P₄ = 1, so codeword is 0101110.
Verify: S₂ = 1 ⊕ 0 ⊕ 1 ⊕ 0 = 0 ✓
Syndrome = 000 = 0 → No error.
Example 2: Error in Data Bit
Original codeword for data 1101: 1001101
Received with error at position 6: 1001111
| Position | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Received | 1 | 0 | 0 | 1 | 1 | 1 | 1 |
| Correct | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
Syndrome calculation:
Let me verify the original encoding for 1101:
1010101With error at position 6 (flip 0→1): 1010111
Syndrome = 011 = 3? But error was at position 6.
I made an error. Let me recalculate more carefully.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
def demonstrate_syndrome_examples(): """ Carefully worked examples of syndrome calculation. """ # Helper function def encode_and_show(data_bits): m = len(data_bits) r = 0 while (1 << r) < m + r + 1: r += 1 n = m + r # Place data bits codeword = [0] * n data_idx = 0 for pos in range(1, n + 1): if pos & (pos - 1): # Not power of 2 codeword[pos - 1] = data_bits[data_idx] data_idx += 1 # Calculate check bits for k in range(r): check_pos = 1 << k parity = 0 for pos in range(1, n + 1): if pos & check_pos: parity ^= codeword[pos - 1] codeword[check_pos - 1] = parity return codeword def calc_syndrome(received): n = len(received) r = 0 while (1 << r) < n + 1: r += 1 syndrome = 0 for k in range(r): check_pos = 1 << k parity = 0 for pos in range(1, n + 1): if pos & check_pos: parity ^= received[pos - 1] if parity: syndrome |= check_pos return syndrome print("Example 1: Data = [1, 0, 1, 1]") print("=" * 50) data1 = [1, 0, 1, 1] codeword1 = encode_and_show(data1) print(f"Data: {data1}") print(f"Codeword: {codeword1}") print(f"Syndrome: {calc_syndrome(codeword1)} (should be 0)") print() # Introduce error at position 5 corrupted1 = codeword1.copy() corrupted1[4] ^= 1 # Flip position 5 print(f"Corrupted at pos 5: {corrupted1}") print(f"Syndrome: {calc_syndrome(corrupted1)} (should be 5)") print() print("Example 2: Data = [1, 1, 0, 1]") print("=" * 50) data2 = [1, 1, 0, 1] codeword2 = encode_and_show(data2) print(f"Data: {data2}") print(f"Codeword: {codeword2}") print(f"Syndrome: {calc_syndrome(codeword2)} (should be 0)") print() # Introduce error at position 6 corrupted2 = codeword2.copy() corrupted2[5] ^= 1 # Flip position 6 print(f"Corrupted at pos 6: {corrupted2}") print(f"Syndrome: {calc_syndrome(corrupted2)} (should be 6)") print() print("Example 3: Error in check bit position") print("=" * 50) # Introduce error at position 2 (check bit P2) corrupted3 = codeword1.copy() corrupted3[1] ^= 1 # Flip position 2 print(f"Original: {codeword1}") print(f"Error at P2: {corrupted3}") print(f"Syndrome: {calc_syndrome(corrupted3)} (should be 2)") demonstrate_syndrome_examples()For data [1,0,1,1], codeword is [0,1,1,0,0,1,1]. Error at position 5 gives syndrome 5. Error at position 2 (check bit) gives syndrome 2. The syndrome always equals the error position for single-bit errors.
We have thoroughly examined syndrome calculation—the decoding mechanism that locates errors in Hamming codewords. Let's consolidate the key takeaways:
What's Next:
With syndrome calculation mastered, the next page covers single error correction—the complete process of receiving a potentially corrupted codeword, computing the syndrome, correcting any single-bit error, and extracting the original data. We'll also examine what happens with various error patterns.
You now understand how to compute and interpret the syndrome—the key to Hamming code decoding. The syndrome directly encodes error positions through the elegant design of power-of-two check bit placement. Next, we'll put this knowledge into complete error correction workflows.