Expand description
Fixed-capacity, runtime-length unsigned bignum. See the module header. Unsigned integer whose width is chosen at runtime, not by the type.
A HeaplessBigInt<T, CAP> carried at len = k is a
k-word integer and behaves bit-for-bit like
FixedUInt<T, k>: arithmetic wraps, << truncates, and
overflow is reported at the value’s width — it is not a growable bignum.
The only difference from FixedUInt is that the width is len, a runtime
(public) shape parameter, rather than the const N. You pick len at
construction the way you pick N; CAP is only the storage ceiling
(len <= CAP), invisible to arithmetic. Every op iterates 0..self.len,
never 0..CAP. Same Personality typestate (Nct / Ct) and
T: MachineWord bound as FixedUInt.
Because the width is len, a value must be constructed at its intended
width: zero() / one() / short decodes are minimal-width, so anywhere the
operating width matters (accumulators, field elements, reduction targets)
pin it from a witness with
WithPrecision — e.g.
zero_with_precision_of(&modulus) — rather than seeding from an identity
and letting it silently run narrow.
§Width contract
Every operation resolves at the value width len·word_bits and returns
bit-for-bit what the same-width FixedUInt would — no op grows past
max(operand len) or narrows the magnitude, and CAP never enters a
result. The result len of each op:
| operation | result len |
|---|---|
wrapping/overflowing/checked add·sub·mul, + - * | max(a.len, b.len) |
Shl (<<), overflowing/wrapping/checked/unbounded/exact shl, FunnelShl | self.len — high bits past the width are discarded |
Shr (>>), overflowing/wrapping/checked/unbounded/exact shr, FunnelShr | self.len minus the whole-word shift |
WideMul / CarryingMul | lo and hi each max(a.len, b.len); reconstruct hi·2^(W·word_bits) + lo |
Div (/), Rem (%) | max(dividend.len, divisor.len) |
BitAnd (&), BitOr, BitXor | max(a.len, b.len) |
NextPowerOfTwo next/checked/wrapping | self.len — one is widened before the shift |
NextMultipleOf next/checked | max(self.len, rhs.len) (via % and +) |
Isqrt, Roots::nth_root | self.len — estimate seeded at the operand width |
Ilog2 / Ilog10 / Ilog | returns u32 — no result width |
HighestOne / LowestOne | returns Option<u32> — no result width |
IsolateHighestOne / IsolateLowestOne | self.len — the single-bit mask carries the operand width |
DepositBits / ExtractBits | max(self.len, mask.len) |
Sum / Product | max(operand len); empty iterator yields the minimal-width identity |
widened / WithPrecision | the requested width (grow-only) |
§Construction & serialization widths
Constructors and byte I/O carry different widths for the same value —
there is no single “natural” one for a runtime carrier. When the width
matters, pick the row you mean, or pin afterward with WithPrecision:
| path | width |
|---|---|
From<u8/u16/u32/u64> | ceil(size_of::<uN>() / word) — the source int’s width |
inherent from_le_bytes / from_be_bytes(&[u8]) | ceil(slice.len() / word) — the slice width |
new_zero_with_len / from_limbs | exactly the given len |
FromBytes trait (BytesHolder<T, CAP>) | CAP — an owned holder can’t be runtime-sized |
inherent to_le_bytes / to_be_bytes(&mut [u8]) | value width (len·word bytes) |
ToBytes trait (BytesHolder<T, CAP>) | CAP — same reason |
The trait (ToBytes/FromBytes) paths are capacity-width because their
owned Bytes associated type is fixed-size — the intended shape for a
full-precision operand (a modulus with len == CAP) and for round-tripping
against FixedUInt<T, CAP>. For value-width bytes use the inherent methods.
Structs§
- Heapless
BigInt - A
len-word unsigned integer whose width is chosen at runtime. - NonZero
Heapless BigInt - Non-zero
HeaplessBigInt. Constructed viaHasNonZero::into_nonzero.