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

kaidokert / fixed-bigint-rs / 16410106030

21 Jul 2025 06:28AM UTC coverage: 93.48% (+0.009%) from 93.471%
16410106030

Pull #50

github

kaidokert
test rotate overflow shifts
Pull Request #50: Improve rotate tests

6 of 8 new or added lines in 2 files covered. (75.0%)

6 existing lines in 1 file now uncovered.

1448 of 1549 relevant lines covered (93.48%)

2029.24 hits per line

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

97.74
/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::ops::overflowing::{OverflowingAdd, OverflowingSub};
16
use num_traits::{Bounded, One, PrimInt, ToPrimitive, Zero};
17

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

21
use crate::machineword::MachineWord;
22

23
#[allow(unused_imports)]
24
use num_traits::{FromPrimitive, Num};
25

26
mod add_sub_impl;
27
mod bit_ops_impl;
28
mod euclid;
29
mod iter_impl;
30
mod mul_div_impl;
31
mod num_integer_impl;
32
mod num_traits_casts;
33
mod num_traits_identity;
34
mod prim_int_impl;
35
mod roots_impl;
36
mod string_conversion;
37
mod to_from_bytes;
38

39
/// Fixed-size unsigned integer, represented by array of N words of builtin unsigned type T
40
#[derive(Debug, Clone, Copy, core::cmp::PartialEq, core::cmp::Eq)]
41
pub struct FixedUInt<T, const N: usize>
42
where
43
    T: MachineWord,
44
{
45
    /// Little-endian word array
46
    array: [T; N],
47
}
48

49
const LONGEST_WORD_IN_BITS: usize = 128;
50

51
impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
52
    const WORD_SIZE: usize = core::mem::size_of::<T>();
53
    const WORD_BITS: usize = Self::WORD_SIZE * 8;
54
    const BYTE_SIZE: usize = Self::WORD_SIZE * N;
55
    const BIT_SIZE: usize = Self::BYTE_SIZE * 8;
56

57
    /// Creates and zero-initializes a FixedUInt.
58
    pub fn new() -> FixedUInt<T, N> {
20,729✔
59
        FixedUInt {
20,729✔
60
            array: [T::zero(); N],
20,729✔
61
        }
20,729✔
62
    }
20,729✔
63

64
    /// Returns number of used bits.
65
    pub fn bit_length(&self) -> u32 {
423✔
66
        Self::BIT_SIZE as u32 - self.leading_zeros()
423✔
67
    }
423✔
68

69
    /// Performs a division, returning both the quotient and reminder in a tuple.
70
    pub fn div_rem(&self, divisor: &Self) -> (Self, Self) {
295✔
71
        let quot = *self / *divisor;
295✔
72
        let tmp = quot * *divisor;
295✔
73
        let rem = *self - tmp;
295✔
74
        (quot, rem)
295✔
75
    }
295✔
76

77
    /// Create a little-endian integer value from its representation as a byte array in little endian.
78
    pub fn from_le_bytes(bytes: &[u8]) -> Self {
4,388✔
79
        let mut ret = Self::new();
4,388✔
80
        let total_bytes = core::cmp::min(bytes.len(), N * Self::WORD_SIZE);
4,388✔
81

82
        for (byte_index, &byte) in bytes.iter().enumerate().take(total_bytes) {
18,043✔
83
            let word_index = byte_index / Self::WORD_SIZE;
18,043✔
84
            let byte_in_word = byte_index % Self::WORD_SIZE;
18,043✔
85

18,043✔
86
            let byte_value: T = byte.into();
18,043✔
87
            let shifted_value = byte_value.shl(byte_in_word * 8);
18,043✔
88
            ret.array[word_index] |= shifted_value;
18,043✔
89
        }
18,043✔
90
        ret
4,388✔
91
    }
4,388✔
92

93
    /// Create a big-endian integer value from its representation as a byte array in big endian.
94
    pub fn from_be_bytes(bytes: &[u8]) -> Self {
13✔
95
        let mut ret = Self::new();
13✔
96
        let capacity_bytes = N * Self::WORD_SIZE;
13✔
97
        let total_bytes = core::cmp::min(bytes.len(), capacity_bytes);
13✔
98

99
        // For consistent truncation semantics with from_le_bytes, always take the
100
        // least significant bytes (rightmost bytes in big-endian representation)
101
        let start_offset = if bytes.len() > capacity_bytes {
13✔
102
            bytes.len() - capacity_bytes
3✔
103
        } else {
104
            0
10✔
105
        };
106

107
        for (byte_index, _) in (0..total_bytes).enumerate() {
40✔
108
            // Take bytes from the end of the input (least significant in BE)
40✔
109
            let be_byte_index = start_offset + total_bytes - 1 - byte_index;
40✔
110
            let word_index = byte_index / Self::WORD_SIZE;
40✔
111
            let byte_in_word = byte_index % Self::WORD_SIZE;
40✔
112

40✔
113
            let byte_value: T = bytes[be_byte_index].into();
40✔
114
            let shifted_value = byte_value.shl(byte_in_word * 8);
40✔
115
            ret.array[word_index] |= shifted_value;
40✔
116
        }
40✔
117
        ret
13✔
118
    }
13✔
119

120
    /// Converts the FixedUInt into a little-endian byte array.
121
    pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
9✔
122
        let total_bytes = N * Self::WORD_SIZE;
9✔
123
        if output_buffer.len() < total_bytes {
9✔
124
            return Err(false); // Buffer too small
1✔
125
        }
8✔
126
        for (i, word) in self.array.iter().enumerate() {
16✔
127
            let start = i * Self::WORD_SIZE;
16✔
128
            let end = start + Self::WORD_SIZE;
16✔
129
            let word_bytes = word.to_le_bytes();
16✔
130
            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
16✔
131
        }
16✔
132
        Ok(&output_buffer[..total_bytes])
8✔
133
    }
9✔
134

135
    /// Converts the FixedUInt into a big-endian byte array.
136
    pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
10✔
137
        let total_bytes = N * Self::WORD_SIZE;
10✔
138
        if output_buffer.len() < total_bytes {
10✔
139
            return Err(false); // Buffer too small
1✔
140
        }
9✔
141
        for (i, word) in self.array.iter().rev().enumerate() {
18✔
142
            let start = i * Self::WORD_SIZE;
18✔
143
            let end = start + Self::WORD_SIZE;
18✔
144
            let word_bytes = word.to_be_bytes();
18✔
145
            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
18✔
146
        }
18✔
147
        Ok(&output_buffer[..total_bytes])
9✔
148
    }
10✔
149

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

154
        let word_size = Self::WORD_SIZE;
25✔
155
        // need length minus leading zeros
156
        let need_bits = self.bit_length() as usize;
25✔
157
        // number of needed characters (bits/4 = bytes * 2)
158
        let need_chars = if need_bits > 0 { need_bits / 4 } else { 0 };
25✔
159

160
        if result.len() < need_chars {
25✔
161
            // not enough space in result...
162
            return Err(Error {});
3✔
163
        }
22✔
164
        let offset = result.len() - need_chars;
22✔
165
        for i in result.iter_mut() {
282✔
166
            *i = b'0';
282✔
167
        }
282✔
168

169
        for iter_words in 0..self.array.len() {
95✔
170
            let word = self.array[iter_words];
95✔
171
            let mut encoded = [0u8; LONGEST_WORD_IN_BITS / 4];
95✔
172
            let encode_slice = &mut encoded[0..word_size * 2];
95✔
173
            let mut wordbytes = word.to_le_bytes();
95✔
174
            wordbytes.as_mut().reverse();
95✔
175
            let wordslice = wordbytes.as_ref();
95✔
176
            to_slice_hex(wordslice, encode_slice).map_err(|_| Error {})?;
95✔
177
            for iter_chars in 0..encode_slice.len() {
288✔
178
                let copy_char_to = (iter_words * word_size * 2) + iter_chars;
288✔
179
                if copy_char_to <= need_chars {
288✔
180
                    let reverse_index = offset + (need_chars - copy_char_to);
88✔
181
                    if reverse_index <= result.len() && reverse_index > 0 {
88✔
182
                        let current_char = encode_slice[(encode_slice.len() - 1) - iter_chars];
88✔
183
                        result[reverse_index - 1] = current_char;
88✔
184
                    }
88✔
185
                }
200✔
186
            }
187
        }
188

189
        let convert = core::str::from_utf8(result).map_err(|_| Error {})?;
22✔
190
        let pos = convert.find(|c: char| c != '0');
217✔
191
        match pos {
22✔
192
            Some(x) => Ok(&convert[x..convert.len()]),
18✔
193
            None => {
194
                if convert.starts_with('0') {
4✔
195
                    Ok("0")
4✔
196
                } else {
197
                    Ok(convert)
×
198
                }
199
            }
200
        }
201
    }
25✔
202

203
    /// Converts to decimal string, given a buffer. CAVEAT: This method removes any leading zeroes
204
    pub fn to_radix_str<'a>(
26✔
205
        &self,
26✔
206
        result: &'a mut [u8],
26✔
207
        radix: u8,
26✔
208
    ) -> Result<&'a str, core::fmt::Error> {
26✔
209
        type Error = core::fmt::Error;
210

211
        if !(2..=16).contains(&radix) {
26✔
212
            return Err(Error {}); // Radix out of supported range
×
213
        }
26✔
214
        for byte in result.iter_mut() {
440✔
215
            *byte = b'0';
440✔
216
        }
440✔
217
        if self.is_zero() {
26✔
218
            if !result.is_empty() {
5✔
219
                result[0] = b'0';
5✔
220
                return core::str::from_utf8(&result[0..1]).map_err(|_| Error {});
5✔
221
            } else {
222
                return Err(Error {});
×
223
            }
224
        }
21✔
225

226
        let mut number = *self;
21✔
227
        let mut idx = result.len();
21✔
228

229
        let radix_t = Self::from(radix);
21✔
230

231
        while !number.is_zero() {
152✔
232
            if idx == 0 {
134✔
233
                return Err(Error {}); // not enough space in result...
3✔
234
            }
131✔
235

236
            idx -= 1;
131✔
237
            let (quotient, remainder) = number.div_rem(&radix_t);
131✔
238

239
            let digit = remainder.to_u8().unwrap();
131✔
240
            result[idx] = match digit {
131✔
241
                0..=9 => b'0' + digit,          // digits
131✔
242
                10..=16 => b'a' + (digit - 10), // alphabetic digits for bases > 10
14✔
243
                _ => return Err(Error {}),
×
244
            };
245

246
            number = quotient;
131✔
247
        }
248

249
        let start = result[idx..].iter().position(|&c| c != b'0').unwrap_or(0);
18✔
250
        let radix_str = core::str::from_utf8(&result[idx + start..]).map_err(|_| Error {})?;
18✔
251
        Ok(radix_str)
18✔
252
    }
26✔
253

254
    fn hex_fmt(
5✔
255
        &self,
5✔
256
        formatter: &mut core::fmt::Formatter<'_>,
5✔
257
        uppercase: bool,
5✔
258
    ) -> Result<(), core::fmt::Error>
5✔
259
    where
5✔
260
        u8: core::convert::TryFrom<T>,
5✔
261
    {
262
        type Err = core::fmt::Error;
263

264
        fn to_casedigit(byte: u8, uppercase: bool) -> Result<char, core::fmt::Error> {
14✔
265
            let digit = core::char::from_digit(byte as u32, 16).ok_or(Err {})?;
14✔
266
            if uppercase {
14✔
267
                digit.to_uppercase().next().ok_or(Err {})
12✔
268
            } else {
269
                digit.to_lowercase().next().ok_or(Err {})
2✔
270
            }
271
        }
14✔
272

273
        let mut leading_zero: bool = true;
5✔
274

275
        let mut maybe_write = |nibble: char| -> Result<(), core::fmt::Error> {
14✔
276
            leading_zero &= nibble == '0';
14✔
277
            if !leading_zero {
14✔
278
                formatter.write_char(nibble)?;
10✔
279
            }
4✔
280
            Ok(())
13✔
281
        };
14✔
282

283
        for index in (0..N).rev() {
6✔
284
            let val = self.array[index];
6✔
285
            let mask: T = 0xff.into();
6✔
286
            for j in (0..Self::WORD_SIZE as u32).rev() {
7✔
287
                let masked = val & mask.shl((j * 8) as usize);
7✔
288

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

291
                maybe_write(to_casedigit((byte & 0xf0) >> 4, uppercase)?)?;
7✔
292
                maybe_write(to_casedigit(byte & 0x0f, uppercase)?)?;
7✔
293
            }
294
        }
295
        Ok(())
4✔
296
    }
5✔
297
    // Add other to target, return overflow status
298
    // Note: in-place, no extra storage is used
299
    fn add_impl(target: &mut Self, other: &Self) -> bool {
2,468✔
300
        let mut carry = T::zero();
2,468✔
301
        for i in 0..N {
8,295✔
302
            let (res, carry1) = target.array[i].overflowing_add(&other.array[i]);
5,827✔
303
            let (res, carry2) = res.overflowing_add(&carry);
5,827✔
304
            carry = if carry1 || carry2 {
5,827✔
305
                T::one()
200✔
306
            } else {
307
                T::zero()
5,627✔
308
            };
309
            target.array[i] = res
5,827✔
310
        }
311
        !carry.is_zero()
2,468✔
312
    }
2,468✔
313

314
    // Here to avoid duplicating this in two traits
315
    fn saturating_add_impl(self, other: &Self) -> Self {
27✔
316
        let res = self.overflowing_add(other);
27✔
317
        if res.1 {
27✔
318
            Self::max_value()
18✔
319
        } else {
320
            res.0
9✔
321
        }
322
    }
27✔
323

324
    // Subtract other from target, return overflow status
325
    // Note: in-place, no extra storage is used
326
    fn sub_impl(target: &mut Self, other: &Self) -> bool {
4,905✔
327
        let mut borrow = T::zero();
4,905✔
328
        for i in 0..N {
15,727✔
329
            let (res, borrow1) = target.array[i].overflowing_sub(&other.array[i]);
10,822✔
330
            let (res, borrow2) = res.overflowing_sub(&borrow);
10,822✔
331
            borrow = if borrow1 || borrow2 {
10,822✔
332
                T::one()
326✔
333
            } else {
334
                T::zero()
10,496✔
335
            };
336
            target.array[i] = res;
10,822✔
337
        }
338
        !borrow.is_zero()
4,905✔
339
    }
4,905✔
340

341
    fn saturating_sub_impl(self, other: &Self) -> Self {
36✔
342
        let res = self.overflowing_sub(other);
36✔
343
        if res.1 {
36✔
344
            Self::zero()
27✔
345
        } else {
346
            res.0
9✔
347
        }
348
    }
36✔
349

350
    // Multiply op1 with op2, return overflow status
351
    // Note: uses extra `result` variable, not sure if in-place multiply is possible at all.
352
    fn mul_impl<const CHECK_OVERFLOW: bool>(op1: &Self, op2: &Self) -> (Self, bool) {
6,747✔
353
        let mut result = Self::zero();
6,747✔
354
        let mut overflowed = false;
6,747✔
355
        // Calculate N+1 rounds, to check for overflow
356
        let max_rounds = if CHECK_OVERFLOW { N + 1 } else { N };
6,747✔
357
        let t_max = T::max_value().to_double();
6,747✔
358
        for i in 0..N {
21,500✔
359
            let mut carry = T::DoubleWord::zero();
14,753✔
360
            for j in 0..N {
53,810✔
361
                let round = i + j;
39,057✔
362
                if round < max_rounds {
39,057✔
363
                    let mul_res = op1.array[i].to_double() * op2.array[j].to_double();
34,861✔
364
                    let mut accumulator = T::DoubleWord::zero();
34,861✔
365
                    if round < N {
34,861✔
366
                        accumulator = result.array[round].to_double();
26,905✔
367
                    }
26,905✔
368
                    accumulator = accumulator + mul_res + carry;
34,861✔
369

370
                    if accumulator > t_max {
34,861✔
371
                        carry = accumulator >> Self::WORD_BITS;
964✔
372
                        accumulator = accumulator & t_max;
964✔
373
                    } else {
33,897✔
374
                        carry = T::DoubleWord::zero();
33,897✔
375
                    }
33,897✔
376
                    if round < N {
34,861✔
377
                        result.array[round] = T::from_double(accumulator);
26,905✔
378
                    } else if CHECK_OVERFLOW {
26,905✔
379
                        overflowed = overflowed || !accumulator.is_zero();
7,956✔
380
                    }
×
381
                }
4,196✔
382
            }
383
            if !carry.is_zero() && CHECK_OVERFLOW {
14,753✔
384
                overflowed = true;
207✔
385
            }
14,546✔
386
        }
387
        (result, overflowed)
6,747✔
388
    }
6,747✔
389

390
    // Divide dividend with divisor, return result
391
    // Note: uses 2x extra storage in `result` and `current`
392
    fn div_impl(dividend: &Self, divisor: &Self) -> Self {
2,195✔
393
        let mut result = Self::zero();
2,195✔
394
        let mut current = Self::one();
2,195✔
395
        let mut tmp = *dividend;
2,195✔
396
        let mut denom = *divisor;
2,195✔
397

398
        let half_max: T::DoubleWord =
2,195✔
399
            T::one().to_double() + (T::max_value().to_double() / (T::one() + T::one()).to_double());
2,195✔
400
        let mut overflow = false;
2,195✔
401
        while denom <= *dividend {
9,680✔
402
            if denom.array[N - 1].to_double() >= half_max {
7,488✔
403
                overflow = true;
3✔
404
                break;
3✔
405
            }
7,485✔
406
            current <<= 1;
7,485✔
407
            denom <<= 1;
7,485✔
408
        }
409
        if !overflow {
2,195✔
410
            current >>= 1;
2,192✔
411
            denom >>= 1;
2,192✔
412
        }
2,192✔
413
        while !current.is_zero() {
9,683✔
414
            if tmp >= denom {
7,488✔
415
                tmp -= denom;
4,436✔
416
                result |= current;
4,436✔
417
            }
4,436✔
418
            current >>= 1;
7,488✔
419
            denom >>= 1;
7,488✔
420
        }
421
        result
2,195✔
422
    }
2,195✔
423

424
    // Shifts left by bits, in-place
425
    fn shl_impl(target: &mut Self, bits: usize) {
15,510✔
426
        let nwords = bits / Self::WORD_BITS;
15,510✔
427
        let nbits = bits - nwords * Self::WORD_BITS;
15,510✔
428

429
        // Move words
430
        for i in (nwords..N).rev() {
33,937✔
431
            target.array[i] = target.array[i - nwords];
33,937✔
432
        }
33,937✔
433
        // Zero out the remainder
434
        for i in 0..nwords {
15,510✔
435
            target.array[i] = T::zero();
59✔
436
        }
59✔
437

438
        if nbits != 0 {
15,510✔
439
            // Shift remaining bits
440
            for i in (1..N).rev() {
18,427✔
441
                let right = target.array[i].shl(nbits);
18,427✔
442
                let left = target.array[i - 1].shr(Self::WORD_BITS - nbits);
18,427✔
443
                target.array[i] = right | left;
18,427✔
444
            }
18,427✔
445
            target.array[0] = target.array[0].shl(nbits);
15,450✔
446
        }
60✔
447
    }
15,510✔
448

449
    // Shifts right by bits, in-place
450
    fn shr_impl(target: &mut Self, bits: usize) {
19,582✔
451
        let nwords = bits / Self::WORD_BITS;
19,582✔
452
        let nbits = bits - nwords * Self::WORD_BITS;
19,582✔
453

454
        let last_index = N - 1;
19,582✔
455
        let last_word = N - nwords;
19,582✔
456

457
        // Move words
458
        for i in 0..last_word {
42,733✔
459
            target.array[i] = target.array[i + nwords];
42,733✔
460
        }
42,733✔
461

462
        // Zero out the remainder
463
        for i in last_word..N {
19,582✔
464
            target.array[i] = T::zero();
57✔
465
        }
57✔
466

467
        if nbits != 0 {
19,582✔
468
            // Shift remaining bits
469
            for i in 0..last_index {
23,135✔
470
                let left = target.array[i].shr(nbits);
23,135✔
471
                let right = target.array[i + 1].shl(Self::WORD_BITS - nbits);
23,135✔
472
                target.array[i] = left | right;
23,135✔
473
            }
23,135✔
474
            target.array[last_index] = target.array[last_index].shr(nbits);
19,512✔
475
        }
70✔
476
    }
19,582✔
477
}
478

479
impl<T: MachineWord, const N: usize> Default for FixedUInt<T, N> {
NEW
480
    fn default() -> Self {
×
NEW
481
        Self::new()
×
UNCOV
482
    }
×
483
}
484

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

487
// #region Ordering
488

489
impl<T: MachineWord, const N: usize> core::cmp::Ord for FixedUInt<T, N> {
490
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
19,406✔
491
        for i in (0..N).rev() {
40,541✔
492
            if self.array[i] > other.array[i] {
40,541✔
493
                return core::cmp::Ordering::Greater;
6,773✔
494
            }
33,768✔
495
            if self.array[i] < other.array[i] {
33,768✔
496
                return core::cmp::Ordering::Less;
11,488✔
497
            }
22,280✔
498
        }
499
        core::cmp::Ordering::Equal
1,145✔
500
    }
19,406✔
501
}
502

503
impl<T: MachineWord, const N: usize> core::cmp::PartialOrd for FixedUInt<T, N> {
504
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
19,406✔
505
        Some(self.cmp(other))
19,406✔
506
    }
19,406✔
507
}
508

509
// #endregion Ordering
510

511
// #region core::convert::From<primitive>
512

513
impl<T: MachineWord, const N: usize> core::convert::From<u8> for FixedUInt<T, N> {
514
    fn from(x: u8) -> Self {
2,760✔
515
        let mut ret = Self::new();
2,760✔
516
        ret.array[0] = x.into();
2,760✔
517
        ret
2,760✔
518
    }
2,760✔
519
}
520

521
impl<T: MachineWord, const N: usize> core::convert::From<u16> for FixedUInt<T, N> {
522
    fn from(x: u16) -> Self {
1,362✔
523
        Self::from_le_bytes(&x.to_le_bytes())
1,362✔
524
    }
1,362✔
525
}
526

527
impl<T: MachineWord, const N: usize> core::convert::From<u32> for FixedUInt<T, N> {
528
    fn from(x: u32) -> Self {
1,945✔
529
        Self::from_le_bytes(&x.to_le_bytes())
1,945✔
530
    }
1,945✔
531
}
532

533
impl<T: MachineWord, const N: usize> core::convert::From<u64> for FixedUInt<T, N> {
534
    fn from(x: u64) -> Self {
182✔
535
        Self::from_le_bytes(&x.to_le_bytes())
182✔
536
    }
182✔
537
}
538

539
// #endregion core::convert::From<primitive>
540

541
// #region helpers
542

543
// This is slightly less than ideal, but PIE isn't directly constructible
544
// due to unstable members.
545
fn make_parse_int_err() -> core::num::ParseIntError {
3✔
546
    <u8>::from_str_radix("-", 2).err().unwrap()
3✔
547
}
3✔
548
fn make_overflow_err() -> core::num::ParseIntError {
1,476✔
549
    <u8>::from_str_radix("101", 16).err().unwrap()
1,476✔
550
}
1,476✔
551
fn make_empty_error() -> core::num::ParseIntError {
50✔
552
    <u8>::from_str_radix("", 8).err().unwrap()
50✔
553
}
50✔
554

555
fn to_slice_hex<T: AsRef<[u8]>>(
95✔
556
    input: T,
95✔
557
    output: &mut [u8],
95✔
558
) -> Result<(), core::num::ParseIntError> {
95✔
559
    fn from_digit(byte: u8) -> Option<char> {
288✔
560
        core::char::from_digit(byte as u32, 16)
288✔
561
    }
288✔
562
    let r = input.as_ref();
95✔
563
    if r.len() * 2 != output.len() {
95✔
UNCOV
564
        return Err(make_parse_int_err());
×
565
    }
95✔
566
    for i in 0..r.len() {
144✔
567
        let byte = r[i];
144✔
568
        output[i * 2] = from_digit((byte & 0xf0) >> 4).ok_or_else(make_parse_int_err)? as u8;
144✔
569
        output[i * 2 + 1] = from_digit(byte & 0x0f).ok_or_else(make_parse_int_err)? as u8;
144✔
570
    }
571

572
    Ok(())
95✔
573
}
95✔
574

575
enum PanicReason {
576
    Add,
577
    Sub,
578
    Mul,
579
    DivByZero,
580
    RemByZero,
581
}
582

583
fn maybe_panic(r: PanicReason) {
3✔
584
    match r {
3✔
UNCOV
585
        PanicReason::Add => panic!("attempt to add with overflow"),
×
UNCOV
586
        PanicReason::Sub => panic!("attempt to subtract with overflow"),
×
UNCOV
587
        PanicReason::Mul => panic!("attempt to multiply with overflow"),
×
588
        PanicReason::DivByZero => panic!("attempt to divide by zero"),
2✔
589
        PanicReason::RemByZero => {
590
            panic!("attempt to calculate the remainder with a divisor of zero")
1✔
591
        }
592
    }
593
}
594

595
// #endregion helpers
596

597
#[cfg(test)]
598
mod tests {
599
    use super::FixedUInt as Bn;
600
    use super::*;
601

602
    type Bn8 = Bn<u8, 8>;
603
    type Bn16 = Bn<u16, 4>;
604
    type Bn32 = Bn<u32, 2>;
605

606
    #[test]
607
    fn test_core_convert_u8() {
1✔
608
        let f = Bn::<u8, 1>::from(1u8);
1✔
609
        assert_eq!(f.array, [1]);
1✔
610
        let f = Bn::<u8, 2>::from(1u8);
1✔
611
        assert_eq!(f.array, [1, 0]);
1✔
612

613
        let f = Bn::<u16, 1>::from(1u8);
1✔
614
        assert_eq!(f.array, [1]);
1✔
615
        let f = Bn::<u16, 2>::from(1u8);
1✔
616
        assert_eq!(f.array, [1, 0]);
1✔
617
    }
1✔
618

619
    #[test]
620
    fn test_core_convert_u16() {
1✔
621
        let f = Bn::<u8, 1>::from(1u16);
1✔
622
        assert_eq!(f.array, [1]);
1✔
623
        let f = Bn::<u8, 2>::from(1u16);
1✔
624
        assert_eq!(f.array, [1, 0]);
1✔
625

626
        let f = Bn::<u8, 1>::from(256u16);
1✔
627
        assert_eq!(f.array, [0]);
1✔
628
        let f = Bn::<u8, 2>::from(257u16);
1✔
629
        assert_eq!(f.array, [1, 1]);
1✔
630
        let f = Bn::<u8, 2>::from(65535u16);
1✔
631
        assert_eq!(f.array, [255, 255]);
1✔
632

633
        let f = Bn::<u16, 1>::from(1u16);
1✔
634
        assert_eq!(f.array, [1]);
1✔
635
        let f = Bn::<u16, 2>::from(1u16);
1✔
636
        assert_eq!(f.array, [1, 0]);
1✔
637

638
        let f = Bn::<u16, 1>::from(65535u16);
1✔
639
        assert_eq!(f.array, [65535]);
1✔
640
    }
1✔
641

642
    #[test]
643
    fn test_core_convert_u32() {
1✔
644
        let f = Bn::<u8, 1>::from(1u32);
1✔
645
        assert_eq!(f.array, [1]);
1✔
646
        let f = Bn::<u8, 1>::from(256u32);
1✔
647
        assert_eq!(f.array, [0]);
1✔
648

649
        let f = Bn::<u8, 2>::from(1u32);
1✔
650
        assert_eq!(f.array, [1, 0]);
1✔
651
        let f = Bn::<u8, 2>::from(257u32);
1✔
652
        assert_eq!(f.array, [1, 1]);
1✔
653
        let f = Bn::<u8, 2>::from(65535u32);
1✔
654
        assert_eq!(f.array, [255, 255]);
1✔
655

656
        let f = Bn::<u8, 4>::from(1u32);
1✔
657
        assert_eq!(f.array, [1, 0, 0, 0]);
1✔
658
        let f = Bn::<u8, 4>::from(257u32);
1✔
659
        assert_eq!(f.array, [1, 1, 0, 0]);
1✔
660
        let f = Bn::<u8, 4>::from(u32::max_value());
1✔
661
        assert_eq!(f.array, [255, 255, 255, 255]);
1✔
662

663
        let f = Bn::<u8, 1>::from(1u32);
1✔
664
        assert_eq!(f.array, [1]);
1✔
665
        let f = Bn::<u8, 1>::from(256u32);
1✔
666
        assert_eq!(f.array, [0]);
1✔
667

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

671
        let f = Bn::<u32, 1>::from(1u32);
1✔
672
        assert_eq!(f.array, [1]);
1✔
673
        let f = Bn::<u32, 2>::from(1u32);
1✔
674
        assert_eq!(f.array, [1, 0]);
1✔
675

676
        let f = Bn::<u32, 1>::from(65537u32);
1✔
677
        assert_eq!(f.array, [65537]);
1✔
678

679
        let f = Bn::<u32, 1>::from(u32::max_value());
1✔
680
        assert_eq!(f.array, [4294967295]);
1✔
681
    }
1✔
682

683
    #[test]
684
    fn testsimple() {
1✔
685
        assert_eq!(Bn::<u8, 8>::new(), Bn::<u8, 8>::new());
1✔
686

687
        assert_eq!(Bn::<u8, 8>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
688
        assert_eq!(Bn::<u16, 4>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
689
        assert_eq!(Bn::<u32, 2>::from_u8(3).unwrap().to_u32(), Some(3));
1✔
690
        assert_eq!(Bn::<u32, 2>::from_u64(3).unwrap().to_u32(), Some(3));
1✔
691
        assert_eq!(Bn::<u8, 8>::from_u64(255).unwrap().to_u32(), Some(255));
1✔
692
        assert_eq!(Bn::<u8, 8>::from_u64(256).unwrap().to_u32(), Some(256));
1✔
693
        assert_eq!(Bn::<u8, 8>::from_u64(65536).unwrap().to_u32(), Some(65536));
1✔
694
    }
1✔
695
    #[test]
696
    fn testfrom() {
1✔
697
        let mut n1 = Bn::<u8, 8>::new();
1✔
698
        n1.array[0] = 1;
1✔
699
        assert_eq!(Some(1), n1.to_u32());
1✔
700
        n1.array[1] = 1;
1✔
701
        assert_eq!(Some(257), n1.to_u32());
1✔
702

703
        let mut n2 = Bn::<u16, 8>::new();
1✔
704
        n2.array[0] = 0xffff;
1✔
705
        assert_eq!(Some(65535), n2.to_u32());
1✔
706
        n2.array[0] = 0x0;
1✔
707
        n2.array[2] = 0x1;
1✔
708
        // Overflow
709
        assert_eq!(None, n2.to_u32());
1✔
710
        assert_eq!(Some(0x100000000), n2.to_u64());
1✔
711
    }
1✔
712

713
    #[test]
714
    fn test_from_str_bitlengths() {
1✔
715
        let test_s64 = "81906f5e4d3c2c01";
1✔
716
        let test_u64: u64 = 0x81906f5e4d3c2c01;
1✔
717
        let bb = Bn8::from_str_radix(test_s64, 16).unwrap();
1✔
718
        let cc = Bn8::from_u64(test_u64).unwrap();
1✔
719
        assert_eq!(cc.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
1✔
720
        assert_eq!(bb.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
1✔
721
        let dd = Bn16::from_u64(test_u64).unwrap();
1✔
722
        let ff = Bn16::from_str_radix(test_s64, 16).unwrap();
1✔
723
        assert_eq!(dd.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
1✔
724
        assert_eq!(ff.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
1✔
725
        let ee = Bn32::from_u64(test_u64).unwrap();
1✔
726
        let gg = Bn32::from_str_radix(test_s64, 16).unwrap();
1✔
727
        assert_eq!(ee.array, [0x4d3c2c01, 0x81906f5e]);
1✔
728
        assert_eq!(gg.array, [0x4d3c2c01, 0x81906f5e]);
1✔
729
    }
1✔
730

731
    #[test]
732
    fn test_from_str_stringlengths() {
1✔
733
        let ab = Bn::<u8, 9>::from_str_radix("2281906f5e4d3c2c01", 16).unwrap();
1✔
734
        assert_eq!(
1✔
735
            ab.array,
736
            [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81, 0x22]
737
        );
738
        assert_eq!(
1✔
739
            [0x2c01, 0x4d3c, 0x6f5e, 0],
740
            Bn::<u16, 4>::from_str_radix("6f5e4d3c2c01", 16)
1✔
741
                .unwrap()
1✔
742
                .array
743
        );
744
        assert_eq!(
1✔
745
            [0x2c01, 0x4d3c, 0x6f5e, 0x190],
746
            Bn::<u16, 4>::from_str_radix("1906f5e4d3c2c01", 16)
1✔
747
                .unwrap()
1✔
748
                .array
749
        );
750
        assert_eq!(
1✔
751
            Err(make_overflow_err()),
1✔
752
            Bn::<u16, 4>::from_str_radix("f81906f5e4d3c2c01", 16)
1✔
753
        );
754
        assert_eq!(
1✔
755
            Err(make_overflow_err()),
1✔
756
            Bn::<u16, 4>::from_str_radix("af81906f5e4d3c2c01", 16)
1✔
757
        );
758
        assert_eq!(
1✔
759
            Err(make_overflow_err()),
1✔
760
            Bn::<u16, 4>::from_str_radix("baaf81906f5e4d3c2c01", 16)
1✔
761
        );
762
        let ac = Bn::<u16, 5>::from_str_radix("baaf81906f5e4d3c2c01", 16).unwrap();
1✔
763
        assert_eq!(ac.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190, 0xbaaf]);
1✔
764
    }
1✔
765

766
    #[test]
767
    fn test_bit_length() {
1✔
768
        assert_eq!(0, Bn8::from_u8(0).unwrap().bit_length());
1✔
769
        assert_eq!(1, Bn8::from_u8(1).unwrap().bit_length());
1✔
770
        assert_eq!(2, Bn8::from_u8(2).unwrap().bit_length());
1✔
771
        assert_eq!(2, Bn8::from_u8(3).unwrap().bit_length());
1✔
772
        assert_eq!(7, Bn8::from_u8(0x70).unwrap().bit_length());
1✔
773
        assert_eq!(8, Bn8::from_u8(0xF0).unwrap().bit_length());
1✔
774
        assert_eq!(9, Bn8::from_u16(0x1F0).unwrap().bit_length());
1✔
775

776
        assert_eq!(20, Bn8::from_u64(990223).unwrap().bit_length());
1✔
777
        assert_eq!(32, Bn8::from_u64(0xefffffff).unwrap().bit_length());
1✔
778
        assert_eq!(32, Bn8::from_u64(0x8fffffff).unwrap().bit_length());
1✔
779
        assert_eq!(31, Bn8::from_u64(0x7fffffff).unwrap().bit_length());
1✔
780
        assert_eq!(34, Bn8::from_u64(0x3ffffffff).unwrap().bit_length());
1✔
781

782
        assert_eq!(0, Bn32::from_u8(0).unwrap().bit_length());
1✔
783
        assert_eq!(1, Bn32::from_u8(1).unwrap().bit_length());
1✔
784
        assert_eq!(2, Bn32::from_u8(2).unwrap().bit_length());
1✔
785
        assert_eq!(2, Bn32::from_u8(3).unwrap().bit_length());
1✔
786
        assert_eq!(7, Bn32::from_u8(0x70).unwrap().bit_length());
1✔
787
        assert_eq!(8, Bn32::from_u8(0xF0).unwrap().bit_length());
1✔
788
        assert_eq!(9, Bn32::from_u16(0x1F0).unwrap().bit_length());
1✔
789

790
        assert_eq!(20, Bn32::from_u64(990223).unwrap().bit_length());
1✔
791
        assert_eq!(32, Bn32::from_u64(0xefffffff).unwrap().bit_length());
1✔
792
        assert_eq!(32, Bn32::from_u64(0x8fffffff).unwrap().bit_length());
1✔
793
        assert_eq!(31, Bn32::from_u64(0x7fffffff).unwrap().bit_length());
1✔
794
        assert_eq!(34, Bn32::from_u64(0x3ffffffff).unwrap().bit_length());
1✔
795
    }
1✔
796

797
    #[test]
798
    fn test_bit_length_1000() {
1✔
799
        // Test bit_length with value 1000
800
        let value = Bn32::from_u16(1000).unwrap();
1✔
801

802
        // 1000 in binary is 1111101000, which has 10 bits
803
        // Let's verify the implementation is working correctly
804
        assert_eq!(value.to_u32().unwrap(), 1000);
1✔
805
        assert_eq!(value.bit_length(), 10);
1✔
806

807
        // Test some edge cases around 1000
808
        assert_eq!(Bn32::from_u16(512).unwrap().bit_length(), 10); // 2^9 = 512
1✔
809
        assert_eq!(Bn32::from_u16(1023).unwrap().bit_length(), 10); // 2^10 - 1 = 1023
1✔
810
        assert_eq!(Bn32::from_u16(1024).unwrap().bit_length(), 11); // 2^10 = 1024
1✔
811

812
        // Test with different word sizes to see if this makes a difference
813
        assert_eq!(Bn8::from_u16(1000).unwrap().bit_length(), 10);
1✔
814
        assert_eq!(Bn16::from_u16(1000).unwrap().bit_length(), 10);
1✔
815

816
        // Test with different initialization methods
817
        let value_from_str = Bn32::from_str_radix("1000", 10).unwrap();
1✔
818
        assert_eq!(value_from_str.bit_length(), 10);
1✔
819

820
        // This is the problematic case - let's debug it
821
        let value_from_bytes = Bn32::from_le_bytes(&1000u16.to_le_bytes());
1✔
822
        // Let's see what the actual value is
823
        assert_eq!(
1✔
824
            value_from_bytes.to_u32().unwrap_or(0),
1✔
825
            1000,
UNCOV
826
            "from_le_bytes didn't create the correct value"
×
827
        );
828
        assert_eq!(value_from_bytes.bit_length(), 10);
1✔
829
    }
1✔
830
    #[test]
831
    fn test_cmp() {
1✔
832
        let f0 = Bn8::zero();
1✔
833
        let f1 = Bn8::zero();
1✔
834
        let f2 = Bn8::one();
1✔
835
        assert_eq!(f0, f1);
1✔
836
        assert!(f2 > f0);
1✔
837
        assert!(f0 < f2);
1✔
838
        let f3 = Bn32::from_u64(990223).unwrap();
1✔
839
        assert_eq!(f3, Bn32::from_u64(990223).unwrap());
1✔
840
        let f4 = Bn32::from_u64(990224).unwrap();
1✔
841
        assert!(f4 > Bn32::from_u64(990223).unwrap());
1✔
842

843
        let f3 = Bn8::from_u64(990223).unwrap();
1✔
844
        assert_eq!(f3, Bn8::from_u64(990223).unwrap());
1✔
845
        let f4 = Bn8::from_u64(990224).unwrap();
1✔
846
        assert!(f4 > Bn8::from_u64(990223).unwrap());
1✔
847
    }
1✔
848

849
    #[test]
850
    fn test_le_be_bytes() {
1✔
851
        let le_bytes = [1, 2, 3, 4];
1✔
852
        let be_bytes = [4, 3, 2, 1];
1✔
853
        let u8_ver = FixedUInt::<u8, 4>::from_le_bytes(&le_bytes);
1✔
854
        let u16_ver = FixedUInt::<u16, 2>::from_le_bytes(&le_bytes);
1✔
855
        let u32_ver = FixedUInt::<u32, 1>::from_le_bytes(&le_bytes);
1✔
856
        let u8_ver_be = FixedUInt::<u8, 4>::from_be_bytes(&be_bytes);
1✔
857
        let u16_ver_be = FixedUInt::<u16, 2>::from_be_bytes(&be_bytes);
1✔
858
        let u32_ver_be = FixedUInt::<u32, 1>::from_be_bytes(&be_bytes);
1✔
859

860
        assert_eq!(u8_ver.array, [1, 2, 3, 4]);
1✔
861
        assert_eq!(u16_ver.array, [0x0201, 0x0403]);
1✔
862
        assert_eq!(u32_ver.array, [0x04030201]);
1✔
863
        assert_eq!(u8_ver_be.array, [1, 2, 3, 4]);
1✔
864
        assert_eq!(u16_ver_be.array, [0x0201, 0x0403]);
1✔
865
        assert_eq!(u32_ver_be.array, [0x04030201]);
1✔
866

867
        let mut output_buffer = [0u8; 16];
1✔
868
        assert_eq!(u8_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
869
        assert_eq!(u8_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
870
        assert_eq!(u16_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
871
        assert_eq!(u16_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
872
        assert_eq!(u32_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
1✔
873
        assert_eq!(u32_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
1✔
874
    }
1✔
875
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc