Skip to main content

fixed_bigint/
fixeduint.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[cfg(feature = "num-traits")]
16use core::fmt::Write;
17
18use crate::machineword::{ConstMachineWord, MachineWord};
19use const_num_traits::ops::overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub};
20use const_num_traits::{
21    BorrowingSub, Bounded, CarryingAdd, ConstOne, ConstZero, One, PrimBits, Zero,
22};
23
24mod abs_diff_impl;
25mod add_sub_impl;
26mod bit_ops_impl;
27mod byte_conversion_panic_free;
28mod checked_pow_impl;
29#[cfg(feature = "cios")]
30mod cios_row_ops_impl;
31mod div_ceil_impl;
32mod euclid;
33mod extended_precision_impl;
34mod from_byte_slice_impl;
35mod has_nonzero_impl;
36mod has_personality_impl;
37mod ilog_impl;
38mod isqrt_impl;
39mod iter_impl;
40mod midpoint_impl;
41mod mul_div_impl;
42mod multiple_impl;
43#[cfg(feature = "num-traits")]
44mod num_integer_impl;
45#[cfg(feature = "num-traits")]
46mod num_traits_casts;
47mod num_traits_identity;
48mod parity_impl;
49mod power_of_two_impl;
50mod power_of_two_ops_impl;
51mod prim_int_impl;
52#[cfg(feature = "num-traits")]
53mod roots_impl;
54mod strict_impl;
55#[cfg(feature = "num-traits")]
56mod string_conversion;
57// ToBytes trait (nightly only, uses generic_const_exprs)
58#[cfg(feature = "nightly")]
59mod const_to_from_bytes;
60// BytesHolder + num_traits::ToBytes/FromBytes + (stable) const_num_traits::ToBytes/FromBytes
61// impls. Stable impl: no generic_const_exprs viral bounds, uses unsafe
62// `from_raw_parts` to reinterpret the limb array as bytes. The num_traits impls
63// inside are additionally gated on `feature = "num-traits"`. The
64// const_num_traits impls inside are additionally gated on
65// `not(feature = "nightly")` since `const_to_from_bytes.rs` provides better
66// impls (via `ConstBytesHolder` + generic_const_exprs) on nightly.
67#[cfg(any(feature = "nightly", feature = "use-unsafe"))]
68mod to_from_bytes;
69
70pub use has_nonzero_impl::NonZeroFixedUInt;
71
72use const_num_traits::{Ct, Nct, Personality, PersonalityMarker, PersonalityTag};
73#[cfg(feature = "zeroize")]
74use zeroize::DefaultIsZeroes;
75
76/// Fixed-size unsigned integer, represented by array of N words of builtin unsigned type T.
77///
78/// The optional `P: Personality` parameter selects which implementations of
79/// operation primitives are used at each call site. Defaults to [`Nct`]
80/// (non-constant-time). Use `FixedUInt<T, N, Ct>` for
81/// values that must be handled in constant time. See [`const_num_traits::personality`].
82///
83/// [`Nct`]: const_num_traits::Nct
84/// [`Ct`]: const_num_traits::Ct
85#[derive(Copy)]
86pub struct FixedUInt<T, const N: usize, P: Personality = Nct>
87where
88    T: MachineWord,
89{
90    /// Little-endian word array
91    pub(super) array: [T; N],
92    /// Personality marker (zero-size).
93    pub(super) _p: PersonalityMarker<P>,
94}
95
96// Debug is implemented manually so the Ct variant can redact its value.
97// Nct keeps the conventional "FixedUInt { array, _p }" format; Ct prints
98// `FixedUInt<…>` (placeholder) to keep limb contents out of panic
99// messages, dbg! output, and logs.
100impl<T: MachineWord + core::fmt::Debug, const N: usize> core::fmt::Debug for FixedUInt<T, N, Nct> {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        f.debug_struct("FixedUInt")
103            .field("array", &self.array)
104            .finish()
105    }
106}
107
108impl<T: MachineWord, const N: usize> core::fmt::Debug for FixedUInt<T, N, Ct> {
109    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110        f.write_str("FixedUInt<…>")
111    }
112}
113
114#[cfg(feature = "zeroize")]
115impl<T: MachineWord, const N: usize, P: Personality> DefaultIsZeroes for FixedUInt<T, N, P> {}
116
117impl<T, const N: usize, P: Personality> From<[T; N]> for FixedUInt<T, N, P>
118where
119    T: MachineWord,
120{
121    fn from(array: [T; N]) -> Self {
122        Self {
123            array,
124            _p: core::marker::PhantomData,
125        }
126    }
127}
128
129// Internal constructor for sites that need to build a FixedUInt from a raw
130// limb array.
131impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
132    pub(crate) const fn from_array(array: [T; N]) -> Self {
133        Self {
134            array,
135            _p: core::marker::PhantomData,
136        }
137    }
138}
139
140// ---------------------------------------------------------------------------
141// Personality conversions.
142// ---------------------------------------------------------------------------
143
144/// Lossless conversion from `Nct` to `Ct`. Tightens the invariant
145/// (declares that the value will be handled under the CT threat model going
146/// forward). Bit representation is identical; this is a free reinterpretation.
147impl<T: MachineWord, const N: usize> From<FixedUInt<T, N, Nct>> for FixedUInt<T, N, Ct> {
148    fn from(v: FixedUInt<T, N, Nct>) -> Self {
149        FixedUInt::from_array(v.array)
150    }
151}
152
153impl<T: MachineWord, const N: usize> FixedUInt<T, N, Ct> {
154    /// Drop the CT guarantee and convert to the `Nct` variant.
155    ///
156    /// **This is an explicit downgrade.** The caller is asserting that the
157    /// value is no longer secret — typically because the CT-handling
158    /// window has ended (e.g. a finalized signature, a published key, a
159    /// post-reduction modular value about to be serialized).
160    pub const fn forget_ct(self) -> FixedUInt<T, N, Nct> {
161        FixedUInt::from_array(self.array)
162    }
163}
164
165/// Branchless "shift amount is in range" flag for the `ct_checked_sh{l,r}`
166/// path. Returns `Choice::from(1)` iff `bits < bit_size`, without a
167/// runtime branch on `bits`. Handles `bit_size == 0` (empty carrier) by
168/// always returning invalid.
169#[inline]
170fn ct_checked_shift_valid(bits: u32, bit_size: usize) -> subtle::Choice {
171    if bit_size == 0 {
172        // N == 0 is a compile-time property, not a secret; the branch
173        // resolves at monomorphization for every real carrier.
174        return subtle::Choice::from(0);
175    }
176    let bit_size_u32 = bit_size as u32;
177    // (BIT_SIZE - 1 - bits) has its high bit set iff bits > BIT_SIZE - 1,
178    // i.e. iff the shift would overflow.
179    let diff = bit_size_u32.wrapping_sub(1).wrapping_sub(bits);
180    let overflow = ((diff >> 31) & 1) as u8;
181    subtle::Choice::from(1 ^ overflow)
182}
183
184impl<T: MachineWord, const N: usize> FixedUInt<T, N, Ct> {
185    /// CT-friendly counterpart to `num_traits::CheckedAdd::checked_add`.
186    /// Returns `CtOption::new(res, Choice::from(!overflow))` — the result is
187    /// always computed (always-iterate via overflowing_add), and the
188    /// validity Choice carries the overflow flag without exposing it as
189    /// a control-flow signal.
190    pub fn ct_checked_add(&self, other: &Self) -> subtle::CtOption<Self> {
191        // Route through `&Self: OverflowingAdd` — reads limbs through
192        // the references (see `add_sub_impl.rs`), avoiding an
193        // intermediate deref-copy of the wrapped secret onto the stack.
194        let (res, overflow) = <&Self as OverflowingAdd>::overflowing_add(self, other);
195        let valid = subtle::Choice::from((!overflow) as u8);
196        subtle::CtOption::new(res, valid)
197    }
198
199    /// CT-friendly counterpart to `num_traits::CheckedSub::checked_sub`.
200    pub fn ct_checked_sub(&self, other: &Self) -> subtle::CtOption<Self> {
201        let (res, overflow) = <&Self as OverflowingSub>::overflowing_sub(self, other);
202        let valid = subtle::Choice::from((!overflow) as u8);
203        subtle::CtOption::new(res, valid)
204    }
205
206    /// CT-friendly counterpart to `num_traits::CheckedMul::checked_mul`.
207    pub fn ct_checked_mul(&self, other: &Self) -> subtle::CtOption<Self> {
208        let (res, overflow) = <&Self as OverflowingMul>::overflowing_mul(self, other);
209        let valid = subtle::Choice::from((!overflow) as u8);
210        subtle::CtOption::new(res, valid)
211    }
212
213    /// CT-friendly counterpart to `CheckedShl::checked_shl`.
214    ///
215    /// The value is computed via `const_unbounded_shl_u32`, which under
216    /// `Ct` uses a branchless barrel shifter and a CT-safe `min(bits,
217    /// BIT_SIZE)` clamp. The overflow flag (`bits >= BIT_SIZE`) is
218    /// derived branchlessly here — never going through
219    /// `OverflowingShl::overflowing_shl` which routes through
220    /// `normalize_shift_amount`'s tainted branch + variable-time modulo.
221    pub fn ct_checked_shl(&self, bits: u32) -> subtle::CtOption<Self> {
222        // `const_unbounded_shl_u32` takes owned `FixedUInt<T,N,Ct>` and
223        // materialises a shifted copy on the stack regardless of input
224        // shape — same cost as `*self`. Kept explicit here so a future
225        // slice-based helper can replace this line without changing
226        // callers.
227        subtle::CtOption::new(
228            bit_ops_impl::const_unbounded_shl_u32::<T, N, Ct>(Self::from_array(self.array), bits),
229            ct_checked_shift_valid(bits, Self::BIT_SIZE),
230        )
231    }
232
233    /// CT-friendly counterpart to `CheckedShr::checked_shr`.
234    ///
235    /// Symmetric to [`Self::ct_checked_shl`]: value through
236    /// `const_unbounded_shr_u32`, validity flag derived branchlessly.
237    pub fn ct_checked_shr(&self, bits: u32) -> subtle::CtOption<Self> {
238        subtle::CtOption::new(
239            bit_ops_impl::const_unbounded_shr_u32::<T, N, Ct>(Self::from_array(self.array), bits),
240            ct_checked_shift_valid(bits, Self::BIT_SIZE),
241        )
242    }
243
244    pub fn ct_checked_pow(self, exp: u32) -> subtle::CtOption<Self> {
245        let mut result = <Self as One>::one();
246        let mut base = self;
247        let mut e = exp;
248        let mut any_overflow: u8 = 0;
249        for _ in 0..u32::BITS {
250            // `black_box` opacifies the per-iteration bit so LLVM can't
251            // recognize the XOR-select as a cmov-on-secret-flag — see
252            // `const_ct_select` for the load-bearing explanation.
253            let bit = core::hint::black_box((e & 1) as u8);
254            let (candidate, mul_ov) = <Self as OverflowingMul>::overflowing_mul(result, base);
255            // Multiply overflow matters iff bit_k is set.
256            any_overflow |= (mul_ov as u8) & bit;
257            // Per-limb CT-select of result vs candidate.
258            let bit_t = <T as core::convert::From<u8>>::from(bit);
259            let mask = core::hint::black_box(bit_t * <T as Bounded>::max_value());
260            for i in 0..N {
261                let diff = result.array[i] ^ candidate.array[i];
262                result.array[i] ^= mask & diff;
263            }
264            e >>= 1;
265            let (new_base, base_ov) = <Self as OverflowingMul>::overflowing_mul(base, base);
266            // Square overflow matters iff there are remaining set bits in e.
267            let any_remaining: u8 = core::hint::black_box((e != 0) as u8);
268            any_overflow |= (base_ov as u8) & any_remaining;
269            base = new_base;
270        }
271        let valid = subtle::Choice::from(1u8 ^ any_overflow);
272        subtle::CtOption::new(result, valid)
273    }
274}
275
276// `ct_checked_next_power_of_two` is the only Ct inherent that consumes
277// `T: subtle::ConditionallySelectable` (via the branchless "1 iff zero
278// else shifted" select). Kept in its own impl block so the six
279// sibling `ct_checked_{add,sub,mul,shl,shr,pow}` methods are reachable
280// under the bare `T: MachineWord` bound that a CT-secure downstream
281// generic normally carries.
282impl<T: MachineWord + subtle::ConditionallySelectable, const N: usize> FixedUInt<T, N, Ct> {
283    /// CT-friendly counterpart to `NextPowerOfTwo::checked_next_power_of_two`.
284    pub fn ct_checked_next_power_of_two(self) -> subtle::CtOption<Self>
285    where
286        T: subtle::ConstantTimeEq,
287    {
288        let one = <Self as One>::one();
289        let m_one = <Self as const_num_traits::WrappingSub>::wrapping_sub(self, one);
290        let leading = <Self as PrimBits>::leading_zeros(m_one);
291        let bits = Self::BIT_SIZE as u32 - leading;
292        let shifted = one << (bits as usize);
293        let is_zero_choice =
294            <Self as subtle::ConstantTimeEq>::ct_eq(&self, &<Self as Zero>::zero());
295        // result = is_zero ? 1 : shifted
296        let result = <Self as subtle::ConditionallySelectable>::conditional_select(
297            &shifted,
298            &one,
299            is_zero_choice,
300        );
301        // overflow iff bits >= BIT_SIZE; when input == 0 we treat as valid
302        // (the answer is 1).
303        let overflow = (bits >= Self::BIT_SIZE as u32) as u8;
304        let valid_otherwise = subtle::Choice::from(1u8 ^ overflow);
305        let valid = <subtle::Choice as subtle::ConditionallySelectable>::conditional_select(
306            &valid_otherwise,
307            &subtle::Choice::from(1u8),
308            is_zero_choice,
309        );
310        subtle::CtOption::new(result, valid)
311    }
312}
313
314// ---------------------------------------------------------------------------
315// subtle integration — Ct variant only.
316// ---------------------------------------------------------------------------
317
318impl<T: MachineWord + subtle::ConstantTimeEq, const N: usize> subtle::ConstantTimeEq
319    for FixedUInt<T, N, Ct>
320{
321    fn ct_eq(&self, other: &Self) -> subtle::Choice {
322        <[T] as subtle::ConstantTimeEq>::ct_eq(self.array.as_slice(), other.array.as_slice())
323    }
324}
325
326impl<T: MachineWord + subtle::ConditionallySelectable, const N: usize>
327    subtle::ConditionallySelectable for FixedUInt<T, N, Ct>
328{
329    fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
330        let mut array = a.array;
331        let mut i = 0;
332        while i < N {
333            array[i] = T::conditional_select(&a.array[i], &b.array[i], choice);
334            i += 1;
335        }
336        FixedUInt::from_array(array)
337    }
338}
339
340// `Ord::cmp` / `PartialOrd::partial_cmp` dispatch on `P::TAG`: the `Ct` arm
341// runs `const_cmp_ct`, a full-width scan with no short-circuit. The returned
342// `Ordering` is still a CT leak if a caller branches on it, so secret-data
343// callers should prefer `ConstantTimeGreater`/`ConstantTimeLess` below, which
344// produce a `Choice` that pairs with `ConditionallySelectable` for branch-free
345// Montgomery conditional-subtract.
346impl<T: MachineWord + subtle::ConstantTimeEq + subtle::ConstantTimeGreater, const N: usize>
347    subtle::ConstantTimeGreater for FixedUInt<T, N, Ct>
348{
349    fn ct_gt(&self, other: &Self) -> subtle::Choice {
350        let mut gt = subtle::Choice::from(0u8);
351        let mut undecided = subtle::Choice::from(1u8);
352        let mut i = N;
353        while i > 0 {
354            i -= 1;
355            let gt_here = self.array[i].ct_gt(&other.array[i]);
356            let eq_here = self.array[i].ct_eq(&other.array[i]);
357            gt |= undecided & gt_here;
358            undecided &= eq_here;
359        }
360        gt
361    }
362}
363
364impl<T: MachineWord + subtle::ConstantTimeEq + subtle::ConstantTimeGreater, const N: usize>
365    subtle::ConstantTimeLess for FixedUInt<T, N, Ct>
366{
367}
368
369const LONGEST_WORD_IN_BITS: usize = 128;
370
371impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
372    const WORD_SIZE: usize = core::mem::size_of::<T>();
373    const WORD_BITS: usize = Self::WORD_SIZE * 8;
374    const BYTE_SIZE: usize = Self::WORD_SIZE * N;
375    const BIT_SIZE: usize = Self::BYTE_SIZE * 8;
376
377    /// The serialized byte width of this `FixedUInt` type, `N * size_of::<T>()`.
378    ///
379    /// Public alias of the internal `BYTE_SIZE` for callers sizing buffers
380    /// to feed the `*_bytes_fixed` panic-free byte conversion methods. Use
381    /// as `const BUF_LEN: usize = MyFixed::BYTE_WIDTH;` then
382    /// `let mut buf = [0u8; BUF_LEN];`.
383    pub const BYTE_WIDTH: usize = Self::BYTE_SIZE;
384
385    /// Creates and zero-initializes a FixedUInt.
386    pub fn new() -> FixedUInt<T, N, P> {
387        FixedUInt::from_array([T::zero(); N])
388    }
389
390    /// Returns the underlying array.
391    pub fn words(&self) -> &[T; N] {
392        &self.array
393    }
394
395    /// Returns number of used bits.
396    pub fn bit_length(&self) -> u32 {
397        // `PrimBits::leading_zeros` takes `self` by value; slice-based
398        // `const_leading_zeros` in the same crate reads the array
399        // directly and avoids materialising a fresh FixedUInt.
400        Self::BIT_SIZE as u32 - const_leading_zeros(&self.array)
401    }
402}
403
404impl<T: MachineWord, const N: usize> FixedUInt<T, N, Nct> {
405    /// Performs a division, returning both the quotient and remainder in a tuple.
406    pub fn div_rem(&self, divisor: &Self) -> (Self, Self) {
407        let (quotient, remainder) = const_div_rem(&self.array, &divisor.array);
408        (Self::from_array(quotient), Self::from_array(remainder))
409    }
410
411    /// Converts to decimal string, given a buffer. CAVEAT: This method removes any leading zeroes
412    pub fn to_radix_str<'a>(
413        &self,
414        result: &'a mut [u8],
415        radix: u8,
416    ) -> Result<&'a str, core::fmt::Error> {
417        type Error = core::fmt::Error;
418
419        if !(2..=16).contains(&radix) {
420            return Err(Error {}); // Radix out of supported range
421        }
422        for byte in result.iter_mut() {
423            *byte = b'0';
424        }
425        if <Self as Zero>::is_zero(self) {
426            if !result.is_empty() {
427                result[0] = b'0';
428                return core::str::from_utf8(&result[0..1]).map_err(|_| Error {});
429            } else {
430                return Err(Error {});
431            }
432        }
433
434        let mut number = *self;
435        let mut idx = result.len();
436
437        let radix_t = Self::from(radix);
438
439        while !<Self as Zero>::is_zero(&number) {
440            if idx == 0 {
441                return Err(Error {}); // not enough space in result...
442            }
443
444            idx -= 1;
445            let (quotient, remainder) = number.div_rem(&radix_t);
446
447            // remainder < radix <= 16, so it fits in the low limb's low byte;
448            // pull it via the MachineWord ToPrimitive supertrait instead of
449            // going through the (optional) `num_traits::ToPrimitive for FixedUInt`.
450            let digit =
451                <T as const_num_traits::ToPrimitive>::to_u8(&remainder.array[0]).unwrap_or(0);
452            result[idx] = match digit {
453                0..=9 => b'0' + digit,          // digits
454                10..=16 => b'a' + (digit - 10), // alphabetic digits for bases > 10
455                _ => return Err(Error {}),
456            };
457
458            number = quotient;
459        }
460
461        let start = result[idx..].iter().position(|&c| c != b'0').unwrap_or(0);
462        let radix_str = core::str::from_utf8(&result[idx + start..]).map_err(|_| Error {})?;
463        Ok(radix_str)
464    }
465}
466
467// Const-compatible from_bytes helper functions
468c0nst::c0nst! {
469    /// Const-compatible from_le_bytes implementation for slices.
470    /// Derives word_size internally from size_of::<T>().
471    pub(crate) c0nst fn impl_from_le_bytes_slice<T: [c0nst] ConstMachineWord, const N: usize>(
472        bytes: &[u8],
473    ) -> [T; N] {
474        let word_size = core::mem::size_of::<T>();
475        let mut ret: [T; N] = [T::zero(); N];
476        let capacity = N * word_size;
477        let total_bytes = if bytes.len() < capacity { bytes.len() } else { capacity };
478
479        let mut byte_index = 0;
480        while byte_index < total_bytes {
481            let word_index = byte_index / word_size;
482            let byte_in_word = byte_index % word_size;
483
484            let byte_value: T = <T as core::convert::From<u8>>::from(bytes[byte_index]);
485            let shifted_value = byte_value.shl(byte_in_word * 8);
486            ret[word_index] = ret[word_index].bitor(shifted_value);
487            byte_index += 1;
488        }
489        ret
490    }
491
492    /// Const-compatible from_be_bytes implementation for slices.
493    /// Derives word_size internally from size_of::<T>().
494    pub(crate) c0nst fn impl_from_be_bytes_slice<T: [c0nst] ConstMachineWord, const N: usize>(
495        bytes: &[u8],
496    ) -> [T; N] {
497        let word_size = core::mem::size_of::<T>();
498        let mut ret: [T; N] = [T::zero(); N];
499        let capacity_bytes = N * word_size;
500        let total_bytes = if bytes.len() < capacity_bytes { bytes.len() } else { capacity_bytes };
501
502        // For consistent truncation semantics with from_le_bytes, always take the
503        // least significant bytes (rightmost bytes in big-endian representation)
504        let start_offset = if bytes.len() > capacity_bytes {
505            bytes.len() - capacity_bytes
506        } else {
507            0
508        };
509
510        let mut byte_index = 0;
511        while byte_index < total_bytes {
512            // Take bytes from the end of the input (least significant in BE)
513            let be_byte_index = start_offset + total_bytes - 1 - byte_index;
514            let word_index = byte_index / word_size;
515            let byte_in_word = byte_index % word_size;
516
517            let byte_value: T = <T as core::convert::From<u8>>::from(bytes[be_byte_index]);
518            let shifted_value = byte_value.shl(byte_in_word * 8);
519            ret[word_index] = ret[word_index].bitor(shifted_value);
520            byte_index += 1;
521        }
522        ret
523    }
524}
525
526// Inherent from_bytes methods (not const - use FromBytes trait for const access)
527impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
528    /// Create a little-endian integer value from its representation as a byte array in little endian.
529    pub fn from_le_bytes(bytes: &[u8]) -> Self {
530        Self::from_array(impl_from_le_bytes_slice::<T, N>(bytes))
531    }
532
533    /// Create a big-endian integer value from its representation as a byte array in big endian.
534    pub fn from_be_bytes(bytes: &[u8]) -> Self {
535        Self::from_array(impl_from_be_bytes_slice::<T, N>(bytes))
536    }
537}
538
539impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
540    /// Converts the FixedUInt into a little-endian byte array.
541    pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
542        let total_bytes = N * Self::WORD_SIZE;
543        if output_buffer.len() < total_bytes {
544            return Err(false); // Buffer too small
545        }
546        for (i, word) in self.array.iter().enumerate() {
547            let start = i * Self::WORD_SIZE;
548            let end = start + Self::WORD_SIZE;
549            let word_bytes = word.to_le_bytes();
550            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
551        }
552        Ok(&output_buffer[..total_bytes])
553    }
554
555    /// Converts the FixedUInt into a big-endian byte array.
556    pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
557        let total_bytes = N * Self::WORD_SIZE;
558        if output_buffer.len() < total_bytes {
559            return Err(false); // Buffer too small
560        }
561        for (i, word) in self.array.iter().rev().enumerate() {
562            let start = i * Self::WORD_SIZE;
563            let end = start + Self::WORD_SIZE;
564            let word_bytes = word.to_be_bytes();
565            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
566        }
567        Ok(&output_buffer[..total_bytes])
568    }
569
570    /// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zeroes
571    pub fn to_hex_str<'a>(&self, result: &'a mut [u8]) -> Result<&'a str, core::fmt::Error> {
572        type Error = core::fmt::Error;
573
574        let word_size = Self::WORD_SIZE;
575        // need length minus leading zeros
576        let need_bits = self.bit_length() as usize;
577        // number of needed characters (bits/4 = bytes * 2)
578        let need_chars = if need_bits > 0 { need_bits / 4 } else { 0 };
579
580        if result.len() < need_chars {
581            // not enough space in result...
582            return Err(Error {});
583        }
584        let offset = result.len() - need_chars;
585        for i in result.iter_mut() {
586            *i = b'0';
587        }
588
589        for iter_words in 0..self.array.len() {
590            let word = self.array[iter_words];
591            let mut encoded = [0u8; LONGEST_WORD_IN_BITS / 4];
592            let encode_slice = &mut encoded[0..word_size * 2];
593            let mut wordbytes = word.to_le_bytes();
594            wordbytes.as_mut().reverse();
595            let wordslice = wordbytes.as_ref();
596            to_slice_hex(wordslice, encode_slice).map_err(|_| Error {})?;
597            for iter_chars in 0..encode_slice.len() {
598                let copy_char_to = (iter_words * word_size * 2) + iter_chars;
599                if copy_char_to <= need_chars {
600                    let reverse_index = offset + (need_chars - copy_char_to);
601                    if reverse_index <= result.len() && reverse_index > 0 {
602                        let current_char = encode_slice[(encode_slice.len() - 1) - iter_chars];
603                        result[reverse_index - 1] = current_char;
604                    }
605                }
606            }
607        }
608
609        let convert = core::str::from_utf8(result).map_err(|_| Error {})?;
610        let pos = convert.find(|c: char| c != '0');
611        match pos {
612            Some(x) => Ok(&convert[x..convert.len()]),
613            None => {
614                if convert.starts_with('0') {
615                    Ok("0")
616                } else {
617                    Ok(convert)
618                }
619            }
620        }
621    }
622
623    /// Construct a new value with a different size.
624    ///
625    /// - If `N2 < N`, the most-significant (upper) words are truncated.
626    /// - If `N2 > N`, the additional most-significant words are filled with zeros.
627    #[must_use]
628    pub fn resize<const N2: usize>(&self) -> FixedUInt<T, N2, P> {
629        let mut array = [T::zero(); N2];
630        let min_size = N.min(N2);
631        array[..min_size].copy_from_slice(&self.array[..min_size]);
632        FixedUInt::<T, N2, P>::from_array(array)
633    }
634
635    #[cfg(feature = "num-traits")]
636    fn hex_fmt(
637        &self,
638        formatter: &mut core::fmt::Formatter<'_>,
639        uppercase: bool,
640    ) -> Result<(), core::fmt::Error>
641    where
642        u8: core::convert::TryFrom<T>,
643    {
644        type Err = core::fmt::Error;
645
646        fn to_casedigit(byte: u8, uppercase: bool) -> Result<char, core::fmt::Error> {
647            let digit = core::char::from_digit(byte as u32, 16).ok_or(Err {})?;
648            if uppercase {
649                digit.to_uppercase().next().ok_or(Err {})
650            } else {
651                digit.to_lowercase().next().ok_or(Err {})
652            }
653        }
654
655        let mut leading_zero: bool = true;
656
657        let mut maybe_write = |nibble: char| -> Result<(), core::fmt::Error> {
658            leading_zero &= nibble == '0';
659            if !leading_zero {
660                formatter.write_char(nibble)?;
661            }
662            Ok(())
663        };
664
665        for index in (0..N).rev() {
666            let val = self.array[index];
667            let mask: T = 0xff.into();
668            for j in (0..Self::WORD_SIZE as u32).rev() {
669                let masked = val & mask.shl((j * 8) as usize);
670
671                let byte = u8::try_from(masked.shr((j * 8) as usize)).map_err(|_| Err {})?;
672
673                maybe_write(to_casedigit((byte & 0xf0) >> 4, uppercase)?)?;
674                maybe_write(to_casedigit(byte & 0x0f, uppercase)?)?;
675            }
676        }
677        Ok(())
678    }
679}
680
681c0nst::c0nst! {
682    /// Single canonical limb-wise add-with-carry over a fixed-width array.
683    /// CT under `Ct`-personality callers: iteration count is `N`, the inner
684    /// `CarryingAdd::carrying_add` lowers to a hardware ADC, and no
685    /// step branches on the data.
686    pub(crate) c0nst fn add_with_carry<T: [c0nst] ConstMachineWord, const N: usize>(
687        a: &[T; N],
688        b: &[T; N],
689        carry_in: bool,
690    ) -> ([T; N], bool) {
691        let mut result = [T::zero(); N];
692        let mut carry = carry_in;
693        let mut i = 0usize;
694        while i < N {
695            let (sum, c) = CarryingAdd::carrying_add(a[i], b[i], carry);
696            result[i] = sum;
697            carry = c;
698            i += 1;
699        }
700        (result, carry)
701    }
702
703    /// Mirror of `add_with_carry` for subtraction.
704    pub(crate) c0nst fn sub_with_borrow<T: [c0nst] ConstMachineWord, const N: usize>(
705        a: &[T; N],
706        b: &[T; N],
707        borrow_in: bool,
708    ) -> ([T; N], bool) {
709        let mut result = [T::zero(); N];
710        let mut borrow = borrow_in;
711        let mut i = 0usize;
712        while i < N {
713            let (diff, br) = BorrowingSub::borrowing_sub(a[i], b[i], borrow);
714            result[i] = diff;
715            borrow = br;
716            i += 1;
717        }
718        (result, borrow)
719    }
720
721    /// In-place limb-wise add, no carry-in. Same per-limb primitive
722    /// (`CarryingAdd::carrying_add`) as `add_with_carry`, just writing
723    /// directly to `target` to avoid a stack-allocated temp array that
724    /// LLVM might not always elide on embedded builds.
725    pub(crate) c0nst fn add_impl<T: [c0nst] ConstMachineWord, const N: usize>(
726        target: &mut [T; N],
727        other: &[T; N]
728    ) -> bool {
729        let mut carry = false;
730        let mut i = 0usize;
731        while i < N {
732            let (sum, c) = CarryingAdd::carrying_add(target[i], other[i], carry);
733            target[i] = sum;
734            carry = c;
735            i += 1;
736        }
737        carry
738    }
739
740    /// In-place limb-wise sub, no borrow-in. Mirror of `add_impl`.
741    pub(crate) c0nst fn sub_impl<T: [c0nst] ConstMachineWord, const N: usize>(
742        target: &mut [T; N],
743        other: &[T; N]
744    ) -> bool {
745        let mut borrow = false;
746        let mut i = 0usize;
747        while i < N {
748            let (diff, br) = BorrowingSub::borrowing_sub(target[i], other[i], borrow);
749            target[i] = diff;
750            borrow = br;
751            i += 1;
752        }
753        borrow
754    }
755}
756
757c0nst::c0nst! {
758    /// Const-compatible left shift implementation
759    pub(crate) c0nst fn const_shl_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(
760        target: &mut FixedUInt<T, N, P>,
761        bits: usize,
762    ) {
763        if N == 0 {
764            return;
765        }
766        let word_bits = FixedUInt::<T, N>::WORD_BITS;
767        let nwords = bits / word_bits;
768        let nbits = bits - nwords * word_bits;
769
770        // If shift >= total bits, result is zero
771        if nwords >= N {
772            let mut i = 0;
773            while i < N {
774                target.array[i] = T::zero();
775                i += 1;
776            }
777            return;
778        }
779
780        // Move words (backwards)
781        let mut i = N;
782        while i > nwords {
783            i -= 1;
784            target.array[i] = target.array[i - nwords];
785        }
786        // Zero out the lower words
787        let mut i = 0;
788        while i < nwords {
789            target.array[i] = T::zero();
790            i += 1;
791        }
792
793        if nbits != 0 {
794            // Shift remaining bits (backwards)
795            let mut i = N;
796            while i > 1 {
797                i -= 1;
798                let right = target.array[i] << nbits;
799                let left = target.array[i - 1] >> (word_bits - nbits);
800                target.array[i] = right | left;
801            }
802            target.array[0] <<= nbits;
803        }
804    }
805
806    /// Const-compatible right shift implementation
807    pub(crate) c0nst fn const_shr_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(
808        target: &mut FixedUInt<T, N, P>,
809        bits: usize,
810    ) {
811        if N == 0 {
812            return;
813        }
814        let word_bits = FixedUInt::<T, N>::WORD_BITS;
815        let nwords = bits / word_bits;
816        let nbits = bits - nwords * word_bits;
817
818        // If shift >= total bits, result is zero
819        if nwords >= N {
820            let mut i = 0;
821            while i < N {
822                target.array[i] = T::zero();
823                i += 1;
824            }
825            return;
826        }
827
828        let last_index = N - 1;
829        let last_word = N - nwords;
830
831        // Move words (forwards)
832        let mut i = 0;
833        while i < last_word {
834            target.array[i] = target.array[i + nwords];
835            i += 1;
836        }
837
838        // Zero out the upper words
839        let mut i = last_word;
840        while i < N {
841            target.array[i] = T::zero();
842            i += 1;
843        }
844
845        if nbits != 0 {
846            // Shift remaining bits (forwards)
847            let mut i = 0;
848            while i < last_index {
849                let left = target.array[i] >> nbits;
850                let right = target.array[i + 1] << (word_bits - nbits);
851                target.array[i] = left | right;
852                i += 1;
853            }
854            target.array[last_index] >>= nbits;
855        }
856    }
857
858    /// CT variant of `const_shl_impl`: barrel shifter. Iterates every
859    /// bit position of `bits` from 0 to `usize::BITS - 1`. At each
860    /// layer k, computes `target << 2^k` (via `const_shl_impl` with a
861    /// publicly-known power-of-two amount — non-CT internally but the
862    /// amount is *not* secret) and CT-selects per-limb between the
863    /// shifted and unshifted forms based on bit k of `bits`. Runtime
864    /// is O(N * usize::BITS), independent of the secret shift amount.
865    /// Used by the `Ct`-personality arm of `Shl<usize>` / `Shl<u32>`.
866    pub(crate) c0nst fn const_shl_ct<
867        T: [c0nst] ConstMachineWord + MachineWord,
868        const N: usize,
869        P: Personality,
870    >(
871        target: &mut FixedUInt<T, N, P>,
872        bits: usize,
873    ) {
874        if N == 0 {
875            return;
876        }
877        // `layers == usize::BITS`, so `k < layers` guarantees `1usize << k`
878        // stays in range. Do not raise this bound without revisiting the shift.
879        let layers = core::mem::size_of::<usize>() * 8;
880        let mut k = 0;
881        while k < layers {
882            let amount = 1usize << k;
883            // Build the "shifted by 2^k" candidate without mutating target.
884            let mut shifted = *target;
885            const_shl_impl(&mut shifted, amount);
886            // Spread bit k of `bits` to a full-T mask: 0 if cleared, T::MAX if set.
887            // `black_box` is load-bearing — see `const_ct_select` for the
888            // address-select rewrite it defeats.
889            let bit_k = core::hint::black_box(((bits >> k) & 1) as u8);
890            let bit_k_t = <T as core::convert::From<u8>>::from(bit_k);
891            let mask = <T as core::ops::Mul>::mul(bit_k_t, <T as Bounded>::max_value());
892            // CT-select per limb: target[i] ^= mask & (target[i] ^ shifted[i])
893            let mut i = 0;
894            while i < N {
895                let diff =
896                    <T as core::ops::BitXor>::bitxor(target.array[i], shifted.array[i]);
897                let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
898                target.array[i] = <T as core::ops::BitXor>::bitxor(target.array[i], masked);
899                i += 1;
900            }
901            k += 1;
902        }
903    }
904
905    /// CT variant of `const_shr_impl`: barrel shifter, mirror of
906    /// `const_shl_ct`. See that helper for the design rationale.
907    pub(crate) c0nst fn const_shr_ct<
908        T: [c0nst] ConstMachineWord + MachineWord,
909        const N: usize,
910        P: Personality,
911    >(
912        target: &mut FixedUInt<T, N, P>,
913        bits: usize,
914    ) {
915        if N == 0 {
916            return;
917        }
918        // See `const_shl_ct`: `layers == usize::BITS` keeps `1usize << k`
919        // in range.
920        let layers = core::mem::size_of::<usize>() * 8;
921        let mut k = 0;
922        while k < layers {
923            let amount = 1usize << k;
924            let mut shifted = *target;
925            const_shr_impl(&mut shifted, amount);
926            // See `const_shl_ct` / `const_ct_select` for why `black_box` is here.
927            let bit_k = core::hint::black_box(((bits >> k) & 1) as u8);
928            let bit_k_t = <T as core::convert::From<u8>>::from(bit_k);
929            let mask = <T as core::ops::Mul>::mul(bit_k_t, <T as Bounded>::max_value());
930            let mut i = 0;
931            while i < N {
932                let diff =
933                    <T as core::ops::BitXor>::bitxor(target.array[i], shifted.array[i]);
934                let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
935                target.array[i] = <T as core::ops::BitXor>::bitxor(target.array[i], masked);
936                i += 1;
937            }
938            k += 1;
939        }
940    }
941
942    /// Standalone const-compatible array multiplication (no FixedUInt dependency).
943    /// Returns (result_array, overflowed).
944    ///
945    /// The carry split (`accumulator > t_max ? ... : 0`) dispatches on
946    /// personality. Nct keeps the original predictable branch (the fast
947    /// path skips the shift+mask when the sum already fits in one word);
948    /// Ct does the shift+mask unconditionally so the body has no
949    /// value-dependent branch. Overflow accumulation is branchless (`|` on
950    /// bools) under both personalities since the per-step cost is tiny.
951    pub(crate) c0nst fn const_mul<T: [c0nst] ConstMachineWord, const N: usize, const CHECK_OVERFLOW: bool, P: Personality>(
952        op1: &[T; N],
953        op2: &[T; N],
954        word_bits: usize,
955    ) -> ([T; N], bool) {
956        let mut result: [T; N] = [<T as ConstZero>::ZERO; N];
957        let mut overflowed = false;
958        let t_max = <T as ConstMachineWord>::to_double(<T as Bounded>::max_value());
959        let dw_zero = <<T as ConstMachineWord>::ConstDoubleWord as ConstZero>::ZERO;
960
961        let mut i = 0;
962        while i < N {
963            let mut carry = dw_zero;
964            let mut j = 0;
965            while j < N {
966                let round = i + j;
967                let op1_dw = <T as ConstMachineWord>::to_double(op1[i]);
968                let op2_dw = <T as ConstMachineWord>::to_double(op2[j]);
969                let mul_res = op1_dw * op2_dw;
970                let mut accumulator = if round < N {
971                    <T as ConstMachineWord>::to_double(result[round])
972                } else {
973                    dw_zero
974                };
975                accumulator += mul_res + carry;
976
977                match P::TAG {
978                    PersonalityTag::Nct => {
979                        if accumulator > t_max {
980                            carry = accumulator >> word_bits;
981                            accumulator &= t_max;
982                        } else {
983                            carry = dw_zero;
984                        }
985                    }
986                    PersonalityTag::Ct => {
987                        carry = accumulator >> word_bits;
988                        accumulator &= t_max;
989                    }
990                }
991                if round < N {
992                    result[round] = <T as ConstMachineWord>::from_double(accumulator);
993                } else if CHECK_OVERFLOW {
994                    overflowed |= accumulator != dw_zero;
995                }
996                j += 1;
997            }
998            if CHECK_OVERFLOW {
999                overflowed |= carry != dw_zero;
1000            }
1001            i += 1;
1002        }
1003        (result, overflowed)
1004    }
1005
1006    /// Get the bit width of a word type.
1007    pub(crate) c0nst fn const_word_bits<T>() -> usize {
1008        core::mem::size_of::<T>() * 8
1009    }
1010
1011    /// Compare two words, returning Some(ordering) if not equal, None if equal.
1012    pub(crate) c0nst fn const_cmp_words<T: [c0nst] ConstMachineWord>(a: T, b: T) -> Option<core::cmp::Ordering> {
1013        if a > b {
1014            Some(core::cmp::Ordering::Greater)
1015        } else if a < b {
1016            Some(core::cmp::Ordering::Less)
1017        } else {
1018            None
1019        }
1020    }
1021
1022    /// Count leading zeros in a const-compatible way
1023    pub(crate) c0nst fn const_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1024        array: &[T; N],
1025    ) -> u32 {
1026        let mut ret = 0u32;
1027        let mut index = N;
1028        while index > 0 {
1029            index -= 1;
1030            let v = array[index];
1031            ret += <T as PrimBits>::leading_zeros(v);
1032            if !<T as Zero>::is_zero(&v) {
1033                break;
1034            }
1035        }
1036        ret
1037    }
1038
1039    /// CT variant of `const_leading_zeros`: scans every limb without
1040    /// short-circuiting. A bitmask tracks whether we're still in the
1041    /// leading-zero region; once a non-zero limb is seen, subsequent
1042    /// limbs contribute 0 to the total. Used by the `Ct`-personality
1043    /// arm of `PrimBits::leading_zeros`. Branchless apart from a
1044    /// `bool -> u32` cast that rustc compiles to a setne.
1045    pub(crate) c0nst fn const_leading_zeros_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1046        array: &[T; N],
1047    ) -> u32 {
1048        let mut total: u32 = 0;
1049        // 0 while still in leading-zero region; u32::MAX once a non-zero limb is seen.
1050        let mut decided: u32 = 0;
1051        let mut index = N;
1052        while index > 0 {
1053            index -= 1;
1054            let v = array[index];
1055            let v_lz = <T as PrimBits>::leading_zeros(v);
1056            // Add this limb's lz contribution iff we haven't decided yet.
1057            // `black_box` defeats the LLVM XOR/AND-select → cmov rewrite —
1058            // see `const_ct_select` for the load-bearing explanation.
1059            let undecided = core::hint::black_box(!decided);
1060            total += undecided & v_lz;
1061            // Lock the decision the moment we see a non-zero limb.
1062            let v_nz_bit = (!<T as Zero>::is_zero(&v)) as u32;
1063            let v_nz_mask = core::hint::black_box(v_nz_bit.wrapping_neg());
1064            decided |= v_nz_mask;
1065        }
1066        total
1067    }
1068
1069    /// Count trailing zeros in a const-compatible way
1070    pub(crate) c0nst fn const_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1071        array: &[T; N],
1072    ) -> u32 {
1073        let mut ret = 0u32;
1074        let mut index = 0;
1075        while index < N {
1076            let v = array[index];
1077            ret += <T as PrimBits>::trailing_zeros(v);
1078            if !<T as Zero>::is_zero(&v) {
1079                break;
1080            }
1081            index += 1;
1082        }
1083        ret
1084    }
1085
1086    /// CT variant of `const_trailing_zeros`: scans LSB-to-MSB without
1087    /// short-circuiting. Mirror of `const_leading_zeros_ct` — see that
1088    /// helper for the rationale. Used by the `Ct`-personality arm of
1089    /// `PrimBits::trailing_zeros`.
1090    pub(crate) c0nst fn const_trailing_zeros_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1091        array: &[T; N],
1092    ) -> u32 {
1093        let mut total: u32 = 0;
1094        // 0 while still in trailing-zero region; u32::MAX once a non-zero limb is seen.
1095        let mut decided: u32 = 0;
1096        let mut index = 0;
1097        while index < N {
1098            let v = array[index];
1099            let v_tz = <T as PrimBits>::trailing_zeros(v);
1100            // See `const_leading_zeros_ct` / `const_ct_select` for why
1101            // `black_box` is here.
1102            let undecided = core::hint::black_box(!decided);
1103            total += undecided & v_tz;
1104            let v_nz_bit = (!<T as Zero>::is_zero(&v)) as u32;
1105            let v_nz_mask = core::hint::black_box(v_nz_bit.wrapping_neg());
1106            decided |= v_nz_mask;
1107            index += 1;
1108        }
1109        total
1110    }
1111
1112    /// Get bit length of array (total bits - leading zeros)
1113    pub(crate) c0nst fn const_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
1114        array: &[T; N],
1115    ) -> usize {
1116        let word_bits = const_word_bits::<T>();
1117        let bit_size = N * word_bits;
1118        bit_size - const_leading_zeros::<T, N>(array) as usize
1119    }
1120
1121    /// Check if array is zero
1122    pub(crate) c0nst fn const_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
1123        array: &[T; N],
1124    ) -> bool {
1125        let mut index = 0;
1126        while index < N {
1127            if !<T as Zero>::is_zero(&array[index]) {
1128                return false;
1129            }
1130            index += 1;
1131        }
1132        true
1133    }
1134
1135    /// CT variant of `const_is_zero`: OR-folds all N limbs into one accumulator
1136    /// before checking, so timing is uniform regardless of where (or whether)
1137    /// a non-zero limb appears. Used by the `Ct`-personality arm of
1138    /// `ConstZero::is_zero`.
1139    pub(crate) c0nst fn const_is_zero_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1140        array: &[T; N],
1141    ) -> bool {
1142        let mut acc = <T as ConstZero>::ZERO;
1143        let mut index = 0;
1144        while index < N {
1145            acc = <T as core::ops::BitOr>::bitor(acc, array[index]);
1146            index += 1;
1147        }
1148        <T as Zero>::is_zero(&acc)
1149    }
1150
1151    /// Check if array is one. Short-circuits as soon as a non-matching limb
1152    /// is found, so timing leaks where the array first deviates from the
1153    /// canonical "one" representation. Used by the `Nct`-personality arm of
1154    /// `ConstOne::is_one`.
1155    pub(crate) c0nst fn const_is_one<T: [c0nst] ConstMachineWord, const N: usize>(
1156        array: &[T; N],
1157    ) -> bool {
1158        if N == 0 || !array[0].is_one() {
1159            return false;
1160        }
1161        let mut i = 1;
1162        while i < N {
1163            if !<T as Zero>::is_zero(&array[i]) {
1164                return false;
1165            }
1166            i += 1;
1167        }
1168        true
1169    }
1170
1171    /// CT variant of `const_is_one`: folds `(array[0] ^ 1) | array[1] | ...`
1172    /// into one accumulator before checking, so timing does not depend on
1173    /// *where* the array first differs from the canonical "one"
1174    /// representation. Used by the `Ct`-personality arm of `ConstOne::is_one`.
1175    pub(crate) c0nst fn const_is_one_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1176        array: &[T; N],
1177    ) -> bool {
1178        if N == 0 {
1179            return false;
1180        }
1181        let mut acc = <T as core::ops::BitXor>::bitxor(array[0], <T as ConstOne>::ONE);
1182        let mut index = 1;
1183        while index < N {
1184            acc = <T as core::ops::BitOr>::bitor(acc, array[index]);
1185            index += 1;
1186        }
1187        <T as Zero>::is_zero(&acc)
1188    }
1189
1190    /// Set a specific bit in the array.
1191    ///
1192    /// The array uses little-endian representation where index 0 contains
1193    /// the least significant word, and bit 0 is the least significant bit
1194    /// of the entire integer.
1195    pub(crate) c0nst fn const_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
1196        array: &mut [T; N],
1197        pos: usize,
1198    ) {
1199        let word_bits = const_word_bits::<T>();
1200        let word_idx = pos / word_bits;
1201        if word_idx >= N {
1202            return;
1203        }
1204        let bit_idx = pos % word_bits;
1205        array[word_idx] |= <T as ConstOne>::ONE << bit_idx;
1206    }
1207
1208    /// Compare two arrays in a const-compatible way.
1209    ///
1210    /// Arrays use little-endian representation where index 0 contains
1211    /// the least significant word.
1212    pub(crate) c0nst fn const_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
1213        a: &[T; N],
1214        b: &[T; N],
1215    ) -> core::cmp::Ordering {
1216        let mut index = N;
1217        while index > 0 {
1218            index -= 1;
1219            if let Some(ord) = const_cmp_words(a[index], b[index]) {
1220                return ord;
1221            }
1222        }
1223        core::cmp::Ordering::Equal
1224    }
1225
1226    /// CT variant of `const_cmp`: scans every limb from high to low without
1227    /// short-circuiting; once the first differing limb is seen, subsequent
1228    /// limbs cannot overturn the locked decision. Used by the `Ct`-personality
1229    /// arm of `Ord::cmp` (and therefore `PartialOrd::partial_cmp`).
1230    pub(crate) c0nst fn const_cmp_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1231        a: &[T; N],
1232        b: &[T; N],
1233    ) -> core::cmp::Ordering {
1234        // result encoding: 2 = Greater, 1 = Less, 0 = Equal.
1235        let mut result: u8 = 0;
1236        // 0 while still undecided; u8::MAX once a differing limb has been seen.
1237        let mut decided: u8 = 0;
1238        let mut index = N;
1239        while index > 0 {
1240            index -= 1;
1241            let gt = (a[index] > b[index]) as u8;
1242            let lt = (a[index] < b[index]) as u8;
1243            // here ∈ {0, 1, 2}: 2 for Greater, 1 for Less, 0 for Equal.
1244            let here = (gt << 1) | lt;
1245            // See `const_ct_select` for why `black_box` is here.
1246            let undecided_mask = core::hint::black_box(!decided);
1247            result |= undecided_mask & here;
1248            // Lock the decision the moment a non-zero `here` is observed.
1249            let here_nz_mask = core::hint::black_box(((here != 0) as u8).wrapping_neg());
1250            decided |= here_nz_mask;
1251        }
1252        match result {
1253            2 => core::cmp::Ordering::Greater,
1254            1 => core::cmp::Ordering::Less,
1255            _ => core::cmp::Ordering::Equal,
1256        }
1257    }
1258
1259    /// Get the value of array's word at position `word_idx` when logically shifted left.
1260    ///
1261    /// This helper computes what value would be at `word_idx` if the array
1262    /// were shifted left by `word_shift` words plus `bit_shift` bits.
1263    pub(crate) c0nst fn const_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
1264        array: &[T; N],
1265        word_idx: usize,
1266        word_shift: usize,
1267        bit_shift: usize,
1268    ) -> T {
1269        let word_bits = const_word_bits::<T>();
1270
1271        // Guard against invalid bit_shift that would cause UB
1272        if bit_shift >= word_bits {
1273            return <T as ConstZero>::ZERO;
1274        }
1275
1276        if word_idx < word_shift {
1277            return <T as ConstZero>::ZERO;
1278        }
1279
1280        let source_idx = word_idx - word_shift;
1281
1282        if bit_shift == 0 {
1283            if source_idx < N {
1284                array[source_idx]
1285            } else {
1286                <T as ConstZero>::ZERO
1287            }
1288        } else {
1289            let mut result = <T as ConstZero>::ZERO;
1290
1291            // Get bits from the primary source word
1292            if source_idx < N {
1293                result |= array[source_idx] << bit_shift;
1294            }
1295
1296            // Get high bits from the next lower word (if it exists)
1297            if source_idx > 0 && source_idx - 1 < N {
1298                let high_bits = array[source_idx - 1] >> (word_bits - bit_shift);
1299                result |= high_bits;
1300            }
1301
1302            result
1303        }
1304    }
1305
1306    /// Compare array vs (other << shift_bits) in a const-compatible way.
1307    ///
1308    /// This is useful for division algorithms where we need to compare
1309    /// the dividend against a shifted divisor without allocating.
1310    pub(crate) c0nst fn const_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1311        array: &[T; N],
1312        other: &[T; N],
1313        shift_bits: usize,
1314    ) -> core::cmp::Ordering {
1315        let word_bits = const_word_bits::<T>();
1316
1317        if shift_bits == 0 {
1318            return const_cmp::<T, N>(array, other);
1319        }
1320
1321        let word_shift = shift_bits / word_bits;
1322        if word_shift >= N {
1323            // other << shift_bits would overflow to 0
1324            if const_is_zero::<T, N>(array) {
1325                return core::cmp::Ordering::Equal;
1326            } else {
1327                return core::cmp::Ordering::Greater;
1328            }
1329        }
1330
1331        let bit_shift = shift_bits % word_bits;
1332
1333        // Compare from most significant words down
1334        let mut index = N;
1335        while index > 0 {
1336            index -= 1;
1337            let self_word = array[index];
1338            let other_shifted_word = const_get_shifted_word::<T, N>(
1339                other, index, word_shift, bit_shift
1340            );
1341
1342            if let Some(ord) = const_cmp_words(self_word, other_shifted_word) {
1343                return ord;
1344            }
1345        }
1346
1347        core::cmp::Ordering::Equal
1348    }
1349
1350    /// Subtract (other << shift_bits) from array in-place.
1351    ///
1352    /// This is used in division algorithms to subtract shifted divisor
1353    /// from the remainder without allocating.
1354    pub(crate) c0nst fn const_sub_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1355        array: &mut [T; N],
1356        other: &[T; N],
1357        shift_bits: usize,
1358    ) {
1359        let word_bits = const_word_bits::<T>();
1360
1361        if shift_bits == 0 {
1362            sub_impl::<T, N>(array, other);
1363            return;
1364        }
1365
1366        let word_shift = shift_bits / word_bits;
1367        if word_shift >= N {
1368            return;
1369        }
1370
1371        let bit_shift = shift_bits % word_bits;
1372        let mut borrow = T::zero();
1373        let mut index = 0;
1374        while index < N {
1375            let other_word = const_get_shifted_word::<T, N>(other, index, word_shift, bit_shift);
1376            let (res, borrow1) = array[index].overflowing_sub(other_word);
1377            let (res, borrow2) = res.overflowing_sub(borrow);
1378            borrow = if borrow1 || borrow2 { T::one() } else { T::zero() };
1379            array[index] = res;
1380            index += 1;
1381        }
1382    }
1383
1384    /// In-place division: dividend becomes quotient, returns remainder.
1385    ///
1386    /// Low-level const-compatible division on arrays.
1387    pub(crate) c0nst fn const_div<T: [c0nst] ConstMachineWord, const N: usize>(
1388        dividend: &mut [T; N],
1389        divisor: &[T; N],
1390    ) -> [T; N] {
1391        use core::cmp::Ordering;
1392
1393        match const_cmp::<T, N>(dividend, divisor) {
1394            // dividend < divisor: quotient = 0, remainder = dividend
1395            Ordering::Less => {
1396                let remainder = *dividend;
1397                let mut i = 0;
1398                while i < N {
1399                    dividend[i] = <T as ConstZero>::ZERO;
1400                    i += 1;
1401                }
1402                return remainder;
1403            }
1404            // dividend == divisor: quotient = 1, remainder = 0
1405            Ordering::Equal => {
1406                let mut i = 0;
1407                while i < N {
1408                    dividend[i] = <T as ConstZero>::ZERO;
1409                    i += 1;
1410                }
1411                if N > 0 {
1412                    dividend[0] = <T as ConstOne>::ONE;
1413                }
1414                return [<T as ConstZero>::ZERO; N];
1415            }
1416            Ordering::Greater => {}
1417        }
1418
1419        let mut quotient = [<T as ConstZero>::ZERO; N];
1420
1421        // Calculate initial bit position
1422        let dividend_bits = const_bit_length::<T, N>(dividend);
1423        let divisor_bits = const_bit_length::<T, N>(divisor);
1424
1425        let mut bit_pos = if dividend_bits >= divisor_bits {
1426            dividend_bits - divisor_bits
1427        } else {
1428            0
1429        };
1430
1431        // Adjust bit position to find the first position where divisor can be subtracted
1432        while bit_pos > 0 {
1433            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
1434            if !matches!(cmp, Ordering::Less) {
1435                break;
1436            }
1437            bit_pos -= 1;
1438        }
1439
1440        // Main division loop
1441        loop {
1442            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
1443            if !matches!(cmp, Ordering::Less) {
1444                const_sub_shifted::<T, N>(dividend, divisor, bit_pos);
1445                const_set_bit::<T, N>(&mut quotient, bit_pos);
1446            }
1447
1448            if bit_pos == 0 {
1449                break;
1450            }
1451            bit_pos -= 1;
1452        }
1453
1454        let remainder = *dividend;
1455        *dividend = quotient;
1456        remainder
1457    }
1458
1459    /// Const-compatible div_rem: returns (quotient, remainder).
1460    ///
1461    /// Panics on divide by zero.
1462    pub(crate) c0nst fn const_div_rem<T: [c0nst] ConstMachineWord, const N: usize>(
1463        dividend: &[T; N],
1464        divisor: &[T; N],
1465    ) -> ([T; N], [T; N]) {
1466        if const_is_zero(divisor) {
1467            maybe_panic(PanicReason::DivByZero)
1468        }
1469        let mut quotient = *dividend;
1470        let remainder = const_div(&mut quotient, divisor);
1471        (quotient, remainder)
1472    }
1473}
1474
1475c0nst::c0nst! {
1476    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> Default for FixedUInt<T, N, P> {
1477        fn default() -> Self {
1478            FixedUInt::from_array([<T as ConstZero>::ZERO; N])
1479        }
1480    }
1481
1482    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> Clone for FixedUInt<T, N, P> {
1483        fn clone(&self) -> Self {
1484            *self
1485        }
1486    }
1487}
1488
1489// num_traits::Unsigned requires Num as a supertrait; Num is Nct-only,
1490// so Unsigned is Nct-only too.
1491#[cfg(feature = "num-traits")]
1492impl<T: MachineWord, const N: usize> num_traits::Unsigned for FixedUInt<T, N, Nct> {}
1493
1494// #region Equality and Ordering
1495
1496c0nst::c0nst! {
1497    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::PartialEq for FixedUInt<T, N, P> {
1498        // Ct arm is branchless (XOR-fold), but the return type is still
1499        // a plain `bool`. A caller that branches on the result of `==`
1500        // — e.g. `if a == b { … } else { … }` — leaks the equality bit.
1501        // Ct-secure equality on secret operands should route through
1502        // `subtle::ConstantTimeEq::ct_eq` and consume the resulting
1503        // `Choice` via `CtOption` / `ConditionallySelectable`.
1504        fn eq(&self, other: &Self) -> bool {
1505            match P::TAG {
1506                PersonalityTag::Nct => self.array == other.array,
1507                PersonalityTag::Ct => {
1508                    let mut diff = <T as ConstZero>::ZERO;
1509                    let mut i = 0;
1510                    while i < N {
1511                        let x = <T as core::ops::BitXor>::bitxor(self.array[i], other.array[i]);
1512                        diff = <T as core::ops::BitOr>::bitor(diff, x);
1513                        i += 1;
1514                    }
1515                    <T as Zero>::is_zero(&diff)
1516                }
1517            }
1518        }
1519    }
1520
1521    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::Eq for FixedUInt<T, N, P> {}
1522
1523    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::Ord for FixedUInt<T, N, P> {
1524        fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1525            match P::TAG {
1526                PersonalityTag::Nct => const_cmp(&self.array, &other.array),
1527                PersonalityTag::Ct => const_cmp_ct(&self.array, &other.array),
1528            }
1529        }
1530    }
1531
1532    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::PartialOrd for FixedUInt<T, N, P> {
1533        fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1534            Some(self.cmp(other))
1535        }
1536    }
1537}
1538
1539// #endregion Equality and Ordering
1540
1541// #region core::convert::From<primitive>
1542
1543c0nst::c0nst! {
1544    /// Const-compatible conversion from little-endian bytes to array of words.
1545    /// Delegates to impl_from_le_bytes_slice to avoid code duplication.
1546    c0nst fn const_from_le_bytes<T: [c0nst] ConstMachineWord, const N: usize, const B: usize>(
1547        bytes: [u8; B],
1548    ) -> [T; N] {
1549        impl_from_le_bytes_slice::<T, N>(&bytes)
1550    }
1551
1552    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u8> for FixedUInt<T, N, P> {
1553        fn from(x: u8) -> Self {
1554            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1555        }
1556    }
1557
1558    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u16> for FixedUInt<T, N, P> {
1559        fn from(x: u16) -> Self {
1560            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1561        }
1562    }
1563
1564    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u32> for FixedUInt<T, N, P> {
1565        fn from(x: u32) -> Self {
1566            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1567        }
1568    }
1569
1570    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u64> for FixedUInt<T, N, P> {
1571        fn from(x: u64) -> Self {
1572            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1573        }
1574    }
1575}
1576
1577// #endregion core::convert::From<primitive>
1578
1579// #region helpers
1580
1581// This is slightly less than ideal, but PIE isn't directly constructible
1582// due to unstable members.
1583fn make_parse_int_err() -> core::num::ParseIntError {
1584    <u8>::from_str_radix("-", 2).err().unwrap()
1585}
1586#[cfg(feature = "num-traits")]
1587fn make_overflow_err() -> core::num::ParseIntError {
1588    <u8>::from_str_radix("101", 16).err().unwrap()
1589}
1590#[cfg(feature = "num-traits")]
1591fn make_empty_error() -> core::num::ParseIntError {
1592    <u8>::from_str_radix("", 8).err().unwrap()
1593}
1594
1595fn to_slice_hex<T: AsRef<[u8]>>(
1596    input: T,
1597    output: &mut [u8],
1598) -> Result<(), core::num::ParseIntError> {
1599    fn from_digit(byte: u8) -> Option<char> {
1600        core::char::from_digit(byte as u32, 16)
1601    }
1602    let r = input.as_ref();
1603    if r.len() * 2 != output.len() {
1604        return Err(make_parse_int_err());
1605    }
1606    for i in 0..r.len() {
1607        let byte = r[i];
1608        output[i * 2] = from_digit((byte & 0xf0) >> 4).ok_or_else(make_parse_int_err)? as u8;
1609        output[i * 2 + 1] = from_digit(byte & 0x0f).ok_or_else(make_parse_int_err)? as u8;
1610    }
1611
1612    Ok(())
1613}
1614
1615pub(super) enum PanicReason {
1616    Add,
1617    Sub,
1618    Mul,
1619    DivByZero,
1620}
1621
1622c0nst::c0nst! {
1623    pub(super) c0nst fn maybe_panic(r: PanicReason) {
1624        match r {
1625            PanicReason::Add => panic!("attempt to add with overflow"),
1626            PanicReason::Sub => panic!("attempt to subtract with overflow"),
1627            PanicReason::Mul => panic!("attempt to multiply with overflow"),
1628            PanicReason::DivByZero => panic!("attempt to divide by zero"),
1629        }
1630    }
1631
1632    /// Branchless per-limb select: returns `if_zero` when `choice == 0`,
1633    /// `if_one` when `choice == 1`.
1634    ///
1635    /// The `black_box` on `choice` is load-bearing. Without it, LLVM
1636    /// recognizes the algebraic identity `a ^ (mask & (a ^ b))` ==
1637    /// `if mask == 0 { a } else { b }` and rewrites the loop into a
1638    /// `csel` of the source ADDRESS followed by a load — a secret-
1639    /// dependent memory access that the asm-grep gate can't see but
1640    /// that the ctgrind taint pass catches. Opacifying the choice
1641    /// before it flows into `mask` keeps LLVM from proving the
1642    /// equivalence in the first place. This mirrors what `subtle`'s
1643    /// `Choice::from(u8)` does internally.
1644    pub(crate) c0nst fn const_ct_select<
1645        T: [c0nst] ConstMachineWord + MachineWord,
1646        const N: usize,
1647        P: Personality,
1648    >(
1649        if_zero: FixedUInt<T, N, P>,
1650        if_one: FixedUInt<T, N, P>,
1651        choice: u8,
1652    ) -> FixedUInt<T, N, P> {
1653        let choice = core::hint::black_box(choice);
1654        let bit_t = <T as core::convert::From<u8>>::from(choice);
1655        let mask = <T as core::ops::Mul>::mul(bit_t, <T as Bounded>::max_value());
1656        let mut result = if_zero;
1657        let mut i = 0;
1658        while i < N {
1659            let diff = <T as core::ops::BitXor>::bitxor(if_zero.array[i], if_one.array[i]);
1660            let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
1661            result.array[i] = <T as core::ops::BitXor>::bitxor(if_zero.array[i], masked);
1662            i += 1;
1663        }
1664        result
1665    }
1666
1667    pub(super) c0nst fn maybe_panic_if<P: Personality>(
1668        overflow: bool,
1669        reason: PanicReason,
1670    ) {
1671        match P::TAG {
1672            PersonalityTag::Nct => {
1673                if overflow {
1674                    maybe_panic(reason);
1675                }
1676            }
1677            PersonalityTag::Ct => {
1678                let _ = overflow;
1679                let _ = reason;
1680            }
1681        }
1682    }
1683}
1684
1685// #endregion helpers
1686
1687#[cfg(test)]
1688#[cfg(feature = "num-traits")]
1689mod tests {
1690    use super::FixedUInt as Bn;
1691    use super::*;
1692    use const_num_traits::{One, Zero};
1693    use num_traits::{FromPrimitive, Num, ToPrimitive};
1694
1695    type Bn8 = Bn<u8, 8>;
1696    type Bn16 = Bn<u16, 4>;
1697    type Bn32 = Bn<u32, 2>;
1698
1699    c0nst::c0nst! {
1700        pub c0nst fn test_add<T: [c0nst] ConstMachineWord, const N: usize>(
1701            a: &mut [T; N],
1702            b: &[T; N]
1703        ) -> bool {
1704            add_impl(a, b)
1705        }
1706
1707        pub c0nst fn test_sub<T: [c0nst] ConstMachineWord, const N: usize>(
1708            a: &mut [T; N],
1709            b: &[T; N]
1710        ) -> bool {
1711            sub_impl(a, b)
1712        }
1713
1714        pub c0nst fn test_mul<T: [c0nst] ConstMachineWord, const N: usize>(
1715            a: &[T; N],
1716            b: &[T; N],
1717            word_bits: usize,
1718        ) -> ([T; N], bool) {
1719            const_mul::<T, N, true, const_num_traits::Nct>(a, b, word_bits)
1720        }
1721
1722        pub c0nst fn arr_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1723            a: &[T; N],
1724        ) -> u32 {
1725            const_leading_zeros::<T, N>(a)
1726        }
1727
1728        pub c0nst fn arr_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1729            a: &[T; N],
1730        ) -> u32 {
1731            const_trailing_zeros::<T, N>(a)
1732        }
1733
1734        pub c0nst fn arr_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
1735            a: &[T; N],
1736        ) -> usize {
1737            const_bit_length::<T, N>(a)
1738        }
1739
1740        pub c0nst fn arr_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
1741            a: &[T; N],
1742        ) -> bool {
1743            const_is_zero::<T, N>(a)
1744        }
1745
1746        pub c0nst fn arr_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
1747            a: &mut [T; N],
1748            pos: usize,
1749        ) {
1750            const_set_bit::<T, N>(a, pos)
1751        }
1752
1753        pub c0nst fn arr_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
1754            a: &[T; N],
1755            b: &[T; N],
1756        ) -> core::cmp::Ordering {
1757            const_cmp::<T, N>(a, b)
1758        }
1759
1760        pub c0nst fn arr_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1761            a: &[T; N],
1762            b: &[T; N],
1763            shift_bits: usize,
1764        ) -> core::cmp::Ordering {
1765            const_cmp_shifted::<T, N>(a, b, shift_bits)
1766        }
1767
1768        pub c0nst fn arr_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
1769            a: &[T; N],
1770            word_idx: usize,
1771            word_shift: usize,
1772            bit_shift: usize,
1773        ) -> T {
1774            const_get_shifted_word::<T, N>(a, word_idx, word_shift, bit_shift)
1775        }
1776    }
1777
1778    #[test]
1779    fn test_const_add_impl() {
1780        // Simple add, no overflow
1781        let mut a: [u8; 4] = [1, 0, 0, 0];
1782        let b: [u8; 4] = [2, 0, 0, 0];
1783        let overflow = test_add(&mut a, &b);
1784        assert_eq!(a, [3, 0, 0, 0]);
1785        assert!(!overflow);
1786
1787        // Add with carry propagation
1788        let mut a: [u8; 4] = [255, 0, 0, 0];
1789        let b: [u8; 4] = [1, 0, 0, 0];
1790        let overflow = test_add(&mut a, &b);
1791        assert_eq!(a, [0, 1, 0, 0]);
1792        assert!(!overflow);
1793
1794        // Add with overflow
1795        let mut a: [u8; 4] = [255, 255, 255, 255];
1796        let b: [u8; 4] = [1, 0, 0, 0];
1797        let overflow = test_add(&mut a, &b);
1798        assert_eq!(a, [0, 0, 0, 0]);
1799        assert!(overflow);
1800
1801        // Test with u32 words
1802        let mut a: [u32; 2] = [0xFFFFFFFF, 0];
1803        let b: [u32; 2] = [1, 0];
1804        let overflow = test_add(&mut a, &b);
1805        assert_eq!(a, [0, 1]);
1806        assert!(!overflow);
1807
1808        #[cfg(feature = "nightly")]
1809        {
1810            const ADD_RESULT: ([u8; 4], bool) = {
1811                let mut a = [1u8, 0, 0, 0];
1812                let b = [2u8, 0, 0, 0];
1813                let overflow = test_add(&mut a, &b);
1814                (a, overflow)
1815            };
1816            assert_eq!(ADD_RESULT, ([3, 0, 0, 0], false));
1817        }
1818    }
1819
1820    #[test]
1821    fn test_const_sub_impl() {
1822        // Simple sub, no overflow
1823        let mut a: [u8; 4] = [3, 0, 0, 0];
1824        let b: [u8; 4] = [1, 0, 0, 0];
1825        let overflow = test_sub(&mut a, &b);
1826        assert_eq!(a, [2, 0, 0, 0]);
1827        assert!(!overflow);
1828
1829        // Sub with borrow propagation
1830        let mut a: [u8; 4] = [0, 1, 0, 0];
1831        let b: [u8; 4] = [1, 0, 0, 0];
1832        let overflow = test_sub(&mut a, &b);
1833        assert_eq!(a, [255, 0, 0, 0]);
1834        assert!(!overflow);
1835
1836        // Sub with underflow
1837        let mut a: [u8; 4] = [0, 0, 0, 0];
1838        let b: [u8; 4] = [1, 0, 0, 0];
1839        let overflow = test_sub(&mut a, &b);
1840        assert_eq!(a, [255, 255, 255, 255]);
1841        assert!(overflow);
1842
1843        // Test with u32 words
1844        let mut a: [u32; 2] = [0, 1];
1845        let b: [u32; 2] = [1, 0];
1846        let overflow = test_sub(&mut a, &b);
1847        assert_eq!(a, [0xFFFFFFFF, 0]);
1848        assert!(!overflow);
1849
1850        #[cfg(feature = "nightly")]
1851        {
1852            const SUB_RESULT: ([u8; 4], bool) = {
1853                let mut a = [3u8, 0, 0, 0];
1854                let b = [1u8, 0, 0, 0];
1855                let overflow = test_sub(&mut a, &b);
1856                (a, overflow)
1857            };
1858            assert_eq!(SUB_RESULT, ([2, 0, 0, 0], false));
1859        }
1860    }
1861
1862    #[test]
1863    fn test_const_mul_impl() {
1864        // Simple mul: 3 * 4 = 12
1865        let a: [u8; 2] = [3, 0];
1866        let b: [u8; 2] = [4, 0];
1867        let (result, overflow) = test_mul(&a, &b, 8);
1868        assert_eq!(result, [12, 0]);
1869        assert!(!overflow);
1870
1871        // Mul with carry: 200 * 2 = 400 = 0x190 = [0x90, 0x01]
1872        let a: [u8; 2] = [200, 0];
1873        let b: [u8; 2] = [2, 0];
1874        let (result, overflow) = test_mul(&a, &b, 8);
1875        assert_eq!(result, [0x90, 0x01]);
1876        assert!(!overflow);
1877
1878        // Mul with overflow: 256 * 256 = 65536 which overflows 16 bits
1879        let a: [u8; 2] = [0, 1]; // 256
1880        let b: [u8; 2] = [0, 1]; // 256
1881        let (_result, overflow) = test_mul(&a, &b, 8);
1882        assert!(overflow);
1883
1884        // N=3 overflow at high position (round=4, i=2, j=2)
1885        // a = [0, 0, 1] = 65536, b = [0, 0, 1] = 65536
1886        // a * b = 65536^2 = 4294967296 which overflows 24 bits
1887        let a: [u8; 3] = [0, 0, 1];
1888        let b: [u8; 3] = [0, 0, 1];
1889        let (_result, overflow) = test_mul(&a, &b, 8);
1890        assert!(overflow, "N=3 high-position overflow not detected");
1891
1892        // N=3 overflow with larger high word values
1893        // a = [0, 0, 2] = 131072, b = [0, 0, 2] = 131072
1894        // a * b = 131072^2 = 17179869184 which overflows 24 bits
1895        let a: [u8; 3] = [0, 0, 2];
1896        let b: [u8; 3] = [0, 0, 2];
1897        let (_result, overflow) = test_mul(&a, &b, 8);
1898        assert!(
1899            overflow,
1900            "N=3 high-position overflow with larger values not detected"
1901        );
1902
1903        // N=3 non-overflow case: values that fit in 24 bits
1904        // a = [0, 1, 0] = 256, b = [0, 1, 0] = 256
1905        // a * b = 256 * 256 = 65536 = [0, 0, 1] which fits in 24 bits
1906        let a: [u8; 3] = [0, 1, 0];
1907        let b: [u8; 3] = [0, 1, 0];
1908        let (result, overflow) = test_mul(&a, &b, 8);
1909        assert_eq!(result, [0, 0, 1]);
1910        assert!(
1911            !overflow,
1912            "N=3 non-overflow incorrectly detected as overflow"
1913        );
1914
1915        // N=3 non-overflow with carry propagation
1916        // a = [255, 0, 0] = 255, b = [255, 0, 0] = 255
1917        // a * b = 255 * 255 = 65025 = 0xFE01 = [0x01, 0xFE, 0x00]
1918        let a: [u8; 3] = [255, 0, 0];
1919        let b: [u8; 3] = [255, 0, 0];
1920        let (result, overflow) = test_mul(&a, &b, 8);
1921        assert_eq!(result, [0x01, 0xFE, 0x00]);
1922        assert!(!overflow);
1923
1924        #[cfg(feature = "nightly")]
1925        {
1926            const MUL_RESULT: ([u8; 2], bool) = test_mul(&[3u8, 0], &[4u8, 0], 8);
1927            assert_eq!(MUL_RESULT, ([12, 0], false));
1928        }
1929    }
1930
1931    #[test]
1932    fn test_const_helpers() {
1933        // Test leading_zeros
1934        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1935        assert_eq!(arr_leading_zeros(&[1u8, 0, 0, 0]), 31); // single bit
1936        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 1]), 7); // high byte has 1
1937        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0x80]), 0); // MSB set
1938        assert_eq!(arr_leading_zeros(&[255u8, 255, 255, 255]), 0); // all ones
1939
1940        // Test trailing_zeros
1941        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1942        assert_eq!(arr_trailing_zeros(&[1u8, 0, 0, 0]), 0); // LSB set
1943        assert_eq!(arr_trailing_zeros(&[0u8, 1, 0, 0]), 8); // second byte
1944        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 1]), 24); // fourth byte
1945        assert_eq!(arr_trailing_zeros(&[0x80u8, 0, 0, 0]), 7); // bit 7 of first byte
1946
1947        // Test bit_length
1948        assert_eq!(arr_bit_length(&[0u8, 0, 0, 0]), 0); // zero
1949        assert_eq!(arr_bit_length(&[1u8, 0, 0, 0]), 1); // 1
1950        assert_eq!(arr_bit_length(&[2u8, 0, 0, 0]), 2); // 2
1951        assert_eq!(arr_bit_length(&[3u8, 0, 0, 0]), 2); // 3
1952        assert_eq!(arr_bit_length(&[0u8, 1, 0, 0]), 9); // 256
1953        assert_eq!(arr_bit_length(&[0xF0u8, 0, 0, 0]), 8); // 240 (0xF0)
1954        assert_eq!(arr_bit_length(&[255u8, 255, 255, 255]), 32); // max
1955
1956        // Test is_zero
1957        assert!(arr_is_zero(&[0u8, 0, 0, 0]));
1958        assert!(!arr_is_zero(&[1u8, 0, 0, 0]));
1959        assert!(!arr_is_zero(&[0u8, 0, 0, 1]));
1960        assert!(!arr_is_zero(&[0u8, 1, 0, 0]));
1961
1962        // Test set_bit
1963        let mut arr: [u8; 4] = [0, 0, 0, 0];
1964        arr_set_bit(&mut arr, 0);
1965        assert_eq!(arr, [1, 0, 0, 0]);
1966
1967        let mut arr: [u8; 4] = [0, 0, 0, 0];
1968        arr_set_bit(&mut arr, 8);
1969        assert_eq!(arr, [0, 1, 0, 0]);
1970
1971        let mut arr: [u8; 4] = [0, 0, 0, 0];
1972        arr_set_bit(&mut arr, 31);
1973        assert_eq!(arr, [0, 0, 0, 0x80]);
1974
1975        // Set multiple bits
1976        let mut arr: [u8; 4] = [0, 0, 0, 0];
1977        arr_set_bit(&mut arr, 0);
1978        arr_set_bit(&mut arr, 3);
1979        arr_set_bit(&mut arr, 8);
1980        assert_eq!(arr, [0b00001001, 1, 0, 0]);
1981
1982        // Out of bounds should be no-op
1983        let mut arr: [u8; 4] = [0, 0, 0, 0];
1984        arr_set_bit(&mut arr, 32);
1985        assert_eq!(arr, [0, 0, 0, 0]);
1986
1987        // Test with u32 words
1988        assert_eq!(arr_leading_zeros(&[0u32, 0]), 64);
1989        assert_eq!(arr_leading_zeros(&[1u32, 0]), 63);
1990        assert_eq!(arr_leading_zeros(&[0u32, 1]), 31);
1991        assert_eq!(arr_trailing_zeros(&[0u32, 0]), 64);
1992        assert_eq!(arr_trailing_zeros(&[0u32, 1]), 32);
1993        assert_eq!(arr_bit_length(&[0u32, 0]), 0);
1994        assert_eq!(arr_bit_length(&[1u32, 0]), 1);
1995        assert_eq!(arr_bit_length(&[0u32, 1]), 33);
1996
1997        #[cfg(feature = "nightly")]
1998        {
1999            const LEADING: u32 = arr_leading_zeros(&[0u8, 0, 1, 0]);
2000            assert_eq!(LEADING, 15);
2001
2002            const TRAILING: u32 = arr_trailing_zeros(&[0u8, 0, 1, 0]);
2003            assert_eq!(TRAILING, 16);
2004
2005            const BIT_LEN: usize = arr_bit_length(&[0u8, 0, 1, 0]);
2006            assert_eq!(BIT_LEN, 17);
2007
2008            const IS_ZERO: bool = arr_is_zero(&[0u8, 0, 0, 0]);
2009            assert!(IS_ZERO);
2010
2011            const NOT_ZERO: bool = arr_is_zero(&[0u8, 1, 0, 0]);
2012            assert!(!NOT_ZERO);
2013
2014            const SET_BIT_RESULT: [u8; 4] = {
2015                let mut arr = [0u8, 0, 0, 0];
2016                arr_set_bit(&mut arr, 10);
2017                arr
2018            };
2019            assert_eq!(SET_BIT_RESULT, [0, 0b00000100, 0, 0]);
2020        }
2021    }
2022
2023    #[test]
2024    fn test_const_cmp() {
2025        use core::cmp::Ordering;
2026
2027        // Equal arrays
2028        assert_eq!(arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]), Ordering::Equal);
2029        assert_eq!(arr_cmp(&[0u8, 0, 0, 0], &[0u8, 0, 0, 0]), Ordering::Equal);
2030
2031        // Greater - high word differs
2032        assert_eq!(arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]), Ordering::Greater);
2033
2034        // Less - high word differs
2035        assert_eq!(arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]), Ordering::Less);
2036
2037        // Greater - low word differs (high words equal)
2038        assert_eq!(arr_cmp(&[2u8, 0, 0, 0], &[1u8, 0, 0, 0]), Ordering::Greater);
2039
2040        // Less - low word differs
2041        assert_eq!(arr_cmp(&[1u8, 0, 0, 0], &[2u8, 0, 0, 0]), Ordering::Less);
2042
2043        // Test with u32 words
2044        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 1]), Ordering::Equal);
2045        assert_eq!(arr_cmp(&[0u32, 2], &[0u32, 1]), Ordering::Greater);
2046        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 2]), Ordering::Less);
2047
2048        #[cfg(feature = "nightly")]
2049        {
2050            const CMP_EQ: Ordering = arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]);
2051            const CMP_GT: Ordering = arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]);
2052            const CMP_LT: Ordering = arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]);
2053            assert_eq!(CMP_EQ, Ordering::Equal);
2054            assert_eq!(CMP_GT, Ordering::Greater);
2055            assert_eq!(CMP_LT, Ordering::Less);
2056        }
2057    }
2058
2059    #[test]
2060    fn test_const_cmp_shifted() {
2061        use core::cmp::Ordering;
2062
2063        // No shift - same as regular cmp
2064        assert_eq!(
2065            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 0),
2066            Ordering::Equal
2067        );
2068
2069        // Compare [0, 1, 0, 0] (256) vs [1, 0, 0, 0] << 8 (256) = Equal
2070        assert_eq!(
2071            arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8),
2072            Ordering::Equal
2073        );
2074
2075        // Compare [0, 2, 0, 0] (512) vs [1, 0, 0, 0] << 8 (256) = Greater
2076        assert_eq!(
2077            arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8),
2078            Ordering::Greater
2079        );
2080
2081        // Compare [0, 0, 0, 0] (0) vs [1, 0, 0, 0] << 8 (256) = Less
2082        assert_eq!(
2083            arr_cmp_shifted(&[0u8, 0, 0, 0], &[1u8, 0, 0, 0], 8),
2084            Ordering::Less
2085        );
2086
2087        // Shift overflow: shift >= bit_size, other becomes 0
2088        // Compare [1, 0, 0, 0] vs [1, 0, 0, 0] << 32 (0) = Greater
2089        assert_eq!(
2090            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 32),
2091            Ordering::Greater
2092        );
2093
2094        // Compare [0, 0, 0, 0] vs anything << 32 (0) = Equal
2095        assert_eq!(
2096            arr_cmp_shifted(&[0u8, 0, 0, 0], &[255u8, 255, 255, 255], 32),
2097            Ordering::Equal
2098        );
2099
2100        // Test get_shifted_word helper with bit_shift == 0
2101        // [1, 2, 3, 4] shifted left by 1 word (8 bits for u8)
2102        // word 0 should be 0, word 1 should be 1, word 2 should be 2, etc.
2103        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 0, 1, 0), 0);
2104        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 1, 1, 0), 1);
2105        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 2, 1, 0), 2);
2106
2107        // Test get_shifted_word with bit_shift != 0 (cross-word bit combination)
2108        // [0x0F, 0xF0, 0, 0] with word_shift=0, bit_shift=4
2109        // word 0: 0x0F << 4 = 0xF0 (no lower word to borrow from)
2110        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 0, 0, 4), 0xF0);
2111        // word 1: (0xF0 << 4) | (0x0F >> 4) = 0x00 | 0x00 = 0x00
2112        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 1, 0, 4), 0x00);
2113
2114        // [0xFF, 0x00, 0, 0] with bit_shift=4
2115        // word 0: 0xFF << 4 = 0xF0
2116        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 0, 0, 4), 0xF0);
2117        // word 1: (0x00 << 4) | (0xFF >> 4) = 0x00 | 0x0F = 0x0F
2118        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 1, 0, 4), 0x0F);
2119
2120        // Combined word_shift and bit_shift
2121        // [0xAB, 0xCD, 0, 0] with word_shift=1, bit_shift=4
2122        // word 0: below word_shift, returns 0
2123        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 0, 1, 4), 0);
2124        // word 1: source_idx=0, 0xAB << 4 = 0xB0 (no lower word)
2125        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 1, 1, 4), 0xB0);
2126        // word 2: source_idx=1, (0xCD << 4) | (0xAB >> 4) = 0xD0 | 0x0A = 0xDA
2127        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 2, 1, 4), 0xDA);
2128
2129        #[cfg(feature = "nightly")]
2130        {
2131            const CMP_SHIFTED_EQ: Ordering = arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8);
2132            const CMP_SHIFTED_GT: Ordering = arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8);
2133            assert_eq!(CMP_SHIFTED_EQ, Ordering::Equal);
2134            assert_eq!(CMP_SHIFTED_GT, Ordering::Greater);
2135        }
2136    }
2137
2138    #[test]
2139    fn test_core_convert_u8() {
2140        let f = Bn::<u8, 1>::from(1u8);
2141        assert_eq!(f.array, [1]);
2142        let f = Bn::<u8, 2>::from(1u8);
2143        assert_eq!(f.array, [1, 0]);
2144
2145        let f = Bn::<u16, 1>::from(1u8);
2146        assert_eq!(f.array, [1]);
2147        let f = Bn::<u16, 2>::from(1u8);
2148        assert_eq!(f.array, [1, 0]);
2149
2150        #[cfg(feature = "nightly")]
2151        {
2152            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(42u8);
2153            assert_eq!(F1.array, [42, 0]);
2154        }
2155    }
2156
2157    #[test]
2158    fn test_core_convert_u16() {
2159        let f = Bn::<u8, 1>::from(1u16);
2160        assert_eq!(f.array, [1]);
2161        let f = Bn::<u8, 2>::from(1u16);
2162        assert_eq!(f.array, [1, 0]);
2163
2164        let f = Bn::<u8, 1>::from(256u16);
2165        assert_eq!(f.array, [0]);
2166        let f = Bn::<u8, 2>::from(257u16);
2167        assert_eq!(f.array, [1, 1]);
2168        let f = Bn::<u8, 2>::from(65535u16);
2169        assert_eq!(f.array, [255, 255]);
2170
2171        let f = Bn::<u16, 1>::from(1u16);
2172        assert_eq!(f.array, [1]);
2173        let f = Bn::<u16, 2>::from(1u16);
2174        assert_eq!(f.array, [1, 0]);
2175
2176        let f = Bn::<u16, 1>::from(65535u16);
2177        assert_eq!(f.array, [65535]);
2178
2179        #[cfg(feature = "nightly")]
2180        {
2181            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(0x0102u16);
2182            assert_eq!(F1.array, [0x02, 0x01]);
2183        }
2184    }
2185
2186    #[test]
2187    fn test_core_convert_u32() {
2188        let f = Bn::<u8, 1>::from(1u32);
2189        assert_eq!(f.array, [1]);
2190        let f = Bn::<u8, 1>::from(256u32);
2191        assert_eq!(f.array, [0]);
2192
2193        let f = Bn::<u8, 2>::from(1u32);
2194        assert_eq!(f.array, [1, 0]);
2195        let f = Bn::<u8, 2>::from(257u32);
2196        assert_eq!(f.array, [1, 1]);
2197        let f = Bn::<u8, 2>::from(65535u32);
2198        assert_eq!(f.array, [255, 255]);
2199
2200        let f = Bn::<u8, 4>::from(1u32);
2201        assert_eq!(f.array, [1, 0, 0, 0]);
2202        let f = Bn::<u8, 4>::from(257u32);
2203        assert_eq!(f.array, [1, 1, 0, 0]);
2204        let f = Bn::<u8, 4>::from(u32::MAX);
2205        assert_eq!(f.array, [255, 255, 255, 255]);
2206
2207        let f = Bn::<u8, 1>::from(1u32);
2208        assert_eq!(f.array, [1]);
2209        let f = Bn::<u8, 1>::from(256u32);
2210        assert_eq!(f.array, [0]);
2211
2212        let f = Bn::<u16, 2>::from(65537u32);
2213        assert_eq!(f.array, [1, 1]);
2214
2215        let f = Bn::<u32, 1>::from(1u32);
2216        assert_eq!(f.array, [1]);
2217        let f = Bn::<u32, 2>::from(1u32);
2218        assert_eq!(f.array, [1, 0]);
2219
2220        let f = Bn::<u32, 1>::from(65537u32);
2221        assert_eq!(f.array, [65537]);
2222
2223        let f = Bn::<u32, 1>::from(u32::MAX);
2224        assert_eq!(f.array, [4294967295]);
2225
2226        #[cfg(feature = "nightly")]
2227        {
2228            const F1: Bn<u8, 4> = Bn::<u8, 4>::from(0x01020304u32);
2229            assert_eq!(F1.array, [0x04, 0x03, 0x02, 0x01]);
2230        }
2231    }
2232
2233    #[test]
2234    fn test_core_convert_u64() {
2235        let f = Bn::<u8, 8>::from(0x0102030405060708u64);
2236        assert_eq!(f.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
2237
2238        let f = Bn::<u16, 4>::from(0x0102030405060708u64);
2239        assert_eq!(f.array, [0x0708, 0x0506, 0x0304, 0x0102]);
2240
2241        let f = Bn::<u32, 2>::from(0x0102030405060708u64);
2242        assert_eq!(f.array, [0x05060708, 0x01020304]);
2243
2244        let f = Bn::<u64, 1>::from(0x0102030405060708u64);
2245        assert_eq!(f.array, [0x0102030405060708]);
2246
2247        #[cfg(feature = "nightly")]
2248        {
2249            const F1: Bn<u8, 8> = Bn::<u8, 8>::from(0x0102030405060708u64);
2250            assert_eq!(F1.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
2251        }
2252    }
2253
2254    #[test]
2255    fn testsimple() {
2256        assert_eq!(Bn::<u8, 8>::new(), Bn::<u8, 8>::new());
2257
2258        assert_eq!(Bn::<u8, 8>::from_u8(3).unwrap().to_u32(), Some(3));
2259        assert_eq!(Bn::<u16, 4>::from_u8(3).unwrap().to_u32(), Some(3));
2260        assert_eq!(Bn::<u32, 2>::from_u8(3).unwrap().to_u32(), Some(3));
2261        assert_eq!(Bn::<u32, 2>::from_u64(3).unwrap().to_u32(), Some(3));
2262        assert_eq!(Bn::<u8, 8>::from_u64(255).unwrap().to_u32(), Some(255));
2263        assert_eq!(Bn::<u8, 8>::from_u64(256).unwrap().to_u32(), Some(256));
2264        assert_eq!(Bn::<u8, 8>::from_u64(65536).unwrap().to_u32(), Some(65536));
2265    }
2266    #[test]
2267    fn testfrom() {
2268        let mut n1 = Bn::<u8, 8>::new();
2269        n1.array[0] = 1;
2270        assert_eq!(Some(1), n1.to_u32());
2271        n1.array[1] = 1;
2272        assert_eq!(Some(257), n1.to_u32());
2273
2274        let mut n2 = Bn::<u16, 8>::new();
2275        n2.array[0] = 0xffff;
2276        assert_eq!(Some(65535), n2.to_u32());
2277        n2.array[0] = 0x0;
2278        n2.array[2] = 0x1;
2279        // Overflow
2280        assert_eq!(None, n2.to_u32());
2281        assert_eq!(Some(0x100000000), n2.to_u64());
2282    }
2283
2284    #[test]
2285    fn test_from_str_bitlengths() {
2286        let test_s64 = "81906f5e4d3c2c01";
2287        let test_u64: u64 = 0x81906f5e4d3c2c01;
2288        let bb = Bn8::from_str_radix(test_s64, 16).unwrap();
2289        let cc = Bn8::from_u64(test_u64).unwrap();
2290        assert_eq!(cc.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
2291        assert_eq!(bb.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
2292        let dd = Bn16::from_u64(test_u64).unwrap();
2293        let ff = Bn16::from_str_radix(test_s64, 16).unwrap();
2294        assert_eq!(dd.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
2295        assert_eq!(ff.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
2296        let ee = Bn32::from_u64(test_u64).unwrap();
2297        let gg = Bn32::from_str_radix(test_s64, 16).unwrap();
2298        assert_eq!(ee.array, [0x4d3c2c01, 0x81906f5e]);
2299        assert_eq!(gg.array, [0x4d3c2c01, 0x81906f5e]);
2300    }
2301
2302    #[test]
2303    fn test_from_str_stringlengths() {
2304        let ab = Bn::<u8, 9>::from_str_radix("2281906f5e4d3c2c01", 16).unwrap();
2305        assert_eq!(
2306            ab.array,
2307            [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81, 0x22]
2308        );
2309        assert_eq!(
2310            [0x2c01, 0x4d3c, 0x6f5e, 0],
2311            Bn::<u16, 4>::from_str_radix("6f5e4d3c2c01", 16)
2312                .unwrap()
2313                .array
2314        );
2315        assert_eq!(
2316            [0x2c01, 0x4d3c, 0x6f5e, 0x190],
2317            Bn::<u16, 4>::from_str_radix("1906f5e4d3c2c01", 16)
2318                .unwrap()
2319                .array
2320        );
2321        assert_eq!(
2322            Err(make_overflow_err()),
2323            Bn::<u16, 4>::from_str_radix("f81906f5e4d3c2c01", 16)
2324        );
2325        assert_eq!(
2326            Err(make_overflow_err()),
2327            Bn::<u16, 4>::from_str_radix("af81906f5e4d3c2c01", 16)
2328        );
2329        assert_eq!(
2330            Err(make_overflow_err()),
2331            Bn::<u16, 4>::from_str_radix("baaf81906f5e4d3c2c01", 16)
2332        );
2333        let ac = Bn::<u16, 5>::from_str_radix("baaf81906f5e4d3c2c01", 16).unwrap();
2334        assert_eq!(ac.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190, 0xbaaf]);
2335    }
2336
2337    #[test]
2338    fn test_resize() {
2339        type TestInt1 = FixedUInt<u32, 1>;
2340        type TestInt2 = FixedUInt<u32, 2>;
2341
2342        let a = TestInt1::from(u32::MAX);
2343        let b: TestInt2 = a.resize();
2344        assert_eq!(b, TestInt2::from([u32::MAX, 0]));
2345
2346        let a = TestInt2::from([u32::MAX, u32::MAX]);
2347        let b: TestInt1 = a.resize();
2348        assert_eq!(b, TestInt1::from(u32::MAX));
2349    }
2350
2351    #[test]
2352    fn test_bit_length() {
2353        assert_eq!(0, Bn8::from_u8(0).unwrap().bit_length());
2354        assert_eq!(1, Bn8::from_u8(1).unwrap().bit_length());
2355        assert_eq!(2, Bn8::from_u8(2).unwrap().bit_length());
2356        assert_eq!(2, Bn8::from_u8(3).unwrap().bit_length());
2357        assert_eq!(7, Bn8::from_u8(0x70).unwrap().bit_length());
2358        assert_eq!(8, Bn8::from_u8(0xF0).unwrap().bit_length());
2359        assert_eq!(9, Bn8::from_u16(0x1F0).unwrap().bit_length());
2360
2361        assert_eq!(20, Bn8::from_u64(990223).unwrap().bit_length());
2362        assert_eq!(32, Bn8::from_u64(0xefffffff).unwrap().bit_length());
2363        assert_eq!(32, Bn8::from_u64(0x8fffffff).unwrap().bit_length());
2364        assert_eq!(31, Bn8::from_u64(0x7fffffff).unwrap().bit_length());
2365        assert_eq!(34, Bn8::from_u64(0x3ffffffff).unwrap().bit_length());
2366
2367        assert_eq!(0, Bn32::from_u8(0).unwrap().bit_length());
2368        assert_eq!(1, Bn32::from_u8(1).unwrap().bit_length());
2369        assert_eq!(2, Bn32::from_u8(2).unwrap().bit_length());
2370        assert_eq!(2, Bn32::from_u8(3).unwrap().bit_length());
2371        assert_eq!(7, Bn32::from_u8(0x70).unwrap().bit_length());
2372        assert_eq!(8, Bn32::from_u8(0xF0).unwrap().bit_length());
2373        assert_eq!(9, Bn32::from_u16(0x1F0).unwrap().bit_length());
2374
2375        assert_eq!(20, Bn32::from_u64(990223).unwrap().bit_length());
2376        assert_eq!(32, Bn32::from_u64(0xefffffff).unwrap().bit_length());
2377        assert_eq!(32, Bn32::from_u64(0x8fffffff).unwrap().bit_length());
2378        assert_eq!(31, Bn32::from_u64(0x7fffffff).unwrap().bit_length());
2379        assert_eq!(34, Bn32::from_u64(0x3ffffffff).unwrap().bit_length());
2380    }
2381
2382    #[test]
2383    fn test_bit_length_1000() {
2384        // Test bit_length with value 1000
2385        let value = Bn32::from_u16(1000).unwrap();
2386
2387        // 1000 in binary is 1111101000, which has 10 bits
2388        // Let's verify the implementation is working correctly
2389        assert_eq!(value.to_u32().unwrap(), 1000);
2390        assert_eq!(value.bit_length(), 10);
2391
2392        // Test some edge cases around 1000
2393        assert_eq!(Bn32::from_u16(512).unwrap().bit_length(), 10); // 2^9 = 512
2394        assert_eq!(Bn32::from_u16(1023).unwrap().bit_length(), 10); // 2^10 - 1 = 1023
2395        assert_eq!(Bn32::from_u16(1024).unwrap().bit_length(), 11); // 2^10 = 1024
2396
2397        // Test with different word sizes to see if this makes a difference
2398        assert_eq!(Bn8::from_u16(1000).unwrap().bit_length(), 10);
2399        assert_eq!(Bn16::from_u16(1000).unwrap().bit_length(), 10);
2400
2401        // Test with different initialization methods
2402        let value_from_str = Bn32::from_str_radix("1000", 10).unwrap();
2403        assert_eq!(value_from_str.bit_length(), 10);
2404
2405        // This is the problematic case - let's debug it
2406        let value_from_bytes = Bn32::from_le_bytes(&1000u16.to_le_bytes());
2407        // Let's see what the actual value is
2408        assert_eq!(
2409            value_from_bytes.to_u32().unwrap_or(0),
2410            1000,
2411            "from_le_bytes didn't create the correct value"
2412        );
2413        assert_eq!(value_from_bytes.bit_length(), 10);
2414    }
2415    #[test]
2416    fn test_cmp() {
2417        let f0 = <Bn8 as Zero>::zero();
2418        let f1 = <Bn8 as Zero>::zero();
2419        let f2 = <Bn8 as One>::one();
2420        assert_eq!(f0, f1);
2421        assert!(f2 > f0);
2422        assert!(f0 < f2);
2423        let f3 = Bn32::from_u64(990223).unwrap();
2424        assert_eq!(f3, Bn32::from_u64(990223).unwrap());
2425        let f4 = Bn32::from_u64(990224).unwrap();
2426        assert!(f4 > Bn32::from_u64(990223).unwrap());
2427
2428        let f3 = Bn8::from_u64(990223).unwrap();
2429        assert_eq!(f3, Bn8::from_u64(990223).unwrap());
2430        let f4 = Bn8::from_u64(990224).unwrap();
2431        assert!(f4 > Bn8::from_u64(990223).unwrap());
2432
2433        #[cfg(feature = "nightly")]
2434        {
2435            use core::cmp::Ordering;
2436
2437            const A: FixedUInt<u8, 2> = FixedUInt::from_array([10, 0]);
2438            const B: FixedUInt<u8, 2> = FixedUInt::from_array([20, 0]);
2439            const C: FixedUInt<u8, 2> = FixedUInt::from_array([10, 0]);
2440
2441            const CMP_LT: Ordering = A.cmp(&B);
2442            const CMP_GT: Ordering = B.cmp(&A);
2443            const CMP_EQ: Ordering = A.cmp(&C);
2444            const EQ_TRUE: bool = A.eq(&C);
2445            const EQ_FALSE: bool = A.eq(&B);
2446
2447            assert_eq!(CMP_LT, Ordering::Less);
2448            assert_eq!(CMP_GT, Ordering::Greater);
2449            assert_eq!(CMP_EQ, Ordering::Equal);
2450            assert!(EQ_TRUE);
2451            assert!(!EQ_FALSE);
2452        }
2453    }
2454
2455    #[test]
2456    fn test_default() {
2457        let d: Bn8 = Default::default();
2458        assert!(<Bn8 as const_num_traits::Zero>::is_zero(&d));
2459
2460        #[cfg(feature = "nightly")]
2461        {
2462            const D: FixedUInt<u8, 2> = <FixedUInt<u8, 2> as Default>::default();
2463            assert!(<FixedUInt<u8, 2> as const_num_traits::Zero>::is_zero(&D));
2464        }
2465    }
2466
2467    #[test]
2468    fn test_clone() {
2469        let a: Bn8 = 42u8.into();
2470        let b = a;
2471        assert_eq!(a, b);
2472
2473        #[cfg(feature = "nightly")]
2474        {
2475            const A: FixedUInt<u8, 2> = FixedUInt::from_array([42, 0]);
2476            const B: FixedUInt<u8, 2> = A.clone();
2477            assert_eq!(A.array, B.array);
2478        }
2479    }
2480
2481    #[test]
2482    fn test_le_be_bytes() {
2483        let le_bytes = [1, 2, 3, 4];
2484        let be_bytes = [4, 3, 2, 1];
2485        let u8_ver = FixedUInt::<u8, 4>::from_le_bytes(&le_bytes);
2486        let u16_ver = FixedUInt::<u16, 2>::from_le_bytes(&le_bytes);
2487        let u32_ver = FixedUInt::<u32, 1>::from_le_bytes(&le_bytes);
2488        let u8_ver_be = FixedUInt::<u8, 4>::from_be_bytes(&be_bytes);
2489        let u16_ver_be = FixedUInt::<u16, 2>::from_be_bytes(&be_bytes);
2490        let u32_ver_be = FixedUInt::<u32, 1>::from_be_bytes(&be_bytes);
2491
2492        assert_eq!(u8_ver.array, [1, 2, 3, 4]);
2493        assert_eq!(u16_ver.array, [0x0201, 0x0403]);
2494        assert_eq!(u32_ver.array, [0x04030201]);
2495        assert_eq!(u8_ver_be.array, [1, 2, 3, 4]);
2496        assert_eq!(u16_ver_be.array, [0x0201, 0x0403]);
2497        assert_eq!(u32_ver_be.array, [0x04030201]);
2498
2499        let mut output_buffer = [0u8; 16];
2500        assert_eq!(u8_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2501        assert_eq!(u8_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2502        assert_eq!(u16_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2503        assert_eq!(u16_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2504        assert_eq!(u32_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2505        assert_eq!(u32_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2506    }
2507
2508    // Test suite for division implementation
2509    #[test]
2510    fn test_div_small() {
2511        type TestInt = FixedUInt<u8, 2>;
2512
2513        // Test small values
2514        let test_cases = [
2515            (20u16, 3u16, 6u16),        // 20 / 3 = 6
2516            (100u16, 7u16, 14u16),      // 100 / 7 = 14
2517            (255u16, 5u16, 51u16),      // 255 / 5 = 51
2518            (65535u16, 256u16, 255u16), // max u16 / 256 = 255
2519        ];
2520
2521        for (dividend_val, divisor_val, expected) in test_cases {
2522            let dividend = TestInt::from(dividend_val);
2523            let divisor = TestInt::from(divisor_val);
2524            let expected_result = TestInt::from(expected);
2525
2526            assert_eq!(
2527                dividend / divisor,
2528                expected_result,
2529                "Division failed for {} / {} = {}",
2530                dividend_val,
2531                divisor_val,
2532                expected
2533            );
2534        }
2535    }
2536
2537    #[test]
2538    fn test_div_edge_cases() {
2539        type TestInt = FixedUInt<u16, 2>;
2540
2541        // Division by 1
2542        let dividend = TestInt::from(1000u16);
2543        let divisor = TestInt::from(1u16);
2544        assert_eq!(dividend / divisor, TestInt::from(1000u16));
2545
2546        // Equal values
2547        let dividend = TestInt::from(42u16);
2548        let divisor = TestInt::from(42u16);
2549        assert_eq!(dividend / divisor, TestInt::from(1u16));
2550
2551        // Dividend < divisor
2552        let dividend = TestInt::from(5u16);
2553        let divisor = TestInt::from(10u16);
2554        assert_eq!(dividend / divisor, TestInt::from(0u16));
2555
2556        // Powers of 2
2557        let dividend = TestInt::from(1024u16);
2558        let divisor = TestInt::from(4u16);
2559        assert_eq!(dividend / divisor, TestInt::from(256u16));
2560    }
2561
2562    #[test]
2563    fn test_helper_methods() {
2564        type TestInt = FixedUInt<u8, 2>;
2565
2566        // Test const_set_bit
2567        let mut val = <TestInt as Zero>::zero();
2568        const_set_bit(&mut val.array, 0);
2569        assert_eq!(val, TestInt::from(1u8));
2570
2571        const_set_bit(&mut val.array, 8);
2572        assert_eq!(val, TestInt::from(257u16)); // bit 0 + bit 8 = 1 + 256 = 257
2573
2574        // Test const_cmp_shifted
2575        let a = TestInt::from(8u8); // 1000 in binary
2576        let b = TestInt::from(1u8); // 0001 in binary
2577
2578        // b << 3 = 8, so a == (b << 3)
2579        assert_eq!(
2580            const_cmp_shifted(&a.array, &b.array, 3),
2581            core::cmp::Ordering::Equal
2582        );
2583
2584        // a > (b << 2) because b << 2 = 4
2585        assert_eq!(
2586            const_cmp_shifted(&a.array, &b.array, 2),
2587            core::cmp::Ordering::Greater
2588        );
2589
2590        // a < (b << 4) because b << 4 = 16
2591        assert_eq!(
2592            const_cmp_shifted(&a.array, &b.array, 4),
2593            core::cmp::Ordering::Less
2594        );
2595
2596        // Test const_sub_shifted
2597        let mut val = TestInt::from(10u8);
2598        let one = TestInt::from(1u8);
2599        const_sub_shifted(&mut val.array, &one.array, 2); // subtract 1 << 2 = 4
2600        assert_eq!(val, TestInt::from(6u8)); // 10 - 4 = 6
2601    }
2602
2603    #[test]
2604    fn test_shifted_operations_comprehensive() {
2605        type TestInt = FixedUInt<u32, 2>;
2606
2607        // Test cmp_shifted with various word boundary cases
2608        let a = TestInt::from(0x12345678u32);
2609        let b = TestInt::from(0x12345678u32);
2610
2611        // Equal comparison
2612        assert_eq!(
2613            const_cmp_shifted(&a.array, &b.array, 0),
2614            core::cmp::Ordering::Equal
2615        );
2616
2617        // Test shifts that cross word boundaries (assuming 32-bit words)
2618        let c = TestInt::from(0x123u32); // Small number
2619        let d = TestInt::from(0x48d159e2u32); // c << 16 + some bits
2620
2621        // c << 16 should be less than d
2622        assert_eq!(
2623            const_cmp_shifted(&d.array, &c.array, 16),
2624            core::cmp::Ordering::Greater
2625        );
2626
2627        // Test large shifts (beyond bit size, so shifted value becomes 0)
2628        let e = TestInt::from(1u32);
2629        let zero = TestInt::from(0u32);
2630        assert_eq!(
2631            const_cmp_shifted(&e.array, &zero.array, 100),
2632            core::cmp::Ordering::Greater
2633        );
2634        // When shift is beyond bit size, 1 << 100 becomes 0, so 0 == 0
2635        assert_eq!(
2636            const_cmp_shifted(&zero.array, &e.array, 100),
2637            core::cmp::Ordering::Equal
2638        );
2639
2640        // Test sub_shifted with word boundary crossing
2641        let mut val = TestInt::from(0x10000u32); // 65536
2642        let one = TestInt::from(1u32);
2643        const_sub_shifted(&mut val.array, &one.array, 15); // subtract 1 << 15 = 32768
2644        assert_eq!(val, TestInt::from(0x8000u32)); // 65536 - 32768 = 32768
2645
2646        // Test sub_shifted with multi-word operations
2647        let mut big_val = TestInt::from(0x100000000u64); // 2^32
2648        const_sub_shifted(&mut big_val.array, &one.array, 31); // subtract 1 << 31 = 2^31
2649        assert_eq!(big_val, TestInt::from(0x80000000u64)); // 2^32 - 2^31 = 2^31
2650    }
2651
2652    #[test]
2653    fn test_shifted_operations_edge_cases() {
2654        type TestInt = FixedUInt<u32, 2>;
2655
2656        // Test zero shifts
2657        let a = TestInt::from(42u32);
2658        let a2 = TestInt::from(42u32);
2659        assert_eq!(
2660            const_cmp_shifted(&a.array, &a2.array, 0),
2661            core::cmp::Ordering::Equal
2662        );
2663
2664        let mut b = TestInt::from(42u32);
2665        let ten = TestInt::from(10u32);
2666        const_sub_shifted(&mut b.array, &ten.array, 0);
2667        assert_eq!(b, TestInt::from(32u32));
2668
2669        // Test massive shifts (beyond bit size)
2670        let c = TestInt::from(123u32);
2671        let large = TestInt::from(456u32);
2672        assert_eq!(
2673            const_cmp_shifted(&c.array, &large.array, 200),
2674            core::cmp::Ordering::Greater
2675        );
2676
2677        let mut d = TestInt::from(123u32);
2678        const_sub_shifted(&mut d.array, &large.array, 200); // Should be no-op
2679        assert_eq!(d, TestInt::from(123u32));
2680
2681        // Test with zero values
2682        let zero = TestInt::from(0u32);
2683        let one = TestInt::from(1u32);
2684        assert_eq!(
2685            const_cmp_shifted(&zero.array, &zero.array, 10),
2686            core::cmp::Ordering::Equal
2687        );
2688        assert_eq!(
2689            const_cmp_shifted(&one.array, &zero.array, 10),
2690            core::cmp::Ordering::Greater
2691        );
2692    }
2693
2694    #[test]
2695    fn test_shifted_operations_equivalence() {
2696        type TestInt = FixedUInt<u32, 2>;
2697
2698        // Test that optimized operations give same results as naive shift+op
2699        let test_cases = [
2700            (0x12345u32, 0x678u32, 4),
2701            (0x1000u32, 0x10u32, 8),
2702            (0xABCDu32, 0x1u32, 16),
2703            (0x80000000u32, 0x1u32, 1),
2704        ];
2705
2706        for (a_val, b_val, shift) in test_cases {
2707            let a = TestInt::from(a_val);
2708            let b = TestInt::from(b_val);
2709
2710            // Test cmp_shifted equivalence
2711            let optimized_cmp = const_cmp_shifted(&a.array, &b.array, shift);
2712            let naive_cmp = a.cmp(&(b << shift));
2713            assert_eq!(
2714                optimized_cmp, naive_cmp,
2715                "cmp_shifted mismatch: {} vs ({} << {})",
2716                a_val, b_val, shift
2717            );
2718
2719            // Test sub_shifted equivalence (if subtraction won't underflow)
2720            if a >= (b << shift) {
2721                let mut optimized_result = a;
2722                const_sub_shifted(&mut optimized_result.array, &b.array, shift);
2723
2724                let naive_result = a - (b << shift);
2725                assert_eq!(
2726                    optimized_result, naive_result,
2727                    "sub_shifted mismatch: {} - ({} << {})",
2728                    a_val, b_val, shift
2729                );
2730            }
2731        }
2732    }
2733
2734    #[test]
2735    fn test_div_assign_in_place_optimization() {
2736        type TestInt = FixedUInt<u32, 2>;
2737
2738        // Test that div_assign uses the optimized in-place algorithm
2739        let test_cases = [
2740            (100u32, 10u32, 10u32, 0u32),     // 100 / 10 = 10 remainder 0
2741            (123u32, 7u32, 17u32, 4u32),      // 123 / 7 = 17 remainder 4
2742            (1000u32, 13u32, 76u32, 12u32),   // 1000 / 13 = 76 remainder 12
2743            (65535u32, 255u32, 257u32, 0u32), // 65535 / 255 = 257 remainder 0
2744        ];
2745
2746        for (dividend_val, divisor_val, expected_quotient, expected_remainder) in test_cases {
2747            // Test div_assign
2748            let mut dividend = TestInt::from(dividend_val);
2749            let divisor = TestInt::from(divisor_val);
2750
2751            dividend /= divisor;
2752            assert_eq!(
2753                dividend,
2754                TestInt::from(expected_quotient),
2755                "div_assign: {} / {} should be {}",
2756                dividend_val,
2757                divisor_val,
2758                expected_quotient
2759            );
2760
2761            // Test div_rem directly
2762            let dividend2 = TestInt::from(dividend_val);
2763            let (quotient, remainder) = dividend2.div_rem(&divisor);
2764            assert_eq!(
2765                quotient,
2766                TestInt::from(expected_quotient),
2767                "div_rem quotient: {} / {} should be {}",
2768                dividend_val,
2769                divisor_val,
2770                expected_quotient
2771            );
2772            assert_eq!(
2773                remainder,
2774                TestInt::from(expected_remainder),
2775                "div_rem remainder: {} % {} should be {}",
2776                dividend_val,
2777                divisor_val,
2778                expected_remainder
2779            );
2780
2781            // Verify: quotient * divisor + remainder == original dividend
2782            assert_eq!(
2783                quotient * divisor + remainder,
2784                TestInt::from(dividend_val),
2785                "Property check failed for {}",
2786                dividend_val
2787            );
2788        }
2789    }
2790
2791    #[test]
2792    fn test_div_assign_stack_efficiency() {
2793        type TestInt = FixedUInt<u32, 4>; // 16 bytes each
2794
2795        // Create test values
2796        let mut dividend = TestInt::from(0x123456789ABCDEFu64);
2797        let divisor = TestInt::from(0x12345u32);
2798        let original_dividend = dividend;
2799
2800        // Perform in-place division
2801        dividend /= divisor;
2802
2803        // Verify correctness
2804        let remainder = original_dividend % divisor;
2805        assert_eq!(dividend * divisor + remainder, original_dividend);
2806    }
2807
2808    #[test]
2809    fn test_rem_assign_optimization() {
2810        type TestInt = FixedUInt<u32, 2>;
2811
2812        let test_cases = [
2813            (100u32, 10u32, 0u32),    // 100 % 10 = 0
2814            (123u32, 7u32, 4u32),     // 123 % 7 = 4
2815            (1000u32, 13u32, 12u32),  // 1000 % 13 = 12
2816            (65535u32, 255u32, 0u32), // 65535 % 255 = 0
2817        ];
2818
2819        for (dividend_val, divisor_val, expected_remainder) in test_cases {
2820            let mut dividend = TestInt::from(dividend_val);
2821            let divisor = TestInt::from(divisor_val);
2822
2823            dividend %= divisor;
2824            assert_eq!(
2825                dividend,
2826                TestInt::from(expected_remainder),
2827                "rem_assign: {} % {} should be {}",
2828                dividend_val,
2829                divisor_val,
2830                expected_remainder
2831            );
2832        }
2833    }
2834
2835    #[test]
2836    fn test_div_with_remainder_property() {
2837        type TestInt = FixedUInt<u32, 2>;
2838
2839        // Test division with remainder property verification
2840        let test_cases = [
2841            (100u32, 10u32, 10u32),     // 100 / 10 = 10
2842            (123u32, 7u32, 17u32),      // 123 / 7 = 17
2843            (1000u32, 13u32, 76u32),    // 1000 / 13 = 76
2844            (65535u32, 255u32, 257u32), // 65535 / 255 = 257
2845        ];
2846
2847        for (dividend_val, divisor_val, expected_quotient) in test_cases {
2848            let dividend = TestInt::from(dividend_val);
2849            let divisor = TestInt::from(divisor_val);
2850
2851            // Test that div operator (which uses div_impl) works correctly
2852            let quotient = dividend / divisor;
2853            assert_eq!(
2854                quotient,
2855                TestInt::from(expected_quotient),
2856                "Division: {} / {} should be {}",
2857                dividend_val,
2858                divisor_val,
2859                expected_quotient
2860            );
2861
2862            // Verify the division property still holds
2863            let remainder = dividend % divisor;
2864            assert_eq!(
2865                quotient * divisor + remainder,
2866                dividend,
2867                "Division property check failed for {}",
2868                dividend_val
2869            );
2870        }
2871    }
2872
2873    #[test]
2874    fn test_code_simplification_benefits() {
2875        type TestInt = FixedUInt<u32, 2>;
2876
2877        // Verify division property holds
2878        let dividend = TestInt::from(12345u32);
2879        let divisor = TestInt::from(67u32);
2880        let quotient = dividend / divisor;
2881        let remainder = dividend % divisor;
2882
2883        // The division property should still hold
2884        assert_eq!(quotient * divisor + remainder, dividend);
2885    }
2886
2887    #[test]
2888    fn test_rem_assign_correctness_after_fix() {
2889        type TestInt = FixedUInt<u32, 2>;
2890
2891        // Test specific case: 17 % 5 = 2
2892        let mut a = TestInt::from(17u32);
2893        let b = TestInt::from(5u32);
2894
2895        // Historical note: an old bug caused quotient corruption during remainder calculation
2896        // Now const_div_rem properly computes both without corrupting intermediate state
2897        a %= b;
2898        assert_eq!(a, TestInt::from(2u32), "17 % 5 should be 2");
2899
2900        // Test that the original RemAssign bug would have failed this
2901        let mut test_val = TestInt::from(100u32);
2902        test_val %= TestInt::from(7u32);
2903        assert_eq!(
2904            test_val,
2905            TestInt::from(2u32),
2906            "100 % 7 should be 2 (not 14, the quotient)"
2907        );
2908    }
2909
2910    #[test]
2911    fn test_div_property_based() {
2912        type TestInt = FixedUInt<u16, 2>;
2913
2914        // Property: quotient * divisor + remainder == dividend
2915        let test_pairs = [
2916            (12345u16, 67u16),
2917            (1000u16, 13u16),
2918            (65535u16, 255u16),
2919            (5000u16, 7u16),
2920        ];
2921
2922        for (dividend_val, divisor_val) in test_pairs {
2923            let dividend = TestInt::from(dividend_val);
2924            let divisor = TestInt::from(divisor_val);
2925
2926            let quotient = dividend / divisor;
2927
2928            // Property verification: quotient * divisor + remainder == dividend
2929            let remainder = dividend - (quotient * divisor);
2930            let reconstructed = quotient * divisor + remainder;
2931
2932            assert_eq!(
2933                reconstructed,
2934                dividend,
2935                "Property failed for {} / {}: {} * {} + {} != {}",
2936                dividend_val,
2937                divisor_val,
2938                quotient.to_u32().unwrap_or(0),
2939                divisor_val,
2940                remainder.to_u32().unwrap_or(0),
2941                dividend_val
2942            );
2943
2944            // Remainder should be less than divisor
2945            assert!(
2946                remainder < divisor,
2947                "Remainder {} >= divisor {} for {} / {}",
2948                remainder.to_u32().unwrap_or(0),
2949                divisor_val,
2950                dividend_val,
2951                divisor_val
2952            );
2953        }
2954    }
2955}