diff --git a/src/iter/checked_product.rs b/src/iter/checked_product.rs new file mode 100644 index 00000000..1fc46db1 --- /dev/null +++ b/src/iter/checked_product.rs @@ -0,0 +1,87 @@ +use crate::identities::One; +use crate::CheckedMul; + +/// Trait to represent types that can be created by multiplying elements of an iterator with overflow checking. +/// This trait should rarely be called directly. +pub trait CheckedProduct : Sized { + /// Method which takes an iterator and generates Self from the elements by multiplying the items with overflow checking, returning `None` if the multiplication would overflow. + /// + /// An empty iterator returns the one value of the type. + /// + /// For iterators containing zero, the order of elements may effect whether the result is `None`. + fn checked_product>(iter: I) -> Option; +} + +impl CheckedProduct for T +where + T: CheckedMul + One, +{ + fn checked_product>(mut iter: I) -> Option { + iter.try_fold(Self::one(), |acc, x| acc.checked_mul(&x)) + } +} + +impl<'a, T> CheckedProduct<&'a T> for T +where + T: CheckedMul + One, +{ + fn checked_product>(mut iter: I) -> Option { + iter.try_fold(Self::one(), |acc, x| acc.checked_mul(&x)) + } +} + +#[test] +fn checked_product_returns_none_instead_of_overflowing() { + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_product { + ($($t:ty)+) => { + $( + assert_eq!(None::<$t>, [<$t>::MAX, 2 ].iter().checked_product() ); + assert_eq!(None::<$t>,IntoIterator::into_iter([<$t>::MAX, 2]).checked_product() ); + )+ + }; + } + + test_checked_product!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_product_returns_one_if_empty() { + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_product { + ($($t:ty)+) => { + $( + assert_eq!(Some(<$t>::one()), ([] as [$t; 0]).iter().checked_product() ); + assert_eq!(Some(<$t>::one()),IntoIterator::into_iter(([] as [$t; 0])).checked_product() ); + )+ + }; + } + + test_checked_product!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_product_returns_correct_product() { + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_product { + ($($t:ty)+) => { + $( + assert_eq!(Some(42), ([3,7,2] as [$t; 3]).iter().checked_product() ); + assert_eq!(Some(42),IntoIterator::into_iter(([3,7,2] as [$t; 3])).checked_product() ); + )+ + }; + } + + test_checked_product!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_product_multiplies_left_to_right() { + use crate::iter::num_iter::NumIter; + + assert_eq!(None::, [100u8, 3u8, 0u8].iter().checked_product()); + assert_eq!(Some(0), [0u8, 100u8, 3u8].iter().checked_product()); +} diff --git a/src/iter/checked_sum.rs b/src/iter/checked_sum.rs new file mode 100644 index 00000000..7d084090 --- /dev/null +++ b/src/iter/checked_sum.rs @@ -0,0 +1,89 @@ +use crate::identities::Zero; +use crate::CheckedAdd; + +/// Trait to represent types that can be created by summing up an iterator with overflow checking. +/// This trait should rarely be called directly. +pub trait CheckedSum: Sized { + /// Method which takes an iterator and generates Self from the elements by “summing up” the items with overflow checking, returning `None` if the addition would overflow. + /// + /// An empty iterator returns the one value of the type. + /// + /// For signed numbers, the order of elements may effect whether the result is `None`. + fn checked_sum>(iter: I) -> Option; +} + +impl CheckedSum for T +where + T: CheckedAdd + Zero, +{ + fn checked_sum>(mut iter: I) -> Option { + iter.try_fold(Self::zero(), |acc, x| acc.checked_add(&x)) + } +} + +impl<'a, T> CheckedSum<&'a T> for T +where + T: CheckedAdd + Zero, +{ + fn checked_sum>(mut iter: I) -> Option { + iter.try_fold(Self::zero(), |acc, x| acc.checked_add(&x)) + } +} + + +#[test] +fn checked_sum_returns_none_instead_of_overflowing() { + use crate::identities::One; + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_sum { + ($($t:ty)+) => { + $( + assert_eq!(None::<$t>, [<$t>::MAX, <$t>::one()].iter().checked_sum() ); + assert_eq!(None::<$t>,IntoIterator::into_iter([<$t>::MAX, <$t>::one()]).checked_sum() ); + )+ + }; + } + + test_checked_sum!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_sum_returns_zero_if_empty() { + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_sum { + ($($t:ty)+) => { + $( + assert_eq!(Some(<$t>::zero()), ([] as [$t; 0]).iter().checked_sum() ); + assert_eq!(Some(<$t>::zero()),IntoIterator::into_iter(([] as [$t; 0])).checked_sum() ); + )+ + }; + } + + test_checked_sum!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_sum_returns_correct_sum() { + use crate::iter::num_iter::NumIter; + + macro_rules! test_checked_sum { + ($($t:ty)+) => { + $( + assert_eq!(Some(42), ([40,2] as [$t; 2]).iter().checked_sum() ); + assert_eq!(Some(42),IntoIterator::into_iter(([40,2] as [$t; 2])).checked_sum() ); + )+ + }; + } + + test_checked_sum!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +fn checked_sum_adds_left_to_right() { + use crate::iter::num_iter::NumIter; + + assert_eq!(None::, [120i8, 8i8, -1i8].iter().checked_sum()); + assert_eq!(Some(127), [-1i8, 120i8, 8i8].iter().checked_sum()); +} diff --git a/src/iter/mod.rs b/src/iter/mod.rs new file mode 100644 index 00000000..a1d7ed05 --- /dev/null +++ b/src/iter/mod.rs @@ -0,0 +1,3 @@ +pub mod checked_product; +pub mod checked_sum; +pub mod num_iter; \ No newline at end of file diff --git a/src/iter/num_iter.rs b/src/iter/num_iter.rs new file mode 100644 index 00000000..6c831b46 --- /dev/null +++ b/src/iter/num_iter.rs @@ -0,0 +1,50 @@ +use crate::CheckedProduct; +use crate::CheckedSum; + +/// An [`Iterator`] blanket implementation that provides extra adaptors and +/// methods. +/// +/// This traits defines methods for summing and multiplying together iterators with overflow checking. +pub trait NumIter: Iterator { + /// Sums the elements of an iterator with overflow checking. + /// + /// Takes each element, adds them together, and returns the result or None if the addition would overflow. + /// + /// An empty iterator returns the zero value of the type. + /// + /// For signed numbers, the order of elements may effect whether the result is `None`. + fn checked_sum>(self) -> Option + where + Self: Iterator, + Self: Sized; + + /// Iterates over the entire iterator, multiplying all the elements with overflow checking. + /// + /// Takes each element, multiplies them together, and returns the result or None if the multiplication would overflow. + /// + /// An empty iterator returns the one value of the type. + /// + /// For iterators containing zero, the order of elements may effect whether the result is `None`. + fn checked_product>(self) -> Option + where + Self: Iterator, + Self: Sized; +} + +impl NumIter for I { + fn checked_sum>(self) -> Option + where + Self: Iterator, + Self: Sized, + { + CheckedSum::checked_sum(self) + } + + fn checked_product>(self) -> Option + where + Self: Iterator, + Self: Sized, + { + CheckedProduct::checked_product(self) + } +} diff --git a/src/lib.rs b/src/lib.rs index f6562d2e..318ad6d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub use crate::float::FloatConst; pub use crate::cast::{cast, AsPrimitive, FromPrimitive, NumCast, ToPrimitive}; pub use crate::identities::{one, zero, One, Zero}; pub use crate::int::PrimInt; +pub use crate::iter::{checked_product::CheckedProduct, checked_sum::CheckedSum, num_iter::NumIter}; pub use crate::ops::checked::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; @@ -56,6 +57,7 @@ pub mod cast; pub mod float; pub mod identities; pub mod int; +pub mod iter; pub mod ops; pub mod pow; pub mod real;