Skip to main content

fixed_bigint/
fixeduint.rs

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