De verborgen valkuilen van arithmetic coding: waarom je compressie bits misloopt

De verborgen valkuilen van arithmetic coding: waarom je compressie bits misloopt

Mei 04, 2026 compression arithmetic-coding entropy-coding algorithms performance-optimization cloud-computing developer-experience

De verborgen valkuilen van arithmetic coding: waarom je compressie bits verspilt

Arithmetic coding lijkt een slimme truc. Je zet bitreeksen om in strakke waarschijnlijkheidsbereiken. Voelt goed als je het zelf bouwt. Maar veel standaardversies presteren stilletjes matig. Niet qua snelheid, maar qua compressieverhouding. Precies waar het om draait bij entropy coding.

De klassieke val

Je kent de basisimplementatie vast. Iets als dit:

let mut left: u32 = 0;
let mut right: u32 = u32::MAX;

fn encode_bit(bit: bool, probability: f32) {
    let mid = left + ((right - left) as f32 * probability) as u32;
    if !bit {
        right = mid;
    } else {
        left = mid + 1;
    }
    
    while left >> 24 == right >> 24 {
        output_byte((left >> 24) as u8);
        left <<= 8;
        right = (right << 8) | 0xff;
    }
}

Logisch: je houdt een interval [left, right] bij. Elke bit vernauwt het op basis van de kans. Zodra een byte vaststaat, spuug je hem uit en maak je ruimte.

Het euvel? Die code heeft een asymmetrie die in de theorie niet hoort.

Byte-grenzen gooien roet in het eten

In een perfecte wereld met oneindige precisie doen alle intervallen van gelijke lengte hetzelfde. Maar met 32-bit integers niet.

Neem twee gevallen:

  1. Interval bij left = 0 krimpt nooit onder 2^24 bits.
  2. Bij left = 2^31 - 1 wel tot 2 bits.

Schuldige: de while-loop left >> 24 == right >> 24. Die hangt af van de positie, niet de grootte.

Als je interval een byte-grens raakt, zoals [2^31 - 1, 2^31], wordt het piepklein. Kans van 0.95 op een bit? Wordt een ruwe 50/50-split. Resultaat: meer bits dan nodig volgens de entropietheorie.

Decoder zit ook vol adders

Kijk naar de decoder:

fn decode_bit(probability: f32) -> bool {
    let mid = left + ((right - left) as f32 * probability) as u32;
    if x <= mid {
        right = mid;
        bit = false;
    } else {
        left = mid + 1;
        bit = true;
    }
    
    while left >> 24 == right >> 24 {
        left <<= 8;
        right = (right << 8) | 0xff;
        x = (x << 8) | (bytes.next().unwrap() as u32);
    }
    
    bit
}

Drie variabelen: left, right en x. Wiskundig volstaan lengte (right - left) en relatieve positie (x - left).

Die while-loop dwingt absolute posities af. Gevolg:

  • Meer registers nodig
  • Moeilijker te optimaliseren
  • Code lastiger te snappen

Alleen maar door een check die eigenlijk alleen grootte zou moeten meten.

Hoe los je het op?

Dump bytes op basis van intervalgrootte, niet positie. Offset-onafhankelijk. Klinkt makkelijk, maar je moet de hele loop herzien. Voordeel: betere compressie, simpelere en snellere decoder.

Waarom dit telt bij NameOcean

Bij NameOcean helpen we devs met AI-apps op Vibe Hosting of geoptimaliseerde systemen. Compressie voor cloud storage, API's of big data? Deze basisdingen maken verschil.

Libraries overnemen zonder checks? Een 5-10% betere ratio telt op bij petabytes of miljoenen requests.

Les: 'standaard' code is zelden perfect. Graaf dieper. Check aannames en scheefgroei. Topingenieurs snappen de randgevallen.


Bouw je cloud-apps met hoge eisen? Kleine tweaks stapelen op bij massa-requests. Optimaliseer je stack? Details beslissen.

Read in other languages:

RU BG EL CS UZ TR SV FI RO PT PL NB HU IT FR ES DE DA ZH-HANS EN