Skip to content

Commit

Permalink
feat: checked math traits (#5410)
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat authored Apr 14, 2024
1 parent 91ba766 commit f913adc
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 1 deletion.
94 changes: 94 additions & 0 deletions corelib/src/integer.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3077,6 +3077,100 @@ impl U64WrappingMul = core::num::traits::ops::wrapping::overflow_based::TWrappin
impl U128WrappingMul = core::num::traits::ops::wrapping::overflow_based::TWrappingMul<u128>;
impl U256WrappingMul = core::num::traits::ops::wrapping::overflow_based::TWrappingMul<u256>;

// CheckedAdd implementations
impl U8CheckedAdd of core::num::traits::CheckedAdd<u8> {
fn checked_add(self: u8, v: u8) -> Option<u8> {
u8_checked_add(self, v)
}
}

impl U16CheckedAdd of core::num::traits::CheckedAdd<u16> {
fn checked_add(self: u16, v: u16) -> Option<u16> {
u16_checked_add(self, v)
}
}

impl U32CheckedAdd of core::num::traits::CheckedAdd<u32> {
fn checked_add(self: u32, v: u32) -> Option<u32> {
u32_checked_add(self, v)
}
}

impl U64CheckedAdd of core::num::traits::CheckedAdd<u64> {
fn checked_add(self: u64, v: u64) -> Option<u64> {
u64_checked_add(self, v)
}
}

impl U128CheckedAdd of core::num::traits::CheckedAdd<u128> {
fn checked_add(self: u128, v: u128) -> Option<u128> {
u128_checked_add(self, v)
}
}

impl U256CheckedAdd of core::num::traits::CheckedAdd<u256> {
fn checked_add(self: u256, v: u256) -> Option<u256> {
u256_checked_add(self, v)
}
}

impl I8CheckedAdd = core::num::traits::ops::checked::overflow_based::TCheckedAdd<i8>;
impl I16CheckedAdd = core::num::traits::ops::checked::overflow_based::TCheckedAdd<i16>;
impl I32CheckedAdd = core::num::traits::ops::checked::overflow_based::TCheckedAdd<i32>;
impl I64CheckedAdd = core::num::traits::ops::checked::overflow_based::TCheckedAdd<i64>;
impl I128CheckedAdd = core::num::traits::ops::checked::overflow_based::TCheckedAdd<i128>;

// CheckedSub implementations
impl U8CheckedSub of core::num::traits::CheckedSub<u8> {
fn checked_sub(self: u8, v: u8) -> Option<u8> {
u8_checked_sub(self, v)
}
}

impl U16CheckedSub of core::num::traits::CheckedSub<u16> {
fn checked_sub(self: u16, v: u16) -> Option<u16> {
u16_checked_sub(self, v)
}
}

impl U32CheckedSub of core::num::traits::CheckedSub<u32> {
fn checked_sub(self: u32, v: u32) -> Option<u32> {
u32_checked_sub(self, v)
}
}

impl U64CheckedSub of core::num::traits::CheckedSub<u64> {
fn checked_sub(self: u64, v: u64) -> Option<u64> {
u64_checked_sub(self, v)
}
}

impl U128CheckedSub of core::num::traits::CheckedSub<u128> {
fn checked_sub(self: u128, v: u128) -> Option<u128> {
u128_checked_sub(self, v)
}
}

impl U256CheckedSub of core::num::traits::CheckedSub<u256> {
fn checked_sub(self: u256, v: u256) -> Option<u256> {
u256_checked_sub(self, v)
}
}

impl I8CheckedSub = core::num::traits::ops::checked::overflow_based::TCheckedSub<i8>;
impl I16CheckedSub = core::num::traits::ops::checked::overflow_based::TCheckedSub<i16>;
impl I32CheckedSub = core::num::traits::ops::checked::overflow_based::TCheckedSub<i32>;
impl I64CheckedSub = core::num::traits::ops::checked::overflow_based::TCheckedSub<i64>;
impl I128CheckedSub = core::num::traits::ops::checked::overflow_based::TCheckedSub<i128>;

// CheckedMul implementations
impl U8CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u8>;
impl U16CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u16>;
impl U32CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u32>;
impl U64CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u64>;
impl U128CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u128>;
impl U256CheckedMul = core::num::traits::ops::checked::overflow_based::TCheckedMul<u256>;

/// Internal trait for easier finding of absolute values.
pub(crate) trait AbsAndSign<Signed, Unsigned> {
/// Returns the absolute value of the `Signed` value as `Unsigned` and the original sign.
Expand Down
1 change: 1 addition & 0 deletions corelib/src/num/traits.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pub use bit_size::BitSize;
pub mod ops;
pub use ops::overflowing::{OverflowingAdd, OverflowingSub, OverflowingMul};
pub use ops::wrapping::{WrappingAdd, WrappingSub, WrappingMul};
pub use ops::checked::{CheckedAdd, CheckedSub, CheckedMul};
1 change: 1 addition & 0 deletions corelib/src/num/traits/ops.cairo
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod overflowing;
pub mod wrapping;
pub mod checked;
60 changes: 60 additions & 0 deletions corelib/src/num/traits/ops/checked.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/// Performs addition that returns `None` instead of wrapping around on
/// overflow.
pub trait CheckedAdd<T> {
/// Adds two numbers, checking for overflow. If overflow happens, `None` is
/// returned.
fn checked_add(self: T, v: T) -> Option<T>;
}

/// Performs subtraction that returns `None` instead of wrapping around on underflow.
pub trait CheckedSub<T> {
/// Subtracts two numbers, checking for underflow. If underflow happens,
/// `None` is returned.
fn checked_sub(self: T, v: T) -> Option<T>;
}

/// Performs multiplication that returns `None` instead of wrapping around on underflow or
/// overflow.
pub trait CheckedMul<T> {
/// Multiplies two numbers, checking for underflow or overflow. If underflow
/// or overflow happens, `None` is returned.
fn checked_mul(self: T, v: T) -> Option<T>;
}

pub(crate) mod overflow_based {
pub(crate) impl TCheckedAdd<
T, +Drop<T>, +core::num::traits::OverflowingAdd<T>
> of core::num::traits::CheckedAdd<T> {
fn checked_add(self: T, v: T) -> Option<T> {
let (result, overflow) = self.overflowing_add(v);
match overflow {
true => Option::None,
false => Option::Some(result),
}
}
}

pub(crate) impl TCheckedSub<
T, +Drop<T>, +core::num::traits::OverflowingSub<T>
> of core::num::traits::CheckedSub<T> {
fn checked_sub(self: T, v: T) -> Option<T> {
let (result, overflow) = self.overflowing_sub(v);
match overflow {
true => Option::None,
false => Option::Some(result),
}
}
}

pub(crate) impl TCheckedMul<
T, +Drop<T>, +core::num::traits::OverflowingMul<T>
> of core::num::traits::CheckedMul<T> {
fn checked_mul(self: T, v: T) -> Option<T> {
let (result, overflow) = self.overflowing_mul(v);
match overflow {
true => Option::None,
false => Option::Some(result),
}
}
}
}
108 changes: 107 additions & 1 deletion corelib/src/test/num_test.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::num::traits::BitSize;
use core::num::traits::{
OverflowingAdd, OverflowingSub, OverflowingMul, WrappingAdd, WrappingSub, WrappingMul
OverflowingAdd, OverflowingSub, OverflowingMul, WrappingAdd, WrappingSub, WrappingMul,
CheckedAdd, CheckedSub, CheckedMul
};
use core::integer::BoundedInt;

Expand Down Expand Up @@ -235,3 +236,108 @@ fn test_wrapping_mul_unsigned_integers() {
assert_eq!(2_u256.wrapping_mul(3), 6);
assert_eq!(BoundedInt::<u256>::max().wrapping_mul(2), BoundedInt::<u256>::max() - 1);
}

// Checked tests
#[test]
fn test_checked_add_unsigned_integers() {
assert_eq!(1_u8.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u8>::max().checked_add(1).is_none());
assert_eq!(1_u16.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u16>::max().checked_add(1).is_none());
assert_eq!(1_u32.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u32>::max().checked_add(1).is_none());
assert_eq!(1_u64.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u64>::max().checked_add(1).is_none());
assert_eq!(1_u128.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u128>::max().checked_add(1).is_none());
assert_eq!(1_u256.checked_add(2), Option::Some(3));
assert!(BoundedInt::<u256>::max().checked_add(1).is_none());
}

#[test]
fn test_checked_add_positive_signed_integers() {
assert_eq!(1_i8.checked_add(2), Option::Some(3));
assert!(BoundedInt::<i8>::max().checked_add(1).is_none());
assert_eq!(1_i16.checked_add(2), Option::Some(3));
assert!(BoundedInt::<i16>::max().checked_add(1).is_none());
assert_eq!(1_i32.checked_add(2), Option::Some(3));
assert!(BoundedInt::<i32>::max().checked_add(1).is_none());
assert_eq!(1_i64.checked_add(2), Option::Some(3));
assert!(BoundedInt::<i64>::max().checked_add(1).is_none());
assert_eq!(1_i128.checked_add(2), Option::Some(3));
assert!(BoundedInt::<i128>::max().checked_add(1).is_none());
}

#[test]
fn test_checked_add_negative_signed_integers() {
assert_eq!((-1_i8).checked_add(-2), Option::Some(-3));
assert!(BoundedInt::<i8>::min().checked_add(-1).is_none());
assert_eq!((-1_i16).checked_add(-2), Option::Some(-3));
assert!(BoundedInt::<i16>::min().checked_add(-1).is_none());
assert_eq!((-1_i32).checked_add(-2), Option::Some(-3));
assert!(BoundedInt::<i32>::min().checked_add(-1).is_none());
assert_eq!((-1_i64).checked_add(-2), Option::Some(-3));
assert!(BoundedInt::<i64>::min().checked_add(-1).is_none());
assert_eq!((-1_i128).checked_add(-2), Option::Some(-3));
assert!(BoundedInt::<i128>::min().checked_add(-1).is_none());
}

#[test]
fn test_checked_sub_unsigned_integers() {
assert_eq!(3_u8.checked_sub(2), Option::Some(1));
assert!(0_u8.checked_sub(1).is_none());
assert_eq!(3_u16.checked_sub(2), Option::Some(1));
assert!(0_u16.checked_sub(1).is_none());
assert_eq!(3_u32.checked_sub(2), Option::Some(1));
assert!(0_u32.checked_sub(1).is_none());
assert_eq!(3_u64.checked_sub(2), Option::Some(1));
assert!(0_u64.checked_sub(1).is_none());
assert_eq!(3_u128.checked_sub(2), Option::Some(1));
assert!(0_u128.checked_sub(1).is_none());
assert_eq!(3_u256.checked_sub(2), Option::Some(1));
assert!(0_u256.checked_sub(1).is_none());
}

#[test]
fn test_checked_sub_positive_signed_integers() {
assert_eq!(3_i8.checked_sub(2), Option::Some(1));
assert!(BoundedInt::<i8>::min().checked_sub(1).is_none());
assert_eq!(3_i16.checked_sub(2), Option::Some(1));
assert!(BoundedInt::<i16>::min().checked_sub(1).is_none());
assert_eq!(3_i32.checked_sub(2), Option::Some(1));
assert!(BoundedInt::<i32>::min().checked_sub(1).is_none());
assert_eq!(3_i64.checked_sub(2), Option::Some(1));
assert!(BoundedInt::<i64>::min().checked_sub(1).is_none());
assert_eq!(3_i128.checked_sub(2), Option::Some(1));
assert!(BoundedInt::<i128>::min().checked_sub(1).is_none());
}

#[test]
fn test_checked_sub_negative_signed_integers() {
assert_eq!((-3_i8).checked_sub(-2), Option::Some(-1));
assert!(BoundedInt::<i8>::max().checked_sub(-1).is_none());
assert_eq!((-3_i16).checked_sub(-2), Option::Some(-1));
assert!(BoundedInt::<i16>::max().checked_sub(-1).is_none());
assert_eq!((-3_i32).checked_sub(-2), Option::Some(-1));
assert!(BoundedInt::<i32>::max().checked_sub(-1).is_none());
assert_eq!((-3_i64).checked_sub(-2), Option::Some(-1));
assert!(BoundedInt::<i64>::max().checked_sub(-1).is_none());
assert_eq!((-3_i128).checked_sub(-2), Option::Some(-1));
assert!(BoundedInt::<i128>::max().checked_sub(-1).is_none());
}

#[test]
fn test_checked_mul_unsigned_integers() {
assert_eq!(2_u8.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u8>::max().checked_mul(2).is_none());
assert_eq!(2_u16.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u16>::max().checked_mul(2).is_none());
assert_eq!(2_u32.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u32>::max().checked_mul(2).is_none());
assert_eq!(2_u64.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u64>::max().checked_mul(2).is_none());
assert_eq!(2_u128.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u128>::max().checked_mul(2).is_none());
assert_eq!(2_u256.checked_mul(3), Option::Some(6));
assert!(BoundedInt::<u256>::max().checked_mul(2).is_none());
}

0 comments on commit f913adc

Please sign in to comment.