CRC is a polynomial remainder
· algorithms
Long division over F_2 leaves a remainder. A CRC is that division after the message shifts left by the generator's degree, which clears room to append the check.
I was learning CRC today. I started from the division I could already do on paper: given and over , compute the remainder of on division by . The CRC is one further shift.
A message may be corrupted on the way out. I append a short check, and the receiver divides the whole word by a generator chosen in advance. That check is the remainder. The place it lives is the end of the message.
Arithmetic in F_2
, also called , is the field with two elements. Addition and subtraction are both XOR:
Multiplication is AND:
A word is a polynomial
The bit string is
The left bit is the highest power, so a uint32_t is a polynomial in . Likewise is , and its degree is : the index of the highest . The loop starts at and adds one on each right shift until the word is zero.
[[nodiscard]] int polynomial_degree(uint32_t polynomial) { int degree = -1; while (polynomial > 0) { polynomial >>= 1; ++degree; } return degree;}Long division
While the dividend still has degree at least , cancel its leading term. If and , the multiple that lines up is , and divisor << shift is that multiple. XOR is the addition.
[[nodiscard]] uint32_t polynomial_remainder(uint32_t dividend, uint32_t divisor) { const int divisor_degree = polynomial_degree(divisor); while (polynomial_degree(dividend) >= divisor_degree) { const int shift = polynomial_degree(dividend) - divisor_degree; dividend = dividend ^ (divisor << shift); } return dividend;}Take and , so and . The first shift is :
1011010000^ 1001100000------------ 0010110000The degree is now , so the shift is :
0010110000^ 0010011000------------ 0000101000The degree is , so the shift is :
0000101000^ 0000100110------------ 0000001110The degree is , below , and the loop stops. The remainder is .
Each step replaces by with . That sum is congruent to modulo , and the two leading terms are the same power, so they cancel and the degree falls. The loop begins with the original dividend, which is congruent to itself, and it ends at a polynomial of degree less than in the same residue class. That remainder is unique. Write for it: the polynomial of degree less than congruent to modulo . The value left in dividend is .
One shift makes it a CRC
The check appended to a message is
Multiplying by shifts the message left by and leaves zeros at the end. Those zeros are the room for the CRC. For , the degree is , and the message shifts to . That is the dividend above, so
[[nodiscard]] uint32_t crc(uint32_t message, uint32_t generator) { const int width = polynomial_degree(generator); return polynomial_remainder(message << width, generator);}message << width is . Appending the remainder gives the word . The low bits were zero, so writing there is (message << width) | R. Here that word is . Division gives , and , so the word equals . The generator divides the message with the CRC appended.
| C++ | In |
|---|---|
uint32_t p | a polynomial |
polynomial_degree(p) | |
p << k | |
a ^ b | |
polynomial_remainder(dividend, divisor) | |
crc(message, generator) |
The next step is to divide without building as one integer.