Скрытые ловушки арифметического кодирования: почему ваша компрессия теряет биты

Скрытые ловушки арифметического кодирования: почему ваша компрессия теряет биты

Май 04, 2026 compression arithmetic-coding entropy-coding algorithms performance-optimization cloud-computing developer-experience

Скрытые ловушки арифметического кодирования: почему ваша компрессия тратит биты зря

Арифметическое кодирование кажется идеальным. Оно красиво преобразует последовательности битов в точные вероятностные диапазоны. Но простые реализации из интернета часто работают хуже, чем могли бы. И это не про скорость процессора. Речь о коэффициенте компрессии — главной цели энтропийного кодирования.

Классика, которая подводит

В учебниках показывают стандартный код. Вот пример на Rust:

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;
    }
}

Логика ясна. Интервал [left, right] сужается по вероятностям. Когда верхние байты совпадают, выводим их и сдвигаем диапазон.

Проблема в скрытой асимметрии. Математическая модель симметрична. А 32-битная реализация — нет.

Предательство границ байтов

В идеале интервалы одинаковой длины равны. В коде всё зависит от позиции.

Пример:

  1. Интервал от left = 0 не сжимается меньше 2^24.
  2. От left = 2^31 - 1 может уйти до 2 битов.

Условие left >> 24 == right >> 24 смотрит на абсолютное положение. Если интервал пересекает границу байта, вроде [2^31 - 1, 2^31], он становится крошечным. Вероятности квантуются грубо. Бит с p=0.95 может разделиться 50/50. Результат — лишние биты в выходе, вопреки теории энтропии.

Декодер усложняет жизнь

В декодере та же беда:

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
}

Три переменные: left, right, x. Математически хватит двух: длины интервала и позиции точки. Но цикл с байтами привязывает всё к абсолютным значениям. Это добавляет нагрузку на регистры, мешает оптимизаторам и запутывает код.

Как исправить

Переходите на вывод по длине интервала, без привязки к позиции. Это упростит логику. Компрессия станет точнее, декодер — проще и шустрее. Нужно переписать циклы, но оно того стоит.

Зачем это важно для NameOcean

В NameOcean мы помогаем разработчикам с Vibe Hosting для AI-приложений и оптимизированными системами. Компрессия нужна везде: от облачного хранилища до API и больших датасетов.

Многие берут готовые библиотеки, не зная их слабостей. 5-10% прироста в компрессии — это заметно на петабайтах или миллионах запросов.

Урок простой: учебные примеры не всегда идеальны. Копайте глубже. Ищите асимметрии. Хороший инженер разбирается в нюансах алгоритмов.


В облаке мелкие оптимизации накапливаются. Миллионы запросов — и разница в деньгах. Думайте о стеке. Детали решают.

Read in other languages:

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