Skip to content

Commit

Permalink
Add BoxedUint::bits (#328)
Browse files Browse the repository at this point in the history
Adds a function for counting the number of bits in a value, i.e. the
index of the highest set bit.

Since `BoxedUint` isn't `const fn`, this can be implemented in
constant-time using `subtle`.
  • Loading branch information
tarcieri authored Nov 27, 2023
1 parent e334064 commit f07de91
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 6 deletions.
6 changes: 1 addition & 5 deletions src/uint/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod add;
mod add_mod;
mod bit_and;
mod bits;
mod cmp;
pub(crate) mod encoding;
mod modular;
Expand Down Expand Up @@ -150,11 +151,6 @@ impl BoxedUint {
self.limbs.len()
}

/// Get the precision of this [`BoxedUint`] in bits.
pub fn bits(&self) -> usize {
self.limbs.len() * Limb::BITS
}

/// Perform a carry chain-like operation over the limbs of the inputs,
/// constructing a result from the returned limbs and carry which is
/// widened to the same width as the widest input.
Expand Down
49 changes: 49 additions & 0 deletions src/uint/boxed/bits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Bit manipulation functions.

use crate::{BoxedUint, Limb, Zero};
use subtle::{ConditionallySelectable, ConstantTimeEq};

impl BoxedUint {
/// Calculate the number of bits needed to represent this number, i.e. the index of the highest
/// set bit.
///
/// Use [`BoxedUint::bits_precision`] to get the total capacity of this integer.
pub fn bits(&self) -> usize {
// Use `u32` because `subtle` can't select on `usize` and it matches what `core` uses for
// the return value of `leading_zeros`
let mut leading_zeros = 0u32;
let mut n = 0u32;

for limb in self.limbs.iter().rev() {
n.conditional_assign(&(n + 1), !limb.is_zero() | !n.ct_eq(&0));

// Set `leading_zeros` for the first nonzero limb we encounter
leading_zeros.conditional_assign(&(limb.leading_zeros() as u32), n.ct_eq(&1));
}

Limb::BITS * (n as usize) - (leading_zeros as usize)
}

/// Get the precision of this [`BoxedUint`] in bits.
pub fn bits_precision(&self) -> usize {
self.limbs.len() * Limb::BITS
}
}

#[cfg(test)]
mod tests {
use super::BoxedUint;
use hex_literal::hex;

#[test]
fn bits() {
assert_eq!(0, BoxedUint::zero().bits());
assert_eq!(128, BoxedUint::max(128).bits());

let n1 = BoxedUint::from_be_slice(&hex!("000000000029ffffffffffffffffffff"), 128).unwrap();
assert_eq!(86, n1.bits());

let n2 = BoxedUint::from_be_slice(&hex!("00000000004000000000000000000000"), 128).unwrap();
assert_eq!(87, n2.bits());
}
}
2 changes: 1 addition & 1 deletion tests/boxed_uint_proptests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ proptest! {
match Option::<BoxedUint>::from(a.checked_add(&b)) {
Some(actual) => prop_assert_eq!(expected, to_biguint(&actual)),
None => {
let max = BoxedUint::max(a.bits());
let max = BoxedUint::max(a.bits_precision());
prop_assert!(expected > to_biguint(&max));
}
}
Expand Down

0 comments on commit f07de91

Please sign in to comment.