Static Arithmetic Coding

hard · compression, arithmetic-coding, entropy

Static Arithmetic Coding

Build an interoperable encoder and decoder using a supplied, fixed byte-frequency model. The lesson derives the interval equations and deferred-bit rule.

func BuildFrequencies(data []byte) []int
func ArithmeticEncode(data []byte, freqs []int) []byte
func ArithmeticDecode(encoded []byte, freqs []int) []byte

Model and input contract

  • freqs contains exactly 257 nonnegative counts: byte symbols 0–255, then EOF at index 256.
  • EOF and every input byte have positive frequency. Zero-frequency symbols are skipped during decoding.
  • Total frequency is positive and at most 1<<20; input length is at most 4096 bytes.
  • BuildFrequencies returns byte counts plus freqs[256]=1, including for empty input.
  • Encoder and decoder receive identical tables. The table need not be built from this particular message.
  • Do not modify data, encoded, or freqs.

Invalid models and corrupted/truncated streams are outside this exercise's contract. These functions have no error-reporting API. Decoding assumes a stream produced according to the format below, including EOF; a stream is not validated merely because decoding produces bytes.

Bitstream format

Use an inclusive interval initially [0, 2^32-1], with arithmetic widened to uint64. Define H=2^31, Q=2^30, cumulative counts C, and total T.

For each input byte, then EOF, let R=high-low+1 and base=low:

low  = base + floor(R*C[s]/T)
high = base + floor(R*C[s+1]/T) - 1

Repeatedly apply the first matching case:

  1. high<H: emit 0.
  2. low>=H: emit 1 and subtract H from both endpoints.
  3. low>=Q && high<3*Q: increment pending and subtract Q from both endpoints.
  4. Otherwise stop normalizing this symbol.

After cases 1–3, double low and set high=2*high+1. Emitting a bit also writes all pending complements and clears pending. After EOF, increment pending and emit 0 if low<Q, otherwise 1.

Write bits MSB-first, padding the last byte with zeros. The decoder initializes a 32-bit code register and supplies zero bits after physical input ends. Select a symbol using:

v = floor(((code-low+1)*T-1)/(high-low+1))
C[s] <= v < C[s+1]

Return decoded bytes before EOF; EOF itself is not output. Decoder normalization subtracts the same offset from code and shifts in a bit whenever it scales the interval.

Worked compatibility vectors

All unspecified frequencies are zero.

Model Input Encoded bytes (hex)
EOF:1 empty 40
A:2, B:1, EOF:1 AB 5a
A:1, B:2, EOF:1 BB 94

The third vector exercises consecutive deferred bits. Explain why those bits become zeros before relying on a round trip.

Implementation checkpoints

Use one cumulative table on each side. Keep the old interval base until both new endpoints are computed. Check values at partition boundaries, sparse frequency tables, empty input, all byte values, and a shared model whose counts differ from the input histogram.

A linear search over 257 symbols is sufficient for decoding. Extra working memory is O(257), excluding returned bytes.

Run tests to see results
No issues detected
    Join Discord