Skip to main content

fixed_bigint/
fixeduint.rs

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