fixed_bigint/heapless/mod.rs
1//! Unsigned integer whose width is chosen at runtime, not by the type.
2//!
3//! A [`HeaplessBigInt<T, CAP>`](HeaplessBigInt) carried at `len = k` **is** a
4//! `k`-word integer and behaves bit-for-bit like
5//! [`FixedUInt<T, k>`](crate::FixedUInt): arithmetic wraps, `<<` truncates, and
6//! overflow is reported at the value's width — it is *not* a growable bignum.
7//! The only difference from `FixedUInt` is that the width is `len`, a runtime
8//! (public) shape parameter, rather than the const `N`. You pick `len` at
9//! construction the way you pick `N`; `CAP` is only the storage ceiling
10//! (`len <= CAP`), invisible to arithmetic. Every op iterates `0..self.len`,
11//! never `0..CAP`. Same `Personality` typestate (`Nct` / `Ct`) and
12//! `T: MachineWord` bound as `FixedUInt`.
13//!
14//! Because the width is `len`, a value must be *constructed* at its intended
15//! width: `zero()` / `one()` / short decodes are minimal-width, so anywhere the
16//! operating width matters (accumulators, field elements, reduction targets)
17//! pin it from a witness with
18//! [`WithPrecision`](const_num_traits::WithPrecision) — e.g.
19//! `zero_with_precision_of(&modulus)` — rather than seeding from an identity
20//! and letting it silently run narrow.
21//!
22//! # Width contract
23//!
24//! Every operation resolves at the *value* width `len·word_bits` and returns
25//! bit-for-bit what the same-width `FixedUInt` would — no op grows past
26//! `max(operand len)` or narrows the magnitude, and `CAP` never enters a
27//! result. The result `len` of each op:
28//!
29//! | operation | result `len` |
30//! |---|---|
31//! | `wrapping`/`overflowing`/`checked` `add`·`sub`·`mul`, `+` `-` `*` | `max(a.len, b.len)` |
32//! | `Shl` (`<<`), `overflowing`/`wrapping`/`checked`/`unbounded`/`exact` `shl`, `FunnelShl` | `self.len` — high bits past the width are discarded |
33//! | `Shr` (`>>`), `overflowing`/`wrapping`/`checked`/`unbounded`/`exact` `shr`, `FunnelShr` | `self.len` minus the whole-word shift |
34//! | `WideMul` / [`CarryingMul`](const_num_traits::CarryingMul) | `lo` and `hi` each `max(a.len, b.len)`; reconstruct `hi·2^(W·word_bits) + lo` |
35//! | `Div` (`/`), `Rem` (`%`) | `max(dividend.len, divisor.len)` |
36//! | `BitAnd` (`&`), `BitOr`, `BitXor` | `max(a.len, b.len)` |
37//! | [`NextPowerOfTwo`](const_num_traits::NextPowerOfTwo) `next`/`checked`/`wrapping` | `self.len` — `one` is widened before the shift |
38//! | [`NextMultipleOf`](const_num_traits::NextMultipleOf) `next`/`checked` | `max(self.len, rhs.len)` (via `%` and `+`) |
39//! | [`Isqrt`](const_num_traits::Isqrt), [`Roots::nth_root`](num_integer::Roots) | `self.len` — estimate seeded at the operand width |
40//! | [`Ilog2`](const_num_traits::Ilog2) / `Ilog10` / `Ilog` | returns `u32` — no result width |
41//! | [`HighestOne`](const_num_traits::HighestOne) / `LowestOne` | returns `Option<u32>` — no result width |
42//! | [`IsolateHighestOne`](const_num_traits::IsolateHighestOne) / `IsolateLowestOne` | `self.len` — the single-bit mask carries the operand width |
43//! | [`DepositBits`](const_num_traits::DepositBits) / `ExtractBits` | `max(self.len, mask.len)` |
44//! | [`Sum`](core::iter::Sum) / [`Product`](core::iter::Product) | `max(operand len)`; empty iterator yields the minimal-width identity |
45//! | [`widened`](HeaplessBigInt::widened) / `WithPrecision` | the requested width (grow-only) |
46//!
47//! ## Construction & serialization widths
48//!
49//! Constructors and byte I/O carry *different* widths for the same value —
50//! there is no single "natural" one for a runtime carrier. When the width
51//! matters, pick the row you mean, or pin afterward with `WithPrecision`:
52//!
53//! | path | width |
54//! |---|---|
55//! | `From<u8/u16/u32/u64>` | `ceil(size_of::<uN>() / word)` — the source int's width |
56//! | inherent [`from_le_bytes`](HeaplessBigInt::from_le_bytes) / `from_be_bytes(&[u8])` | `ceil(slice.len() / word)` — the slice width |
57//! | [`new_zero_with_len`](HeaplessBigInt::new_zero_with_len) / [`from_limbs`](HeaplessBigInt::from_limbs) | exactly the given `len` |
58//! | `FromBytes` **trait** (`BytesHolder<T, CAP>`) | **`CAP`** — an owned holder can't be runtime-sized |
59//! | inherent [`to_le_bytes`](HeaplessBigInt::to_le_bytes) / `to_be_bytes(&mut [u8])` | value width (`len·word` bytes) |
60//! | `ToBytes` **trait** (`BytesHolder<T, CAP>`) | **`CAP`** — same reason |
61//!
62//! The trait (`ToBytes`/`FromBytes`) paths are capacity-width because their
63//! owned `Bytes` associated type is fixed-size — the intended shape for a
64//! full-precision operand (a modulus with `len == CAP`) and for round-tripping
65//! against `FixedUInt<T, CAP>`. For value-width bytes use the inherent methods.
66
67use crate::MachineWord;
68use const_num_traits::{Ct, Nct, Personality};
69use core::marker::PhantomData;
70
71mod abs_diff;
72mod arith;
73mod bit_deposit;
74mod bit_scan;
75mod bits;
76mod bitwise;
77mod bytes;
78#[cfg(feature = "cios")]
79mod cios;
80mod cmp;
81mod div_rem;
82mod euclid;
83mod from_prim;
84mod has_nonzero;
85mod has_personality;
86mod identities;
87mod ilog;
88mod isqrt;
89mod iter;
90mod midpoint;
91mod multiple;
92#[cfg(feature = "num-traits")]
93mod num_integer_impl;
94#[cfg(feature = "num-traits")]
95mod num_traits_bridge;
96mod parity;
97mod pow;
98mod power_of_two;
99mod prim_bits;
100#[cfg(feature = "num-traits")]
101mod prim_int;
102#[cfg(feature = "num-traits")]
103mod roots_impl;
104mod shift;
105mod shift_ops;
106mod strict;
107mod string_conversion;
108#[cfg(any(feature = "nightly", feature = "use-unsafe"))]
109mod to_bytes;
110#[cfg(feature = "zeroize")]
111mod zeroize_impl;
112
113pub use has_nonzero::NonZeroHeaplessBigInt;
114
115/// A `len`-word unsigned integer whose width is chosen at runtime.
116///
117/// Behaves bit-for-bit like [`FixedUInt<T, len>`](crate::FixedUInt): every op
118/// resolves at the value's width (`len·word_bits`), never a growable bignum.
119/// `CAP` is the maximum limb count (compile-time storage ceiling); `len` is the
120/// logical used-limb count (runtime) and the operating width — the words in
121/// `[len, CAP)` do not exist for arithmetic. Invariants (enforced by the
122/// module):
123///
124/// - `CAP <= u16::MAX as usize` (compile-time-asserted).
125/// - `(len as usize) <= CAP`.
126/// - `limbs[len as usize..CAP]` is all zero at every observable state.
127/// - `len` is set only from public shape parameters, never from limb content.
128pub struct HeaplessBigInt<T, const CAP: usize, P: Personality = Nct>
129where
130 T: MachineWord,
131{
132 pub(crate) limbs: [T; CAP],
133 pub(crate) len: u16,
134 pub(crate) _p: PhantomData<P>,
135}
136
137/// Compile-time assertion that `CAP` fits in `u16` (the `len` field type).
138pub(crate) trait AssertCapFits {
139 const CHECK: ();
140}
141
142impl<T: MachineWord, const CAP: usize, P: Personality> AssertCapFits for HeaplessBigInt<T, CAP, P> {
143 const CHECK: () = assert!(
144 CAP <= u16::MAX as usize,
145 "HeaplessBigInt: CAP exceeds u16::MAX; len type cannot represent this capacity"
146 );
147}
148
149// ── Constructors (shape-setting from public parameters) ──
150
151impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
152 /// Zero with a caller-supplied logical length. The `len` is treated as
153 /// a public shape parameter from here on. Panics if `len > CAP`.
154 #[inline]
155 pub fn new_zero_with_len(len: u16) -> Self {
156 let () = <Self as AssertCapFits>::CHECK;
157 assert!(
158 (len as usize) <= CAP,
159 "HeaplessBigInt::new_zero_with_len: len {} > CAP {}",
160 len,
161 CAP,
162 );
163 Self {
164 limbs: [zero::<T>(); CAP],
165 len,
166 _p: PhantomData,
167 }
168 }
169
170 /// Full-capacity zero: `len = CAP`. Used where an algorithm needs a
171 /// pre-sized workspace (CIOS accumulator, product buffer).
172 #[inline]
173 pub fn zero_full_cap() -> Self {
174 Self::new_zero_with_len(CAP as u16)
175 }
176
177 /// Construct from a limb array + explicit `len`. Panics if `len > CAP`
178 /// or if any limb at index `>= len` is non-zero (invariant check).
179 ///
180 /// The tail check runs in every build, not just under `debug_assertions`:
181 /// downstream arithmetic, equality, and `widened` all assume the tail is
182 /// zero, so a release build that skipped it would silently promote a
183 /// hidden limb into the value.
184 #[inline]
185 pub fn from_limbs(limbs: [T; CAP], len: u16) -> Self {
186 let () = <Self as AssertCapFits>::CHECK;
187 assert!((len as usize) <= CAP);
188 let mut i = len as usize;
189 while i < CAP {
190 assert!(
191 is_zero(&limbs[i]),
192 "HeaplessBigInt::from_limbs: zero-tail invariant violated at index {}",
193 i
194 );
195 i += 1;
196 }
197 Self {
198 limbs,
199 len,
200 _p: PhantomData,
201 }
202 }
203}
204
205// ── Accessors ──
206
207impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
208 /// Logical length in used limbs. Public shape parameter.
209 #[inline]
210 pub const fn len(&self) -> u16 {
211 self.len
212 }
213
214 /// True iff `len == 0` (mathematical zero shape).
215 #[inline]
216 pub const fn is_empty(&self) -> bool {
217 self.len == 0
218 }
219
220 /// Maximum limb count.
221 #[inline]
222 pub const fn capacity(&self) -> usize {
223 CAP
224 }
225
226 /// Return a copy carried at `new_len` words, pinning the operating
227 /// width without changing the value.
228 ///
229 /// Arithmetic here is width-driven: a `HeaplessBigInt` at `len = k`
230 /// behaves exactly like `FixedUInt<T, k>`, and every op resolves at
231 /// `max(operand len)`. So a value assembled from small pieces (e.g. an
232 /// accumulator seeded from [`zero`](const_num_traits::Zero::zero) then
233 /// added to a one-word digit) is a *narrow* type and wraps at that
234 /// narrow width. To carry it at a chosen width — the way you pick `N`
235 /// for `FixedUInt` — pin it here once; subsequent ops keep that width
236 /// because `max` preserves it. Widening only relabels the width: the
237 /// limbs in `[len, new_len)` are already zero by the zero-tail
238 /// invariant.
239 ///
240 /// Panics if `new_len < len` (this only widens) or `new_len > CAP`.
241 #[inline]
242 #[must_use]
243 pub fn widened(&self, new_len: u16) -> Self {
244 assert!(
245 new_len >= self.len && new_len as usize <= CAP,
246 "widened: new_len {new_len} must be in [len {}, CAP {CAP}]",
247 self.len
248 );
249 let mut out = *self;
250 out.len = new_len;
251 out
252 }
253
254 /// Read-only view of the used limbs.
255 #[inline]
256 pub fn limbs(&self) -> &[T] {
257 &self.limbs[..self.len as usize]
258 }
259
260 /// Full-buffer view including the zero tail. Only meaningful under
261 /// the zero-tail invariant.
262 #[inline]
263 pub fn all_limbs(&self) -> &[T; CAP] {
264 &self.limbs
265 }
266}
267
268// ── Copy / Clone ──
269
270impl<T: MachineWord, const CAP: usize, P: Personality> Clone for HeaplessBigInt<T, CAP, P> {
271 #[inline]
272 fn clone(&self) -> Self {
273 *self
274 }
275}
276
277impl<T: MachineWord, const CAP: usize, P: Personality> Copy for HeaplessBigInt<T, CAP, P> {}
278
279// Debug is personality-split like `FixedUInt`: `Nct` prints the limbs,
280// `Ct` is opaque so secret values never reach a formatter.
281impl<T: MachineWord + core::fmt::Debug, const CAP: usize> core::fmt::Debug
282 for HeaplessBigInt<T, CAP, Nct>
283{
284 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285 write!(
286 f,
287 "HeaplessBigInt<{}, {}, _>{{ limbs: {:?}, len: {} }}",
288 core::any::type_name::<T>(),
289 CAP,
290 &self.limbs[..self.len as usize],
291 self.len,
292 )
293 }
294}
295
296impl<T: MachineWord, const CAP: usize> core::fmt::Debug for HeaplessBigInt<T, CAP, Ct> {
297 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
298 f.write_str("HeaplessBigInt<…>")
299 }
300}
301
302// ── Internal helpers ──
303
304#[inline]
305pub(crate) fn zero<T: MachineWord>() -> T {
306 <T as const_num_traits::ConstZero>::ZERO
307}
308
309#[inline]
310pub(crate) fn is_zero<T: MachineWord>(v: &T) -> bool {
311 <T as const_num_traits::Zero>::is_zero(v)
312}