use crate::{Choice, WideWord, Word};
use core::hint::black_box;
#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn carrying_add(lhs: Word, rhs: Word, carry: Word) -> (Word, Word) {
let a = lhs as WideWord;
let b = rhs as WideWord;
let carry = carry as WideWord;
let ret = a + b + carry;
(ret as Word, (ret >> Word::BITS) as Word)
}
#[inline(always)]
pub(crate) const fn overflowing_add(lhs: Word, rhs: Word) -> (Word, Word) {
let (res, carry) = lhs.overflowing_add(rhs);
(res, carry as Word)
}
#[inline]
pub(crate) const fn borrowing_sub(lhs: Word, rhs: Word, borrow: Word) -> (Word, Word) {
let difference = lhs
.wrapping_sub(rhs)
.wrapping_sub(borrow >> (Word::BITS - 1));
let borrow = ((!lhs & rhs) | ((!lhs | rhs) & difference)) >> (Word::BITS - 1);
(difference, black_box(borrow).wrapping_neg())
}
#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn widening_mul(lhs: Word, rhs: Word) -> (Word, Word) {
let a = lhs as WideWord;
let b = rhs as WideWord;
let ret = a * b;
(ret as Word, (ret >> Word::BITS) as Word)
}
#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn carrying_mul_add(
lhs: Word,
rhs: Word,
addend: Word,
carry: Word,
) -> (Word, Word) {
let lhs = lhs as WideWord;
let rhs = rhs as WideWord;
let addend = addend as WideWord;
let carry = carry as WideWord;
let ret = ((lhs * rhs) + addend) + carry;
(ret as Word, (ret >> Word::BITS) as Word)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn leading_zeros(value: Word) -> u32 {
if cfg!(any(target_arch = "aarch64", target_arch = "x86_64")) {
value.leading_zeros()
} else {
cpubits::cpubits! {
32 => { leading_zeros_u32(value) }
64 => {
let hi = (value >> 32) as u32;
let lo = value as u32;
let mask = Choice::from_u32_nz(hi).not().to_u32_mask();
leading_zeros_u32(hi).wrapping_add(mask & leading_zeros_u32(lo))
}
}
}
}
#[inline]
const fn leading_zeros_u32(value: u32) -> u32 {
if cfg!(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
)) {
value.leading_zeros()
} else {
leading_zeros_u32_soft(value)
}
}
#[inline]
const fn leading_zeros_u32_soft(mut value: u32) -> u32 {
let mut count = 0u32;
let mut shift = 16;
while shift != 0 {
let mask = Choice::from_u32_nz(value >> (u32::BITS - shift))
.not()
.to_u32_mask();
value ^= mask & (value ^ (value << shift));
count = count.wrapping_add(mask & shift);
shift >>= 1;
}
count.wrapping_add(Choice::from_u32_nz(value).not().to_u32_mask() & 1)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn trailing_zeros(value: Word) -> u32 {
if cfg!(any(target_arch = "aarch64", target_arch = "x86_64")) {
value.trailing_zeros()
} else {
cpubits::cpubits! {
32 => { trailing_zeros_u32(value) }
64 => {
let hi = (value >> 32) as u32;
let lo = value as u32;
let mask = Choice::from_u32_nz(lo).not().to_u32_mask();
trailing_zeros_u32(lo).wrapping_add(mask & trailing_zeros_u32(hi))
}
}
}
}
#[inline]
const fn trailing_zeros_u32(value: u32) -> u32 {
if cfg!(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
)) {
value.trailing_zeros()
} else {
leading_zeros_u32_soft(value.reverse_bits())
}
}
#[inline]
pub(crate) const fn u32_max(a: u32, b: u32) -> u32 {
Choice::from_u32_lt(a, b).select_u32(a, b)
}
#[inline]
pub(crate) const fn u32_min(a: u32, b: u32) -> u32 {
Choice::from_u32_lt(a, b).select_u32(b, a)
}
#[inline(never)]
#[allow(clippy::cast_possible_truncation, reason = "needs triage")]
#[allow(clippy::integer_division_remainder_used, reason = "needs triage")]
pub(crate) const fn u32_rem(n: u32, d: u32) -> u32 {
assert!(d > 0, "divisor must be nonzero");
let c = u64::MAX / (d as u64) + 1;
(((c.wrapping_mul(n as u64) as u128) * d as u128) >> 64) as u32
}
#[inline(always)]
pub(crate) const fn u32_bits(n: u32) -> u32 {
u32::BITS - n.leading_zeros()
}
#[allow(clippy::cast_possible_truncation)]
#[cfg(target_pointer_width = "32")]
#[inline]
pub(crate) const fn usize_lt(a: usize, b: usize) -> Choice {
Choice::from_u32_lt(a as u32, b as u32)
}
#[allow(clippy::cast_possible_truncation)]
#[cfg(target_pointer_width = "64")]
#[inline]
pub(crate) const fn usize_lt(a: usize, b: usize) -> Choice {
Choice::from_u64_lt(a as u64, b as u64)
}
cpubits::cpubits! {
32 => {
#[must_use]
pub(crate) const fn u32_invert_odd(value: u32) -> u32 {
debug_assert!(value & 1 == 1, "value must be odd");
let x = value.wrapping_mul(3) ^ 2;
let y = 1u32.wrapping_sub(x.wrapping_mul(value));
let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
x.wrapping_mul(y.wrapping_add(1))
}
}
}
#[must_use]
pub(crate) const fn u64_invert_odd(value: u64) -> u64 {
debug_assert!(value & 1 == 1, "value must be odd");
let x = value.wrapping_mul(3) ^ 2;
let y = 1u64.wrapping_sub(x.wrapping_mul(value));
let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
x.wrapping_mul(y.wrapping_add(1))
}
#[cfg(test)]
mod tests {
use super::{u32_max, u32_min, u32_rem, usize_lt};
use crate::Word;
const _: () = {
assert!(super::borrowing_sub(0, 0, Word::MAX).0 == Word::MAX);
assert!(super::borrowing_sub(0, 0, Word::MAX).1 == Word::MAX);
assert!(super::borrowing_sub(1, 0, Word::MAX).1 == 0);
assert!(super::leading_zeros(0) == Word::BITS);
assert!(super::trailing_zeros(0) == Word::BITS);
assert!(super::leading_zeros_u32_soft(0) == u32::BITS);
};
#[test]
fn zero_counts() {
for bit in 0..Word::BITS {
for value in [0, 1 << bit, !(1 << bit), Word::MAX >> bit] {
assert_eq!(super::leading_zeros(value), value.leading_zeros());
assert_eq!(super::trailing_zeros(value), value.trailing_zeros());
}
}
}
#[test]
fn zero_counts_u32() {
for bit in 0..u32::BITS {
for value in [0, 1 << bit, !(1 << bit), u32::MAX >> bit] {
assert_eq!(super::leading_zeros_u32(value), value.leading_zeros());
assert_eq!(super::trailing_zeros_u32(value), value.trailing_zeros());
assert_eq!(super::leading_zeros_u32_soft(value), value.leading_zeros());
assert_eq!(
super::leading_zeros_u32_soft(value.reverse_bits()),
value.trailing_zeros()
);
}
}
}
#[test]
fn carrying_mul_add_cannot_overflow() {
let lhs = Word::MAX;
let rhs = Word::MAX;
let addend = Word::MAX;
let carry_in = Word::MAX;
let (result, carry_out) = super::carrying_mul_add(lhs, rhs, addend, carry_in);
assert_eq!(result, Word::MAX);
assert_eq!(carry_out, Word::MAX);
}
#[test]
fn test_u32_const_min() {
assert_eq!(u32_min(0, 5), 0);
assert_eq!(u32_min(7, 0), 0);
assert_eq!(u32_min(7, 5), 5);
assert_eq!(u32_min(7, 7), 7);
}
#[test]
fn test_u32_const_max() {
assert_eq!(u32_max(0, 5), 5);
assert_eq!(u32_max(7, 0), 7);
assert_eq!(u32_max(7, 5), 7);
assert_eq!(u32_max(7, 7), 7);
}
#[test]
fn test_u32_const_rem() {
assert_eq!(u32_rem(0, 5), 0);
assert_eq!(u32_rem(4, 5), 4);
assert_eq!(u32_rem(7, 5), 2);
assert_eq!(u32_rem(101, 5), 1);
}
#[test]
fn test_usize_const_lt() {
assert!(usize_lt(0, 5).to_bool());
assert!(!usize_lt(7, 0).to_bool());
assert!(!usize_lt(7, 5).to_bool());
assert!(!usize_lt(7, 7).to_bool());
}
cpubits::cpubits! {
32 => {
#[test]
fn test_u32_invert_odd() {
use super::u32_invert_odd;
assert_eq!(u32_invert_odd(1), 1);
assert_eq!(u32_invert_odd(5).wrapping_mul(5), 1);
assert_eq!(u32_invert_odd(u32::MAX).wrapping_mul(u32::MAX), 1);
}
}
}
}