Skip to main content

cram_codec/
rans.rs

1//! Deterministic static rANS over a hybrid-integer alphabet.
2//!
3//! Signed integers are zigzag-mapped to `u`, then split into a *token* that is
4//! entropy coded and *extra bits* that are stored raw:
5//!
6//! - `u < 16` → token `u`, no extra bits;
7//! - otherwise with `nb = ⌊log2 u⌋`, the token carries `nb` and the two bits
8//!   below the leading one (`16 + 4(nb−4) + m`), and the remaining `nb − 2`
9//!   low bits go to the raw stream.
10//!
11//! This is the JPEG-XL "hybrid uint" idea: the coded token captures the shape
12//! of the distribution, and the raw bits are (nearly) uniform, so storing them
13//! verbatim costs little. Each block's token frequencies are normalised to a
14//! `2^14` total and **transmitted** (varint per symbol), and the rANS state is
15//! the 32-bit / byte-renormalised variant (Giesen's `rans_byte`). Integer
16//! arithmetic only: encode and decode are bit-exact on every platform.
17
18use crate::bitio::{unzigzag, zigzag, BitReader, BitWriter, ByteReader, ByteWriter, DecodeError};
19
20/// Probability resolution of the frequency tables, in bits.
21pub const SCALE_BITS: u32 = 14;
22const M: u32 = 1 << SCALE_BITS;
23const RANS_L: u32 = 1 << 23;
24
25/// Map `u` to `(token, extra_bits_value, extra_bit_count)`.
26pub fn split_uint(u: u64) -> (u32, u64, u32) {
27    if u < 16 {
28        return (u as u32, 0, 0);
29    }
30    let nb = 63 - u.leading_zeros();
31    let m = ((u >> (nb - 2)) & 3) as u32;
32    let token = 16 + 4 * (nb - 4) + m;
33    let extra = nb - 2;
34    (token, u & ((1u64 << extra) - 1), extra)
35}
36
37/// Extra-bit count and base value for `token`.
38fn token_base(token: u32) -> (u64, u32) {
39    if token < 16 {
40        return (token as u64, 0);
41    }
42    let t = token - 16;
43    let nb = t / 4 + 4;
44    let m = (t % 4) as u64;
45    (((4 | m) << (nb - 2)), nb - 2)
46}
47
48/// Normalise raw counts to a `2^SCALE_BITS` total, every present symbol ≥ 1.
49fn normalize(counts: &[u64]) -> Vec<u32> {
50    let total: u64 = counts.iter().sum();
51    let mut f: Vec<u32> = counts
52        .iter()
53        .map(|&c| {
54            if c == 0 {
55                0
56            } else {
57                (((c as u128) * (M as u128) / (total as u128)) as u32).max(1)
58            }
59        })
60        .collect();
61    let mut sum: i64 = f.iter().map(|&x| x as i64).sum();
62    // Fix the residual deterministically: take from / give to the largest bins.
63    while sum != M as i64 {
64        let mut order: Vec<usize> = (0..f.len()).filter(|&i| f[i] > 0).collect();
65        order.sort_by(|&a, &b| f[b].cmp(&f[a]).then(a.cmp(&b)));
66        if sum < M as i64 {
67            f[order[0]] += (M as i64 - sum) as u32;
68            sum = M as i64;
69        } else {
70            for &i in &order {
71                if sum == M as i64 {
72                    break;
73                }
74                let can = (f[i] - 1) as i64;
75                let take = can.min(sum - M as i64);
76                f[i] -= take as u32;
77                sum -= take;
78            }
79        }
80    }
81    f
82}
83
84/// One static model: frequencies and cumulative starts.
85struct Model {
86    freq: Vec<u32>,
87    start: Vec<u32>,
88    /// Slot → symbol lookup for decoding.
89    lut: Vec<u16>,
90}
91
92impl Model {
93    fn new(freq: Vec<u32>) -> Result<Self, DecodeError> {
94        let mut start = Vec::with_capacity(freq.len());
95        let mut acc = 0u32;
96        for &f in &freq {
97            start.push(acc);
98            acc = acc.checked_add(f).ok_or(DecodeError::Invalid("freq sum"))?;
99        }
100        if acc != M {
101            return Err(DecodeError::Invalid("freq table does not sum to 2^14"));
102        }
103        let mut lut = vec![0u16; M as usize];
104        for (s, (&st, &f)) in start.iter().zip(&freq).enumerate() {
105            for slot in lut.iter_mut().skip(st as usize).take(f as usize) {
106                *slot = s as u16;
107            }
108        }
109        Ok(Self { freq, start, lut })
110    }
111}
112
113fn write_table(w: &mut ByteWriter, freq: &[u32]) {
114    w.varint(freq.len() as u64);
115    for &f in freq {
116        w.varint(f as u64);
117    }
118}
119
120fn read_table(r: &mut ByteReader) -> Result<Vec<u32>, DecodeError> {
121    let n = r.varint()? as usize;
122    if n == 0 || n > 300 {
123        return Err(DecodeError::Invalid("alphabet size"));
124    }
125    (0..n).map(|_| Ok(r.varint()? as u32)).collect()
126}
127
128fn token_counts<'a>(vals: impl Iterator<Item = &'a i64>) -> Vec<u64> {
129    let mut counts = vec![0u64; 16];
130    for &v in vals {
131        let (t, _, _) = split_uint(zigzag(v));
132        if t as usize >= counts.len() {
133            counts.resize(t as usize + 1, 0);
134        }
135        counts[t as usize] += 1;
136    }
137    while counts.len() > 1 && *counts.last().unwrap() == 0 {
138        counts.pop();
139    }
140    counts
141}
142
143/// rANS-encode `vals` under `model`; returns (rANS bytes, raw extra bits).
144fn encode_with(vals: &[i64], model: &Model) -> (Vec<u8>, Vec<u8>) {
145    let mut bits = BitWriter::new();
146    let mut toks = Vec::with_capacity(vals.len());
147    for &v in vals {
148        let (t, e, n) = split_uint(zigzag(v));
149        bits.put(e, n);
150        toks.push(t);
151    }
152    let mut out = Vec::new();
153    let mut x: u32 = RANS_L;
154    for &t in toks.iter().rev() {
155        let f = model.freq[t as usize];
156        let x_max = ((RANS_L >> SCALE_BITS) << 8) * f;
157        while x >= x_max {
158            out.push(x as u8);
159            x >>= 8;
160        }
161        x = ((x / f) << SCALE_BITS) + (x % f) + model.start[t as usize];
162    }
163    out.extend_from_slice(&x.to_le_bytes());
164    out.reverse();
165    (out, bits.finish())
166}
167
168fn decode_with(n: usize, model: &Model, rans: &[u8], raw: &[u8]) -> Result<Vec<i64>, DecodeError> {
169    if rans.len() < 4 {
170        return Err(DecodeError::Truncated);
171    }
172    let mut x = u32::from_be_bytes(rans[..4].try_into().unwrap());
173    let mut pos = 4;
174    let mut bits = BitReader::new(raw);
175    let mut out = Vec::with_capacity(n);
176    for _ in 0..n {
177        let slot = x & (M - 1);
178        let t = model.lut[slot as usize] as u32;
179        x = model.freq[t as usize] * (x >> SCALE_BITS) + slot - model.start[t as usize];
180        while x < RANS_L {
181            let b = *rans.get(pos).ok_or(DecodeError::Truncated)?;
182            pos += 1;
183            x = (x << 8) | b as u32;
184        }
185        let (base, nextra) = token_base(t);
186        let u = base + bits.get(nextra)?;
187        out.push(unzigzag(u));
188    }
189    Ok(out)
190}
191
192/// Encode one block (table + rANS payload + raw bits) into `w`.
193fn encode_single(w: &mut ByteWriter, vals: &[i64]) {
194    let freq = normalize(&token_counts(vals.iter()));
195    write_table(w, &freq);
196    let model = Model::new(freq).expect("normalised table");
197    let (rans, raw) = encode_with(vals, &model);
198    w.block(&rans);
199    w.block(&raw);
200}
201
202/// Entropy-code a list of integer blocks whose lengths the decoder already
203/// knows. The encoder tries one shared table vs one table per block and
204/// keeps the smaller (1 flag byte). Empty blocks cost nothing.
205pub fn encode_blocks(w: &mut ByteWriter, blocks: &[&[i64]]) {
206    let total: usize = blocks.iter().map(|b| b.len()).sum();
207    if total == 0 {
208        return;
209    }
210    // Shared table.
211    let mut shared = ByteWriter::new();
212    {
213        let all: Vec<i64> = blocks.iter().flat_map(|b| b.iter().copied()).collect();
214        encode_single(&mut shared, &all);
215    }
216    // Per-block tables.
217    let mut per = ByteWriter::new();
218    for b in blocks {
219        if !b.is_empty() {
220            encode_single(&mut per, b);
221        }
222    }
223    if shared.buf.len() <= per.buf.len() {
224        w.u8(0);
225        w.bytes(&shared.buf);
226    } else {
227        w.u8(1);
228        w.bytes(&per.buf);
229    }
230}
231
232fn decode_single(r: &mut ByteReader, n: usize) -> Result<Vec<i64>, DecodeError> {
233    let model = Model::new(read_table(r)?)?;
234    let rans = r.block()?;
235    let raw = r.block()?;
236    decode_with(n, &model, rans, raw)
237}
238
239/// Inverse of [`encode_blocks`].
240pub fn decode_blocks(r: &mut ByteReader, lens: &[usize]) -> Result<Vec<Vec<i64>>, DecodeError> {
241    let total: usize = lens.iter().sum();
242    if total == 0 {
243        return Ok(lens.iter().map(|_| Vec::new()).collect());
244    }
245    match r.u8()? {
246        0 => {
247            let all = decode_single(r, total)?;
248            let mut out = Vec::with_capacity(lens.len());
249            let mut off = 0;
250            for &l in lens {
251                out.push(all[off..off + l].to_vec());
252                off += l;
253            }
254            Ok(out)
255        }
256        1 => lens
257            .iter()
258            .map(|&l| {
259                if l == 0 {
260                    Ok(Vec::new())
261                } else {
262                    decode_single(r, l)
263                }
264            })
265            .collect(),
266        _ => Err(DecodeError::Invalid("table mode")),
267    }
268}
269
270/// Zeroth-order empirical entropy of the token stream plus raw bits, in
271/// bits — the floor a static per-block coder can approach (ignores tables).
272pub fn empirical_bits(vals: &[i64]) -> f64 {
273    let counts = token_counts(vals.iter());
274    let n = vals.len() as f64;
275    let mut h = 0.0;
276    for &c in &counts {
277        if c > 0 {
278            let p = c as f64 / n;
279            h -= c as f64 * p.log2();
280        }
281    }
282    let raw: u64 = vals.iter().map(|&v| split_uint(zigzag(v)).2 as u64).sum();
283    h + raw as f64
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    fn lcg(seed: &mut u64) -> u64 {
291        *seed = seed
292            .wrapping_mul(6364136223846793005)
293            .wrapping_add(1442695040888963407);
294        *seed >> 33
295    }
296
297    #[test]
298    fn token_split_is_invertible() {
299        for u in (0..5000u64).chain([1 << 20, (1 << 40) + 12345, u32::MAX as u64]) {
300            let (t, e, n) = split_uint(u);
301            let (base, nb) = token_base(t);
302            assert_eq!(nb, n);
303            assert_eq!(base + e, u, "u={u}");
304        }
305    }
306
307    #[test]
308    fn rans_roundtrip_and_near_entropy() {
309        let mut seed = 7u64;
310        // Laplacian-ish integers.
311        let a: Vec<i64> = (0..20000)
312            .map(|_| {
313                let r = lcg(&mut seed) % 1000;
314                let mag = (-(1.0 - r as f64 / 1000.0).ln() * 3.0) as i64;
315                if lcg(&mut seed) & 1 == 0 {
316                    mag
317                } else {
318                    -mag
319                }
320            })
321            .collect();
322        let b: Vec<i64> = vec![5; 300];
323        let c: Vec<i64> = (0..500).map(|i| (i * i) as i64 - 100_000).collect();
324        let mut w = ByteWriter::new();
325        encode_blocks(&mut w, &[&a, &b, &[], &c]);
326        let mut r = ByteReader::new(&w.buf);
327        let out = decode_blocks(&mut r, &[a.len(), b.len(), 0, c.len()]).unwrap();
328        assert_eq!(out[0], a);
329        assert_eq!(out[1], b);
330        assert!(out[2].is_empty());
331        assert_eq!(out[3], c);
332        assert_eq!(r.position(), w.buf.len());
333        // Coding overhead vs zeroth-order entropy stays small for a big block.
334        let mut wa = ByteWriter::new();
335        encode_blocks(&mut wa, &[&a]);
336        let ideal = empirical_bits(&a) / 8.0;
337        let actual = wa.buf.len() as f64;
338        assert!(
339            actual < ideal * 1.02 + 64.0,
340            "actual {actual} ideal {ideal}"
341        );
342    }
343
344    #[test]
345    fn rans_is_deterministic() {
346        let v: Vec<i64> = (0..1000).map(|i| (i % 17) - 8).collect();
347        let mut w1 = ByteWriter::new();
348        let mut w2 = ByteWriter::new();
349        encode_blocks(&mut w1, &[&v]);
350        encode_blocks(&mut w2, &[&v]);
351        assert_eq!(w1.buf, w2.buf);
352    }
353}