Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CheckedSum and CheckedProduct traits #251

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/iter/checked_product.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::identities::One;
use crate::CheckedMul;

/// This trait represents types of which an iterator can be multiplied up with overflow checking.
/// This trait should rarely be called directly.
pub trait CheckedProduct<Result = Self> {
/// Multiplies up the elements of an iterator, returning `None` if an overflow would occur.
///
/// Multiplies up an empty iterator returns a value representing One.
///
/// If the iterator contains Zero, the order of elements may effect whether the result is `None`.
fn checked_product<I: Iterator<Item = Self>>(iter: I) -> Option<Result>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is opposite of how Product is defined, where the type parameter is the input (Item) and it always outputs Self. I would rather match that for consistency, and same for Sum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree. I feel very silly for having done it the other way around. I have changed it now.

}

impl<T> CheckedProduct<T> for T
where
T: CheckedMul + One,
{
fn checked_product<I: Iterator<Item = Self>>(mut iter: I) -> Option<Self> {
iter.try_fold(Self::one(), |acc, x| acc.checked_mul(&x))
}
}

impl<'a, T> CheckedProduct<T> for &'a T
where
T: CheckedMul + Sized + One,
{
fn checked_product<I: Iterator<Item = Self>>(mut iter: I) -> Option<T> {
iter.try_fold(T::one(), |acc, x| acc.checked_mul(x))
}
}

///This trait is for iterators that can be multiplied up with overflow checking.
trait CheckedProductIter<T, Result>: Iterator<Item = T> {
/// Multiplies up the elements of an iterator, returning `None` if an overflow would occur.
///
/// Multiplies up an empty iterator returns a value representing One.
///
/// If the iterator contains Zero, the order of elements may effect whether the result is `None`.
fn checked_product(self) -> Option<Result>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, for consistency with Iterator, this trait should have no parameters, and the method should be parameterized checked_product<P: CheckedProduct<Self::Item>>.

I think then we could also combine your two iterator extensions into one CheckedIterator trait, or even NumIterator to leave room for more extension methods.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another great idea, thankyou. I have done this and put both into NumIter

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than a new iter module here, I think it would make more sense to move this to the num-iter crate.

I wasn't sure. I'm very happy to close this pull request and move the code changes over there if that's best.

}

impl<Result, T: CheckedProduct<Result>, I: Iterator<Item = T>> CheckedProductIter<T, Result> for I {
fn checked_product(self) -> Option<Result> {
T::checked_product(self)
}
}

#[test]
fn checked_product_returns_none_instead_of_overflowing() {
macro_rules! test_checked_product {
($($t:ty)+) => {
$(
assert_eq!(None, [<$t>::MAX, 2 ].iter().checked_product() );
assert_eq!(None,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() {
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() {
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() {
assert_eq!(None, [100u8, 3u8, 0u8].iter().checked_product());
assert_eq!(Some(0), [0u8, 100u8, 3u8].iter().checked_product());
}
97 changes: 97 additions & 0 deletions src/iter/checked_sum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::identities::Zero;
use crate::CheckedAdd;

/// This trait represents types of which an iterator can be summed up with overflow checking.
/// This trait should rarely be called directly.
pub trait CheckedSum<Result = Self> {
/// Adds the elements of an iterator, returning `None` if an overflow would occur.
///
/// Summing an empty iterator returns a value representing Zero.
///
/// For signed numbers, the order of elements may effect whether the result is `None`.
fn checked_sum<I: Iterator<Item = Self>>(iter: I) -> Option<Result>;
}

impl<T> CheckedSum<T> for T
where
T: CheckedAdd + Zero,
{
fn checked_sum<I: Iterator<Item = Self>>(mut iter: I) -> Option<Self> {
iter.try_fold(Self::zero(), |acc, x| acc.checked_add(&x))
}
}

impl<'a, T> CheckedSum<T> for &'a T
where
T: CheckedAdd + Sized + Zero,
{
fn checked_sum<I: Iterator<Item = Self>>(mut iter: I) -> Option<T> {
iter.try_fold(T::zero(), |acc, x| acc.checked_add(x))
}
}

///This trait is for iterators that can be summed up with overflow checking.
trait CheckedSumIter<T, Result>: Iterator<Item = T> {
/// Adds the elements of an iterator, returning `None` if an overflow would occur.
///
/// Summing an empty iterator returns a value representing Zero.
///
/// For signed numbers, the order of elements may effect whether the result is `None`.
fn checked_sum(self) -> Option<Result>;
}

impl<Result, T: CheckedSum<Result>, I: Iterator<Item = T>> CheckedSumIter<T, Result> for I {
fn checked_sum(self) -> Option<Result> {
T::checked_sum(self)
}
}

#[test]
fn checked_sum_returns_none_instead_of_overflowing() {
use crate::identities::One;

macro_rules! test_checked_sum {
($($t:ty)+) => {
$(
assert_eq!(None, [<$t>::MAX, <$t>::one()].iter().checked_sum() );
assert_eq!(None,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() {
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() {
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() {
assert_eq!(None, [120i8, 8i8, -1i8].iter().checked_sum());
assert_eq!(Some(127), [-1i8, 120i8, 8i8].iter().checked_sum());
}
2 changes: 2 additions & 0 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod checked_product;
pub mod checked_sum;
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
pub use crate::ops::checked::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub,
};
Expand All @@ -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;
Expand Down