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