use crate::{impl_bin_op, Uint};
use core::{
iter::Sum,
ops::{Add, AddAssign, Neg, Sub, SubAssign},
};
impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn abs_diff(self, other: Self) -> Self {
if self < other {
other.wrapping_sub(self)
} else {
self.wrapping_sub(other)
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn checked_add(self, rhs: Self) -> Option<Self> {
match self.overflowing_add(rhs) {
(value, false) => Some(value),
_ => None,
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn checked_neg(self) -> Option<Self> {
match self.overflowing_neg() {
(value, false) => Some(value),
_ => None,
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
match self.overflowing_sub(rhs) {
(value, false) => Some(value),
_ => None,
}
}
#[allow(clippy::doc_markdown)]
#[must_use]
pub fn overflowing_add(mut self, rhs: Self) -> (Self, bool) {
if BITS == 0 {
return (Self::ZERO, false);
}
let mut carry = 0_u128;
#[allow(clippy::cast_possible_truncation)] for (lhs, rhs) in self.limbs.iter_mut().zip(rhs.limbs.into_iter()) {
carry += u128::from(*lhs) + u128::from(rhs);
*lhs = carry as u64;
carry >>= 64;
}
let overflow = carry != 0 || self.limbs[LIMBS - 1] > Self::MASK;
self.limbs[LIMBS - 1] &= Self::MASK;
(self, overflow)
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn overflowing_neg(self) -> (Self, bool) {
Self::ZERO.overflowing_sub(self)
}
#[allow(clippy::doc_markdown)]
#[must_use]
pub fn overflowing_sub(mut self, rhs: Self) -> (Self, bool) {
if BITS == 0 {
return (Self::ZERO, false);
}
let mut carry = 0_i128;
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] for (lhs, rhs) in self.limbs.iter_mut().zip(rhs.limbs.into_iter()) {
carry += i128::from(*lhs) - i128::from(rhs);
*lhs = carry as u64;
carry >>= 64; }
let overflow = carry != 0 || self.limbs[LIMBS - 1] > Self::MASK;
self.limbs[LIMBS - 1] &= Self::MASK;
(self, overflow)
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn saturating_add(self, rhs: Self) -> Self {
match self.overflowing_add(rhs) {
(value, false) => value,
_ => Self::MAX,
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn saturating_sub(self, rhs: Self) -> Self {
match self.overflowing_sub(rhs) {
(value, false) => value,
_ => Self::ZERO,
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn wrapping_add(self, rhs: Self) -> Self {
self.overflowing_add(rhs).0
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn wrapping_neg(self) -> Self {
self.overflowing_neg().0
}
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn wrapping_sub(self, rhs: Self) -> Self {
self.overflowing_sub(rhs).0
}
}
impl<const BITS: usize, const LIMBS: usize> Neg for Uint<BITS, LIMBS> {
type Output = Self;
#[allow(clippy::inline_always)]
#[inline(always)]
fn neg(self) -> Self::Output {
self.wrapping_neg()
}
}
impl<const BITS: usize, const LIMBS: usize> Neg for &Uint<BITS, LIMBS> {
type Output = Uint<BITS, LIMBS>;
#[allow(clippy::inline_always)]
#[inline(always)]
fn neg(self) -> Self::Output {
self.wrapping_neg()
}
}
impl<const BITS: usize, const LIMBS: usize> Sum<Self> for Uint<BITS, LIMBS> {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
iter.fold(Self::ZERO, Self::wrapping_add)
}
}
impl<'a, const BITS: usize, const LIMBS: usize> Sum<&'a Self> for Uint<BITS, LIMBS> {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = &'a Self>,
{
iter.copied().fold(Self::ZERO, Self::wrapping_add)
}
}
impl_bin_op!(Add, add, AddAssign, add_assign, wrapping_add);
impl_bin_op!(Sub, sub, SubAssign, sub_assign, wrapping_sub);
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{const_for, nlimbs};
use proptest::proptest;
#[test]
fn test_neg_one() {
const_for!(BITS in NON_ZERO {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
assert_eq!(-U::from(1), !U::ZERO);
});
}
#[test]
fn test_commutative() {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(a: U, b: U)| {
assert_eq!(a + b, b + a);
assert_eq!(a - b, -(b - a));
});
});
}
#[test]
fn test_associative() {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(a: U, b: U, c: U)| {
assert_eq!(a + (b + c), (a + b) + c);
});
});
}
#[test]
fn test_identity() {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(value: U)| {
assert_eq!(value + U::ZERO, value);
assert_eq!(value - U::ZERO, value);
});
});
}
#[test]
fn test_inverse() {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(a: U)| {
assert_eq!(a + (-a), U::ZERO);
assert_eq!(a - a, U::ZERO);
assert_eq!(-(-a), a);
});
});
}
}
#[cfg(feature = "bench")]
#[doc(hidden)]
pub mod bench {
use super::*;
use crate::{const_for, nlimbs};
use ::proptest::{
arbitrary::Arbitrary,
strategy::{Strategy, ValueTree},
test_runner::TestRunner,
};
use criterion::{black_box, BatchSize, Criterion};
pub fn group(criterion: &mut Criterion) {
const_for!(BITS in BENCH {
const LIMBS: usize = nlimbs(BITS);
bench_neg::<BITS, LIMBS>(criterion);
bench_add::<BITS, LIMBS>(criterion);
bench_sub::<BITS, LIMBS>(criterion);
});
}
fn bench_neg<const BITS: usize, const LIMBS: usize>(criterion: &mut Criterion) {
let input = Uint::<BITS, LIMBS>::arbitrary();
let mut runner = TestRunner::deterministic();
criterion.bench_function(&format!("neg/{BITS}"), move |bencher| {
bencher.iter_batched(
|| input.new_tree(&mut runner).unwrap().current(),
|a| black_box(-black_box(a)),
BatchSize::SmallInput,
);
});
}
fn bench_add<const BITS: usize, const LIMBS: usize>(criterion: &mut Criterion) {
let input = (Uint::<BITS, LIMBS>::arbitrary(), Uint::arbitrary());
let mut runner = TestRunner::deterministic();
criterion.bench_function(&format!("add/{BITS}"), move |bencher| {
bencher.iter_batched(
|| input.new_tree(&mut runner).unwrap().current(),
|(a, b)| black_box(black_box(a) + black_box(b)),
BatchSize::SmallInput,
);
});
}
fn bench_sub<const BITS: usize, const LIMBS: usize>(criterion: &mut Criterion) {
let input = (Uint::<BITS, LIMBS>::arbitrary(), Uint::arbitrary());
let mut runner = TestRunner::deterministic();
criterion.bench_function(&format!("sub/{BITS}"), move |bencher| {
bencher.iter_batched(
|| input.new_tree(&mut runner).unwrap().current(),
|(a, b)| black_box(black_box(a) - black_box(b)),
BatchSize::SmallInput,
);
});
}
}