• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaidokert / fixed-bigint-rs / 22211997546

20 Feb 2026 05:00AM UTC coverage: 97.325% (-0.3%) from 97.595%
22211997546

push

github

web-flow
Add constructor from array (#95)

* Add constructor from array

* Add getter for `array`

* Rename `bytes` into `words`

0 of 6 new or added lines in 1 file covered. (0.0%)

2110 of 2168 relevant lines covered (97.32%)

399.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.29
/src/fixeduint.rs
1
// Copyright 2021 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
use num_traits::{ToPrimitive, Zero};
16

17
use core::convert::TryFrom;
18
use core::fmt::Write;
19

20
pub use crate::const_numtrait::{
21
    ConstAbsDiff, ConstBounded, ConstCheckedPow, ConstDivCeil, ConstIlog, ConstIsqrt,
22
    ConstMultiple, ConstOne, ConstPowerOfTwo, ConstPrimInt, ConstZero,
23
};
24
use crate::machineword::{ConstMachineWord, MachineWord};
25

26
#[allow(unused_imports)]
27
use num_traits::{FromPrimitive, Num};
28

29
mod abs_diff_impl;
30
mod add_sub_impl;
31
mod bit_ops_impl;
32
mod checked_pow_impl;
33
mod div_ceil_impl;
34
mod euclid;
35
mod ilog_impl;
36
mod isqrt_impl;
37
mod iter_impl;
38
mod mul_div_impl;
39
mod multiple_impl;
40
mod num_integer_impl;
41
mod num_traits_casts;
42
mod num_traits_identity;
43
mod power_of_two_impl;
44
mod prim_int_impl;
45
mod roots_impl;
46
mod string_conversion;
47
// Prefer nightly (safe const impl) over use-unsafe when both are enabled
48
#[cfg(feature = "nightly")]
49
mod const_to_from_bytes;
50
#[cfg(all(feature = "use-unsafe", not(feature = "nightly")))]
51
mod to_from_bytes;
52

53
#[cfg(feature = "zeroize")]
54
use zeroize::DefaultIsZeroes;
55

56
/// Fixed-size unsigned integer, represented by array of N words of builtin unsigned type T
57
#[derive(Debug, Copy)]
58
pub struct FixedUInt<T, const N: usize>
59
where
60
    T: MachineWord,
61
{
62
    /// Little-endian word array
63
    pub(super) array: [T; N],
64
}
65

66
#[cfg(feature = "zeroize")]
67
impl<T: MachineWord, const N: usize> DefaultIsZeroes for FixedUInt<T, N> {}
68

69
impl<T, const N: usize> From<[T; N]> for FixedUInt<T, N>
70
where
71
    T: MachineWord,
72
{
NEW
73
    fn from(array: [T; N]) -> Self {
×
NEW
74
        Self { array }
×
NEW
75
    }
×
76
}
77

78
const LONGEST_WORD_IN_BITS: usize = 128;
79

80
impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
81
    const WORD_SIZE: usize = core::mem::size_of::<T>();
82
    const WORD_BITS: usize = Self::WORD_SIZE * 8;
83
    const BYTE_SIZE: usize = Self::WORD_SIZE * N;
84
    const BIT_SIZE: usize = Self::BYTE_SIZE * 8;
85

86
    /// Creates and zero-initializes a FixedUInt.
87
    pub fn new() -> FixedUInt<T, N> {
951✔
88
        FixedUInt {
951✔
89
            array: [T::zero(); N],
951✔
90
        }
951✔
91
    }
951✔
92

93
    /// Returns the underlying array.
NEW
94
    pub fn words(&self) -> &[T; N] {
×
NEW
95
        &self.array
×
NEW
96
    }
×
97

98
    /// Returns number of used bits.
99
    pub fn bit_length(&self) -> u32 {
423✔
100
        Self::BIT_SIZE as u32 - ConstPrimInt::leading_zeros(*self)
423✔
101
    }
423✔
102

103
    /// Performs a division, returning both the quotient and remainder in a tuple.
104
    pub fn div_rem(&self, divisor: &Self) -> (Self, Self) {
206✔
105
        if const_is_zero(&divisor.array) {
206✔
106
            maybe_panic(PanicReason::DivByZero)
1✔
107
        }
205✔
108
        let mut dividend = *self;
206✔
109
        let remainder = Self::div_assign_impl(&mut dividend, divisor);
206✔
110
        (dividend, remainder)
206✔
111
    }
206✔
112

113
    /// Create a little-endian integer value from its representation as a byte array in little endian.
114
    pub fn from_le_bytes(bytes: &[u8]) -> Self {
908✔
115
        let mut ret = Self::new();
908✔
116
        let total_bytes = core::cmp::min(bytes.len(), N * Self::WORD_SIZE);
908✔
117

118
        for (byte_index, &byte) in bytes.iter().enumerate().take(total_bytes) {
7,074✔
119
            let word_index = byte_index / Self::WORD_SIZE;
7,074✔
120
            let byte_in_word = byte_index % Self::WORD_SIZE;
7,074✔
121

7,074✔
122
            let byte_value: T = byte.into();
7,074✔
123
            let shifted_value = byte_value.shl(byte_in_word * 8);
7,074✔
124
            ret.array[word_index] |= shifted_value;
7,074✔
125
        }
7,074✔
126
        ret
908✔
127
    }
908✔
128

129
    /// Create a big-endian integer value from its representation as a byte array in big endian.
130
    pub fn from_be_bytes(bytes: &[u8]) -> Self {
16✔
131
        let mut ret = Self::new();
16✔
132
        let capacity_bytes = N * Self::WORD_SIZE;
16✔
133
        let total_bytes = core::cmp::min(bytes.len(), capacity_bytes);
16✔
134

135
        // For consistent truncation semantics with from_le_bytes, always take the
136
        // least significant bytes (rightmost bytes in big-endian representation)
137
        let start_offset = if bytes.len() > capacity_bytes {
16✔
138
            bytes.len() - capacity_bytes
3✔
139
        } else {
140
            0
13✔
141
        };
142

143
        for (byte_index, _) in (0..total_bytes).enumerate() {
52✔
144
            // Take bytes from the end of the input (least significant in BE)
52✔
145
            let be_byte_index = start_offset + total_bytes - 1 - byte_index;
52✔
146
            let word_index = byte_index / Self::WORD_SIZE;
52✔
147
            let byte_in_word = byte_index % Self::WORD_SIZE;
52✔
148

52✔
149
            let byte_value: T = bytes[be_byte_index].into();
52✔
150
            let shifted_value = byte_value.shl(byte_in_word * 8);
52✔
151
            ret.array[word_index] |= shifted_value;
52✔
152
        }
52✔
153
        ret
16✔
154
    }
16✔
155

156
    /// Converts the FixedUInt into a little-endian byte array.
157
    pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
12✔
158
        let total_bytes = N * Self::WORD_SIZE;
12✔
159
        if output_buffer.len() < total_bytes {
12✔
160
            return Err(false); // Buffer too small
1✔
161
        }
11✔
162
        for (i, word) in self.array.iter().enumerate() {
23✔
163
            let start = i * Self::WORD_SIZE;
23✔
164
            let end = start + Self::WORD_SIZE;
23✔
165
            let word_bytes = word.to_le_bytes();
23✔
166
            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
23✔
167
        }
23✔
168
        Ok(&output_buffer[..total_bytes])
11✔
169
    }
12✔
170

171
    /// Converts the FixedUInt into a big-endian byte array.
172
    pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
13✔
173
        let total_bytes = N * Self::WORD_SIZE;
13✔
174
        if output_buffer.len() < total_bytes {
13✔
175
            return Err(false); // Buffer too small
1✔
176
        }
12✔
177
        for (i, word) in self.array.iter().rev().enumerate() {
25✔
178
            let start = i * Self::WORD_SIZE;
25✔
179
            let end = start + Self::WORD_SIZE;
25✔
180
            let word_bytes = word.to_be_bytes();
25✔
181
            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
25✔
182
        }
25✔
183
        Ok(&output_buffer[..total_bytes])
12✔
184
    }
13✔
185

186
    /// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zeroes
187
    pub fn to_hex_str<'a>(&self, result: &'a mut [u8]) -> Result<&'a str, core::fmt::Error> {
25✔
188
        type Error = core::fmt::Error;
189

190
        let word_size = Self::WORD_SIZE;
25✔
191
        // need length minus leading zeros
192
        let need_bits = self.bit_length() as usize;
25✔
193
        // number of needed characters (bits/4 = bytes * 2)
194
        let need_chars = if need_bits > 0 { need_bits / 4 } else { 0 };
25✔
195

196
        if result.len() < need_chars {
25✔
197
            // not enough space in result...
198
            return Err(Error {});
3✔
199
        }
22✔
200
        let offset = result.len() - need_chars;
22✔
201
        for i in result.iter_mut() {
282✔
202
            *i = b'0';
282✔
203
        }
282✔
204

205
        for iter_words in 0..self.array.len() {
95✔
206
            let word = self.array[iter_words];
95✔
207
            let mut encoded = [0u8; LONGEST_WORD_IN_BITS / 4];
95✔
208
            let encode_slice = &mut encoded[0..word_size * 2];
95✔
209
            let mut wordbytes = word.to_le_bytes();
95✔
210
            wordbytes.as_mut().reverse();
95✔
211
            let wordslice = wordbytes.as_ref();
95✔
212
            to_slice_hex(wordslice, encode_slice).map_err(|_| Error {})?;
95✔
213
            for iter_chars in 0..encode_slice.len() {
288✔
214
                let copy_char_to = (iter_words * word_size * 2) + iter_chars;
288✔
215
                if copy_char_to <= need_chars {
288✔
216
                    let reverse_index = offset + (need_chars - copy_char_to);
88✔
217
                    if reverse_index <= result.len() && reverse_index > 0 {
88✔
218
                        let current_char = encode_slice[(encode_slice.len() - 1) - iter_chars];
88✔
219
                        result[reverse_index - 1] = current_char;
88✔
220
                    }
88✔
221
                }
200✔
222
            }
223
        }
224

225
        let convert = core::str::from_utf8(result).map_err(|_| Error {})?;
22✔
226
        let pos = convert.find(|c: char| c != '0');
217✔
227
        match pos {
22✔
228
            Some(x) => Ok(&convert[x..convert.len()]),
18✔
229
            None => {
230
                if convert.starts_with('0') {
4✔
231
                    Ok("0")
4✔
232
                } else {
233
                    Ok(convert)
×
234
                }
235
            }
236
        }
237
    }
25✔
238

239
    /// Converts to decimal string, given a buffer. CAVEAT: This method removes any leading zeroes
240
    pub fn to_radix_str<'a>(
26✔
241
        &self,
26✔
242
        result: &'a mut [u8],
26✔
243
        radix: u8,
26✔
244
    ) -> Result<&'a str, core::fmt::Error> {
26✔
245
        type Error = core::fmt::Error;
246

247
        if !(2..=16).contains(&radix) {
26✔
248
            return Err(Error {}); // Radix out of supported range
×
249
        }
26✔
250
        for byte in result.iter_mut() {
440✔
251
            *byte = b'0';
440✔
252
        }
440✔
253
        if Zero::is_zero(self) {
26✔
254
            if !result.is_empty() {
5✔
255
                result[0] = b'0';
5✔
256
                return core::str::from_utf8(&result[0..1]).map_err(|_| Error {});
5✔
257
            } else {
258
                return Err(Error {});
×
259
            }
260
        }
21✔
261

262
        let mut number = *self;
21✔
263
        let mut idx = result.len();
21✔
264

265
        let radix_t = Self::from(radix);
21✔
266

267
        while !Zero::is_zero(&number) {
152✔
268
            if idx == 0 {
134✔
269
                return Err(Error {}); // not enough space in result...
3✔
270
            }
131✔
271

272
            idx -= 1;
131✔
273
            let (quotient, remainder) = number.div_rem(&radix_t);
131✔
274

275
            let digit = remainder.to_u8().unwrap();
131✔
276
            result[idx] = match digit {
131✔
277
                0..=9 => b'0' + digit,          // digits
131✔
278
                10..=16 => b'a' + (digit - 10), // alphabetic digits for bases > 10
14✔
279
                _ => return Err(Error {}),
×
280
            };
281

282
            number = quotient;
131✔
283
        }
284

285
        let start = result[idx..].iter().position(|&c| c != b'0').unwrap_or(0);
18✔
286
        let radix_str = core::str::from_utf8(&result[idx + start..]).map_err(|_| Error {})?;
18✔
287
        Ok(radix_str)
18✔
288
    }
26✔
289

290
    fn hex_fmt(
5✔
291
        &self,
5✔
292
        formatter: &mut core::fmt::Formatter<'_>,
5✔
293
        uppercase: bool,
5✔
294
    ) -> Result<(), core::fmt::Error>
5✔
295
    where
5✔
296
        u8: core::convert::TryFrom<T>,
5✔
297
    {
298
        type Err = core::fmt::Error;
299

300
        fn to_casedigit(byte: u8, uppercase: bool) -> Result<char, core::fmt::Error> {
14✔
301
            let digit = core::char::from_digit(byte as u32, 16).ok_or(Err {})?;
14✔
302
            if uppercase {
14✔
303
                digit.to_uppercase().next().ok_or(Err {})
12✔
304
            } else {
305
                digit.to_lowercase().next().ok_or(Err {})
2✔
306
            }
307
        }
14✔
308

309
        let mut leading_zero: bool = true;
5✔
310

311
        let mut maybe_write = |nibble: char| -> Result<(), core::fmt::Error> {
14✔
312
            leading_zero &= nibble == '0';
14✔
313
            if !leading_zero {
14✔
314
                formatter.write_char(nibble)?;
10✔
315
            }
4✔
316
            Ok(())
13✔
317
        };
14✔
318

319
        for index in (0..N).rev() {
6✔
320
            let val = self.array[index];
6✔
321
            let mask: T = 0xff.into();
6✔
322
            for j in (0..Self::WORD_SIZE as u32).rev() {
7✔
323
                let masked = val & mask.shl((j * 8) as usize);
7✔
324

325
                let byte = u8::try_from(masked.shr((j * 8) as usize)).map_err(|_| Err {})?;
7✔
326

327
                maybe_write(to_casedigit((byte & 0xf0) >> 4, uppercase)?)?;
7✔
328
                maybe_write(to_casedigit(byte & 0x0f, uppercase)?)?;
7✔
329
            }
330
        }
331
        Ok(())
4✔
332
    }
5✔
333
}
334

335
c0nst::c0nst! {
336
    /// Const-compatible add implementation operating on raw arrays
337
    pub(crate) c0nst fn add_impl<T: [c0nst] ConstMachineWord, const N: usize>(
338
        target: &mut [T; N],
339
        other: &[T; N]
340
    ) -> bool {
341
        let mut carry = T::zero();
342
        let mut i = 0usize;
343
        while i < N {
344
            let (res, carry1) = target[i].overflowing_add(&other[i]);
345
            let (res, carry2) = res.overflowing_add(&carry);
346
            carry = if carry1 || carry2 {
347
                T::one()
348
            } else {
349
                T::zero()
350
            };
351
            target[i] = res;
352
            i += 1;
353
        }
354
        !carry.is_zero()
355
    }
356
}
3,610✔
357

358
c0nst::c0nst! {
359
    /// Const-compatible sub implementation operating on raw arrays
360
    pub(crate) c0nst fn sub_impl<T: [c0nst] ConstMachineWord, const N: usize>(
361
        target: &mut [T; N],
362
        other: &[T; N]
363
    ) -> bool {
364
        let mut borrow = T::zero();
365
        let mut i = 0usize;
366
        while i < N {
367
            let (res, borrow1) = target[i].overflowing_sub(&other[i]);
368
            let (res, borrow2) = res.overflowing_sub(&borrow);
369
            borrow = if borrow1 || borrow2 {
370
                T::one()
371
            } else {
372
                T::zero()
373
            };
374
            target[i] = res;
375
            i += 1;
376
        }
377
        !borrow.is_zero()
378
    }
379
}
1,375✔
380

381
impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
382
    /// In-place division: dividend becomes quotient, returns remainder
383
    fn div_assign_impl(dividend: &mut Self, divisor: &Self) -> Self {
209✔
384
        let remainder_array = const_div(&mut dividend.array, &divisor.array);
209✔
385
        Self {
209✔
386
            array: remainder_array,
209✔
387
        }
209✔
388
    }
209✔
389
}
390

391
c0nst::c0nst! {
392
    /// Const-compatible left shift implementation
393
    pub(crate) c0nst fn const_shl_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(
394
        target: &mut FixedUInt<T, N>,
395
        bits: usize,
396
    ) {
397
        if N == 0 {
398
            return;
399
        }
400
        let word_bits = FixedUInt::<T, N>::WORD_BITS;
401
        let nwords = bits / word_bits;
402
        let nbits = bits - nwords * word_bits;
403

404
        // If shift >= total bits, result is zero
405
        if nwords >= N {
406
            let mut i = 0;
407
            while i < N {
408
                target.array[i] = T::zero();
409
                i += 1;
410
            }
411
            return;
412
        }
413

414
        // Move words (backwards)
415
        let mut i = N;
416
        while i > nwords {
417
            i -= 1;
418
            target.array[i] = target.array[i - nwords];
419
        }
420
        // Zero out the lower words
421
        let mut i = 0;
422
        while i < nwords {
423
            target.array[i] = T::zero();
424
            i += 1;
425
        }
426

427
        if nbits != 0 {
428
            // Shift remaining bits (backwards)
429
            let mut i = N;
430
            while i > 1 {
431
                i -= 1;
432
                let right = target.array[i] << nbits;
433
                let left = target.array[i - 1] >> (word_bits - nbits);
434
                target.array[i] = right | left;
435
            }
436
            target.array[0] <<= nbits;
437
        }
438
    }
439

440
    /// Const-compatible right shift implementation
441
    pub(crate) c0nst fn const_shr_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(
442
        target: &mut FixedUInt<T, N>,
443
        bits: usize,
444
    ) {
445
        if N == 0 {
446
            return;
447
        }
448
        let word_bits = FixedUInt::<T, N>::WORD_BITS;
449
        let nwords = bits / word_bits;
450
        let nbits = bits - nwords * word_bits;
451

452
        // If shift >= total bits, result is zero
453
        if nwords >= N {
454
            let mut i = 0;
455
            while i < N {
456
                target.array[i] = T::zero();
457
                i += 1;
458
            }
459
            return;
460
        }
461

462
        let last_index = N - 1;
463
        let last_word = N - nwords;
464

465
        // Move words (forwards)
466
        let mut i = 0;
467
        while i < last_word {
468
            target.array[i] = target.array[i + nwords];
469
            i += 1;
470
        }
471

472
        // Zero out the upper words
473
        let mut i = last_word;
474
        while i < N {
475
            target.array[i] = T::zero();
476
            i += 1;
477
        }
478

479
        if nbits != 0 {
480
            // Shift remaining bits (forwards)
481
            let mut i = 0;
482
            while i < last_index {
483
                let left = target.array[i] >> nbits;
484
                let right = target.array[i + 1] << (word_bits - nbits);
485
                target.array[i] = left | right;
486
                i += 1;
487
            }
488
            target.array[last_index] >>= nbits;
489
        }
490
    }
491

492
    /// Standalone const-compatible array multiplication (no FixedUInt dependency)
493
    /// Returns (result_array, overflowed)
494
    pub(crate) c0nst fn const_mul<T: [c0nst] ConstMachineWord, const N: usize, const CHECK_OVERFLOW: bool>(
495
        op1: &[T; N],
496
        op2: &[T; N],
497
        word_bits: usize,
498
    ) -> ([T; N], bool) {
499
        let mut result: [T; N] = [<T as ConstZero>::zero(); N];
500
        let mut overflowed = false;
501
        let t_max = <T as ConstMachineWord>::to_double(<T as ConstBounded>::max_value());
502
        // Zero for double word type
503
        let dw_zero = <<T as ConstMachineWord>::ConstDoubleWord as ConstZero>::zero();
504

505
        let mut i = 0;
506
        while i < N {
507
            let mut carry = dw_zero;
508
            let mut j = 0;
509
            while j < N {
510
                let round = i + j;
511
                let op1_dw = <T as ConstMachineWord>::to_double(op1[i]);
512
                let op2_dw = <T as ConstMachineWord>::to_double(op2[j]);
513
                let mul_res = op1_dw * op2_dw;
514
                let mut accumulator = if round < N {
515
                    <T as ConstMachineWord>::to_double(result[round])
516
                } else {
517
                    dw_zero
518
                };
519
                accumulator = accumulator + mul_res + carry;
520

521
                if accumulator > t_max {
522
                    carry = accumulator >> word_bits;
523
                    accumulator &= t_max;
524
                } else {
525
                    carry = dw_zero;
526
                }
527
                if round < N {
528
                    result[round] = <T as ConstMachineWord>::from_double(accumulator);
529
                } else if CHECK_OVERFLOW {
530
                    overflowed = overflowed || accumulator != dw_zero;
531
                }
532
                j += 1;
533
            }
534
            if carry != dw_zero && CHECK_OVERFLOW {
535
                overflowed = true;
536
            }
537
            i += 1;
538
        }
539
        (result, overflowed)
540
    }
541

542
    /// Get the bit width of a word type.
543
    pub(crate) c0nst fn const_word_bits<T>() -> usize {
544
        core::mem::size_of::<T>() * 8
545
    }
546

547
    /// Compare two words, returning Some(ordering) if not equal, None if equal.
548
    pub(crate) c0nst fn const_cmp_words<T: [c0nst] ConstMachineWord>(a: T, b: T) -> Option<core::cmp::Ordering> {
549
        if a > b {
550
            Some(core::cmp::Ordering::Greater)
551
        } else if a < b {
552
            Some(core::cmp::Ordering::Less)
553
        } else {
554
            None
555
        }
556
    }
557

558
    /// Count leading zeros in a const-compatible way
559
    pub(crate) c0nst fn const_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
560
        array: &[T; N],
561
    ) -> u32 {
562
        let mut ret = 0u32;
563
        let mut index = N;
564
        while index > 0 {
565
            index -= 1;
566
            let v = array[index];
567
            ret += <T as ConstPrimInt>::leading_zeros(v);
568
            if !<T as ConstZero>::is_zero(&v) {
569
                break;
570
            }
571
        }
572
        ret
573
    }
574

575
    /// Count trailing zeros in a const-compatible way
576
    pub(crate) c0nst fn const_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
577
        array: &[T; N],
578
    ) -> u32 {
579
        let mut ret = 0u32;
580
        let mut index = 0;
581
        while index < N {
582
            let v = array[index];
583
            ret += <T as ConstPrimInt>::trailing_zeros(v);
584
            if !<T as ConstZero>::is_zero(&v) {
585
                break;
586
            }
587
            index += 1;
588
        }
589
        ret
590
    }
591

592
    /// Get bit length of array (total bits - leading zeros)
593
    pub(crate) c0nst fn const_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
594
        array: &[T; N],
595
    ) -> usize {
596
        let word_bits = const_word_bits::<T>();
597
        let bit_size = N * word_bits;
598
        bit_size - const_leading_zeros::<T, N>(array) as usize
599
    }
600

601
    /// Check if array is zero
602
    pub(crate) c0nst fn const_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
603
        array: &[T; N],
604
    ) -> bool {
605
        let mut index = 0;
606
        while index < N {
607
            if !<T as ConstZero>::is_zero(&array[index]) {
608
                return false;
609
            }
610
            index += 1;
611
        }
612
        true
613
    }
614

615
    /// Set a specific bit in the array.
616
    ///
617
    /// The array uses little-endian representation where index 0 contains
618
    /// the least significant word, and bit 0 is the least significant bit
619
    /// of the entire integer.
620
    pub(crate) c0nst fn const_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
621
        array: &mut [T; N],
622
        pos: usize,
623
    ) {
624
        let word_bits = const_word_bits::<T>();
625
        let word_idx = pos / word_bits;
626
        if word_idx >= N {
627
            return;
628
        }
629
        let bit_idx = pos % word_bits;
630
        array[word_idx] |= <T as ConstOne>::one() << bit_idx;
631
    }
632

633
    /// Compare two arrays in a const-compatible way.
634
    ///
635
    /// Arrays use little-endian representation where index 0 contains
636
    /// the least significant word.
637
    pub(crate) c0nst fn const_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
638
        a: &[T; N],
639
        b: &[T; N],
640
    ) -> core::cmp::Ordering {
641
        let mut index = N;
642
        while index > 0 {
643
            index -= 1;
644
            if let Some(ord) = const_cmp_words(a[index], b[index]) {
645
                return ord;
646
            }
647
        }
648
        core::cmp::Ordering::Equal
649
    }
650

651
    /// Get the value of array's word at position `word_idx` when logically shifted left.
652
    ///
653
    /// This helper computes what value would be at `word_idx` if the array
654
    /// were shifted left by `word_shift` words plus `bit_shift` bits.
655
    pub(crate) c0nst fn const_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
656
        array: &[T; N],
657
        word_idx: usize,
658
        word_shift: usize,
659
        bit_shift: usize,
660
    ) -> T {
661
        let word_bits = const_word_bits::<T>();
662

663
        // Guard against invalid bit_shift that would cause UB
664
        if bit_shift >= word_bits {
665
            return <T as ConstZero>::zero();
666
        }
667

668
        if word_idx < word_shift {
669
            return <T as ConstZero>::zero();
670
        }
671

672
        let source_idx = word_idx - word_shift;
673

674
        if bit_shift == 0 {
675
            if source_idx < N {
676
                array[source_idx]
677
            } else {
678
                <T as ConstZero>::zero()
679
            }
680
        } else {
681
            let mut result = <T as ConstZero>::zero();
682

683
            // Get bits from the primary source word
684
            if source_idx < N {
685
                result |= array[source_idx] << bit_shift;
686
            }
687

688
            // Get high bits from the next lower word (if it exists)
689
            if source_idx > 0 && source_idx - 1 < N {
690
                let high_bits = array[source_idx - 1] >> (word_bits - bit_shift);
691
                result |= high_bits;
692
            }
693

694
            result
695
        }
696
    }
697

698
    /// Compare array vs (other << shift_bits) in a const-compatible way.
699
    ///
700
    /// This is useful for division algorithms where we need to compare
701
    /// the dividend against a shifted divisor without allocating.
702
    pub(crate) c0nst fn const_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
703
        array: &[T; N],
704
        other: &[T; N],
705
        shift_bits: usize,
706
    ) -> core::cmp::Ordering {
707
        let word_bits = const_word_bits::<T>();
708

709
        if shift_bits == 0 {
710
            return const_cmp::<T, N>(array, other);
711
        }
712

713
        let word_shift = shift_bits / word_bits;
714
        if word_shift >= N {
715
            // other << shift_bits would overflow to 0
716
            if const_is_zero::<T, N>(array) {
717
                return core::cmp::Ordering::Equal;
718
            } else {
719
                return core::cmp::Ordering::Greater;
720
            }
721
        }
722

723
        let bit_shift = shift_bits % word_bits;
724

725
        // Compare from most significant words down
726
        let mut index = N;
727
        while index > 0 {
728
            index -= 1;
729
            let self_word = array[index];
730
            let other_shifted_word = const_get_shifted_word::<T, N>(
731
                other, index, word_shift, bit_shift
732
            );
733

734
            if let Some(ord) = const_cmp_words(self_word, other_shifted_word) {
735
                return ord;
736
            }
737
        }
738

739
        core::cmp::Ordering::Equal
740
    }
741

742
    /// Subtract (other << shift_bits) from array in-place.
743
    ///
744
    /// This is used in division algorithms to subtract shifted divisor
745
    /// from the remainder without allocating.
746
    pub(crate) c0nst fn const_sub_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
747
        array: &mut [T; N],
748
        other: &[T; N],
749
        shift_bits: usize,
750
    ) {
751
        let word_bits = const_word_bits::<T>();
752

753
        if shift_bits == 0 {
754
            sub_impl::<T, N>(array, other);
755
            return;
756
        }
757

758
        let word_shift = shift_bits / word_bits;
759
        if word_shift >= N {
760
            return;
761
        }
762

763
        let bit_shift = shift_bits % word_bits;
764
        let mut borrow = T::zero();
765
        let mut index = 0;
766
        while index < N {
767
            let other_word = const_get_shifted_word::<T, N>(other, index, word_shift, bit_shift);
768
            let (res, borrow1) = array[index].overflowing_sub(&other_word);
769
            let (res, borrow2) = res.overflowing_sub(&borrow);
770
            borrow = if borrow1 || borrow2 { T::one() } else { T::zero() };
771
            array[index] = res;
772
            index += 1;
773
        }
774
    }
775

776
    /// In-place division: dividend becomes quotient, returns remainder.
777
    ///
778
    /// This is the const-compatible version of div_assign_impl.
779
    pub(crate) c0nst fn const_div<T: [c0nst] ConstMachineWord, const N: usize>(
780
        dividend: &mut [T; N],
781
        divisor: &[T; N],
782
    ) -> [T; N] {
783
        use core::cmp::Ordering;
784

785
        match const_cmp::<T, N>(dividend, divisor) {
786
            // dividend < divisor: quotient = 0, remainder = dividend
787
            Ordering::Less => {
788
                let remainder = *dividend;
789
                let mut i = 0;
790
                while i < N {
791
                    dividend[i] = <T as ConstZero>::zero();
792
                    i += 1;
793
                }
794
                return remainder;
795
            }
796
            // dividend == divisor: quotient = 1, remainder = 0
797
            Ordering::Equal => {
798
                let mut i = 0;
799
                while i < N {
800
                    dividend[i] = <T as ConstZero>::zero();
801
                    i += 1;
802
                }
803
                if N > 0 {
804
                    dividend[0] = <T as ConstOne>::one();
805
                }
806
                return [<T as ConstZero>::zero(); N];
807
            }
808
            Ordering::Greater => {}
809
        }
810

811
        let mut quotient = [<T as ConstZero>::zero(); N];
812

813
        // Calculate initial bit position
814
        let dividend_bits = const_bit_length::<T, N>(dividend);
815
        let divisor_bits = const_bit_length::<T, N>(divisor);
816

817
        let mut bit_pos = if dividend_bits >= divisor_bits {
818
            dividend_bits - divisor_bits
819
        } else {
820
            0
821
        };
822

823
        // Adjust bit position to find the first position where divisor can be subtracted
824
        while bit_pos > 0 {
825
            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
826
            if !matches!(cmp, Ordering::Less) {
827
                break;
828
            }
829
            bit_pos -= 1;
830
        }
831

832
        // Main division loop
833
        loop {
834
            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
835
            if !matches!(cmp, Ordering::Less) {
836
                const_sub_shifted::<T, N>(dividend, divisor, bit_pos);
837
                const_set_bit::<T, N>(&mut quotient, bit_pos);
838
            }
839

840
            if bit_pos == 0 {
841
                break;
842
            }
843
            bit_pos -= 1;
844
        }
845

846
        let remainder = *dividend;
847
        *dividend = quotient;
848
        remainder
849
    }
850
}
199,805✔
851

852
c0nst::c0nst! {
853
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst Default for FixedUInt<T, N> {
854
        fn default() -> Self {
855
            <Self as ConstZero>::zero()
856
        }
857
    }
858

859
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst Clone for FixedUInt<T, N> {
860
        fn clone(&self) -> Self {
861
            *self
862
        }
863
    }
864
}
24✔
865

866
impl<T: MachineWord, const N: usize> num_traits::Unsigned for FixedUInt<T, N> {}
867

868
// #region Equality and Ordering
869

870
c0nst::c0nst! {
871
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::cmp::PartialEq for FixedUInt<T, N> {
872
        fn eq(&self, other: &Self) -> bool {
873
            self.array == other.array
874
        }
875
    }
876

877
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::cmp::Eq for FixedUInt<T, N> {}
878

879
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::cmp::Ord for FixedUInt<T, N> {
880
        fn cmp(&self, other: &Self) -> core::cmp::Ordering {
881
            const_cmp(&self.array, &other.array)
882
        }
883
    }
884

885
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::cmp::PartialOrd for FixedUInt<T, N> {
886
        fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
887
            Some(self.cmp(other))
888
        }
889
    }
890
}
22,491✔
891

892
// #endregion Equality and Ordering
893

894
// #region core::convert::From<primitive>
895

896
c0nst::c0nst! {
897
    /// Const-compatible conversion from little-endian bytes to array of words
898
    c0nst fn const_from_le_bytes<T: [c0nst] ConstMachineWord, const N: usize, const B: usize>(
899
        bytes: [u8; B],
900
    ) -> [T; N] {
901
        let mut result: [T; N] = [T::zero(); N];
902
        let word_size = core::mem::size_of::<T>();
903
        let mut byte_idx = 0;
904
        while byte_idx < B && byte_idx < N * word_size {
905
            let word_idx = byte_idx / word_size;
906
            let byte_in_word = byte_idx % word_size;
907
            let byte_value: T = T::from(bytes[byte_idx]);
908
            let shifted: T = byte_value.shl(byte_in_word * 8);
909
            result[word_idx] = result[word_idx].bitor(shifted);
910
            byte_idx += 1;
911
        }
912
        result
913
    }
914

915
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::convert::From<u8> for FixedUInt<T, N> {
916
        fn from(x: u8) -> Self {
917
            Self { array: const_from_le_bytes(x.to_le_bytes()) }
918
        }
919
    }
920

921
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::convert::From<u16> for FixedUInt<T, N> {
922
        fn from(x: u16) -> Self {
923
            Self { array: const_from_le_bytes(x.to_le_bytes()) }
924
        }
925
    }
926

927
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::convert::From<u32> for FixedUInt<T, N> {
928
        fn from(x: u32) -> Self {
929
            Self { array: const_from_le_bytes(x.to_le_bytes()) }
930
        }
931
    }
932

933
    impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> c0nst core::convert::From<u64> for FixedUInt<T, N> {
934
        fn from(x: u64) -> Self {
935
            Self { array: const_from_le_bytes(x.to_le_bytes()) }
936
        }
937
    }
938
}
17,960✔
939

940
// #endregion core::convert::From<primitive>
941

942
// #region helpers
943

944
// This is slightly less than ideal, but PIE isn't directly constructible
945
// due to unstable members.
946
fn make_parse_int_err() -> core::num::ParseIntError {
3✔
947
    <u8>::from_str_radix("-", 2).err().unwrap()
3✔
948
}
3✔
949
fn make_overflow_err() -> core::num::ParseIntError {
1,476✔
950
    <u8>::from_str_radix("101", 16).err().unwrap()
1,476✔
951
}
1,476✔
952
fn make_empty_error() -> core::num::ParseIntError {
50✔
953
    <u8>::from_str_radix("", 8).err().unwrap()
50✔
954
}
50✔
955

956
fn to_slice_hex<T: AsRef<[u8]>>(
95✔
957
    input: T,
95✔
958
    output: &mut [u8],
95✔
959
) -> Result<(), core::num::ParseIntError> {
95✔
960
    fn from_digit(byte: u8) -> Option<char> {
288✔
961
        core::char::from_digit(byte as u32, 16)
288✔
962
    }
288✔
963
    let r = input.as_ref();
95✔
964
    if r.len() * 2 != output.len() {
95✔
965
        return Err(make_parse_int_err());
×
966
    }
95✔
967
    for i in 0..r.len() {
144✔
968
        let byte = r[i];
144✔
969
        output[i * 2] = from_digit((byte & 0xf0) >> 4).ok_or_else(make_parse_int_err)? as u8;
144✔
970
        output[i * 2 + 1] = from_digit(byte & 0x0f).ok_or_else(make_parse_int_err)? as u8;
144✔
971
    }
972

973
    Ok(())
95✔
974
}
95✔
975

976
enum PanicReason {
977
    Add,
978
    Sub,
979
    Mul,
980
    DivByZero,
981
    RemByZero,
982
}
983

984
c0nst::c0nst! {
985
    pub(super) c0nst fn maybe_panic(r: PanicReason) {
986
        match r {
987
            PanicReason::Add => panic!("attempt to add with overflow"),
988
            PanicReason::Sub => panic!("attempt to subtract with overflow"),
989
            PanicReason::Mul => panic!("attempt to multiply with overflow"),
990
            PanicReason::DivByZero => panic!("attempt to divide by zero"),
991
            PanicReason::RemByZero => {
992
                panic!("attempt to calculate the remainder with a divisor of zero")
993
            }
994
        }
995
    }
996
}
997

998
// #endregion helpers
999

1000
#[cfg(test)]
1001
mod tests {
1002
    use super::FixedUInt as Bn;
1003
    use super::*;
1004
    use num_traits::One;
1005

1006
    type Bn8 = Bn<u8, 8>;
1007
    type Bn16 = Bn<u16, 4>;
1008
    type Bn32 = Bn<u32, 2>;
1009

1010
    c0nst::c0nst! {
1011
        pub c0nst fn test_add<T: [c0nst] ConstMachineWord, const N: usize>(
1012
            a: &mut [T; N],
1013
            b: &[T; N]
1014
        ) -> bool {
1015
            add_impl(a, b)
1016
        }
1017

1018
        pub c0nst fn test_sub<T: [c0nst] ConstMachineWord, const N: usize>(
1019
            a: &mut [T; N],
1020
            b: &[T; N]
1021
        ) -> bool {
1022
            sub_impl(a, b)
1023
        }
1024

1025
        pub c0nst fn test_mul<T: [c0nst] ConstMachineWord, const N: usize>(
1026
            a: &[T; N],
1027
            b: &[T; N],
1028
            word_bits: usize,
1029
        ) -> ([T; N], bool) {
1030
            const_mul::<T, N, true>(a, b, word_bits)
1031
        }
1032

1033
        pub c0nst fn arr_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1034
            a: &[T; N],
1035
        ) -> u32 {
1036
            const_leading_zeros::<T, N>(a)
1037
        }
1038

1039
        pub c0nst fn arr_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1040
            a: &[T; N],
1041
        ) -> u32 {
1042
            const_trailing_zeros::<T, N>(a)
1043
        }
1044

1045
        pub c0nst fn arr_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
1046
            a: &[T; N],
1047
        ) -> usize {
1048
            const_bit_length::<T, N>(a)
1049
        }
1050

1051
        pub c0nst fn arr_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
1052
            a: &[T; N],
1053
        ) -> bool {
1054
            const_is_zero::<T, N>(a)
1055
        }
1056

1057
        pub c0nst fn arr_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
1058
            a: &mut [T; N],
1059
            pos: usize,
1060
        ) {
1061
            const_set_bit::<T, N>(a, pos)
1062
        }
1063

1064
        pub c0nst fn arr_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
1065
            a: &[T; N],
1066
            b: &[T; N],
1067
        ) -> core::cmp::Ordering {
1068
            const_cmp::<T, N>(a, b)
1069
        }
1070

1071
        pub c0nst fn arr_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1072
            a: &[T; N],
1073
            b: &[T; N],
1074
            shift_bits: usize,
1075
        ) -> core::cmp::Ordering {
1076
            const_cmp_shifted::<T, N>(a, b, shift_bits)
1077
        }
1078

1079
        pub c0nst fn arr_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
1080
            a: &[T; N],
1081
            word_idx: usize,
1082
            word_shift: usize,
1083
            bit_shift: usize,
1084
        ) -> T {
1085
            const_get_shifted_word::<T, N>(a, word_idx, word_shift, bit_shift)
1086
        }
1087
    }
76✔
1088

1089
    #[test]
1090
    fn test_const_add_impl() {
1✔
1091
        // Simple add, no overflow
1092
        let mut a: [u8; 4] = [1, 0, 0, 0];
1✔
1093
        let b: [u8; 4] = [2, 0, 0, 0];
1✔
1094
        let overflow = test_add(&mut a, &b);
1✔
1095
        assert_eq!(a, [3, 0, 0, 0]);
1✔
1096
        assert!(!overflow);
1✔
1097

1098
        // Add with carry propagation
1099
        let mut a: [u8; 4] = [255, 0, 0, 0];
1✔
1100
        let b: [u8; 4] = [1, 0, 0, 0];
1✔
1101
        let overflow = test_add(&mut a, &b);
1✔
1102
        assert_eq!(a, [0, 1, 0, 0]);
1✔
1103
        assert!(!overflow);
1✔
1104

1105
        // Add with overflow
1106
        let mut a: [u8; 4] = [255, 255, 255, 255];
1✔
1107
        let b: [u8; 4] = [1, 0, 0, 0];
1✔
1108
        let overflow = test_add(&mut a, &b);
1✔
1109
        assert_eq!(a, [0, 0, 0, 0]);
1✔
1110
        assert!(overflow);
1✔
1111

1112
        // Test with u32 words
1113
        let mut a: [u32; 2] = [0xFFFFFFFF, 0];
1✔
1114
        let b: [u32; 2] = [1, 0];
1✔
1115
        let overflow = test_add(&mut a, &b);
1✔
1116
        assert_eq!(a, [0, 1]);
1✔
1117
        assert!(!overflow);
1✔
1118

1119
        #[cfg(feature = "nightly")]
1120
        {
1121
            const ADD_RESULT: ([u8; 4], bool) = {
1122
                let mut a = [1u8, 0, 0, 0];
1123
                let b = [2u8, 0, 0, 0];
1124
                let overflow = test_add(&mut a, &b);
1125
                (a, overflow)
1126
            };
1127
            assert_eq!(ADD_RESULT, ([3, 0, 0, 0], false));
1128
        }
1129
    }
1✔
1130

1131
    #[test]
1132
    fn test_const_sub_impl() {
1✔
1133
        // Simple sub, no overflow
1134
        let mut a: [u8; 4] = [3, 0, 0, 0];
1✔
1135
        let b: [u8; 4] = [1, 0, 0, 0];
1✔
1136
        let overflow = test_sub(&mut a, &b);
1✔
1137
        assert_eq!(a, [2, 0, 0, 0]);
1✔
1138
        assert!(!overflow);
1✔
1139

1140
        // Sub with borrow propagation
1141
        let mut a: [u8; 4] = [0, 1, 0, 0];
1✔
1142
        let b: [u8; 4] = [1, 0, 0, 0];
1✔
1143
        let overflow = test_sub(&mut a, &b);
1✔
1144
        assert_eq!(a, [255, 0, 0, 0]);
1✔
1145
        assert!(!overflow);
1✔
1146

1147
        // Sub with underflow
1148
        let mut a: [u8; 4] = [0, 0, 0, 0];
1✔
1149
        let b: [u8; 4] = [1, 0, 0, 0];
1✔
1150
        let overflow = test_sub(&mut a, &b);
1✔
1151
        assert_eq!(a, [255, 255, 255, 255]);
1✔
1152
        assert!(overflow);
1✔
1153

1154
        // Test with u32 words
1155
        let mut a: [u32; 2] = [0, 1];
1✔
1156
        let b: [u32; 2] = [1, 0];
1✔
1157
        let overflow = test_sub(&mut a, &b);
1✔
1158
        assert_eq!(a, [0xFFFFFFFF, 0]);
1✔
1159
        assert!(!overflow);
1✔
1160

1161
        #[cfg(feature = "nightly")]
1162
        {
1163
            const SUB_RESULT: ([u8; 4], bool) = {
1164
                let mut a = [3u8, 0, 0, 0];
1165
                let b = [1u8, 0, 0, 0];
1166
                let overflow = test_sub(&mut a, &b);
1167
                (a, overflow)
1168
            };
1169
            assert_eq!(SUB_RESULT, ([2, 0, 0, 0], false));
1170
        }
1171
    }
1✔
1172

1173
    #[test]
1174
    fn test_const_mul_impl() {
1✔
1175
        // Simple mul: 3 * 4 = 12
1176
        let a: [u8; 2] = [3, 0];
1✔
1177
        let b: [u8; 2] = [4, 0];
1✔
1178
        let (result, overflow) = test_mul(&a, &b, 8);
1✔
1179
        assert_eq!(result, [12, 0]);
1✔
1180
        assert!(!overflow);
1✔
1181

1182
        // Mul with carry: 200 * 2 = 400 = 0x190 = [0x90, 0x01]
1183
        let a: [u8; 2] = [200, 0];
1✔
1184
        let b: [u8; 2] = [2, 0];
1✔
1185
        let (result, overflow) = test_mul(&a, &b, 8);
1✔
1186
        assert_eq!(result, [0x90, 0x01]);
1✔
1187
        assert!(!overflow);
1✔
1188

1189
        // Mul with overflow: 256 * 256 = 65536 which overflows 16 bits
1190
        let a: [u8; 2] = [0, 1]; // 256
1✔
1191
        let b: [u8; 2] = [0, 1]; // 256
1✔
1192
        let (_result, overflow) = test_mul(&a, &b, 8);
1✔
1193
        assert!(overflow);
1✔
1194

1195
        // N=3 overflow at high position (round=4, i=2, j=2)
1196
        // a = [0, 0, 1] = 65536, b = [0, 0, 1] = 65536
1197
        // a * b = 65536^2 = 4294967296 which overflows 24 bits
1198
        let a: [u8; 3] = [0, 0, 1];
1✔
1199
        let b: [u8; 3] = [0, 0, 1];
1✔
1200
        let (_result, overflow) = test_mul(&a, &b, 8);
1✔
1201
        assert!(overflow, "N=3 high-position overflow not detected");
1✔
1202

1203
        // N=3 overflow with larger high word values
1204
        // a = [0, 0, 2] = 131072, b = [0, 0, 2] = 131072
1205
        // a * b = 131072^2 = 17179869184 which overflows 24 bits
1206
        let a: [u8; 3] = [0, 0, 2];
1✔
1207
        let b: [u8; 3] = [0, 0, 2];
1✔
1208
        let (_result, overflow) = test_mul(&a, &b, 8);
1✔
1209
        assert!(
1✔
1210
            overflow,
1✔
1211
            "N=3 high-position overflow with larger values not detected"
1212
        );
1213

1214
        // N=3 non-overflow case: values that fit in 24 bits
1215
        // a = [0, 1, 0] = 256, b = [0, 1, 0] = 256
1216
        // a * b = 256 * 256 = 65536 = [0, 0, 1] which fits in 24 bits
1217
        let a: [u8; 3] = [0, 1, 0];
1✔
1218
        let b: [u8; 3] = [0, 1, 0];
1✔
1219
        let (result, overflow) = test_mul(&a, &b, 8);
1✔
1220
        assert_eq!(result, [0, 0, 1]);
1✔
1221
        assert!(
1✔
1222
            !overflow,
1✔
1223
            "N=3 non-overflow incorrectly detected as overflow"
1224
        );
1225

1226
        // N=3 non-overflow with carry propagation
1227
        // a = [255, 0, 0] = 255, b = [255, 0, 0] = 255
1228
        // a * b = 255 * 255 = 65025 = 0xFE01 = [0x01, 0xFE, 0x00]
1229
        let a: [u8; 3] = [255, 0, 0];
1✔
1230
        let b: [u8; 3] = [255, 0, 0];
1✔
1231
        let (result, overflow) = test_mul(&a, &b, 8);
1✔
1232
        assert_eq!(result, [0x01, 0xFE, 0x00]);
1✔
1233
        assert!(!overflow);
1✔
1234

1235
        #[cfg(feature = "nightly")]
1236
        {
1237
            const MUL_RESULT: ([u8; 2], bool) = test_mul(&[3u8, 0], &[4u8, 0], 8);
1238
            assert_eq!(MUL_RESULT, ([12, 0], false));
1239
        }
1240
    }
1✔
1241

1242
    #[test]
1243
    fn test_const_helpers() {
1✔
1244
        // Test leading_zeros
1245
        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1✔
1246
        assert_eq!(arr_leading_zeros(&[1u8, 0, 0, 0]), 31); // single bit
1✔
1247
        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 1]), 7); // high byte has 1
1✔
1248
        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0x80]), 0); // MSB set
1✔
1249
        assert_eq!(arr_leading_zeros(&[255u8, 255, 255, 255]), 0); // all ones
1✔
1250

1251
        // Test trailing_zeros
1252
        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1✔
1253
        assert_eq!(arr_trailing_zeros(&[1u8, 0, 0, 0]), 0); // LSB set
1✔
1254
        assert_eq!(arr_trailing_zeros(&[0u8, 1, 0, 0]), 8); // second byte
1✔
1255
        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 1]), 24); // fourth byte
1✔
1256
        assert_eq!(arr_trailing_zeros(&[0x80u8, 0, 0, 0]), 7); // bit 7 of first byte
1✔
1257

1258
        // Test bit_length
1259
        assert_eq!(arr_bit_length(&[0u8, 0, 0, 0]), 0); // zero
1✔
1260
        assert_eq!(arr_bit_length(&[1u8, 0, 0, 0]), 1); // 1
1✔
1261
        assert_eq!(arr_bit_length(&[2u8, 0, 0, 0]), 2); // 2
1✔
1262
        assert_eq!(arr_bit_length(&[3u8, 0, 0, 0]), 2); // 3
1✔
1263
        assert_eq!(arr_bit_length(&[0u8, 1, 0, 0]), 9); // 256
1✔
1264
        assert_eq!(arr_bit_length(&[0xF0u8, 0, 0, 0]), 8); // 240 (0xF0)
1✔
1265
        assert_eq!(arr_bit_length(&[255u8, 255, 255, 255]), 32); // max
1✔
1266

1267
        // Test is_zero
1268
        assert!(arr_is_zero(&[0u8, 0, 0, 0]));
1✔
1269
        assert!(!arr_is_zero(&[1u8, 0, 0, 0]));
1✔
1270
        assert!(!arr_is_zero(&[0u8, 0, 0, 1]));
1✔
1271
        assert!(!arr_is_zero(&[0u8, 1, 0, 0]));
1✔
1272

1273
        // Test set_bit
1274
        let mut arr: [u8; 4] = [0, 0, 0, 0];
1✔
1275
        arr_set_bit(&mut arr, 0);
1✔
1276
        assert_eq!(arr, [1, 0, 0, 0]);
1✔
1277

1278
        let mut arr: [u8; 4] = [0, 0, 0, 0];
1✔
1279
        arr_set_bit(&mut arr, 8);
1✔
1280
        assert_eq!(arr, [0, 1, 0, 0]);
1✔
1281

1282
        let mut arr: [u8; 4] = [0, 0, 0, 0];
1✔
1283
        arr_set_bit(&mut arr, 31);
1✔
1284
        assert_eq!(arr, [0, 0, 0, 0x80]);
1✔
1285

1286
        // Set multiple bits
1287
        let mut arr: [u8; 4] = [0, 0, 0, 0];
1✔
1288
        arr_set_bit(&mut arr, 0);
1✔
1289
        arr_set_bit(&mut arr, 3);
1✔
1290
        arr_set_bit(&mut arr, 8);
1✔
1291
        assert_eq!(arr, [0b00001001, 1, 0, 0]);
1✔
1292

1293
        // Out of bounds should be no-op
1294
        let mut arr: [u8; 4] = [0, 0, 0, 0];
1✔
1295
        arr_set_bit(&mut arr, 32);
1✔
1296
        assert_eq!(arr, [0, 0, 0, 0]);
1✔
1297

1298
        // Test with u32 words
1299
        assert_eq!(arr_leading_zeros(&[0u32, 0]), 64);
1✔
1300
        assert_eq!(arr_leading_zeros(&[1u32, 0]), 63);
1✔
1301
        assert_eq!(arr_leading_zeros(&[0u32, 1]), 31);
1✔
1302
        assert_eq!(arr_trailing_zeros(&[0u32, 0]), 64);
1✔
1303
        assert_eq!(arr_trailing_zeros(&[0u32, 1]), 32);
1✔
1304
        assert_eq!(arr_bit_length(&[0u32, 0]), 0);
1✔
1305
        assert_eq!(arr_bit_length(&[1u32, 0]), 1);
1✔
1306
        assert_eq!(arr_bit_length(&[0u32, 1]), 33);
1✔
1307

1308
        #[cfg(feature = "nightly")]
1309
        {
1310
            const LEADING: u32 = arr_leading_zeros(&[0u8, 0, 1, 0]);
1311
            assert_eq!(LEADING, 15);
1312

1313
            const TRAILING: u32 = arr_trailing_zeros(&[0u8, 0, 1, 0]);
1314
            assert_eq!(TRAILING, 16);
1315

1316
            const BIT_LEN: usize = arr_bit_length(&[0u8, 0, 1, 0]);
1317
            assert_eq!(BIT_LEN, 17);
1318

1319
            const IS_ZERO: bool = arr_is_zero(&[0u8, 0, 0, 0]);
1320
            assert!(IS_ZERO);
1321

1322
            const NOT_ZERO: bool = arr_is_zero(&[0u8, 1, 0, 0]);
1323
            assert!(!NOT_ZERO);
1324

1325
            const SET_BIT_RESULT: [u8; 4] = {
1326
                let mut arr = [0u8, 0, 0, 0];
1327
                arr_set_bit(&mut arr, 10);
1328
                arr
1329
            };
1330
            assert_eq!(SET_BIT_RESULT, [0, 0b00000100, 0, 0]);
1331
        }
1332
    }
1✔
1333

1334
    #[test]
1335
    fn test_const_cmp() {
1✔
1336
        use core::cmp::Ordering;
1337

1338
        // Equal arrays
1339
        assert_eq!(arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]), Ordering::Equal);
1✔
1340
        assert_eq!(arr_cmp(&[0u8, 0, 0, 0], &[0u8, 0, 0, 0]), Ordering::Equal);
1✔
1341

1342
        // Greater - high word differs
1343
        assert_eq!(arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]), Ordering::Greater);
1✔
1344

1345
        // Less - high word differs
1346
        assert_eq!(arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]), Ordering::Less);
1✔
1347

1348
        // Greater - low word differs (high words equal)
1349
        assert_eq!(arr_cmp(&[2u8, 0, 0, 0], &[1u8, 0, 0, 0]), Ordering::Greater);
1✔
1350

1351
        // Less - low word differs
1352
        assert_eq!(arr_cmp(&[1u8, 0, 0, 0], &[2u8, 0, 0, 0]), Ordering::Less);
1✔
1353

1354
        // Test with u32 words
1355
        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 1]), Ordering::Equal);
1✔
1356
        assert_eq!(arr_cmp(&[0u32, 2], &[0u32, 1]), Ordering::Greater);
1✔
1357
        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 2]), Ordering::Less);
1✔
1358

1359
        #[cfg(feature = "nightly")]
1360
        {
1361
            const CMP_EQ: Ordering = arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]);
1362
            const CMP_GT: Ordering = arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]);
1363
            const CMP_LT: Ordering = arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]);
1364
            assert_eq!(CMP_EQ, Ordering::Equal);
1365
            assert_eq!(CMP_GT, Ordering::Greater);
1366
            assert_eq!(CMP_LT, Ordering::Less);
1367
        }
1368
    }
1✔
1369

1370
    #[test]
1371
    fn test_const_cmp_shifted() {
1✔
1372
        use core::cmp::Ordering;
1373

1374
        // No shift - same as regular cmp
1375
        assert_eq!(
1✔
1376
            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 0),
1✔
1377
            Ordering::Equal
1378
        );
1379

1380
        // Compare [0, 1, 0, 0] (256) vs [1, 0, 0, 0] << 8 (256) = Equal
1381
        assert_eq!(
1✔
1382
            arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8),
1✔
1383
            Ordering::Equal
1384
        );
1385

1386
        // Compare [0, 2, 0, 0] (512) vs [1, 0, 0, 0] << 8 (256) = Greater
1387
        assert_eq!(
1✔
1388
            arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8),
1✔
1389
            Ordering::Greater
1390
        );
1391

1392
        // Compare [0, 0, 0, 0] (0) vs [1, 0, 0, 0] << 8 (256) = Less
1393
        assert_eq!(
1✔
1394
            arr_cmp_shifted(&[0u8, 0, 0, 0], &[1u8, 0, 0, 0], 8),
1✔
1395
            Ordering::Less
1396
        );
1397

1398
        // Shift overflow: shift >= bit_size, other becomes 0
1399
        // Compare [1, 0, 0, 0] vs [1, 0, 0, 0] << 32 (0) = Greater
1400
        assert_eq!(
1✔
1401
            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 32),
1✔
1402
            Ordering::Greater
1403
        );
1404

1405
        // Compare [0, 0, 0, 0] vs anything << 32 (0) = Equal
1406
        assert_eq!(
1✔
1407
            arr_cmp_shifted(&[0u8, 0, 0, 0], &[255u8, 255, 255, 255], 32),
1✔
1408
            Ordering::Equal
1409
        );
1410

1411
        // Test get_shifted_word helper with bit_shift == 0
1412
        // [1, 2, 3, 4] shifted left by 1 word (8 bits for u8)
1413
        // word 0 should be 0, word 1 should be 1, word 2 should be 2, etc.
1414
        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 0, 1, 0), 0);
1✔
1415
        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 1, 1, 0), 1);
1✔
1416
        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 2, 1, 0), 2);
1✔
1417

1418
        // Test get_shifted_word with bit_shift != 0 (cross-word bit combination)
1419
        // [0x0F, 0xF0, 0, 0] with word_shift=0, bit_shift=4
1420
        // word 0: 0x0F << 4 = 0xF0 (no lower word to borrow from)
1421
        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 0, 0, 4), 0xF0);
1✔
1422
        // word 1: (0xF0 << 4) | (0x0F >> 4) = 0x00 | 0x00 = 0x00
1423
        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 1, 0, 4), 0x00);
1✔
1424

1425
        // [0xFF, 0x00, 0, 0] with bit_shift=4
1426
        // word 0: 0xFF << 4 = 0xF0
1427
        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 0, 0, 4), 0xF0);
1✔
1428
        // word 1: (0x00 << 4) | (0xFF >> 4) = 0x00 | 0x0F = 0x0F
1429
        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 1, 0, 4), 0x0F);
1✔
1430

1431
        // Combined word_shift and bit_shift
1432
        // [0xAB, 0xCD, 0, 0] with word_shift=1, bit_shift=4
1433
        // word 0: below word_shift, returns 0
1434
        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 0, 1, 4), 0);
1✔
1435
        // word 1: source_idx=0, 0xAB << 4 = 0xB0 (no lower word)
1436
        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 1, 1, 4), 0xB0);
1✔
1437
        // word 2: source_idx=1, (0xCD << 4) | (0xAB >> 4) = 0xD0 | 0x0A = 0xDA
1438
        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 2, 1, 4), 0xDA);
1✔
1439

1440
        #[cfg(feature = "nightly")]
1441
        {
1442
            const CMP_SHIFTED_EQ: Ordering = arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8);
1443
            const CMP_SHIFTED_GT: Ordering = arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8);
1444
            assert_eq!(CMP_SHIFTED_EQ, Ordering::Equal);
1445
            assert_eq!(CMP_SHIFTED_GT, Ordering::Greater);
1446
        }
1447
    }
1✔
1448

1449
    #[test]
1450
    fn test_core_convert_u8() {
1✔
1451
        let f = Bn::<u8, 1>::from(1u8);
1✔
1452
        assert_eq!(f.array, [1]);
1✔
1453
        let f = Bn::<u8, 2>::from(1u8);
1✔
1454
        assert_eq!(f.array, [1, 0]);
1✔
1455

1456
        let f = Bn::<u16, 1>::from(1u8);
1✔
1457
        assert_eq!(f.array, [1]);
1✔
1458
        let f = Bn::<u16, 2>::from(1u8);
1✔
1459
        assert_eq!(f.array, [1, 0]);
1✔
1460

1461
        #[cfg(feature = "nightly")]
1462
        {
1463
            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(42u8);
1464
            assert_eq!(F1.array, [42, 0]);
1465
        }
1466
    }
1✔
1467

1468
    #[test]
1469
    fn test_core_convert_u16() {
1✔
1470
        let f = Bn::<u8, 1>::from(1u16);
1✔
1471
        assert_eq!(f.array, [1]);
1✔
1472
        let f = Bn::<u8, 2>::from(1u16);
1✔
1473
        assert_eq!(f.array, [1, 0]);
1✔
1474

1475
        let f = Bn::<u8, 1>::from(256u16);
1✔
1476
        assert_eq!(f.array, [0]);
1✔
1477
        let f = Bn::<u8, 2>::from(257u16);
1✔
1478
        assert_eq!(f.array, [1, 1]);
1✔
1479
        let f = Bn::<u8, 2>::from(65535u16);
1✔
1480
        assert_eq!(f.array, [255, 255]);
1✔
1481

1482
        let f = Bn::<u16, 1>::from(1u16);
1✔
1483
        assert_eq!(f.array, [1]);
1✔
1484
        let f = Bn::<u16, 2>::from(1u16);
1✔
1485
        assert_eq!(f.array, [1, 0]);
1✔
1486

1487
        let f = Bn::<u16, 1>::from(65535u16);
1✔
1488
        assert_eq!(f.array, [65535]);
1✔
1489

1490
        #[cfg(feature = "nightly")]
1491
        {
1492
            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(0x0102u16);
1493
            assert_eq!(F1.array, [0x02, 0x01]);
1494
        }
1495
    }
1✔
1496

1497
    #[test]
1498
    fn test_core_convert_u32() {
1✔
1499
        let f = Bn::<u8, 1>::from(1u32);
1✔
1500
        assert_eq!(f.array, [1]);
1✔
1501
        let f = Bn::<u8, 1>::from(256u32);
1✔
1502
        assert_eq!(f.array, [0]);
1✔
1503

1504
        let f = Bn::<u8, 2>::from(1u32);
1✔
1505
        assert_eq!(f.array, [1, 0]);
1✔
1506
        let f = Bn::<u8, 2>::from(257u32);
1✔
1507
        assert_eq!(f.array, [1, 1]);
1✔
1508
        let f = Bn::<u8, 2>::from(65535u32);
1✔
1509
        assert_eq!(f.array, [255, 255]);
1✔
1510

1511
        let f = Bn::<u8, 4>::from(1u32);
1✔
1512
        assert_eq!(f.array, [1, 0, 0, 0]);
1✔
1513
        let f = Bn::<u8, 4>::from(257u32);
1✔
1514
        assert_eq!(f.array, [1, 1, 0, 0]);
1✔
1515
        let f = Bn::<u8, 4>::from(u32::max_value());
1✔
1516
        assert_eq!(f.array, [255, 255, 255, 255]);
1✔
1517

1518
        let f = Bn::<u8, 1>::from(1u32);
1✔
1519
        assert_eq!(f.array, [1]);
1✔
1520
        let f = Bn::<u8, 1>::from(256u32);
1✔
1521
        assert_eq!(f.array, [0]);
1✔
1522

1523
        let f = Bn::<u16, 2>::from(65537u32);
1✔
1524
        assert_eq!(f.array, [1, 1]);
1✔
1525

1526
        let f = Bn::<u32, 1>::from(1u32);
1✔
1527
        assert_eq!(f.array, [1]);
1✔
1528
        let f = Bn::<u32, 2>::from(1u32);
1✔
1529
        assert_eq!(f.array, [1, 0]);
1✔
1530

1531
        let f = Bn::<u32, 1>::from(65537u32);
1✔
1532
        assert_eq!(f.array, [65537]);
1✔
1533

1534
        let f = Bn::<u32, 1>::from(u32::max_value());
1✔
1535
        assert_eq!(f.array, [4294967295]);
1✔
1536

1537
        #[cfg(feature = "nightly")]
1538
        {
1539
            const F1: Bn<u8, 4> = Bn::<u8, 4>::from(0x01020304u32);
1540
            assert_eq!(F1.array, [0x04, 0x03, 0x02, 0x01]);
1541
        }
1542
    }
1✔
1543

1544
    #[test]
1545
    fn test_core_convert_u64() {
1✔
1546
        let f = Bn::<u8, 8>::from(0x0102030405060708u64);
1✔
1547
        assert_eq!(f.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1✔
1548

1549
        let f = Bn::<u16, 4>::from(0x0102030405060708u64);
1✔
1550
        assert_eq!(f.array, [0x0708, 0x0506, 0x0304, 0x0102]);
1✔
1551

1552
        let f = Bn::<u32, 2>::from(0x0102030405060708u64);
1✔
1553
        assert_eq!(f.array, [0x05060708, 0x01020304]);
1✔
1554

1555
        let f = Bn::<u64, 1>::from(0x0102030405060708u64);
1✔
1556
        assert_eq!(f.array, [0x0102030405060708]);
1✔
1557

1558
        #[cfg(feature = "nightly")]
1559
        {
1560
            const F1: Bn<u8, 8> = Bn::<u8, 8>::from(0x0102030405060708u64);
1561
            assert_eq!(F1.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1562
        }
1563
    }
1✔
1564

1565
    #[test]
1566
    fn testsimple() {
1✔
1567
        assert_eq!(Bn::<u8, 8>::new(), Bn::<u8, 8>::new());
1✔
1568

1569
        assert_eq!(Bn::<u8, 8>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
1570
        assert_eq!(Bn::<u16, 4>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
1571
        assert_eq!(Bn::<u32, 2>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
1572
        assert_eq!(Bn::<u32, 2>::from_u64(3).unwrap().to_u32(), Some(3));
1✔
1573
        assert_eq!(Bn::<u8, 8>::from_u64(255).unwrap().to_u32(), Some(255));
1✔
1574
        assert_eq!(Bn::<u8, 8>::from_u64(256).unwrap().to_u32(), Some(256));
1✔
1575
        assert_eq!(Bn::<u8, 8>::from_u64(65536).unwrap().to_u32(), Some(65536));
1✔
1576
    }
1✔
1577
    #[test]
1578
    fn testfrom() {
1✔
1579
        let mut n1 = Bn::<u8, 8>::new();
1✔
1580
        n1.array[0] = 1;
1✔
1581
        assert_eq!(Some(1), n1.to_u32());
1✔
1582
        n1.array[1] = 1;
1✔
1583
        assert_eq!(Some(257), n1.to_u32());
1✔
1584

1585
        let mut n2 = Bn::<u16, 8>::new();
1✔
1586
        n2.array[0] = 0xffff;
1✔
1587
        assert_eq!(Some(65535), n2.to_u32());
1✔
1588
        n2.array[0] = 0x0;
1✔
1589
        n2.array[2] = 0x1;
1✔
1590
        // Overflow
1591
        assert_eq!(None, n2.to_u32());
1✔
1592
        assert_eq!(Some(0x100000000), n2.to_u64());
1✔
1593
    }
1✔
1594

1595
    #[test]
1596
    fn test_from_str_bitlengths() {
1✔
1597
        let test_s64 = "81906f5e4d3c2c01";
1✔
1598
        let test_u64: u64 = 0x81906f5e4d3c2c01;
1✔
1599
        let bb = Bn8::from_str_radix(test_s64, 16).unwrap();
1✔
1600
        let cc = Bn8::from_u64(test_u64).unwrap();
1✔
1601
        assert_eq!(cc.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
1✔
1602
        assert_eq!(bb.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
1✔
1603
        let dd = Bn16::from_u64(test_u64).unwrap();
1✔
1604
        let ff = Bn16::from_str_radix(test_s64, 16).unwrap();
1✔
1605
        assert_eq!(dd.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
1✔
1606
        assert_eq!(ff.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
1✔
1607
        let ee = Bn32::from_u64(test_u64).unwrap();
1✔
1608
        let gg = Bn32::from_str_radix(test_s64, 16).unwrap();
1✔
1609
        assert_eq!(ee.array, [0x4d3c2c01, 0x81906f5e]);
1✔
1610
        assert_eq!(gg.array, [0x4d3c2c01, 0x81906f5e]);
1✔
1611
    }
1✔
1612

1613
    #[test]
1614
    fn test_from_str_stringlengths() {
1✔
1615
        let ab = Bn::<u8, 9>::from_str_radix("2281906f5e4d3c2c01", 16).unwrap();
1✔
1616
        assert_eq!(
1✔
1617
            ab.array,
1618
            [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81, 0x22]
1619
        );
1620
        assert_eq!(
1✔
1621
            [0x2c01, 0x4d3c, 0x6f5e, 0],
1622
            Bn::<u16, 4>::from_str_radix("6f5e4d3c2c01", 16)
1✔
1623
                .unwrap()
1✔
1624
                .array
1625
        );
1626
        assert_eq!(
1✔
1627
            [0x2c01, 0x4d3c, 0x6f5e, 0x190],
1628
            Bn::<u16, 4>::from_str_radix("1906f5e4d3c2c01", 16)
1✔
1629
                .unwrap()
1✔
1630
                .array
1631
        );
1632
        assert_eq!(
1✔
1633
            Err(make_overflow_err()),
1✔
1634
            Bn::<u16, 4>::from_str_radix("f81906f5e4d3c2c01", 16)
1✔
1635
        );
1636
        assert_eq!(
1✔
1637
            Err(make_overflow_err()),
1✔
1638
            Bn::<u16, 4>::from_str_radix("af81906f5e4d3c2c01", 16)
1✔
1639
        );
1640
        assert_eq!(
1✔
1641
            Err(make_overflow_err()),
1✔
1642
            Bn::<u16, 4>::from_str_radix("baaf81906f5e4d3c2c01", 16)
1✔
1643
        );
1644
        let ac = Bn::<u16, 5>::from_str_radix("baaf81906f5e4d3c2c01", 16).unwrap();
1✔
1645
        assert_eq!(ac.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190, 0xbaaf]);
1✔
1646
    }
1✔
1647

1648
    #[test]
1649
    fn test_bit_length() {
1✔
1650
        assert_eq!(0, Bn8::from_u8(0).unwrap().bit_length());
1✔
1651
        assert_eq!(1, Bn8::from_u8(1).unwrap().bit_length());
1✔
1652
        assert_eq!(2, Bn8::from_u8(2).unwrap().bit_length());
1✔
1653
        assert_eq!(2, Bn8::from_u8(3).unwrap().bit_length());
1✔
1654
        assert_eq!(7, Bn8::from_u8(0x70).unwrap().bit_length());
1✔
1655
        assert_eq!(8, Bn8::from_u8(0xF0).unwrap().bit_length());
1✔
1656
        assert_eq!(9, Bn8::from_u16(0x1F0).unwrap().bit_length());
1✔
1657

1658
        assert_eq!(20, Bn8::from_u64(990223).unwrap().bit_length());
1✔
1659
        assert_eq!(32, Bn8::from_u64(0xefffffff).unwrap().bit_length());
1✔
1660
        assert_eq!(32, Bn8::from_u64(0x8fffffff).unwrap().bit_length());
1✔
1661
        assert_eq!(31, Bn8::from_u64(0x7fffffff).unwrap().bit_length());
1✔
1662
        assert_eq!(34, Bn8::from_u64(0x3ffffffff).unwrap().bit_length());
1✔
1663

1664
        assert_eq!(0, Bn32::from_u8(0).unwrap().bit_length());
1✔
1665
        assert_eq!(1, Bn32::from_u8(1).unwrap().bit_length());
1✔
1666
        assert_eq!(2, Bn32::from_u8(2).unwrap().bit_length());
1✔
1667
        assert_eq!(2, Bn32::from_u8(3).unwrap().bit_length());
1✔
1668
        assert_eq!(7, Bn32::from_u8(0x70).unwrap().bit_length());
1✔
1669
        assert_eq!(8, Bn32::from_u8(0xF0).unwrap().bit_length());
1✔
1670
        assert_eq!(9, Bn32::from_u16(0x1F0).unwrap().bit_length());
1✔
1671

1672
        assert_eq!(20, Bn32::from_u64(990223).unwrap().bit_length());
1✔
1673
        assert_eq!(32, Bn32::from_u64(0xefffffff).unwrap().bit_length());
1✔
1674
        assert_eq!(32, Bn32::from_u64(0x8fffffff).unwrap().bit_length());
1✔
1675
        assert_eq!(31, Bn32::from_u64(0x7fffffff).unwrap().bit_length());
1✔
1676
        assert_eq!(34, Bn32::from_u64(0x3ffffffff).unwrap().bit_length());
1✔
1677
    }
1✔
1678

1679
    #[test]
1680
    fn test_bit_length_1000() {
1✔
1681
        // Test bit_length with value 1000
1682
        let value = Bn32::from_u16(1000).unwrap();
1✔
1683

1684
        // 1000 in binary is 1111101000, which has 10 bits
1685
        // Let's verify the implementation is working correctly
1686
        assert_eq!(value.to_u32().unwrap(), 1000);
1✔
1687
        assert_eq!(value.bit_length(), 10);
1✔
1688

1689
        // Test some edge cases around 1000
1690
        assert_eq!(Bn32::from_u16(512).unwrap().bit_length(), 10); // 2^9 = 512
1✔
1691
        assert_eq!(Bn32::from_u16(1023).unwrap().bit_length(), 10); // 2^10 - 1 = 1023
1✔
1692
        assert_eq!(Bn32::from_u16(1024).unwrap().bit_length(), 11); // 2^10 = 1024
1✔
1693

1694
        // Test with different word sizes to see if this makes a difference
1695
        assert_eq!(Bn8::from_u16(1000).unwrap().bit_length(), 10);
1✔
1696
        assert_eq!(Bn16::from_u16(1000).unwrap().bit_length(), 10);
1✔
1697

1698
        // Test with different initialization methods
1699
        let value_from_str = Bn32::from_str_radix("1000", 10).unwrap();
1✔
1700
        assert_eq!(value_from_str.bit_length(), 10);
1✔
1701

1702
        // This is the problematic case - let's debug it
1703
        let value_from_bytes = Bn32::from_le_bytes(&1000u16.to_le_bytes());
1✔
1704
        // Let's see what the actual value is
1705
        assert_eq!(
1✔
1706
            value_from_bytes.to_u32().unwrap_or(0),
1✔
1707
            1000,
1708
            "from_le_bytes didn't create the correct value"
1709
        );
1710
        assert_eq!(value_from_bytes.bit_length(), 10);
1✔
1711
    }
1✔
1712
    #[test]
1713
    fn test_cmp() {
1✔
1714
        let f0 = <Bn8 as Zero>::zero();
1✔
1715
        let f1 = <Bn8 as Zero>::zero();
1✔
1716
        let f2 = <Bn8 as One>::one();
1✔
1717
        assert_eq!(f0, f1);
1✔
1718
        assert!(f2 > f0);
1✔
1719
        assert!(f0 < f2);
1✔
1720
        let f3 = Bn32::from_u64(990223).unwrap();
1✔
1721
        assert_eq!(f3, Bn32::from_u64(990223).unwrap());
1✔
1722
        let f4 = Bn32::from_u64(990224).unwrap();
1✔
1723
        assert!(f4 > Bn32::from_u64(990223).unwrap());
1✔
1724

1725
        let f3 = Bn8::from_u64(990223).unwrap();
1✔
1726
        assert_eq!(f3, Bn8::from_u64(990223).unwrap());
1✔
1727
        let f4 = Bn8::from_u64(990224).unwrap();
1✔
1728
        assert!(f4 > Bn8::from_u64(990223).unwrap());
1✔
1729

1730
        #[cfg(feature = "nightly")]
1731
        {
1732
            use core::cmp::Ordering;
1733

1734
            const A: FixedUInt<u8, 2> = FixedUInt { array: [10, 0] };
1735
            const B: FixedUInt<u8, 2> = FixedUInt { array: [20, 0] };
1736
            const C: FixedUInt<u8, 2> = FixedUInt { array: [10, 0] };
1737

1738
            const CMP_LT: Ordering = A.cmp(&B);
1739
            const CMP_GT: Ordering = B.cmp(&A);
1740
            const CMP_EQ: Ordering = A.cmp(&C);
1741
            const EQ_TRUE: bool = A.eq(&C);
1742
            const EQ_FALSE: bool = A.eq(&B);
1743

1744
            assert_eq!(CMP_LT, Ordering::Less);
1745
            assert_eq!(CMP_GT, Ordering::Greater);
1746
            assert_eq!(CMP_EQ, Ordering::Equal);
1747
            assert!(EQ_TRUE);
1748
            assert!(!EQ_FALSE);
1749
        }
1750
    }
1✔
1751

1752
    #[test]
1753
    fn test_default() {
1✔
1754
        let d: Bn8 = Default::default();
1✔
1755
        assert!(Zero::is_zero(&d));
1✔
1756

1757
        #[cfg(feature = "nightly")]
1758
        {
1759
            const D: FixedUInt<u8, 2> = <FixedUInt<u8, 2> as Default>::default();
1760
            assert!(Zero::is_zero(&D));
1761
        }
1762
    }
1✔
1763

1764
    #[test]
1765
    fn test_clone() {
1✔
1766
        let a: Bn8 = 42u8.into();
1✔
1767
        let b = a.clone();
1✔
1768
        assert_eq!(a, b);
1✔
1769

1770
        #[cfg(feature = "nightly")]
1771
        {
1772
            const A: FixedUInt<u8, 2> = FixedUInt { array: [42, 0] };
1773
            const B: FixedUInt<u8, 2> = A.clone();
1774
            assert_eq!(A.array, B.array);
1775
        }
1776
    }
1✔
1777

1778
    #[test]
1779
    fn test_le_be_bytes() {
1✔
1780
        let le_bytes = [1, 2, 3, 4];
1✔
1781
        let be_bytes = [4, 3, 2, 1];
1✔
1782
        let u8_ver = FixedUInt::<u8, 4>::from_le_bytes(&le_bytes);
1✔
1783
        let u16_ver = FixedUInt::<u16, 2>::from_le_bytes(&le_bytes);
1✔
1784
        let u32_ver = FixedUInt::<u32, 1>::from_le_bytes(&le_bytes);
1✔
1785
        let u8_ver_be = FixedUInt::<u8, 4>::from_be_bytes(&be_bytes);
1✔
1786
        let u16_ver_be = FixedUInt::<u16, 2>::from_be_bytes(&be_bytes);
1✔
1787
        let u32_ver_be = FixedUInt::<u32, 1>::from_be_bytes(&be_bytes);
1✔
1788

1789
        assert_eq!(u8_ver.array, [1, 2, 3, 4]);
1✔
1790
        assert_eq!(u16_ver.array, [0x0201, 0x0403]);
1✔
1791
        assert_eq!(u32_ver.array, [0x04030201]);
1✔
1792
        assert_eq!(u8_ver_be.array, [1, 2, 3, 4]);
1✔
1793
        assert_eq!(u16_ver_be.array, [0x0201, 0x0403]);
1✔
1794
        assert_eq!(u32_ver_be.array, [0x04030201]);
1✔
1795

1796
        let mut output_buffer = [0u8; 16];
1✔
1797
        assert_eq!(u8_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
1798
        assert_eq!(u8_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
1799
        assert_eq!(u16_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
1800
        assert_eq!(u16_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
1801
        assert_eq!(u32_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
1802
        assert_eq!(u32_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
1803
    }
1✔
1804

1805
    // Test suite for division implementation
1806
    #[test]
1807
    fn test_div_small() {
1✔
1808
        type TestInt = FixedUInt<u8, 2>;
1809

1810
        // Test small values
1811
        let test_cases = [
1✔
1812
            (20u16, 3u16, 6u16),        // 20 / 3 = 6
1✔
1813
            (100u16, 7u16, 14u16),      // 100 / 7 = 14
1✔
1814
            (255u16, 5u16, 51u16),      // 255 / 5 = 51
1✔
1815
            (65535u16, 256u16, 255u16), // max u16 / 256 = 255
1✔
1816
        ];
1✔
1817

1818
        for (dividend_val, divisor_val, expected) in test_cases {
4✔
1819
            let dividend = TestInt::from(dividend_val);
4✔
1820
            let divisor = TestInt::from(divisor_val);
4✔
1821
            let expected_result = TestInt::from(expected);
4✔
1822

1823
            assert_eq!(
4✔
1824
                dividend / divisor,
4✔
1825
                expected_result,
1826
                "Division failed for {} / {} = {}",
1827
                dividend_val,
1828
                divisor_val,
1829
                expected
1830
            );
1831
        }
1832
    }
1✔
1833

1834
    #[test]
1835
    fn test_div_edge_cases() {
1✔
1836
        type TestInt = FixedUInt<u16, 2>;
1837

1838
        // Division by 1
1839
        let dividend = TestInt::from(1000u16);
1✔
1840
        let divisor = TestInt::from(1u16);
1✔
1841
        assert_eq!(dividend / divisor, TestInt::from(1000u16));
1✔
1842

1843
        // Equal values
1844
        let dividend = TestInt::from(42u16);
1✔
1845
        let divisor = TestInt::from(42u16);
1✔
1846
        assert_eq!(dividend / divisor, TestInt::from(1u16));
1✔
1847

1848
        // Dividend < divisor
1849
        let dividend = TestInt::from(5u16);
1✔
1850
        let divisor = TestInt::from(10u16);
1✔
1851
        assert_eq!(dividend / divisor, TestInt::from(0u16));
1✔
1852

1853
        // Powers of 2
1854
        let dividend = TestInt::from(1024u16);
1✔
1855
        let divisor = TestInt::from(4u16);
1✔
1856
        assert_eq!(dividend / divisor, TestInt::from(256u16));
1✔
1857
    }
1✔
1858

1859
    #[test]
1860
    fn test_helper_methods() {
1✔
1861
        type TestInt = FixedUInt<u8, 2>;
1862

1863
        // Test const_set_bit
1864
        let mut val = <TestInt as Zero>::zero();
1✔
1865
        const_set_bit(&mut val.array, 0);
1✔
1866
        assert_eq!(val, TestInt::from(1u8));
1✔
1867

1868
        const_set_bit(&mut val.array, 8);
1✔
1869
        assert_eq!(val, TestInt::from(257u16)); // bit 0 + bit 8 = 1 + 256 = 257
1✔
1870

1871
        // Test const_cmp_shifted
1872
        let a = TestInt::from(8u8); // 1000 in binary
1✔
1873
        let b = TestInt::from(1u8); // 0001 in binary
1✔
1874

1875
        // b << 3 = 8, so a == (b << 3)
1876
        assert_eq!(
1✔
1877
            const_cmp_shifted(&a.array, &b.array, 3),
1✔
1878
            core::cmp::Ordering::Equal
1879
        );
1880

1881
        // a > (b << 2) because b << 2 = 4
1882
        assert_eq!(
1✔
1883
            const_cmp_shifted(&a.array, &b.array, 2),
1✔
1884
            core::cmp::Ordering::Greater
1885
        );
1886

1887
        // a < (b << 4) because b << 4 = 16
1888
        assert_eq!(
1✔
1889
            const_cmp_shifted(&a.array, &b.array, 4),
1✔
1890
            core::cmp::Ordering::Less
1891
        );
1892

1893
        // Test const_sub_shifted
1894
        let mut val = TestInt::from(10u8);
1✔
1895
        let one = TestInt::from(1u8);
1✔
1896
        const_sub_shifted(&mut val.array, &one.array, 2); // subtract 1 << 2 = 4
1✔
1897
        assert_eq!(val, TestInt::from(6u8)); // 10 - 4 = 6
1✔
1898
    }
1✔
1899

1900
    #[test]
1901
    fn test_shifted_operations_comprehensive() {
1✔
1902
        type TestInt = FixedUInt<u32, 2>;
1903

1904
        // Test cmp_shifted with various word boundary cases
1905
        let a = TestInt::from(0x12345678u32);
1✔
1906
        let b = TestInt::from(0x12345678u32);
1✔
1907

1908
        // Equal comparison
1909
        assert_eq!(
1✔
1910
            const_cmp_shifted(&a.array, &b.array, 0),
1✔
1911
            core::cmp::Ordering::Equal
1912
        );
1913

1914
        // Test shifts that cross word boundaries (assuming 32-bit words)
1915
        let c = TestInt::from(0x123u32); // Small number
1✔
1916
        let d = TestInt::from(0x48d159e2u32); // c << 16 + some bits
1✔
1917

1918
        // c << 16 should be less than d
1919
        assert_eq!(
1✔
1920
            const_cmp_shifted(&d.array, &c.array, 16),
1✔
1921
            core::cmp::Ordering::Greater
1922
        );
1923

1924
        // Test large shifts (beyond bit size, so shifted value becomes 0)
1925
        let e = TestInt::from(1u32);
1✔
1926
        let zero = TestInt::from(0u32);
1✔
1927
        assert_eq!(
1✔
1928
            const_cmp_shifted(&e.array, &zero.array, 100),
1✔
1929
            core::cmp::Ordering::Greater
1930
        );
1931
        // When shift is beyond bit size, 1 << 100 becomes 0, so 0 == 0
1932
        assert_eq!(
1✔
1933
            const_cmp_shifted(&zero.array, &e.array, 100),
1✔
1934
            core::cmp::Ordering::Equal
1935
        );
1936

1937
        // Test sub_shifted with word boundary crossing
1938
        let mut val = TestInt::from(0x10000u32); // 65536
1✔
1939
        let one = TestInt::from(1u32);
1✔
1940
        const_sub_shifted(&mut val.array, &one.array, 15); // subtract 1 << 15 = 32768
1✔
1941
        assert_eq!(val, TestInt::from(0x8000u32)); // 65536 - 32768 = 32768
1✔
1942

1943
        // Test sub_shifted with multi-word operations
1944
        let mut big_val = TestInt::from(0x100000000u64); // 2^32
1✔
1945
        const_sub_shifted(&mut big_val.array, &one.array, 31); // subtract 1 << 31 = 2^31
1✔
1946
        assert_eq!(big_val, TestInt::from(0x80000000u64)); // 2^32 - 2^31 = 2^31
1✔
1947
    }
1✔
1948

1949
    #[test]
1950
    fn test_shifted_operations_edge_cases() {
1✔
1951
        type TestInt = FixedUInt<u32, 2>;
1952

1953
        // Test zero shifts
1954
        let a = TestInt::from(42u32);
1✔
1955
        let a2 = TestInt::from(42u32);
1✔
1956
        assert_eq!(
1✔
1957
            const_cmp_shifted(&a.array, &a2.array, 0),
1✔
1958
            core::cmp::Ordering::Equal
1959
        );
1960

1961
        let mut b = TestInt::from(42u32);
1✔
1962
        let ten = TestInt::from(10u32);
1✔
1963
        const_sub_shifted(&mut b.array, &ten.array, 0);
1✔
1964
        assert_eq!(b, TestInt::from(32u32));
1✔
1965

1966
        // Test massive shifts (beyond bit size)
1967
        let c = TestInt::from(123u32);
1✔
1968
        let large = TestInt::from(456u32);
1✔
1969
        assert_eq!(
1✔
1970
            const_cmp_shifted(&c.array, &large.array, 200),
1✔
1971
            core::cmp::Ordering::Greater
1972
        );
1973

1974
        let mut d = TestInt::from(123u32);
1✔
1975
        const_sub_shifted(&mut d.array, &large.array, 200); // Should be no-op
1✔
1976
        assert_eq!(d, TestInt::from(123u32));
1✔
1977

1978
        // Test with zero values
1979
        let zero = TestInt::from(0u32);
1✔
1980
        let one = TestInt::from(1u32);
1✔
1981
        assert_eq!(
1✔
1982
            const_cmp_shifted(&zero.array, &zero.array, 10),
1✔
1983
            core::cmp::Ordering::Equal
1984
        );
1985
        assert_eq!(
1✔
1986
            const_cmp_shifted(&one.array, &zero.array, 10),
1✔
1987
            core::cmp::Ordering::Greater
1988
        );
1989
    }
1✔
1990

1991
    #[test]
1992
    fn test_shifted_operations_equivalence() {
1✔
1993
        type TestInt = FixedUInt<u32, 2>;
1994

1995
        // Test that optimized operations give same results as naive shift+op
1996
        let test_cases = [
1✔
1997
            (0x12345u32, 0x678u32, 4),
1✔
1998
            (0x1000u32, 0x10u32, 8),
1✔
1999
            (0xABCDu32, 0x1u32, 16),
1✔
2000
            (0x80000000u32, 0x1u32, 1),
1✔
2001
        ];
1✔
2002

2003
        for (a_val, b_val, shift) in test_cases {
4✔
2004
            let a = TestInt::from(a_val);
4✔
2005
            let b = TestInt::from(b_val);
4✔
2006

2007
            // Test cmp_shifted equivalence
2008
            let optimized_cmp = const_cmp_shifted(&a.array, &b.array, shift);
4✔
2009
            let naive_cmp = a.cmp(&(b << shift));
4✔
2010
            assert_eq!(
4✔
2011
                optimized_cmp, naive_cmp,
2012
                "cmp_shifted mismatch: {} vs ({} << {})",
2013
                a_val, b_val, shift
2014
            );
2015

2016
            // Test sub_shifted equivalence (if subtraction won't underflow)
2017
            if a >= (b << shift) {
4✔
2018
                let mut optimized_result = a;
3✔
2019
                const_sub_shifted(&mut optimized_result.array, &b.array, shift);
3✔
2020

2021
                let naive_result = a - (b << shift);
3✔
2022
                assert_eq!(
3✔
2023
                    optimized_result, naive_result,
2024
                    "sub_shifted mismatch: {} - ({} << {})",
2025
                    a_val, b_val, shift
2026
                );
2027
            }
1✔
2028
        }
2029
    }
1✔
2030

2031
    #[test]
2032
    fn test_div_assign_in_place_optimization() {
1✔
2033
        type TestInt = FixedUInt<u32, 2>;
2034

2035
        // Test that div_assign uses the optimized in-place algorithm
2036
        let test_cases = [
1✔
2037
            (100u32, 10u32, 10u32, 0u32),     // 100 / 10 = 10 remainder 0
1✔
2038
            (123u32, 7u32, 17u32, 4u32),      // 123 / 7 = 17 remainder 4
1✔
2039
            (1000u32, 13u32, 76u32, 12u32),   // 1000 / 13 = 76 remainder 12
1✔
2040
            (65535u32, 255u32, 257u32, 0u32), // 65535 / 255 = 257 remainder 0
1✔
2041
        ];
1✔
2042

2043
        for (dividend_val, divisor_val, expected_quotient, expected_remainder) in test_cases {
4✔
2044
            // Test div_assign
2045
            let mut dividend = TestInt::from(dividend_val);
4✔
2046
            let divisor = TestInt::from(divisor_val);
4✔
2047

2048
            dividend /= divisor;
4✔
2049
            assert_eq!(
4✔
2050
                dividend,
2051
                TestInt::from(expected_quotient),
4✔
2052
                "div_assign: {} / {} should be {}",
2053
                dividend_val,
2054
                divisor_val,
2055
                expected_quotient
2056
            );
2057

2058
            // Test div_assign_impl directly and verify it returns remainder
2059
            let mut dividend2 = TestInt::from(dividend_val);
4✔
2060
            let remainder = TestInt::div_assign_impl(&mut dividend2, &divisor);
4✔
2061
            assert_eq!(
4✔
2062
                dividend2,
2063
                TestInt::from(expected_quotient),
4✔
2064
                "div_assign_impl quotient: {} / {} should be {}",
2065
                dividend_val,
2066
                divisor_val,
2067
                expected_quotient
2068
            );
2069
            assert_eq!(
4✔
2070
                remainder,
2071
                TestInt::from(expected_remainder),
4✔
2072
                "div_assign_impl remainder: {} % {} should be {}",
2073
                dividend_val,
2074
                divisor_val,
2075
                expected_remainder
2076
            );
2077

2078
            // Verify: quotient * divisor + remainder == original dividend
2079
            assert_eq!(
4✔
2080
                dividend2 * divisor + remainder,
4✔
2081
                TestInt::from(dividend_val),
4✔
2082
                "Property check failed for {}",
2083
                dividend_val
2084
            );
2085
        }
2086
    }
1✔
2087

2088
    #[test]
2089
    fn test_div_assign_stack_efficiency() {
1✔
2090
        type TestInt = FixedUInt<u32, 4>; // 16 bytes each
2091

2092
        // Create test values
2093
        let mut dividend = TestInt::from(0x123456789ABCDEFu64);
1✔
2094
        let divisor = TestInt::from(0x12345u32);
1✔
2095
        let original_dividend = dividend;
1✔
2096

2097
        // Perform in-place division
2098
        dividend /= divisor;
1✔
2099

2100
        // Verify correctness
2101
        let remainder = original_dividend % divisor;
1✔
2102
        assert_eq!(dividend * divisor + remainder, original_dividend);
1✔
2103
    }
1✔
2104

2105
    #[test]
2106
    fn test_rem_assign_optimization() {
1✔
2107
        type TestInt = FixedUInt<u32, 2>;
2108

2109
        let test_cases = [
1✔
2110
            (100u32, 10u32, 0u32),    // 100 % 10 = 0
1✔
2111
            (123u32, 7u32, 4u32),     // 123 % 7 = 4
1✔
2112
            (1000u32, 13u32, 12u32),  // 1000 % 13 = 12
1✔
2113
            (65535u32, 255u32, 0u32), // 65535 % 255 = 0
1✔
2114
        ];
1✔
2115

2116
        for (dividend_val, divisor_val, expected_remainder) in test_cases {
4✔
2117
            let mut dividend = TestInt::from(dividend_val);
4✔
2118
            let divisor = TestInt::from(divisor_val);
4✔
2119

2120
            dividend %= divisor;
4✔
2121
            assert_eq!(
4✔
2122
                dividend,
2123
                TestInt::from(expected_remainder),
4✔
2124
                "rem_assign: {} % {} should be {}",
2125
                dividend_val,
2126
                divisor_val,
2127
                expected_remainder
2128
            );
2129
        }
2130
    }
1✔
2131

2132
    #[test]
2133
    fn test_div_with_remainder_property() {
1✔
2134
        type TestInt = FixedUInt<u32, 2>;
2135

2136
        // Test division with remainder property verification
2137
        let test_cases = [
1✔
2138
            (100u32, 10u32, 10u32),     // 100 / 10 = 10
1✔
2139
            (123u32, 7u32, 17u32),      // 123 / 7 = 17
1✔
2140
            (1000u32, 13u32, 76u32),    // 1000 / 13 = 76
1✔
2141
            (65535u32, 255u32, 257u32), // 65535 / 255 = 257
1✔
2142
        ];
1✔
2143

2144
        for (dividend_val, divisor_val, expected_quotient) in test_cases {
4✔
2145
            let dividend = TestInt::from(dividend_val);
4✔
2146
            let divisor = TestInt::from(divisor_val);
4✔
2147

2148
            // Test that div operator (which uses div_impl) works correctly
2149
            let quotient = dividend / divisor;
4✔
2150
            assert_eq!(
4✔
2151
                quotient,
2152
                TestInt::from(expected_quotient),
4✔
2153
                "Division: {} / {} should be {}",
2154
                dividend_val,
2155
                divisor_val,
2156
                expected_quotient
2157
            );
2158

2159
            // Verify the division property still holds
2160
            let remainder = dividend % divisor;
4✔
2161
            assert_eq!(
4✔
2162
                quotient * divisor + remainder,
4✔
2163
                dividend,
2164
                "Division property check failed for {}",
2165
                dividend_val
2166
            );
2167
        }
2168
    }
1✔
2169

2170
    #[test]
2171
    fn test_code_simplification_benefits() {
1✔
2172
        type TestInt = FixedUInt<u32, 2>;
2173

2174
        // Verify division property holds
2175
        let dividend = TestInt::from(12345u32);
1✔
2176
        let divisor = TestInt::from(67u32);
1✔
2177
        let quotient = dividend / divisor;
1✔
2178
        let remainder = dividend % divisor;
1✔
2179

2180
        // The division property should still hold
2181
        assert_eq!(quotient * divisor + remainder, dividend);
1✔
2182
    }
1✔
2183

2184
    #[test]
2185
    fn test_rem_assign_correctness_after_fix() {
1✔
2186
        type TestInt = FixedUInt<u32, 2>;
2187

2188
        // Test specific case: 17 % 5 = 2
2189
        let mut a = TestInt::from(17u32);
1✔
2190
        let b = TestInt::from(5u32);
1✔
2191

2192
        // Before fix: div_assign_impl would modify a to quotient (3), then assign remainder (2)
2193
        // After fix: div_rem properly computes remainder without corrupting intermediate state
2194
        a %= b;
1✔
2195
        assert_eq!(a, TestInt::from(2u32), "17 % 5 should be 2");
1✔
2196

2197
        // Test that the original RemAssign bug would have failed this
2198
        let mut test_val = TestInt::from(100u32);
1✔
2199
        test_val %= TestInt::from(7u32);
1✔
2200
        assert_eq!(
1✔
2201
            test_val,
2202
            TestInt::from(2u32),
1✔
2203
            "100 % 7 should be 2 (not 14, the quotient)"
2204
        );
2205
    }
1✔
2206

2207
    #[test]
2208
    fn test_div_property_based() {
1✔
2209
        type TestInt = FixedUInt<u16, 2>;
2210

2211
        // Property: quotient * divisor + remainder == dividend
2212
        let test_pairs = [
1✔
2213
            (12345u16, 67u16),
1✔
2214
            (1000u16, 13u16),
1✔
2215
            (65535u16, 255u16),
1✔
2216
            (5000u16, 7u16),
1✔
2217
        ];
1✔
2218

2219
        for (dividend_val, divisor_val) in test_pairs {
4✔
2220
            let dividend = TestInt::from(dividend_val);
4✔
2221
            let divisor = TestInt::from(divisor_val);
4✔
2222

2223
            let quotient = dividend / divisor;
4✔
2224

2225
            // Property verification: quotient * divisor + remainder == dividend
2226
            let remainder = dividend - (quotient * divisor);
4✔
2227
            let reconstructed = quotient * divisor + remainder;
4✔
2228

2229
            assert_eq!(
4✔
2230
                reconstructed,
2231
                dividend,
2232
                "Property failed for {} / {}: {} * {} + {} != {}",
2233
                dividend_val,
2234
                divisor_val,
2235
                quotient.to_u32().unwrap_or(0),
×
2236
                divisor_val,
2237
                remainder.to_u32().unwrap_or(0),
×
2238
                dividend_val
2239
            );
2240

2241
            // Remainder should be less than divisor
2242
            assert!(
4✔
2243
                remainder < divisor,
4✔
2244
                "Remainder {} >= divisor {} for {} / {}",
2245
                remainder.to_u32().unwrap_or(0),
×
2246
                divisor_val,
2247
                dividend_val,
2248
                divisor_val
2249
            );
2250
        }
2251
    }
1✔
2252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc