Notes

Each new bit updates the remainder

· algorithms

Bits already cancelled never return. The remainder of the next prefix is rem_G(x R_k + b_{k+1}), and r trailing zeros make that the CRC.

Last time I divided the whole shifted message at once. A high bit, once cancelled, never comes back. The past of the dividend compresses into the remainder I already have, and the next bit is the only new information.

The next prefix

Read the message from the high bit down, bits b1b2⋯b_{1}b_{2}\cdots. The prefixes of 101101101101 are

1
10
101
1011
10110
101101

Let MkM_{k} be the polynomial of the first kk bits, and let Rk=rem⁡G(Mk)R_{k}=\operatorname{rem}_{G}(M_{k}). Appending the next bit bk+1b_{k+1} shifts that prefix left and writes bk+1b_{k+1} in the empty place:

Mk+1(x)=xMk(x)+bk+1.M_{k+1}(x)=x M_{k}(x)+b_{k+1}.

If Mk=QkG+RkM_{k}=Q_{k}G+R_{k}, then

Mk+1=xQkG+(xRk+bk+1),M_{k+1}=x Q_{k}G+(x R_{k}+b_{k+1}),

so

Rk+1=rem⁡G(xRk+bk+1).R_{k+1}=\operatorname{rem}_{G}(x R_{k}+b_{k+1}).

I never need MkM_{k} again. After kk bits the remainder has degree less than r=deg⁡Gr=\deg G, which is rr bits of state. The loop starts at R0=0R_{0}=0, the empty prefix.

The direct translation shifts the remainder and then calls the long division from last time:

remainder = (remainder << 1) | static_cast<uint32_t>(bit);
remainder = polynomial_remainder(remainder, generator);

remainder << 1 with bit, that is bk+1b_{k+1}, in the low place is xRk+bk+1x R_{k}+b_{k+1}. The call is rem⁡G\operatorname{rem}_{G} of that polynomial.

The same message

Take G=10011G=10011, so r=4r=4, and the message 101101101101. The running remainder stays under degree 44 for the first four bits: 11, 1010, 101101, 10111011. The fifth bit shifts 10111011 to 1011010110, and the degree reaches 44:

10110
^ 10011
-------
00101

The sixth bit shifts 01010101 to 0101101011. The degree is 33, so nothing is cancelled, and

rem⁡G(101101)=1011.\operatorname{rem}_{G}(101101)=1011.

That is rem⁡G(M)\operatorname{rem}_{G}(M), not the CRC. The CRC is rem⁡G(x4M)\operatorname{rem}_{G}(x^{4}M), which is this same walk continued through four zero bits, the room at the end of the message. From 10111011:

10110
^ 10011
-------
00101

then 0101001010, then

10100
^ 10011
-------
00111

then 0111001110. The remainder is 11101110, the same CRC as the one-shot division.

streaming_crc appends those width zeros and hands the whole string to streaming_remainder.

One reduction is enough

After kk bits, deg⁡Rk<r\deg R_{k}<r, so

deg⁡(xRk+bk+1)≤r.\deg(x R_{k}+b_{k+1})\le r.

The shifted remainder has at most one bit above GG. If that bit is 00, the degree is already less than rr. If it is 11, a single XOR with GG cancels it, because both leading coefficients are 11. The long-division loop would run at most once. Bit width is that leading coefficient:

[[nodiscard]] uint32_t streaming_remainder(
const std::vector<int>& dividend_bits, uint32_t divisor) {
uint32_t remainder = 0;
const int width = polynomial_degree(divisor);
for (const int bit : dividend_bits) {
remainder = (remainder << 1) | static_cast<uint32_t>(bit);
if ((remainder & (1LL << width)) > 0) {
remainder = remainder ^ divisor;
}
}
return remainder;
}
[[nodiscard]] uint32_t streaming_crc(const std::vector<int>& message_bits,
uint32_t generator) {
const int width = polynomial_degree(generator);
std::vector<int> augmented = message_bits;
augmented.resize(augmented.size() + static_cast<size_t>(width), 0);
return streaming_remainder(augmented, generator);
}

The generator’s leading 11 is there to cancel the bit that just reached degree rr. The next step is a register that does not store that bit.