#[inline(always)]
pub const fn checked_mul_u64(a: u64, b: u64) -> Option<u64> {
let (ah, al) = (a >> 32, a & 0xFFFF_FFFF);
let (bh, bl) = (b >> 32, b & 0xFFFF_FFFF);
if ah != 0 && bh != 0 {
return None;
}
let cross = ah * bl + al * bh;
if cross >> 32 != 0 {
return None;
}
(cross << 32).checked_add(al * bl)
}
#[inline(always)]
pub const fn saturating_mul_u64(a: u64, b: u64) -> u64 {
match checked_mul_u64(a, b) {
Some(product) => product,
None => u64::MAX,
}
}
pub trait LeanMul: Sized + Copy {
fn checked_mul_lean(self, rhs: Self) -> Option<Self>;
}
macro_rules! lean_mul_library {
( $( $t:ty ),* $(,)? ) => {
$(
impl LeanMul for $t {
#[inline(always)]
fn checked_mul_lean(self, rhs: Self) -> Option<Self> {
self.checked_mul(rhs)
}
}
)*
};
}
lean_mul_library!(u8, u16, u32, u128, i8, i16, i32, i64, i128, isize);
impl LeanMul for u64 {
#[inline(always)]
fn checked_mul_lean(self, rhs: Self) -> Option<Self> {
checked_mul_u64(self, rhs)
}
}
impl LeanMul for usize {
#[inline(always)]
fn checked_mul_lean(self, rhs: Self) -> Option<Self> {
match checked_mul_u64(self as u64, rhs as u64) {
Some(product) if product <= usize::MAX as u64 => Some(product as usize),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLES: [u64; 18] = [
0,
1,
2,
3,
128,
153,
3_480,
5_080,
6_333,
10_485_888,
u32::MAX as u64,
u32::MAX as u64 + 1,
1 << 40,
u64::MAX / 3,
u64::MAX / 2,
u64::MAX / 2 + 1,
u64::MAX - 1,
u64::MAX,
];
#[test]
fn checked_mul_u64_matches_the_library_operator() {
for &a in &SAMPLES {
for &b in &SAMPLES {
assert_eq!(checked_mul_u64(a, b), a.checked_mul(b), "{a} * {b}");
assert_eq!(saturating_mul_u64(a, b), a.saturating_mul(b), "{a} * {b}");
}
}
}
#[test]
fn lean_mul_covers_every_width_like_the_library() {
assert_eq!(7u8.checked_mul_lean(3), Some(21));
assert_eq!(7u8.checked_mul_lean(40), None);
assert_eq!(300u16.checked_mul_lean(300), None);
assert_eq!(70_000u32.checked_mul_lean(70_000), None);
assert_eq!((1u64 << 32).checked_mul_lean(1 << 32), None);
assert_eq!((1u64 << 31).checked_mul_lean(1 << 32), Some(1 << 63));
assert_eq!((1usize << 20).checked_mul_lean(1 << 20), Some(1 << 40));
assert_eq!((-3i64).checked_mul_lean(4), Some(-12));
assert_eq!(i64::MIN.checked_mul_lean(-1), None);
assert_eq!((1u128 << 100).checked_mul_lean(1 << 20), Some(1 << 120));
}
}