Skip to main content

fixed_bigint/heapless/
bits.rs

1//! `bit_length` and `leading_zeros` for `HeaplessBigInt`.
2//!
3//! Both dispatch on `P` like `FixedUInt`: `Nct` scans MSB-to-LSB and
4//! stops at the highest non-zero limb; `Ct` scans the full width with an
5//! `undecided` lock so the loop is value-independent (mirroring
6//! `const_leading_zeros_ct`). The returned count is the magnitude either
7//! way — a caller that must keep the magnitude secret does not call these.
8//!
9//! `bit_length` is the position of the highest set bit plus one, so
10//! `bit_length(0) == 0` and `bit_length(1) == 1`. `leading_zeros` is
11//! taken against the value's **width** (`len * word_bits`), not capacity
12//! — so `bit_length + leading_zeros == bits_precision()` and `CAP` never
13//! enters. A caller that wants zeros relative to a wider window sizes it
14//! from that window's own `bits_precision`, not this value's capacity.
15
16use super::HeaplessBigInt;
17use crate::MachineWord;
18use const_num_traits::{Personality, PersonalityTag};
19
20impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
21    /// Number of bits needed to represent the value: `0` for zero,
22    /// otherwise the position of the highest set bit plus one.
23    pub fn bit_length(&self) -> usize {
24        let width = self.len as usize * core::mem::size_of::<T>() * 8;
25        width - self.leading_zeros()
26    }
27
28    /// Leading zeros against the value's width (`len * word_bits`), so
29    /// `leading_zeros + bit_length == bits_precision()`. A `len = 0`
30    /// value has width 0, hence `leading_zeros() == 0`.
31    pub fn leading_zeros(&self) -> usize {
32        let word_bits = core::mem::size_of::<T>() * 8;
33        match P::TAG {
34            // MSB-to-LSB; the first non-zero limb fixes the count and the
35            // invariant guarantees limbs above it are zero. Zero words above
36            // that limb contribute a full `word_bits` each.
37            PersonalityTag::Nct => {
38                let len = self.len as usize;
39                let mut i = len;
40                while i > 0 {
41                    i -= 1;
42                    let limb = self.limbs[i];
43                    if !super::is_zero(&limb) {
44                        return (len - 1 - i) * word_bits + limb.leading_zeros() as usize;
45                    }
46                }
47                len * word_bits
48            }
49            // Full scan: accumulate each limb's leading-zero contribution
50            // until a non-zero limb locks `decided`; later limbs add 0.
51            PersonalityTag::Ct => {
52                let mut total = 0usize;
53                let mut decided = 0usize;
54                let mut i = self.len as usize;
55                while i > 0 {
56                    i -= 1;
57                    let v = self.limbs[i];
58                    let v_lz = v.leading_zeros() as usize;
59                    let undecided = core::hint::black_box(!decided);
60                    total += undecided & v_lz;
61                    let v_nz_mask =
62                        core::hint::black_box(((!super::is_zero(&v)) as usize).wrapping_neg());
63                    decided |= v_nz_mask;
64                }
65                total
66            }
67        }
68    }
69}
70
71// ── const_num_traits::BitWidth (bit-length) / BitsPrecision (width) ──
72//
73// Two distinct quantities (bit-vocabulary canon): `bit_width` is the
74// significant-bit count (per-value magnitude); `bits_precision` is the
75// operating width, which for this variable-width carrier is the
76// constructed `len·word_bits` — NOT `CAP` (capacity stays out of any
77// trait answer). `bit_length <= bits_precision` always. Value receiver
78// per the traits; `&Self` mirrors (no reference blanket upstream).
79
80impl<T: MachineWord, const CAP: usize, P: Personality> const_num_traits::BitWidth
81    for HeaplessBigInt<T, CAP, P>
82{
83    fn bit_width(self) -> u32 {
84        self.bit_length() as u32
85    }
86}
87
88impl<T: MachineWord, const CAP: usize, P: Personality> const_num_traits::BitWidth
89    for &HeaplessBigInt<T, CAP, P>
90{
91    fn bit_width(self) -> u32 {
92        self.bit_length() as u32
93    }
94}
95
96impl<T: MachineWord, const CAP: usize, P: Personality> const_num_traits::BitsPrecision
97    for HeaplessBigInt<T, CAP, P>
98{
99    fn bits_precision(&self) -> u32 {
100        self.len as u32 * (core::mem::size_of::<T>() as u32 * 8)
101    }
102}
103
104impl<T: MachineWord, const CAP: usize, P: Personality> const_num_traits::BitsPrecision
105    for &HeaplessBigInt<T, CAP, P>
106{
107    fn bits_precision(&self) -> u32 {
108        self.len as u32 * (core::mem::size_of::<T>() as u32 * 8)
109    }
110}
111
112// Establish a width from a witness. Grow-only, value-preserving: widen `self`
113// to the whole-word length covering `bits_precision` bits, never shrinking.
114// The zero/one/witness constructors (`zero_with_precision_of(&modulus)` etc.)
115// default over this, giving generic reducers a correctly-sized seed instead of
116// the minimal-width `zero()` that silently truncates. `CAP` is the ceiling:
117// `widened` panics if the requested width exceeds it.
118impl<T: MachineWord, const CAP: usize, P: Personality> const_num_traits::WithPrecision
119    for HeaplessBigInt<T, CAP, P>
120{
121    fn widen_to_precision(self, bits_precision: u32) -> Self {
122        let word_bits = core::mem::size_of::<T>() as u32 * 8;
123        let target_len = bits_precision.div_ceil(word_bits) as u16;
124        self.widened(target_len.max(self.len()))
125    }
126}