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