Skip to content

feat: constify bitwise and shift functions #441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/algorithms/div/reciprocal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ pub fn reciprocal_2_mg10(d: u128) -> u64 {
v
}

#[allow(clippy::missing_const_for_fn)] // False positive
#[inline]
#[must_use]
fn mul_hi(a: Wrapping<u64>, b: Wrapping<u64>) -> Wrapping<u64> {
Expand All @@ -144,7 +143,6 @@ fn mul_hi(a: Wrapping<u64>, b: Wrapping<u64>) -> Wrapping<u64> {
Wrapping((r >> 64) as u64)
}

#[allow(clippy::missing_const_for_fn)] // False positive
#[inline]
#[must_use]
fn muladd_hi(a: Wrapping<u64>, b: Wrapping<u64>, c: Wrapping<u64>) -> Wrapping<u64> {
Expand Down
109 changes: 62 additions & 47 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
self
}

/// Inverts all the bits in the integer.
#[inline]
#[must_use]
pub const fn not(mut self) -> Self {
if BITS == 0 {
return Self::ZERO;
}

let mut i = 0;
while i < LIMBS {
self.limbs[i] = !self.limbs[i];
i += 1;
}

self.limbs[LIMBS - 1] &= Self::MASK;
self
}

/// Returns the number of leading zeros in the binary representation of
/// `self`.
#[inline]
Expand Down Expand Up @@ -196,15 +178,13 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[inline]
#[must_use]
pub const fn count_ones(&self) -> usize {
let mut total = 0;

let mut ones = 0;
let mut i = 0;
while i < LIMBS {
total += self.limbs[i].count_ones() as usize;
ones += self.limbs[i].count_ones() as usize;
i += 1;
}

total
ones
}

/// Returns the number of zeros in the binary representation of `self`.
Expand Down Expand Up @@ -279,7 +259,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// shift is larger than BITS (which is IMHO not very useful).
#[inline(always)]
#[must_use]
pub fn checked_shl(self, rhs: usize) -> Option<Self> {
pub const fn checked_shl(self, rhs: usize) -> Option<Self> {
match self.overflowing_shl(rhs) {
(value, false) => Some(value),
_ => None,
Expand All @@ -293,7 +273,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// [`Uint::MAX`] if the bits shifted out would be non-zero.
#[inline(always)]
#[must_use]
pub fn saturating_shl(self, rhs: usize) -> Self {
pub const fn saturating_shl(self, rhs: usize) -> Self {
match self.overflowing_shl(rhs) {
(value, false) => value,
_ => Self::MAX,
Expand All @@ -310,19 +290,21 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// the shift is larger than `BITS` (which is IMHO not very useful).
#[inline]
#[must_use]
pub fn overflowing_shl(self, rhs: usize) -> (Self, bool) {
pub const fn overflowing_shl(self, rhs: usize) -> (Self, bool) {
let (limbs, bits) = (rhs / 64, rhs % 64);
if limbs >= LIMBS {
return (Self::ZERO, self != Self::ZERO);
return (Self::ZERO, !self.is_zero());
}

let word_bits = 64;
let mut r = Self::ZERO;
let mut carry = 0;
for i in 0..Self::LIMBS - limbs {
let mut i = 0;
while i < Self::LIMBS - limbs {
let x = self.limbs[i];
r.limbs[i + limbs] = (x << bits) | carry;
carry = (x >> (word_bits - bits - 1)) >> 1;
i += 1;
}
r.limbs[LIMBS - 1] &= Self::MASK;
(r, carry != 0)
Expand All @@ -336,7 +318,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// by `BITS` (which is IMHO not very useful).
#[inline(always)]
#[must_use]
pub fn wrapping_shl(self, rhs: usize) -> Self {
pub const fn wrapping_shl(self, rhs: usize) -> Self {
self.overflowing_shl(rhs).0
}

Expand All @@ -353,7 +335,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// shift is larger than BITS (which is IMHO not very useful).
#[inline(always)]
#[must_use]
pub fn checked_shr(self, rhs: usize) -> Option<Self> {
pub const fn checked_shr(self, rhs: usize) -> Option<Self> {
match self.overflowing_shr(rhs) {
(value, false) => Some(value),
_ => None,
Expand All @@ -374,19 +356,21 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// the shift is larger than `BITS` (which is IMHO not very useful).
#[inline]
#[must_use]
pub fn overflowing_shr(self, rhs: usize) -> (Self, bool) {
pub const fn overflowing_shr(self, rhs: usize) -> (Self, bool) {
let (limbs, bits) = (rhs / 64, rhs % 64);
if limbs >= LIMBS {
return (Self::ZERO, self != Self::ZERO);
return (Self::ZERO, !self.is_zero());
}

let word_bits = 64;
let mut r = Self::ZERO;
let mut carry = 0;
for i in 0..LIMBS - limbs {
let mut i = 0;
while i < LIMBS - limbs {
let x = self.limbs[LIMBS - 1 - i];
r.limbs[LIMBS - 1 - i - limbs] = (x >> bits) | carry;
carry = (x << (word_bits - bits - 1)) << 1;
i += 1;
}
(r, carry != 0)
}
Expand All @@ -402,21 +386,22 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// by `BITS` (which is IMHO not very useful).
#[inline(always)]
#[must_use]
pub fn wrapping_shr(self, rhs: usize) -> Self {
pub const fn wrapping_shr(self, rhs: usize) -> Self {
self.overflowing_shr(rhs).0
}

/// Arithmetic shift right by `rhs` bits.
#[inline]
#[must_use]
pub fn arithmetic_shr(self, rhs: usize) -> Self {
pub const fn arithmetic_shr(self, rhs: usize) -> Self {
if BITS == 0 {
return Self::ZERO;
}
let sign = self.bit(BITS - 1);
let mut r = self >> rhs;
let mut r = self.wrapping_shr(rhs);
if sign {
r |= Self::MAX << BITS.saturating_sub(rhs);
// r |= Self::MAX << BITS.saturating_sub(rhs);
r = r.bitor(Self::MAX.wrapping_shl(BITS.saturating_sub(rhs)));
}
r
}
Expand All @@ -425,34 +410,50 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// truncated bits to the end of the resulting integer.
#[inline]
#[must_use]
#[allow(clippy::missing_const_for_fn)] // False positive
pub fn rotate_left(self, rhs: usize) -> Self {
pub const fn rotate_left(self, rhs: usize) -> Self {
if BITS == 0 {
return Self::ZERO;
}
let rhs = rhs % BITS;
(self << rhs) | (self >> (BITS - rhs))
// (self << rhs) | (self >> (BITS - rhs))
self.wrapping_shl(rhs).bitor(self.wrapping_shr(BITS - rhs))
}

/// Shifts the bits to the right by a specified amount, `rhs`, wrapping the
/// truncated bits to the beginning of the resulting integer.
#[inline(always)]
#[must_use]
pub fn rotate_right(self, rhs: usize) -> Self {
pub const fn rotate_right(self, rhs: usize) -> Self {
if BITS == 0 {
return Self::ZERO;
}
let rhs = rhs % BITS;
self.rotate_left(BITS - rhs)
}

/// Inverts all the bits in the integer.
#[inline]
#[must_use]
pub const fn not(mut self) -> Self {
if BITS == 0 {
return Self::ZERO;
}
let mut i = 0;
while i < LIMBS {
self.limbs[i] = !self.limbs[i];
i += 1;
}
self.limbs[LIMBS - 1] &= Self::MASK;
self
}
}

impl<const BITS: usize, const LIMBS: usize> Not for Uint<BITS, LIMBS> {
type Output = Self;

#[inline]
fn not(self) -> Self::Output {
Self::not(self)
self.not()
}
}

Expand All @@ -466,7 +467,7 @@ impl<const BITS: usize, const LIMBS: usize> Not for &Uint<BITS, LIMBS> {
}

macro_rules! impl_bit_op {
($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => {
($op:tt, $assign_op:tt, $trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => {
impl<const BITS: usize, const LIMBS: usize> $trait_assign<Uint<BITS, LIMBS>>
for Uint<BITS, LIMBS>
{
Expand Down Expand Up @@ -530,15 +531,29 @@ macro_rules! impl_bit_op {

#[inline(always)]
fn $fn(self, rhs: &Uint<BITS, LIMBS>) -> Self::Output {
self.clone().$fn(rhs)
self.clone().$fn(*rhs)
}
}

impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[doc = concat!("Returns the bitwise `", stringify!($op), "` of the two numbers.")]
#[inline(always)]
#[must_use]
pub const fn $fn(mut self, rhs: Uint<BITS, LIMBS>) -> Uint<BITS, LIMBS> {
let mut i = 0;
while i < LIMBS {
self.limbs[i] $assign_op rhs.limbs[i];
i += 1;
}
self
}
}
};
}

impl_bit_op!(BitOr, bitor, BitOrAssign, bitor_assign);
impl_bit_op!(BitAnd, bitand, BitAndAssign, bitand_assign);
impl_bit_op!(BitXor, bitxor, BitXorAssign, bitxor_assign);
impl_bit_op!(|, |=, BitOr, bitor, BitOrAssign, bitor_assign);
impl_bit_op!(&, &=, BitAnd, bitand, BitAndAssign, bitand_assign);
impl_bit_op!(^, ^=, BitXor, bitxor, BitXorAssign, bitxor_assign);

impl<const BITS: usize, const LIMBS: usize> Shl<Self> for Uint<BITS, LIMBS> {
type Output = Self;
Expand Down
8 changes: 4 additions & 4 deletions src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[cfg(feature = "alloc")]
#[must_use]
#[inline]
#[allow(clippy::missing_const_for_fn)]
#[allow(clippy::missing_const_for_fn)] // Not const in big-endian.
pub fn as_le_bytes(&self) -> Cow<'_, [u8]> {
// On little endian platforms this is a no-op.
#[cfg(target_endian = "little")]
Expand All @@ -58,11 +58,11 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
// In others, reverse each limb and return a copy.
#[cfg(target_endian = "big")]
return Cow::Owned({
let mut cpy = *self;
for limb in &mut cpy.limbs {
let mut limbs = self.limbs;
for limb in &mut limbs {
*limb = limb.swap_bytes();
}
unsafe { slice::from_raw_parts(cpy.limbs.as_ptr().cast(), Self::BYTES).to_vec() }
unsafe { slice::from_raw_parts(limbs.as_ptr().cast(), Self::BYTES).to_vec() }
});
}

Expand Down
25 changes: 23 additions & 2 deletions src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,29 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// Returns true if the value is zero.
#[inline]
#[must_use]
pub fn is_zero(&self) -> bool {
*self == Self::ZERO
pub const fn is_zero(&self) -> bool {
self.const_eq(Self::ZERO)
}

/// Returns `true` if `self` equals `other`.
///
/// Note that this currently performs worse than the derived `PartialEq`
/// implementation when one of the numbers is known at compile-time due to
/// language limitations.
// https://godbolt.org/z/aM1nE1Pfh
#[inline]
#[must_use]
pub const fn const_eq(self, other: Self) -> bool {
// TODO: Replace with `self == other` and deprecate once `PartialEq` is const.
let a = self.as_limbs();
let b = other.as_limbs();
let mut i = 0;
let mut r = true;
while i < a.len() {
r &= a[i] == b[i];
i += 1;
}
r
}
}

Expand Down
Loading