Skip to main content

msrtc_rans_core/
raw.rs

1// Licensed under the MIT license.
2// Author: Riaan de Beer - github.com/infinityabundance - rdebeer.infinityabundance@gmail.com
3
4//! Raw rANS encoder/decoder implementations for both RansByte and Rans64 variants.
5//!
6//! The code is generated via a macro to avoid duplication while matching the
7//! C++ template structure exactly.
8//!
9//! # Scale-bits edge cases
10//!
11//! When `scale_bits == 32` (the maximum for Rans64), the expression `1u32 << scale_bits`
12//! overflows in Rust (and is undefined behavior in C++). The upstream C++ code also
13//! enters indefinite shift territory here. This implementation defines the behavior
14//! deterministically: `scale_bits == 32` is **rejected** for the prepared-symbol path
15//! where it would cause overflow, matching an intentional safety divergence.
16//! See residual `MSRTC.RAW.SCALE32`.
17
18use crate::Freq;
19use crate::arithmetic;
20#[allow(unused_imports)]
21use crate::sink::Sink;
22#[allow(unused_imports)]
23use crate::source::Source;
24
25// ###########################################################################
26// Macro: generate_rans_impl
27// ###########################################################################
28// Generates encoder, decoder, and symbol types for a given state/unit pair.
29
30macro_rules! generate_rans_impl {
31    (
32        $enc_symbol:ident,   // EncSymbol type name
33        $dec_symbol:ident,   // DecSymbol type name
34        $encoder:ident,      // Encoder type name
35        $decoder:ident,      // Decoder type name
36        $state_ty:ty,        // State type (u32, u64)
37        $unit_ty:ty,         // Unit type (u8, u32)
38        $state_bits:expr,    // STATE_BITS constant
39        $max_scale_bits:expr,// MAX_SCALE_BITS constant
40        $lower_bound:expr,   // LOWER_BOUND constant
41        $unit_bits:expr,     // UNIT_BITS constant
42        $units_per_state:expr, // UNITS_PER_STATE constant
43    ) => {
44        /// Prepared encoder symbol.
45        #[derive(Debug, Clone, Copy)]
46        pub struct $enc_symbol {
47            pub x_max_hi: Freq,
48            pub freq_rcp_shift: u32,
49            pub freq_rcp: $state_ty,
50            pub freq_cmpl: Freq,
51            pub bias: Freq,
52        }
53
54        impl $enc_symbol {
55            /// Create a new prepared encoder symbol.
56            #[inline]
57            pub fn new(start: Freq, freq: Freq, scale_bits: Freq) -> Self {
58                let scale = 1u32 << scale_bits;
59                debug_assert!(
60                    0 < scale_bits && scale_bits as u32 <= $max_scale_bits,
61                    "invalid scale_bits"
62                );
63                debug_assert!(start < scale, "start out of range");
64                debug_assert!(freq > 0 && freq <= scale - start, "freq out of range");
65
66                let min_bits =
67                    core::cmp::min($state_bits, (core::mem::size_of::<Freq>() * 8 - 1) as u32);
68                let x_max_hi = freq << (min_bits - scale_bits);
69
70                let (freq_rcp, mut freq_rcp_shift, bias) = if freq > 1 {
71                    let shift = arithmetic::reciprocal_shift(freq);
72                    let rcp: $state_ty = if core::mem::size_of::<$state_ty>() >= 8 {
73                        arithmetic::compute_reciprocal_u64(freq) as $state_ty
74                    } else {
75                        arithmetic::compute_reciprocal_u32(freq) as $state_ty
76                    };
77                    (rcp, shift - 1, start)
78                } else {
79                    let rcp: $state_ty = if core::mem::size_of::<$state_ty>() >= 8 {
80                        !0u64 as $state_ty
81                    } else {
82                        !0u32 as $state_ty
83                    };
84                    let bias_adj = start + scale - 1;
85                    (rcp, 0, bias_adj)
86                };
87
88                if core::mem::size_of::<$state_ty>() < 8 {
89                    freq_rcp_shift += (core::mem::size_of::<$state_ty>() * 8) as u32;
90                }
91
92                let freq_cmpl = scale - freq;
93
94                Self {
95                    x_max_hi,
96                    freq_rcp_shift,
97                    freq_rcp,
98                    freq_cmpl,
99                    bias,
100                }
101            }
102
103            /// Create a new prepared encoder symbol with bounds checking.
104            ///
105            /// Returns `Err(RawRansError::InvalidScaleBits)` if `scale_bits` is outside
106            /// the safe range `[2, min($max_scale_bits, 31)]`. The value 32 is rejected
107            /// because `1u32 << 32` overflows (a defined Rust panic and C++ UB).
108            #[inline]
109            pub fn try_new(
110                start: Freq,
111                freq: Freq,
112                scale_bits: Freq,
113            ) -> core::result::Result<Self, crate::error::RawRansError> {
114                // Bounds check BEFORE computing `1u32 << scale_bits` (which would
115                // overflow at scale_bits=32)
116                if scale_bits < 2 || scale_bits > $max_scale_bits || scale_bits >= 32 {
117                    return Err(crate::error::RawRansError::InvalidScaleBits {
118                        provided: scale_bits as u32,
119                        max_safe: core::cmp::min($max_scale_bits, 31),
120                    });
121                }
122                let scale = 1u32 << scale_bits;
123                if start >= scale {
124                    return Err(crate::error::RawRansError::InvalidParameters);
125                }
126                if freq == 0 || freq > scale - start {
127                    return Err(crate::error::RawRansError::InvalidParameters);
128                }
129
130                let min_bits =
131                    core::cmp::min($state_bits, (core::mem::size_of::<Freq>() * 8 - 1) as u32);
132                let x_max_hi = freq << (min_bits - scale_bits);
133
134                let (freq_rcp, mut freq_rcp_shift, bias) = if freq > 1 {
135                    let shift = arithmetic::reciprocal_shift(freq);
136                    let rcp: $state_ty = if core::mem::size_of::<$state_ty>() >= 8 {
137                        arithmetic::compute_reciprocal_u64(freq) as $state_ty
138                    } else {
139                        arithmetic::compute_reciprocal_u32(freq) as $state_ty
140                    };
141                    (rcp, shift - 1, start)
142                } else {
143                    let rcp: $state_ty = if core::mem::size_of::<$state_ty>() >= 8 {
144                        !0u64 as $state_ty
145                    } else {
146                        !0u32 as $state_ty
147                    };
148                    let bias_adj = start + scale - 1;
149                    (rcp, 0, bias_adj)
150                };
151
152                if core::mem::size_of::<$state_ty>() < 8 {
153                    freq_rcp_shift += (core::mem::size_of::<$state_ty>() * 8) as u32;
154                }
155
156                let freq_cmpl = scale - freq;
157
158                Ok(Self {
159                    x_max_hi,
160                    freq_rcp_shift,
161                    freq_rcp,
162                    freq_cmpl,
163                    bias,
164                })
165            }
166
167            /// Compute quotient: fast division using precomputed reciprocal.
168            #[inline]
169            pub fn quotient(&self, x: $state_ty) -> $state_ty {
170                if core::mem::size_of::<$state_ty>() >= 8 {
171                    let x_u64 = x as u64;
172                    let rcp_u64 = self.freq_rcp as u64;
173                    arithmetic::fast_quotient_u64(x_u64, rcp_u64, self.freq_rcp_shift) as $state_ty
174                } else {
175                    let x_u32 = x as u32;
176                    let rcp_u32 = self.freq_rcp as u32;
177                    arithmetic::fast_quotient_u32(x_u32, rcp_u32, self.freq_rcp_shift) as $state_ty
178                }
179            }
180        }
181
182        /// Prepared decoder symbol.
183        #[derive(Debug, Clone, Copy)]
184        pub struct $dec_symbol {
185            pub freq: Freq,
186            pub start: Freq,
187        }
188
189        impl $dec_symbol {
190            /// Create a new decoder symbol.
191            #[inline]
192            pub fn new(start: Freq, freq: Freq) -> Self {
193                debug_assert!(freq > 0, "frequency must be positive");
194                Self { freq, start }
195            }
196        }
197
198        /// Raw rANS encoder.
199        #[derive(Debug)]
200        pub struct $encoder<Sk: Sink<$unit_ty>> {
201            sink: Sk,
202            state: $state_ty,
203        }
204
205        impl<Sk: Sink<$unit_ty>> $encoder<Sk> {
206            #[inline]
207            pub fn new(sink: Sk) -> Self {
208                Self {
209                    sink,
210                    state: $lower_bound,
211                }
212            }
213
214            #[inline]
215            pub fn sink(&self) -> &Sk {
216                &self.sink
217            }
218            #[inline]
219            pub fn sink_mut(&mut self) -> &mut Sk {
220                &mut self.sink
221            }
222            #[inline]
223            pub fn into_sink(self) -> Sk {
224                self.sink
225            }
226            #[inline]
227            pub fn state(&self) -> $state_ty {
228                self.state
229            }
230            #[inline]
231            pub fn reset(&mut self) {
232                self.state = $lower_bound;
233            }
234
235            /// Put a raw symbol (start, freq, scale_bits) using division.
236            ///
237            /// Panics if `scale_bits >= 32` (overflow at `1u32 << scale_bits`).
238            /// Use `try_put_raw` for a `Result`-returning checked variant.
239            #[inline]
240            pub fn put_raw(&mut self, start: Freq, freq: Freq, scale_bits: Freq) {
241                assert!(
242                    scale_bits < 32,
243                    "scale_bits={} causes overflow at 1u32<<scale_bits (use try_put_raw)",
244                    scale_bits
245                );
246                self.put_raw_unchecked(start, freq, scale_bits);
247            }
248
249            /// Internal unchecked raw put — caller must ensure `scale_bits < 32`.
250            #[inline]
251            pub(crate) fn put_raw_unchecked(&mut self, start: Freq, freq: Freq, scale_bits: Freq) {
252                debug_assert!(
253                    0 < scale_bits && scale_bits as u32 <= $max_scale_bits,
254                    "invalid scale_bits"
255                );
256                debug_assert!(0 < scale_bits && scale_bits as u32 <= $max_scale_bits);
257                debug_assert!(start < (1u32 << scale_bits));
258                debug_assert!(freq > 0 && freq <= (1u32 << scale_bits) - start);
259
260                let x_max: $state_ty = (freq as $state_ty) << ($state_bits - scale_bits as u32);
261                let x = self.renormalize(x_max);
262
263                let shift = scale_bits as u32;
264                self.state = ((x / freq as $state_ty) << shift)
265                    + (start as $state_ty)
266                    + (x % freq as $state_ty);
267            }
268
269            /// Put a raw symbol with bounds checking on `scale_bits`.
270            ///
271            /// Returns `Err(RawRansError::InvalidScaleBits)` if `scale_bits` is
272            /// outside the safe range (see `$enc_symbol::try_new`).
273            #[inline]
274            pub fn try_put_raw(
275                &mut self,
276                start: Freq,
277                freq: Freq,
278                scale_bits: Freq,
279            ) -> core::result::Result<(), crate::error::RawRansError> {
280                // Bounds check BEFORE any shift that could overflow
281                if scale_bits < 2 || scale_bits > $max_scale_bits || scale_bits >= 32 {
282                    return Err(crate::error::RawRansError::InvalidScaleBits {
283                        provided: scale_bits as u32,
284                        max_safe: core::cmp::min($max_scale_bits, 31),
285                    });
286                }
287                let scale = 1u32 << scale_bits;
288                if start >= scale {
289                    return Err(crate::error::RawRansError::InvalidParameters);
290                }
291                if freq == 0 || freq > scale - start {
292                    return Err(crate::error::RawRansError::InvalidParameters);
293                }
294                self.put_raw_unchecked(start, freq, scale_bits);
295                Ok(())
296            }
297
298            /// Put a prepared symbol (fast reciprocal-multiply path).
299            #[inline]
300            pub fn put(&mut self, symbol: &$enc_symbol) {
301                let mut x_max = symbol.x_max_hi as $state_ty;
302                if $state_bits > (core::mem::size_of::<Freq>() * 8 - 1) as u32 {
303                    let shift =
304                        ($state_bits - (core::mem::size_of::<Freq>() * 8 - 1) as u32) as usize;
305                    x_max = x_max << shift;
306                }
307                let x = self.renormalize(x_max);
308
309                let q = symbol.quotient(x);
310                self.state = x + (q * symbol.freq_cmpl as $state_ty) + symbol.bias as $state_ty;
311            }
312
313            /// Flush the encoder state to the sink.
314            #[inline]
315            pub fn flush(&mut self) {
316                let x = self.state;
317                for i in (1..$units_per_state).rev() {
318                    let shift = i * $unit_bits as usize;
319                    self.sink.write((x >> shift) as $unit_ty);
320                }
321                self.sink.write(x as $unit_ty);
322            }
323
324            #[inline]
325            fn renormalize(&mut self, x_max: $state_ty) -> $state_ty {
326                let mut x = self.state;
327                while x >= x_max {
328                    self.sink.write(x as $unit_ty);
329                    x >>= $unit_bits;
330                    if $max_scale_bits <= $unit_bits {
331                        debug_assert!(x < x_max);
332                        break;
333                    }
334                }
335                x
336            }
337        }
338
339        /// Raw rANS decoder.
340        #[derive(Debug)]
341        pub struct $decoder<Sr: Source<$unit_ty>> {
342            source: Sr,
343            state: $state_ty,
344        }
345
346        impl<Sr: Source<$unit_ty>> $decoder<Sr> {
347            #[inline]
348            pub fn new(source: Sr) -> Self {
349                Self {
350                    source,
351                    state: $lower_bound,
352                }
353            }
354
355            #[inline]
356            pub fn source(&self) -> &Sr {
357                &self.source
358            }
359            #[inline]
360            pub fn source_mut(&mut self) -> &mut Sr {
361                &mut self.source
362            }
363            #[inline]
364            pub fn into_source(self) -> Sr {
365                self.source
366            }
367            #[inline]
368            pub fn state(&self) -> $state_ty {
369                self.state
370            }
371            #[inline]
372            pub fn check_eof(&self) -> bool {
373                self.state == $lower_bound
374            }
375
376            /// Initialize the decoder by reading the initial state from the source.
377            #[inline]
378            pub fn init(&mut self) -> bool {
379                let mut unit = <$unit_ty>::default();
380                if !self.source.read(&mut unit) {
381                    return false;
382                }
383                let mut x = unit as $state_ty;
384
385                for i in 1..$units_per_state {
386                    if !self.source.read(&mut unit) {
387                        return false;
388                    }
389                    x = x.wrapping_add((unit as $state_ty) << (i * $unit_bits as usize));
390                }
391
392                if x < $lower_bound {
393                    return false;
394                }
395                self.state = x;
396                true
397            }
398
399            /// Get the next symbol frequency (low `scale_bits` of state).
400            ///
401            /// Panics if `scale_bits >= 32` (overflow at `1u32 << scale_bits`).
402            /// Use `try_get` for a `Result`-returning checked variant.
403            #[inline]
404            pub fn get(&self, scale_bits: Freq) -> Freq {
405                assert!(
406                    scale_bits < 32,
407                    "scale_bits={} causes overflow (use try_get)",
408                    scale_bits
409                );
410                self.get_unchecked(scale_bits)
411            }
412
413            /// Get the next symbol frequency, unchecked.
414            /// Caller must ensure `scale_bits < 32`.
415            #[inline]
416            pub(crate) fn get_unchecked(&self, scale_bits: Freq) -> Freq {
417                let mask = (1u32 << scale_bits) - 1;
418                (self.state as Freq) & mask
419            }
420
421            /// Get the next symbol frequency with bounds checking.
422            #[inline]
423            pub fn try_get(
424                &self,
425                scale_bits: Freq,
426            ) -> core::result::Result<Freq, crate::error::RawRansError> {
427                if scale_bits < 2 || scale_bits > $max_scale_bits || scale_bits >= 32 {
428                    return Err(crate::error::RawRansError::InvalidScaleBits {
429                        provided: scale_bits as u32,
430                        max_safe: core::cmp::min($max_scale_bits, 31),
431                    });
432                }
433                Ok(self.get_unchecked(scale_bits))
434            }
435
436            /// Advance the decoder past a symbol (public checked wrapper).
437            ///
438            /// Uses transactional state: computes into a local variable and only
439            /// assigns to `self.state` after renormalization succeeds.
440            ///
441            /// Panics if `scale_bits >= 32` (overflow at `1u32 << scale_bits`).
442            /// Use `try_advance` for a `Result`-returning checked variant.
443            ///
444            /// Equivalent to: `RansDecoder::Advance(start, freq, scale_bits)`.
445            #[inline]
446            pub fn advance(&mut self, start: Freq, freq: Freq, scale_bits: Freq) -> bool {
447                assert!(
448                    scale_bits < 32,
449                    "scale_bits={} causes overflow (use try_advance)",
450                    scale_bits
451                );
452                self.advance_unchecked(start, freq, scale_bits)
453            }
454
455            /// Internal unchecked advance — caller must ensure `scale_bits < 32`.
456            #[inline]
457            pub(crate) fn advance_unchecked(
458                &mut self,
459                start: Freq,
460                freq: Freq,
461                scale_bits: Freq,
462            ) -> bool {
463                let scale = 1u32 << scale_bits;
464                debug_assert!(start < scale);
465                debug_assert!(freq > 0 && freq <= scale - start);
466
467                let x = self.state;
468                let mask = (scale - 1) as $state_ty;
469                let value = x & mask;
470                debug_assert!(value >= start as $state_ty);
471
472                // Compute new state into a LOCAL — do not mutate self.state yet
473                let shift = scale_bits as u32;
474                let mut x_new = (freq as $state_ty) * (x >> shift) + (value - start as $state_ty);
475
476                // Renormalize using local state; reads source but does not commit
477                let mut renorm_unit = <$unit_ty>::default();
478                while x_new < $lower_bound {
479                    if !self.source.read(&mut renorm_unit) {
480                        return false;
481                    }
482                    x_new = (x_new << $unit_bits) + renorm_unit as $state_ty;
483                    if $max_scale_bits <= $unit_bits {
484                        debug_assert!(x_new >= $lower_bound);
485                        break;
486                    }
487                }
488
489                // All reads succeeded — commit the new state
490                self.state = x_new;
491                true
492            }
493
494            /// Advance the decoder with bounds checking on `scale_bits`.
495            #[inline]
496            pub fn try_advance(
497                &mut self,
498                start: Freq,
499                freq: Freq,
500                scale_bits: Freq,
501            ) -> core::result::Result<bool, crate::error::RawRansError> {
502                if scale_bits < 2 || scale_bits > $max_scale_bits || scale_bits >= 32 {
503                    return Err(crate::error::RawRansError::InvalidScaleBits {
504                        provided: scale_bits as u32,
505                        max_safe: core::cmp::min($max_scale_bits, 31),
506                    });
507                }
508                Ok(self.advance_unchecked(start, freq, scale_bits))
509            }
510
511            /// Advance using a prepared decoder symbol.
512            #[inline]
513            pub fn advance_symbol(&mut self, symbol: &$dec_symbol, scale_bits: Freq) -> bool {
514                self.advance(symbol.start, symbol.freq, scale_bits)
515            }
516        }
517    };
518}
519
520// ###########################################################################
521// Generate implementations for both variants
522// ###########################################################################
523
524generate_rans_impl! {
525    RansByteEncSymbol,    // enc symbol name
526    RansByteDecSymbol,    // dec symbol name
527    RansByteEncoder,      // encoder name
528    RansByteDecoder,      // decoder name
529    u32, u8,              // state, unit types
530    31,                   // STATE_BITS
531    30,                   // MAX_SCALE_BITS
532    1u32 << 23,           // LOWER_BOUND
533    8,                    // UNIT_BITS
534    4,                    // UNITS_PER_STATE
535}
536
537generate_rans_impl! {
538    Rans64EncSymbol,    // enc symbol name
539    Rans64DecSymbol,    // dec symbol name
540    Rans64Encoder,      // encoder name
541    Rans64Decoder,      // decoder name
542    u64, u32,           // state, unit types
543    63,                 // STATE_BITS
544    32,                 // MAX_SCALE_BITS
545    1u64 << 31,         // LOWER_BOUND
546    32,                 // UNIT_BITS
547    2,                  // UNITS_PER_STATE
548}
549
550// ###########################################################################
551// Tests
552// ###########################################################################
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557    use crate::sink::VecSink;
558    use crate::source::SliceSource;
559
560    // ---- RansByte tests ----
561
562    #[test]
563    fn test_ransbyte_initial_state() {
564        let sink = VecSink::<u8>::new(64);
565        let encoder = RansByteEncoder::new(sink);
566        assert_eq!(encoder.state(), 1u32 << 23);
567    }
568
569    #[test]
570    fn test_ransbyte_raw_roundtrip() {
571        let sink = VecSink::<u8>::new(64);
572        let mut encoder = RansByteEncoder::new(sink);
573        encoder.put_raw(0, 128, 8);
574        encoder.flush();
575        let encoded = encoder.into_sink().encoded().to_vec();
576        assert!(!encoded.is_empty());
577
578        let source = SliceSource::new(&encoded[..]);
579        let mut decoder = RansByteDecoder::new(source);
580        assert!(decoder.init());
581
582        let freq_val = decoder.get(8);
583        assert_eq!(freq_val, 0);
584        assert!(decoder.advance(0, 128, 8));
585        assert!(decoder.check_eof());
586    }
587
588    #[test]
589    fn test_ransbyte_prepared_symbol_matches_raw() {
590        let sink1 = VecSink::<u8>::new(64);
591        let mut enc1 = RansByteEncoder::new(sink1);
592        enc1.put_raw(0, 128, 8);
593        enc1.flush();
594        let out1 = enc1.into_sink().encoded().to_vec();
595
596        let sink2 = VecSink::<u8>::new(64);
597        let mut enc2 = RansByteEncoder::new(sink2);
598        let sym = RansByteEncSymbol::new(0, 128, 8);
599        enc2.put(&sym);
600        enc2.flush();
601        let out2 = enc2.into_sink().encoded().to_vec();
602
603        assert_eq!(out1, out2);
604    }
605
606    #[test]
607    fn test_ransbyte_decoder_rejects_empty() {
608        let data: [u8; 0] = [];
609        let source = SliceSource::new(&data[..]);
610        let mut decoder = RansByteDecoder::new(source);
611        assert!(!decoder.init());
612    }
613
614    #[test]
615    fn test_ransbyte_encoder_reset() {
616        let sink = VecSink::<u8>::new(64);
617        let mut encoder = RansByteEncoder::new(sink);
618        encoder.put_raw(0, 128, 8);
619        encoder.reset();
620        assert_eq!(encoder.state(), 1u32 << 23);
621    }
622
623    // ---- Rans64 tests ----
624
625    #[test]
626    fn test_rans64_initial_state() {
627        let sink = VecSink::<u32>::new(64);
628        let encoder = Rans64Encoder::new(sink);
629        assert_eq!(encoder.state(), 1u64 << 31);
630    }
631
632    #[test]
633    fn test_rans64_raw_roundtrip() {
634        let sink = VecSink::<u32>::new(64);
635        let mut encoder = Rans64Encoder::new(sink);
636        encoder.put_raw(0, 128, 8);
637        encoder.flush();
638        let encoded = encoder.into_sink().encoded().to_vec();
639        assert!(!encoded.is_empty());
640
641        let source = SliceSource::new(&encoded[..]);
642        let mut decoder = Rans64Decoder::new(source);
643        assert!(decoder.init());
644        assert!(decoder.advance(0, 128, 8));
645        assert!(decoder.check_eof());
646    }
647
648    #[test]
649    fn test_rans64_prepared_symbol_matches_raw() {
650        let sink1 = VecSink::<u32>::new(64);
651        let mut enc1 = Rans64Encoder::new(sink1);
652        enc1.put_raw(0, 128, 8);
653        enc1.flush();
654        let out1 = enc1.into_sink().encoded().to_vec();
655
656        let sink2 = VecSink::<u32>::new(64);
657        let mut enc2 = Rans64Encoder::new(sink2);
658        let sym = Rans64EncSymbol::new(0, 128, 8);
659        enc2.put(&sym);
660        enc2.flush();
661        let out2 = enc2.into_sink().encoded().to_vec();
662
663        assert_eq!(out1, out2);
664    }
665
666    #[test]
667    fn test_rans64_encoder_reset() {
668        let sink = VecSink::<u32>::new(64);
669        let mut encoder = Rans64Encoder::new(sink);
670        encoder.put_raw(0, 128, 8);
671        encoder.reset();
672        assert_eq!(encoder.state(), 1u64 << 31);
673    }
674
675    #[test]
676    fn test_scale_bits_32_rejected_for_try_new() {
677        let result = Rans64EncSymbol::try_new(0, 128, 32);
678        assert!(result.is_err(), "scale_bits=32 must be rejected");
679        if let Err(e) = result {
680            match e {
681                crate::error::RawRansError::InvalidScaleBits { provided, max_safe } => {
682                    assert_eq!(provided, 32);
683                    assert_eq!(max_safe, 31);
684                }
685                _ => panic!("wrong error type"),
686            }
687        }
688    }
689
690    #[test]
691    fn test_scale_bits_32_rejected_for_try_put_raw() {
692        let sink = VecSink::<u32>::new(64);
693        let mut encoder = Rans64Encoder::new(sink);
694        let result = encoder.try_put_raw(0, 128, 32);
695        assert!(result.is_err(), "scale_bits=32 must be rejected");
696    }
697
698    #[test]
699    fn test_scale_bits_31_accepted_for_rans64() {
700        let result = Rans64EncSymbol::try_new(0, 1, 31);
701        assert!(result.is_ok(), "scale_bits=31 must be accepted for Rans64");
702    }
703
704    #[test]
705    fn test_scale_bits_1_rejected() {
706        let result = RansByteEncSymbol::try_new(0, 128, 1);
707        assert!(
708            result.is_err(),
709            "scale_bits=1 must be rejected (minimum is 2)"
710        );
711    }
712
713    #[test]
714    fn test_try_new_rejects_freq_zero() {
715        let result = RansByteEncSymbol::try_new(0, 0, 8);
716        assert!(result.is_err(), "freq=0 must be rejected");
717    }
718
719    #[test]
720    fn test_try_new_accepts_valid_params() {
721        let result = RansByteEncSymbol::try_new(0, 128, 8);
722        assert!(result.is_ok(), "valid params must be accepted");
723    }
724}