Skip to main content

dsi_bitstream/utils/
stats.rs

1/*
2 * SPDX-FileCopyrightText: 2023 Inria
3 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4 * SPDX-FileCopyrightText: 2024 Tommaso Fontana
5 *
6 * SPDX-License-Identifier: Apache-2.0 OR MIT
7 */
8
9#[cfg(feature = "mem_dbg")]
10use mem_dbg::{MemDbg, MemSize};
11
12use crate::prelude::{
13    Codes, bit_len_vbyte, len_delta, len_exp_golomb, len_gamma, len_minimal_binary, len_omega,
14    len_pi, len_zeta,
15};
16use core::fmt::Debug;
17
18#[cfg(feature = "std")]
19use crate::dispatch::{CodesRead, CodesWrite};
20#[cfg(feature = "std")]
21use crate::prelude::Endianness;
22#[cfg(feature = "std")]
23use crate::prelude::{DynamicCodeRead, DynamicCodeWrite, StaticCodeRead, StaticCodeWrite};
24#[cfg(feature = "std")]
25use std::sync::Mutex;
26
27#[cfg(feature = "serde")]
28use alloc::string::ToString;
29#[cfg(feature = "alloc")]
30use alloc::{vec, vec::Vec};
31
32/// Keeps track of the space needed to store a stream of integers using
33/// different codes.
34///
35/// This structure can be used to determine empirically which code provides the
36/// best compression for a given stream. You have to [update the structure] with
37/// the integers in the stream; at any time, you can examine the statistics or
38/// call [`best_code`] to get the best code.
39///
40/// The structure keeps tracks of the codes for which the module [`code_consts`]
41/// provide constants.
42///
43/// [update the structure]: Self::update
44/// [`best_code`]: Self::best_code
45/// [`code_consts`]: crate::dispatch::code_consts
46#[derive(Debug, Copy, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "mem_dbg", derive(MemDbg, MemSize))]
48pub struct CodesStats<
49    // How many ζ codes to consider.
50    const ZETA: usize = 10,
51    // How many Golomb codes to consider.
52    const GOLOMB: usize = 10,
53    // How many Exponential Golomb codes to consider.
54    const EXP_GOLOMB: usize = 10,
55    // How many Rice codes to consider.
56    const RICE: usize = 10,
57    // How many streamlined π codes to consider.
58    const PI: usize = 10,
59> {
60    /// The total number of elements observed.
61    pub total: u64,
62    /// The total space used to store the elements if
63    /// they were stored using the unary code.
64    pub unary: u64,
65    /// The total space used to store the elements if
66    /// they were stored using the gamma code.
67    pub gamma: u64,
68    /// The total space used to store the elements if
69    /// they were stored using the delta code.
70    pub delta: u64,
71    /// The total space used to store the elements if
72    /// they were stored using the omega code.
73    pub omega: u64,
74    /// The total space used to store the elements if
75    /// they were stored using the variable byte code.
76    pub vbyte: u64,
77    /// The total space used to store the elements if they were stored using a
78    /// zeta code. `zeta[0]` represents ζ₁, `zeta[1]` represents ζ₂, and so on.
79    pub zeta: [u64; ZETA],
80    /// The total space used to store the elements if they were stored using a
81    /// Golomb code. `golomb[0]` represents the Golomb code with modulus 1,
82    /// `golomb[1]` represents the Golomb code with modulus 2, and so on.
83    pub golomb: [u64; GOLOMB],
84    /// The total space used to store the elements if they were stored using an
85    /// exponential Golomb code. `exp_golomb[0]` represents the exponential
86    /// Golomb code with parameter 0, `exp_golomb[1]` with parameter 1, and
87    /// so on.
88    pub exp_golomb: [u64; EXP_GOLOMB],
89    /// The total space used to store the elements if they were stored using a
90    /// Rice code. `rice[0]` represents the Rice code with log₂(*b*) = 0,
91    /// `rice[1]` with log₂(*b*) = 1, and so on.
92    pub rice: [u64; RICE],
93    /// The total space used to store the elements if they were stored using a
94    /// pi code. `pi[0]` represents π₂, `pi[1]` represents π₃, and so on.
95    pub pi: [u64; PI],
96}
97
98impl<
99    const ZETA: usize,
100    const GOLOMB: usize,
101    const EXP_GOLOMB: usize,
102    const RICE: usize,
103    const PI: usize,
104> core::default::Default for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
105{
106    fn default() -> Self {
107        Self {
108            total: 0,
109            unary: 0,
110            gamma: 0,
111            delta: 0,
112            omega: 0,
113            vbyte: 0,
114            zeta: [0; ZETA],
115            golomb: [0; GOLOMB],
116            exp_golomb: [0; EXP_GOLOMB],
117            rice: [0; RICE],
118            pi: [0; PI],
119        }
120    }
121}
122
123impl<
124    const ZETA: usize,
125    const GOLOMB: usize,
126    const EXP_GOLOMB: usize,
127    const RICE: usize,
128    const PI: usize,
129> CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
130{
131    /// Update the stats with the lengths of the codes for `n` and return
132    /// `n` for convenience.
133    pub fn update(&mut self, n: u64) -> u64 {
134        self.update_many(n, 1)
135    }
136
137    /// Update the stats with `count` occurrences of `n` and return `n` for convenience.
138    ///
139    /// # Panics
140    ///
141    /// If `n` is [`u64::MAX`]: the totals for every code are updated on each
142    /// call, and some code-length helpers (e.g. `len_gamma`) reject
143    /// [`u64::MAX`] because they compute `n + 1` -- even though other codes
144    /// (VByte, Golomb with b > 1, Rice/exp-Golomb with k > 0) could
145    /// represent it.
146    #[inline]
147    pub fn update_many(&mut self, n: u64, count: u64) -> u64 {
148        // No occurrences: record nothing.
149        if count == 0 {
150            return n;
151        }
152        self.total = self.total.saturating_add(count);
153
154        // Saturating arithmetic: an overflowing total means the code is
155        // impractically large for this data, so it saturates (and never wins
156        // best_code) instead of panicking (debug) or wrapping to a small,
157        // wrongly-winning value (release).
158        self.unary = self.unary.saturating_add((n + 1).saturating_mul(count));
159        self.gamma = self
160            .gamma
161            .saturating_add((len_gamma(n) as u64).saturating_mul(count));
162        self.delta = self
163            .delta
164            .saturating_add((len_delta(n) as u64).saturating_mul(count));
165        self.omega = self
166            .omega
167            .saturating_add((len_omega(n) as u64).saturating_mul(count));
168        self.vbyte = self
169            .vbyte
170            .saturating_add((bit_len_vbyte(n) as u64).saturating_mul(count));
171        for (k, val) in self.zeta.iter_mut().enumerate() {
172            *val = val.saturating_add((len_zeta(n, (k + 1) as _) as u64).saturating_mul(count));
173        }
174        for (b, val) in self.golomb.iter_mut().enumerate() {
175            // Length in u64 to avoid truncating n / b on 32-bit targets, where
176            // len_golomb's usize return can drop high bits.
177            let b = (b + 1) as u64;
178            let len = (n / b) + 1 + len_minimal_binary(n % b, b) as u64;
179            *val = val.saturating_add(len.saturating_mul(count));
180        }
181        for (k, val) in self.exp_golomb.iter_mut().enumerate() {
182            *val = val.saturating_add((len_exp_golomb(n, k as _) as u64).saturating_mul(count));
183        }
184        for (log2_b, val) in self.rice.iter_mut().enumerate() {
185            // Length in u64 to avoid truncating n >> log2_b on 32-bit targets.
186            let len = (n >> log2_b) + 1 + log2_b as u64;
187            *val = val.saturating_add(len.saturating_mul(count));
188        }
189        for (k, val) in self.pi.iter_mut().enumerate() {
190            *val = val.saturating_add((len_pi(n, (k + 2) as _) as u64).saturating_mul(count));
191        }
192        n
193    }
194
195    /// Combines additively this stats with another one.
196    pub fn add(&mut self, rhs: &Self) {
197        self.total = self.total.saturating_add(rhs.total);
198        self.unary = self.unary.saturating_add(rhs.unary);
199        self.gamma = self.gamma.saturating_add(rhs.gamma);
200        self.delta = self.delta.saturating_add(rhs.delta);
201        self.omega = self.omega.saturating_add(rhs.omega);
202        self.vbyte = self.vbyte.saturating_add(rhs.vbyte);
203        for (a, b) in self.zeta.iter_mut().zip(rhs.zeta.iter()) {
204            *a = a.saturating_add(*b);
205        }
206        for (a, b) in self.golomb.iter_mut().zip(rhs.golomb.iter()) {
207            *a = a.saturating_add(*b);
208        }
209        for (a, b) in self.exp_golomb.iter_mut().zip(rhs.exp_golomb.iter()) {
210            *a = a.saturating_add(*b);
211        }
212        for (a, b) in self.rice.iter_mut().zip(rhs.rice.iter()) {
213            *a = a.saturating_add(*b);
214        }
215        for (a, b) in self.pi.iter_mut().zip(rhs.pi.iter()) {
216            *a = a.saturating_add(*b);
217        }
218    }
219
220    /// Returns the best code for the stream and its space usage.
221    ///
222    /// When VByte is the best code, [`Codes::VByteBe`] is returned as the
223    /// canonical representative (both variants have the same bit length).
224    #[must_use]
225    pub fn best_code(&self) -> (Codes, u64) {
226        let mut best = (Codes::Unary, self.unary);
227        if self.gamma < best.1 {
228            best = (Codes::Gamma, self.gamma);
229        }
230        if self.delta < best.1 {
231            best = (Codes::Delta, self.delta);
232        }
233        if self.omega < best.1 {
234            best = (Codes::Omega, self.omega);
235        }
236        if self.vbyte < best.1 {
237            best = (Codes::VByteBe, self.vbyte);
238        }
239        for (k, val) in self.zeta.iter().enumerate() {
240            if *val < best.1 {
241                best = (Codes::Zeta((k + 1) as _), *val);
242            }
243        }
244        for (b, val) in self.golomb.iter().enumerate() {
245            if *val < best.1 {
246                best = (Codes::Golomb((b + 1) as _), *val);
247            }
248        }
249        for (k, val) in self.exp_golomb.iter().enumerate() {
250            if *val < best.1 {
251                best = (Codes::ExpGolomb(k as _), *val);
252            }
253        }
254        for (log2_b, val) in self.rice.iter().enumerate() {
255            if *val < best.1 {
256                best = (Codes::Rice(log2_b as _), *val);
257            }
258        }
259        for (k, val) in self.pi.iter().enumerate() {
260            if *val < best.1 {
261                best = (Codes::Pi((k + 2) as _), *val);
262            }
263        }
264        best
265    }
266
267    /// Returns a vector of all codes and their space usage, in ascending order by space usage.
268    #[cfg(feature = "alloc")]
269    #[must_use]
270    pub fn get_codes(&self) -> Vec<(Codes, u64)> {
271        let mut codes = vec![
272            (Codes::Unary, self.unary),
273            (Codes::Gamma, self.gamma),
274            (Codes::Delta, self.delta),
275            (Codes::Omega, self.omega),
276            (Codes::VByteBe, self.vbyte),
277        ];
278        for (k, val) in self.zeta.iter().enumerate() {
279            codes.push((Codes::Zeta((k + 1) as _), *val));
280        }
281        for (b, val) in self.golomb.iter().enumerate() {
282            codes.push((Codes::Golomb((b + 1) as _), *val));
283        }
284        for (k, val) in self.exp_golomb.iter().enumerate() {
285            codes.push((Codes::ExpGolomb(k as _), *val));
286        }
287        for (log2_b, val) in self.rice.iter().enumerate() {
288            codes.push((Codes::Rice(log2_b as _), *val));
289        }
290        for (k, val) in self.pi.iter().enumerate() {
291            codes.push((Codes::Pi((k + 2) as _), *val));
292        }
293        // sort them by length
294        codes.sort_by_key(|&(_, len)| len);
295        codes
296    }
297
298    /// Returns the number of bits used by the given code.
299    #[must_use]
300    pub fn bits_for(&self, code: Codes) -> Option<u64> {
301        match code {
302            Codes::Unary => Some(self.unary),
303            Codes::Gamma => Some(self.gamma),
304            Codes::Delta => Some(self.delta),
305            Codes::Omega => Some(self.omega),
306            Codes::VByteBe | Codes::VByteLe => Some(self.vbyte),
307            Codes::Zeta(k) => self.zeta.get(k.checked_sub(1)?).copied(),
308            Codes::Golomb(b) => self.golomb.get(b.checked_sub(1)? as usize).copied(),
309            Codes::ExpGolomb(k) => self.exp_golomb.get(k).copied(),
310            Codes::Rice(log2_b) => self.rice.get(log2_b).copied(),
311            Codes::Pi(k) => self.pi.get(k.checked_sub(2)?).copied(),
312        }
313    }
314}
315
316impl<
317    const ZETA: usize,
318    const GOLOMB: usize,
319    const EXP_GOLOMB: usize,
320    const RICE: usize,
321    const PI: usize,
322> core::ops::AddAssign for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
323{
324    /// Combines additively this stats with another one.
325    fn add_assign(&mut self, rhs: Self) {
326        self.add(&rhs);
327    }
328}
329
330impl<
331    const ZETA: usize,
332    const GOLOMB: usize,
333    const EXP_GOLOMB: usize,
334    const RICE: usize,
335    const PI: usize,
336> core::ops::Add for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
337{
338    type Output = Self;
339
340    /// Combines additively this stats with another one, creating a new one.
341    fn add(self, rhs: Self) -> Self {
342        let mut res = self;
343        res += rhs;
344        res
345    }
346}
347
348/// Allows calling `.sum()` on an iterator of [`CodesStats`].
349impl<
350    const ZETA: usize,
351    const GOLOMB: usize,
352    const EXP_GOLOMB: usize,
353    const RICE: usize,
354    const PI: usize,
355> core::iter::Sum for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
356{
357    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
358        iter.fold(Self::default(), |a, b| a + b)
359    }
360}
361
362#[cfg(feature = "serde")]
363impl<
364    const ZETA: usize,
365    const GOLOMB: usize,
366    const EXP_GOLOMB: usize,
367    const RICE: usize,
368    const PI: usize,
369> serde::Serialize for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
370{
371    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
372        use serde::ser::SerializeStruct;
373
374        let mut state = serializer.serialize_struct("CodesStats", 11)?;
375        state.serialize_field("total", &self.total)?;
376        state.serialize_field("unary", &self.unary)?;
377        state.serialize_field("gamma", &self.gamma)?;
378        state.serialize_field("delta", &self.delta)?;
379        state.serialize_field("omega", &self.omega)?;
380        state.serialize_field("vbyte", &self.vbyte)?;
381        // these are array which don't play well with serde, so we convert them to slices
382        state.serialize_field("zeta", &self.zeta.as_slice())?;
383        state.serialize_field("golomb", &self.golomb.as_slice())?;
384        state.serialize_field("exp_golomb", &self.exp_golomb.as_slice())?;
385        state.serialize_field("rice", &self.rice.as_slice())?;
386        state.serialize_field("pi", &self.pi.as_slice())?;
387        state.end()
388    }
389}
390
391#[cfg(feature = "serde")]
392impl<
393    'de,
394    const ZETA: usize,
395    const GOLOMB: usize,
396    const EXP_GOLOMB: usize,
397    const RICE: usize,
398    const PI: usize,
399> serde::Deserialize<'de> for CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
400{
401    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
402        use serde::de::{MapAccess, Visitor};
403
404        struct CodesStatsVisitor<
405            const ZETA: usize,
406            const GOLOMB: usize,
407            const EXP_GOLOMB: usize,
408            const RICE: usize,
409            const PI: usize,
410        >;
411
412        impl<
413            'de,
414            const ZETA: usize,
415            const GOLOMB: usize,
416            const EXP_GOLOMB: usize,
417            const RICE: usize,
418            const PI: usize,
419        > Visitor<'de> for CodesStatsVisitor<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
420        {
421            type Value = CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>;
422
423            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
424                formatter.write_str("struct CodesStats")
425            }
426
427            fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
428                let mut total = None;
429                let mut unary = None;
430                let mut gamma = None;
431                let mut delta = None;
432                let mut omega = None;
433                let mut vbyte = None;
434                let mut zeta: Option<[u64; ZETA]> = None;
435                let mut golomb: Option<[u64; GOLOMB]> = None;
436                let mut exp_golomb: Option<[u64; EXP_GOLOMB]> = None;
437                let mut rice: Option<[u64; RICE]> = None;
438                let mut pi: Option<[u64; PI]> = None;
439
440                // Helper to deserialize a Vec<u64> into a fixed-size array
441                fn vec_to_array<E: serde::de::Error, const N: usize>(
442                    v: Vec<u64>,
443                ) -> Result<[u64; N], E> {
444                    v.try_into().map_err(|v: Vec<u64>| {
445                        serde::de::Error::invalid_length(v.len(), &N.to_string().as_str())
446                    })
447                }
448
449                while let Some(key) = map.next_key::<&str>()? {
450                    match key {
451                        "total" => total = Some(map.next_value()?),
452                        "unary" => unary = Some(map.next_value()?),
453                        "gamma" => gamma = Some(map.next_value()?),
454                        "delta" => delta = Some(map.next_value()?),
455                        "omega" => omega = Some(map.next_value()?),
456                        "vbyte" => vbyte = Some(map.next_value()?),
457                        "zeta" => zeta = Some(vec_to_array(map.next_value()?)?),
458                        "golomb" => golomb = Some(vec_to_array(map.next_value()?)?),
459                        "exp_golomb" => exp_golomb = Some(vec_to_array(map.next_value()?)?),
460                        "rice" => rice = Some(vec_to_array(map.next_value()?)?),
461                        "pi" => pi = Some(vec_to_array(map.next_value()?)?),
462                        _ => {
463                            let _ = map.next_value::<serde::de::IgnoredAny>()?;
464                        }
465                    }
466                }
467
468                Ok(CodesStats {
469                    total: total.unwrap_or_default(),
470                    unary: unary.unwrap_or_default(),
471                    gamma: gamma.unwrap_or_default(),
472                    delta: delta.unwrap_or_default(),
473                    omega: omega.unwrap_or_default(),
474                    vbyte: vbyte.unwrap_or_default(),
475                    zeta: zeta.unwrap_or([0; ZETA]),
476                    golomb: golomb.unwrap_or([0; GOLOMB]),
477                    exp_golomb: exp_golomb.unwrap_or([0; EXP_GOLOMB]),
478                    rice: rice.unwrap_or([0; RICE]),
479                    pi: pi.unwrap_or([0; PI]),
480                })
481            }
482        }
483
484        deserializer.deserialize_struct(
485            "CodesStats",
486            &[
487                "total",
488                "unary",
489                "gamma",
490                "delta",
491                "omega",
492                "vbyte",
493                "zeta",
494                "golomb",
495                "exp_golomb",
496                "rice",
497                "pi",
498            ],
499            CodesStatsVisitor::<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>,
500        )
501    }
502}
503
504/// A struct that can wrap a [`DynamicCodeRead`], [`DynamicCodeWrite`],
505/// [`StaticCodeRead`], or [`StaticCodeWrite`] and compute [`CodesStats`]
506/// for a given stream.
507#[derive(Debug)]
508#[cfg_attr(feature = "mem_dbg", derive(MemDbg, MemSize))]
509#[cfg(feature = "std")]
510pub struct CodesStatsWrapper<
511    W,
512    // How many ζ codes to consider.
513    const ZETA: usize = 10,
514    // How many Golomb codes to consider.
515    const GOLOMB: usize = 10,
516    // How many Exponential Golomb codes to consider.
517    const EXP_GOLOMB: usize = 10,
518    // How many Rice codes to consider.
519    const RICE: usize = 10,
520    // How many streamlined π codes to consider.
521    const PI: usize = 10,
522> {
523    // TODO: figure out how we can do this without a lock.
524    // This is needed because the [`DynamicCodeRead`] and [`DynamicCodeWrite`] traits must have
525    // &self and not &mut self.
526    stats: Mutex<CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>>,
527    wrapped: W,
528}
529
530#[cfg(feature = "std")]
531impl<
532    W,
533    const ZETA: usize,
534    const GOLOMB: usize,
535    const EXP_GOLOMB: usize,
536    const RICE: usize,
537    const PI: usize,
538> CodesStatsWrapper<W, ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
539{
540    /// Creates a new `CodesStatsWrapper` with the given wrapped value.
541    #[must_use]
542    pub fn new(wrapped: W) -> Self {
543        Self {
544            stats: Mutex::new(CodesStats::default()),
545            wrapped,
546        }
547    }
548
549    /// Returns a reference to the stats.
550    pub const fn stats(&self) -> &Mutex<CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>> {
551        &self.stats
552    }
553
554    /// Consumes the wrapper and returns the inner wrapped value and the stats.
555    #[must_use]
556    pub fn into_inner(self) -> (W, CodesStats<ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>) {
557        (
558            self.wrapped,
559            self.stats.into_inner().expect("mutex is not poisoned"),
560        )
561    }
562}
563
564#[cfg(feature = "std")]
565impl<
566    W: DynamicCodeRead,
567    const ZETA: usize,
568    const GOLOMB: usize,
569    const EXP_GOLOMB: usize,
570    const RICE: usize,
571    const PI: usize,
572> DynamicCodeRead for CodesStatsWrapper<W, ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
573{
574    #[inline]
575    fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
576        &self,
577        reader: &mut CR,
578    ) -> Result<u64, CR::Error> {
579        let res = self.wrapped.read(reader)?;
580        self.stats.lock().unwrap().update(res);
581        Ok(res)
582    }
583}
584
585#[cfg(feature = "std")]
586impl<
587    W: StaticCodeRead<E, CR>,
588    const ZETA: usize,
589    const GOLOMB: usize,
590    const EXP_GOLOMB: usize,
591    const RICE: usize,
592    const PI: usize,
593    E: Endianness,
594    CR: CodesRead<E> + ?Sized,
595> StaticCodeRead<E, CR> for CodesStatsWrapper<W, ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
596{
597    #[inline]
598    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
599        let res = self.wrapped.read(reader)?;
600        self.stats.lock().unwrap().update(res);
601        Ok(res)
602    }
603}
604
605#[cfg(feature = "std")]
606impl<
607    W: DynamicCodeWrite,
608    const ZETA: usize,
609    const GOLOMB: usize,
610    const EXP_GOLOMB: usize,
611    const RICE: usize,
612    const PI: usize,
613> DynamicCodeWrite for CodesStatsWrapper<W, ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
614{
615    #[inline]
616    fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
617        &self,
618        writer: &mut CW,
619        n: u64,
620    ) -> Result<usize, CW::Error> {
621        let res = self.wrapped.write(writer, n)?;
622        self.stats.lock().unwrap().update(n);
623        Ok(res)
624    }
625}
626
627#[cfg(feature = "std")]
628impl<
629    W: StaticCodeWrite<E, CW>,
630    const ZETA: usize,
631    const GOLOMB: usize,
632    const EXP_GOLOMB: usize,
633    const RICE: usize,
634    const PI: usize,
635    E: Endianness,
636    CW: CodesWrite<E> + ?Sized,
637> StaticCodeWrite<E, CW> for CodesStatsWrapper<W, ZETA, GOLOMB, EXP_GOLOMB, RICE, PI>
638{
639    #[inline]
640    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
641        let res = self.wrapped.write(writer, n)?;
642        self.stats.lock().unwrap().update(n);
643        Ok(res)
644    }
645}
646
647#[cfg(test)]
648#[cfg(feature = "serde")]
649mod serde_tests {
650    use super::*;
651
652    #[test]
653    fn test_serde_code_stats() -> serde_json::Result<()> {
654        let mut stats: CodesStats = CodesStats::default();
655        for i in 0..100 {
656            stats.update(i);
657        }
658        let json = serde_json::to_string(&stats)?;
659        let deserialized: CodesStats = serde_json::from_str(&json)?;
660        assert_eq!(stats, deserialized);
661        Ok(())
662    }
663
664    #[test]
665    fn test_roundtrip_different_sizes() -> serde_json::Result<()> {
666        let mut stats: CodesStats<10, 20, 5, 8, 6> = CodesStats::default();
667        for i in 0..1000 {
668            stats.update(i);
669        }
670        let json = serde_json::to_string_pretty(&stats)?;
671        let deserialized: CodesStats<10, 20, 5, 8, 6> = serde_json::from_str(&json)?;
672        assert_eq!(stats, deserialized);
673        Ok(())
674    }
675
676    #[test]
677    #[should_panic]
678    fn test_mismatched_sizes() {
679        let mut stats: CodesStats<10, 20, 5, 8, 6> = CodesStats::default();
680        for i in 0..1000 {
681            stats.update(i);
682        }
683        let json = serde_json::to_string_pretty(&stats).unwrap();
684        // This should panic because the JSON has 20 golomb values but we expect 21
685        let _deserialized: CodesStats<10, 21, 5, 8, 6> = serde_json::from_str(&json).unwrap();
686    }
687}
688
689#[cfg(test)]
690mod robustness_tests {
691    use super::*;
692
693    #[test]
694    #[should_panic]
695    fn u64_max_panics_as_documented() {
696        let mut stats: CodesStats = CodesStats::default();
697        // update evaluates every code's length helper, and helpers such as
698        // len_gamma reject u64::MAX (they compute n + 1); the documented
699        // contract is a panic.
700        stats.update(u64::MAX);
701    }
702
703    #[test]
704    fn test_count_zero_is_a_noop() {
705        let mut stats: CodesStats = CodesStats::default();
706        let before = stats;
707        stats.update_many(u64::MAX, 0);
708        assert_eq!(
709            stats, before,
710            "update_many with count 0 must change nothing"
711        );
712    }
713
714    #[test]
715    fn test_large_count_saturates_without_panic() {
716        let mut stats: CodesStats = CodesStats::default();
717        // (n + 1) * count overflows u64; it must saturate rather than panic
718        // (debug) or wrap to a small, wrongly-winning value (release).
719        stats.update_many(1 << 40, 1 << 30);
720        let (_, bits) = stats.best_code();
721        // A finite-length code (e.g. gamma) still wins; unary saturated.
722        assert!(bits < u64::MAX);
723    }
724}