Codes
Why Errors Need Detecting
In Cryptosystems the whole point was privacy; we wanted a message that an eavesdropper could not read. Sometimes we are much less worried about privacy and much more worried about the message arriving intact. Wires pick up noise, discs get scratched, satellites get hit by cosmic rays; digits flip. What we want is a way of writing the message down so that a flipped digit is at least noticeable, and ideally repairable.
To see why this is a genuine problem, take the standard letter-to-number conversion , , and send the letter . Then , and (recalling Base Number Systems) in binary is
so we transmit the string . Now suppose the leading digit gets knocked from to in transit, so the receiver sees . They decode this perfectly happily:
The receiver reads , believes it, and has absolutely no reason to suspect anything went wrong. This is the failure mode we are trying to design away. The problem is not that an error occurred; the problem is that every one of the sixteen -digit strings is a legal message, so a corrupted string is still a legal message.
The fix, in every code in this lecture, is the same idea: deliberately send more digits than the message needs, arranged so that only some of the strings are legal. Then a single flip drops you off the list of legal strings and the receiver can see it. Codes come in two strengths:
- an error-detecting code lets the receiver tell that something has gone wrong (so they can ask for a retransmission);
- an error-correcting code lets the receiver work out which digit is wrong and repair it without asking for anything.
All the arithmetic in this lecture happens in , which by Rings and Fields is a field, and in which the only rules you need are
Basically, addition in is "flip the digit if you add , leave it alone if you add ", and it is its own inverse: , so in there is no difference between adding and subtracting, and you never have to worry about signs. That single fact makes every calculation below shorter than it looks.
Parity Check Codes
The cheapest possible thing you can do is add one extra digit that records whether the message had an even or odd number of s.
Note
Definition (Simple Parity Check Code)
Encoding. Convert the message to binary, and then append an extra check digit that is either if there are an even number of s in the original binary message, or if there are an odd number of s in the original binary message.
Decoding. Count the number of s in the encoded message. If there are an odd number of s, at least one error must have occurred. When reading the message, ignore the final digit.
Basically, the check digit is chosen to make the total number of s even; so if the receiver ever counts an odd number of s, a digit must have flipped. Said in , if the message digits are then the check digit is simply
and a legal encoded word is exactly one satisfying in . Keep that phrasing in mind; every code in this lecture will turn out to be "the strings satisfying some system of equations".
Example. Encode the letter with a simple parity check, and show what happens when the leading digit is flipped in transit.
As above, . The original message contains three s, which is odd, so the check digit is and the transmitted word is
Suppose the leading digit is flipped, so the receiver gets . Counting,
which is odd; therefore the receiver knows that at least one error has occurred and can request a retransmission. Contrast this with the bare -digit code, where the same flip silently turned into ; one extra digit bought us the ability to notice.
Example. The same word is sent, but this time the first two digits are flipped, so the receiver gets . What happens?
Counting the s,
which is even, so the parity check passes and the receiver suspects nothing. They discard the final digit and read . Therefore, the parity check completely misses this pair of errors and silently reports the letter instead of . A parity check detects exactly an odd number of errors; any even number of errors cancels out and slips through unnoticed.
It is worth being blunt about the other limitation as well. Even when the check does fire, all it tells you is "somewhere in these five digits, an odd number of things are wrong". It does not tell you where, and it does not tell you how many, so there is nothing you can do except ask for the message again. The simple parity check is a pure error-detecting code; it can never correct anything.
The cost of the code is one extra digit per chunk. If we parity-check in chunks of information digits, then digits get transmitted for every digits of actual message, so of the traffic is useful; we will call this proportion the rate of the code. A rate of is excellent value, which is precisely why parity bits are everywhere in real hardware. The trouble is that we got what we paid for: cheap code, weak guarantees.
Reduplication Codes
The obvious way to buy more protection is brute force; just say everything twice.
Note
Definition (Double Reduplication Code)
Encoding. Convert the message to binary, repeating every digit twice.
Decoding. When reading the message, interpret every pair of digits as a single digit. If the pair of digits is , read this as , and if the pair of digits is , read this as . If the pair of digits is or , we know (at least) one error must have occurred at this position in the message.
Basically, the legal pairs are and ; anything else is a red flag, and it even tells you which pair went wrong. This is strictly better than a parity check in one respect and strictly worse in another, so it is worth being careful about exactly what it gives you.
Example. Encode under double reduplication, and decode the received word .
Repeating each digit twice, the transmitted word is
The receiver gets . Splitting into pairs and reading them off,
Therefore, the receiver knows that an error occurred, and knows it happened in the second digit of the message — but that is as far as it goes. The true second digit could have been a (with the second copy flipped) or a (with the first copy flipped), and nothing in the received word distinguishes those two stories. Double reduplication detects and locates an error, but it still cannot correct one.
Notice also that multiple errors are often, but not always, caught: two errors in different pairs both show up, whereas two errors inside the same pair turn back into and are invisible. In exchange for all this we have doubled the length of the message, so the rate has crashed from to . That is a lot of bandwidth for a code that still cannot fix anything.
If saying everything twice does not let you correct, the natural next move is to say everything three times and take a vote.
Note
Definition (Triple Reduplication Code)
Encoding. Convert the message to binary, repeating every digit three times.
Decoding. When reading the message, interpret every triple of digits as a single digit. If the triple of digits contains at least two s, read this as , and if the triple of digits contains at least two s, read this as . Whenever a triple is not or , we know (at least) one error has occurred, and can fairly reliably correct the error.
Basically, this is majority rule: each digit gets three votes, and the minority is assumed to be the mistake. The correction is only valid under the assumption that there is at most one error per triple; if two digits inside the same triple flip, the majority vote confidently returns the wrong answer, which is worse than not correcting at all.
Example. Encode under triple reduplication, and decode the received word .
Repeating each digit three times, the transmitted word is
which is digits long. Splitting the received word into triples and taking a majority vote in each,
Therefore, the decoded message is , recovered correctly despite two transmission errors, and the receiver can even point at exactly which two digits were wrong. This is a genuine error-correcting code.
Example. Under the same code, what does the receiver make of the received triple ?
Majority vote gives , since two of the three digits are . If exactly one error occurred, this is right. But if the sent triple was and two digits flipped, the true digit was and the decoder has just committed confidently to the wrong value; and if the sent triple was with one flip, it is right. Therefore, the answer the code returns is , but that answer is only trustworthy under the at-most-one-error-per-triple assumption. Every error-correcting code in this course carries an assumption of that kind; quoting the answer without quoting the assumption is only half the answer.
The price is brutal: the rate has fallen to , so two out of every three digits you transmit are pure redundancy, and all of that only buys you one correctable error per three digits. Compare the three codes so far:
| Code | Rate | Detects | Locates | Corrects |
|---|---|---|---|---|
| Simple parity check | odd number of errors | no | no | |
| Double reduplication | one error per pair | yes | no | |
| Triple reduplication | one error per triple | yes | yes |
The obvious question is whether we can get the correcting power of the third row at anything like the rate of the first. The answer is yes, and it comes from linear algebra.
Vectors and Matrices
Before building the good codes, we need a little machinery. Nothing here is deep; it is just a compact language for saying "this collection of digits satisfies this collection of equations".
Note
Definition (Row Vector)
A real-valued row vector is a mathematical object containing an ordered list of real numbers. We write for a row vector of size , where .
Note
Definition (Matrix)
A real-valued matrix is an array of real numbers which can be thought of as several row vectors stacked together. We write
for a matrix of size , where each .
Sizes are always quoted as (rows) (columns), in that order; getting this backwards is the single most common slip in matrix questions, and it makes every subsequent product illegal.
Note
Notation
The transpose of a vector or matrix, denoted by the superscript , is the original object with its rows converted to columns. For example, a column vector can be written as
Note
Notation
The zero vector is a vector all of whose entries are ; for example the zero vector is . The standard basis vector is the vector whose th entry is , with all other entries being ; for example the standard basis vectors are
The transpose is mostly a typographical convenience here: our codewords are naturally written left-to-right as row vectors, but matrices multiply column vectors, so is how a codeword gets fed into a matrix.
Multiplication is defined by "row against column". The product of a row vector with a column vector is the single number
and to multiply two matrices and we create a new matrix whose th entry is the product of the th row of with the th column of . The case we actually use is a matrix times a column vector: the product of an matrix with a column vector is an vector whose th entry is . Written out for a matrix,
Basically, each row of the matrix eats the whole vector and produces one number, so the answer has as many entries as has rows.
Example. What is the product of and ?
This is a matrix times a column vector, so the answer is . Taking each row against the column in turn,
Therefore, the product is .
For codes we do exactly this arithmetic but in , which is much easier: every entry of the matrix is or , so a row against a column is just "add up the entries of the vector sitting underneath a , then reduce mod ". In other words, the th entry of over is if the positions where row has a contain an even number of s in , and if they contain an odd number. Every Hamming calculation below is really a stack of parity checks in disguise.
Example. Compute over , where and .
Reading off which positions of each row selects,
all in . Therefore, . Notice that no multiplication was ever performed; over you only ever count s and ask whether the count is even or odd.
The Hamming (7,4) Code
A Hamming code is a very efficient type of error-correcting code, in the sense that it minimises the number of check digits required per message while maximising the number of errors that can be detected or corrected. The smallest useful one squeezes information digits into transmitted digits, so it corrects a single error at a rate of — compare that with triple reduplication, which needs digits to protect the same .
Everything is controlled by one matrix.
Note
Definition (Standard Hamming Matrix)
The Hamming code uses the standard Hamming matrix
whose columns are the integers through to in binary, reading from bottom to top.
That last sentence is the whole trick, so make sure you can see it. Reading each column upwards gives a -digit binary string, and those strings run through in order:
| Position | |||||||
|---|---|---|---|---|---|---|---|
| Column of | |||||||
| Read bottom to top | |||||||
| In decimal |
The columns are written bottom-to-top, not top-to-bottom; if you read them the wrong way round every position you report will be the bit-reversal of the truth. A quick way to remember it: the top row is the s bit, the middle row is the s bit, the bottom row is the s bit — the same order as an ordinary binary expansion once you turn your head sideways.
Notice that the columns in positions and are exactly the standard basis vectors . Those are the positions that will hold the check digits, and it is no accident; a check digit whose column is appears in the th equation and no other, so each check digit can be solved for on its own.
Encoding
Note
Definition (Standard Hamming Encoding)
Convert the message to binary and break it into chunks of information digits . Then output the vector
where all entries are calculated in . This vector contains check digits at positions and , and has the property that over .
Basically, the information digits are dropped into positions untouched, and the three check digits at positions are each a parity check over a carefully chosen subset of them. The rule is not arbitrary; each check digit exists to make one row of sum to zero. Reading the equation row by row with , , , ,
where we used that in so moving a term across the equals sign changes nothing. That derivation is worth internalising, because it is exactly what you repeat when the matrix changes.
Example. Using the standard Hamming code, encode the message .
Here , , , , so the three check digits are
all in . Slotting these into positions and the information digits into positions ,
As a check, should be the zero vector; row selects positions giving , row selects positions giving , and row selects positions giving . Therefore, the encoded message is , and it is a legal codeword.
Example. Using the standard Hamming code, encode the message .
Now , , , , so
in . Therefore, the encoded message is . Checking against once more: positions give ; positions give ; positions give , so as required. Always run this check; it costs three additions and catches every arithmetic slip you could have made in the encoding.
Decoding
Note
Definition (Standard Hamming Decoding)
To check for errors (assuming there is at most one) in the received message , find . If , there are no errors. Otherwise, an error exists, and its position is given by the column in that matches . To read the message, extract the digits of in positions and .
Basically, is a three-digit binary number that is the position of the broken digit, spelled out bottom-to-top. That is why the columns of were arranged in counting order in the first place; the matrix is designed so that the error announces its own address.
Example. Using the standard Hamming code, decode the received message , assuming there is at most one error.
Write and compute one row at a time, remembering that each row just adds up the entries of in the positions where that row of has a :
all in , so
Since there is an error, and is the first column of ; equivalently, reading from bottom to top gives . So the error is in position . Flipping that digit gives the corrected codeword
and extracting positions gives the message digits . Therefore, the decoded message is . As a sanity check, encoding gives check digits , and , i.e. the codeword — exactly the corrected word, so everything is consistent.
Example. Assuming there is at most one error, decode using the standard Hamming code.
Computing over ,
so . Reading bottom to top, , and indeed is the sixth column of ; the error is in position . Flipping it gives , whose digits in positions are . Therefore, the decoded message is . Checking: encoding gives , , , i.e. , which matches.
Example. Assuming there is at most one error, decode .
Computing the three parities,
so . Therefore, no error occurred, and the message is read straight off positions as — which is exactly the message we encoded in the first example of this section, as it should be. When you do not "correct" anything; you read the message off the received word unchanged.
Non-Standard Hamming Codes
Why does the decoding rule work at all? The argument is short and it is the most important paragraph in the lecture, because it tells you exactly how much freedom you have in choosing .
Suppose a single error occurs at position . Over , flipping the th digit is the same as adding to it, so the received word is related to the sent word by
where is the th standard basis vector. Then, using that matrix multiplication distributes over addition and that for every codeword,
and is precisely the th column of (multiplying by picks out column and kills everything else). So:
Basically, the syndrome does not know or care what the message was; it only sees the error. And notice what the argument never used: it never used the fact that the columns of were the numbers through in order. All it needs is that the columns are distinct and nonzero, so that "the column matching " picks out a unique position.
This tells us that the decoding method will work regardless of the matrix used, implying that variations of the standard Hamming code exist where a different matrix uses some permutation of the original columns. With such an we still calculate , and (assuming at most one error) if then there are no errors, while otherwise there is one error at the position corresponding with the column in that matches .
The error-checking method is unchanged, but the encoding rule certainly is not. The check digits are always determined by insisting that , and you have to redo the row-by-row solve to find out what they are. The other feature that may change is where the check and information digits sit: typically the check digits are placed so that their corresponding columns in are the standard basis vectors , because then each check digit appears in exactly one of the three equations and can be read off immediately. If you put a check digit somewhere else, the three equations tangle together and you have to solve a genuine simultaneous system.
Example. Consider the non-standard Hamming matrix
Supposing that the information digits are in the last four positions, decode the received message , assuming at most one error.
First read off which positions each row of selects: row has s in positions ; row in positions ; row in positions . Computing over ,
so and an error has occurred. Now hunt for the column of equal to ; scanning across, the only all-ones column is the fourth. Do not convert to a number here — with a non-standard matrix the columns are not in counting order, so position has nothing to do with "being" in binary; you must physically match the column. Flipping digit of gives the corrected codeword
and since the information digits live in the last four positions, the message is . Therefore, the decoded message is .
Example. Find the encoding rule for the previous Hamming code, using the same non-standard Hamming matrix , where the information digits are placed in the last four positions.
Write the codeword as , so that are the check digits and are the information digits. Notice first that the columns in positions of are , so each check digit appears in exactly one equation; this is the situation we wanted. Now impose row by row:
where again we used in , so rearranging never introduces a minus sign. Therefore, the encoding rule is
with all entries computed in .
As a sanity check, run the message recovered in the previous example through this rule:
giving , which is exactly the corrected codeword we obtained. Two independent calculations agreeing like this is the best evidence you will get in an exam that both of them are right.
Example. Consider the non-standard Hamming matrix
over , where the last three entries of an encoded message are the check digits.
(a) Find the encoding rule.
(b) Assuming there is at most one error, decode the message .
(a) The columns in positions are , which is consistent with the check digits being placed there. Write . Row has s in positions ; row in positions ; row in positions . Imposing ,
Therefore, the encoding rule is .
(b) With , computing over ,
so , which matches the first column of . Flipping digit gives the corrected codeword , and the information digits are the first four, namely . Therefore, the decoded message is . Checking against part (a) with : , , , giving as required.
Notice how in both non-standard examples the answer came out of the same three-step routine, and the matrix never had to be memorised:
- Identify which positions hold check digits (look for the columns that are ).
- For the encoding rule, read each row of as "the sum of the digits in these positions is ", and solve each row for its check digit.
- For decoding, compute by the same row-selects-positions rule, then match against the columns of by eye.
Larger Hamming Codes
The Hamming code is so-called because it encodes messages of length as messages of length ; that is, it introduces check digits. Hamming codes of other sizes are designed in exactly the same fashion, and the pattern is driven entirely by the columns.
With rows, the columns of can be any nonzero binary strings of length , and there are of those; so the longest codeword we can protect with check digits has digits, of which carry information. This gives the family
so gives , gives , gives , and so on. Notice what happens to the rate: , then , then . Bigger Hamming codes are much more efficient, but they still only correct one error per block; a long block is a bigger target, so the single-error assumption gets harder to justify as grows. That trade-off is the whole design problem.
The next one up uses the standard Hamming matrix
whose columns are the integers through to in binary, reading from bottom to top. The rows are exactly the s bit, the s bit, the s bit and the s bit of the column number, which is why each row looks like a doubling of the pattern above it: alternate singles, then pairs, then fours, then eights.
Note
Definition (Standard Hamming Code)
Encoding. Convert the message to binary and break it into chunks of information digits . Then to ensure , output the vector
where, in ,
Decoding. As before, if is the th column of , correct the th digit of .
You do not have to memorise those four formulas, and you should not try; they are just the same row-by-row solve applied to a bigger matrix. The check digits sit at positions (the powers of , which are exactly the positions where the column of is a standard basis vector), and is the sum of every information digit whose position has a in the th bit. For instance row of has s at the odd positions , and dropping out of that list leaves positions holding — which is precisely the formula for .
Example. Using the standard Hamming code, encode the message .
Here . Computing the four check digits in ,
Slotting the check digits into positions and the information digits into the rest,
Therefore, the encoded message is . (Counting s in the odd positions gives , which is even, so row of checks out; the other three rows check the same way.)
Example. Assuming there is at most one error, decode using the standard Hamming code.
Each row of selects the positions where that row has a , so
all in , so . Reading bottom to top gives , so the error is in position . Flipping that digit gives the corrected codeword
and deleting the check digits at positions leaves the information digits at positions :
Therefore, the decoded message is . (Recomputing on the corrected word gives , confirming that it is now a legal codeword.)
Codes that can correct multiple errors do exist, but they need more machinery than we have; that idea gets picked up again in Topic 9.
What Hamming Codes Can and Cannot Do
It is worth being very precise about the guarantee, because the phrasing "assuming there is at most one error" is doing enormous work.
If exactly one error occurs, the code always corrects it: we proved above that is column , the columns are distinct, so the position is pinned down uniquely and flipping it restores the sent word. If no error occurs, and nothing is touched. So far so good.
Now suppose two errors occur, at positions and . The same computation gives
which is nonzero (the columns are distinct, so their sum cannot be zero). The receiver therefore does see that something is wrong — but the sum of two columns is itself a column, so the decoder happily concludes "one error, at position ", flips a third perfectly good digit, and hands over a legal-looking codeword that is not the one you sent.
Example. The codeword (the encoding of ) is sent and the first two digits are flipped, so arrives. What does the standard decoder report?
Computing ,
so , which is column of (bottom to top, ). The decoder therefore "corrects" position , producing , and reads the message off positions as . Therefore, the decoder confidently returns when the true message was ; it has flagged an error, mis-located it, and made the word more wrong. The plain Hamming code corrects one error, but it does not reliably detect two; with two errors it will silently mis-correct into a different valid codeword. (Real systems fix this by appending one extra overall parity digit, giving a code that corrects one error and detects two — but that is not the code you are given here, so do not claim double-error detection in an exam.)
The standard traps, collected in one place:
- Reading the columns of top-to-bottom instead of bottom-to-top. Position has column , not ; getting this backwards swaps positions and and gives a wrong answer that still "looks" plausible.
- Converting to a decimal number when the matrix is non-standard. The "read as a binary number" shortcut is a coincidence of the standard matrix having its columns in counting order; for any you must match against the columns physically.
- Forgetting to correct before extracting. The message digits come from the corrected word, not the received one. If and the flagged position happens to be an information digit, extracting from directly gives the wrong message.
- Forgetting where the information digits live. For the standard code they are at positions ; for a non-standard code they can be anywhere, and the question will tell you. Read that sentence twice.
- Doing the arithmetic over instead of . A check digit is never or ; reduce mod at every step (see Modular Rings and Units if you want the general story).
- Quoting the encoding rule of the standard code for a non-standard matrix. The decoding method is matrix-independent by the argument; the encoding rule is not, and must be re-derived from every single time.
Stepping back, the whole lecture is one idea repeated at increasing levels of cleverness. A parity check imposes one linear equation on the transmitted digits and buys you detection of an odd number of errors for one extra digit. Reduplication imposes many equations and buys correction, but at a rate of or . Hamming codes impose carefully chosen equations — one per row of — and arrange the columns so that the failed equations spell out the address of the broken digit in binary; that gets you single-error correction at a rate of , which tends to as grows. Redundancy is the price of reliability, and Hamming's contribution was working out how little of it you actually have to pay.