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