use std::cmp::min;
use std::cmp::Ordering;
use std::mem;
pub enum SignChange {
None,
Flip,
Zero,
}
macro_rules! rational {
(
$add_name:ident, $sub_name:ident, $common_name:ident, $reduce_name:ident,
$mul_name: ident, $uty:ty, $wide:ty, $gcd_name:ident, $simplify_name:ident
) => {
#[inline]
#[allow(clippy::unnecessary_cast, reason = "`$wide` is `$uty` for the widest type")]
fn $common_name(
left_numerator: $uty, left_denominator: $uty,
right_numerator: $uty, right_denominator: $uty,
) -> ($wide, $wide, $wide, $uty) {
debug_assert_ne!(left_denominator, 0);
debug_assert_ne!(right_denominator, 0);
#[inline]
fn multiply(left: $uty, right: $uty) -> $wide {
match (left as $wide).checked_mul(right as $wide) {
Some(product) => product,
None => panic!(concat!(
"an intermediate value of this ", stringify!($uty),
" rational operation does not fit ", stringify!($wide),
)),
}
}
if left_denominator == right_denominator {
(
left_numerator as $wide,
right_numerator as $wide,
left_denominator as $wide,
left_denominator,
)
} else if left_denominator == 1 {
(
multiply(left_numerator, right_denominator),
right_numerator as $wide,
right_denominator as $wide,
1,
)
} else if right_denominator == 1 {
(
left_numerator as $wide,
multiply(right_numerator, left_denominator),
left_denominator as $wide,
1,
)
} else {
let g = $gcd_name(left_denominator, right_denominator);
let left_over_g = left_denominator / g;
(
multiply(left_numerator, right_denominator / g),
multiply(right_numerator, left_over_g),
multiply(left_over_g, right_denominator),
g,
)
}
}
#[inline]
#[allow(clippy::unnecessary_fallible_conversions, reason = "`$wide` is `$uty` for the widest type")]
fn $reduce_name(numerator: $wide, denominator: $wide, g: $uty) -> ($uty, $uty) {
debug_assert_ne!(numerator, 0);
debug_assert_ne!(denominator, 0);
debug_assert_ne!(g, 0);
let common = if g == 1 {
1
} else {
match (numerator % g as $wide) as $uty {
0 => g,
1 => 1,
remainder => $gcd_name(remainder, g),
}
};
match (
<$uty>::try_from(numerator / common as $wide),
<$uty>::try_from(denominator / common as $wide),
) {
(Ok(numerator), Ok(denominator)) => (numerator, denominator),
_ => panic!(concat!(
"the result of this operation is not representable by a ", stringify!($uty),
" rational",
)),
}
}
#[inline]
pub fn $add_name(
left_numerator: &mut $uty, left_denominator: &mut $uty,
right_numerator: $uty, right_denominator: $uty,
) {
debug_assert_ne!(*left_numerator, 0);
debug_assert_ne!(right_numerator, 0);
let (left, right, denominator, g) = $common_name(
*left_numerator, *left_denominator, right_numerator, right_denominator,
);
let numerator = match left.checked_add(right) {
Some(numerator) => numerator,
None => panic!(concat!(
"the result of this operation is not representable by a ", stringify!($uty),
" rational",
)),
};
let (numerator, denominator) = $reduce_name(numerator, denominator, g);
*left_numerator = numerator;
*left_denominator = denominator;
}
#[inline]
pub fn $sub_name(
left_numerator: &mut $uty, left_denominator: &mut $uty,
right_numerator: $uty, right_denominator: $uty,
) -> SignChange {
debug_assert_ne!(*left_numerator, 0);
debug_assert_ne!(right_numerator, 0);
let (left, right, denominator, g) = $common_name(
*left_numerator, *left_denominator, right_numerator, right_denominator,
);
let (numerator, sign_change) = match left.cmp(&right) {
Ordering::Greater => (left - right, SignChange::None),
Ordering::Less => (right - left, SignChange::Flip),
Ordering::Equal => {
*left_numerator = 0;
*left_denominator = 1;
return SignChange::Zero;
}
};
let (numerator, denominator) = $reduce_name(numerator, denominator, g);
*left_numerator = numerator;
*left_denominator = denominator;
sign_change
}
#[inline]
pub fn $mul_name(
left_numerator: &mut $uty, left_denominator: &mut $uty,
mut rhs_numerator: $uty, mut rhs_denominator: $uty,
) {
debug_assert_ne!(*left_numerator, 0);
debug_assert_ne!(rhs_numerator, 0);
#[inline]
fn multiply(left: $uty, right: $uty) -> $uty {
match left.checked_mul(right) {
Some(product) => product,
None => panic!(concat!(
"the result of this operation is not representable by a ", stringify!($uty),
" rational",
)),
}
}
if *left_numerator != 1 && rhs_denominator != 1 {
if *left_numerator == rhs_denominator {
*left_numerator = rhs_numerator;
let (n, d) = $simplify_name(*left_numerator, *left_denominator);
*left_numerator = n;
*left_denominator = d;
return
}
let gcd_ad = $gcd_name(*left_numerator, rhs_denominator);
debug_assert!(gcd_ad > 0);
*left_numerator /= gcd_ad;
rhs_denominator /= gcd_ad;
}
if rhs_numerator != 1 && *left_denominator != 1 {
if rhs_numerator == *left_denominator {
*left_denominator = rhs_denominator;
let (n, d) = $simplify_name(*left_numerator, *left_denominator);
*left_numerator = n;
*left_denominator = d;
return
}
let gcd_bc = $gcd_name(*left_denominator, rhs_numerator);
debug_assert!(gcd_bc > 0);
rhs_numerator /= gcd_bc;
*left_denominator /= gcd_bc;
}
*left_numerator = multiply(*left_numerator, rhs_numerator);
*left_denominator = multiply(*left_denominator, rhs_denominator);
}
#[inline]
pub fn $simplify_name(numerator: $uty, denominator: $uty) -> ($uty, $uty) {
debug_assert_ne!(numerator, 0);
debug_assert_ne!(denominator, 0);
if numerator == 1 || denominator == 1 {
(numerator, denominator)
} else {
let gcd = $gcd_name(numerator, denominator);
(numerator / gcd, denominator / gcd)
}
}
#[inline]
pub fn $gcd_name(mut left: $uty, mut right: $uty) -> $uty {
debug_assert_ne!(left, 0);
debug_assert_ne!(right, 0);
debug_assert_ne!(left, 1);
debug_assert_ne!(right, 1);
if left == 0 {
return right;
}
if right == 0 {
return left;
}
let left_trailing = left.trailing_zeros();
let right_trailing = right.trailing_zeros();
let min_trailing = min(left_trailing, right_trailing);
left >>= left_trailing;
right >>= right_trailing;
loop {
debug_assert_eq!(left % 2, 1);
debug_assert_eq!(left % 2, 1);
if left == right {
break left << min_trailing;
}
if left > right {
mem::swap(&mut left, &mut right);
}
right -= left;
right >>= right.trailing_zeros();
}
}
}
}
rational!(add8, sub8, common8, reduce8, mul8, u8, u16, gcd8, simplify8);
rational!(add16, sub16, common16, reduce16, mul16, u16, u32, gcd16, simplify16);
rational!(add32, sub32, common32, reduce32, mul32, u32, u64, gcd32, simplify32);
rational!(add64, sub64, common64, reduce64, mul64, u64, u128, gcd64, simplify64);
rational!(add128, sub128, common128, reduce128, mul128, u128, u128, gcd128, simplify128);
rational!(add_usize, sub_usize, common_usize, reduce_usize, mul_usize, usize, u128, gcd_usize, simplify_usize);
#[cfg(test)]
mod test {
use super::{gcd128, gcd16, gcd64, gcd8};
#[test]
fn test_gcd() {
assert_eq!(gcd8(4, 6), 2);
assert_eq!(gcd16(9, 6), 3);
assert_eq!(gcd64(2 * 3 * 5, 3 * 7), 3);
assert_eq!(gcd128(1 << 100, 1 << 60), 1 << 60);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn test_gcd_zero() {
gcd8(0, 6);
}
#[cfg(not(debug_assertions))]
#[test]
fn test_gcd_zero() {
assert_eq!(gcd8(0, 6), 6);
assert_eq!(gcd8(6, 0), 6);
assert_eq!(gcd64(0, 0), 0);
}
}