From ba9e6294489ffe29441b08b68a0f75a0eefbf38f Mon Sep 17 00:00:00 2001 From: arch lw2333 <2374046775@qq.com> Date: Mon, 16 Jun 2025 07:53:20 +0000 Subject: [PATCH] feat: add checked_add method with overflow detection --- rational/rational.mbt | 46 +++++++++++++++++++++++++++++++++++++++++- rational/rational.mbti | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/rational/rational.mbt b/rational/rational.mbt index 4ba17b454..e8dbb452c 100644 --- a/rational/rational.mbt +++ b/rational/rational.mbt @@ -65,7 +65,6 @@ fn new_unchecked(numerator : Int64, denominator : Int64) -> T { ///| /// NOTE: we don't check overflow here, to align with the `op_add` of `Int64`. -/// TODO: add a `checked_add` method. pub impl Add for T with op_add(self : T, other : T) -> T { new_unchecked( self.numerator * other.denominator + other.numerator * self.denominator, @@ -73,6 +72,31 @@ pub impl Add for T with op_add(self : T, other : T) -> T { ) } +///| +/// Checked addition for rational numbers. +/// Returns `None` if any intermediate calculation overflows, +/// otherwise returns the correctly reduced rational. +pub fn T::checked_add(self : T, other : T) -> T? { + let (a, b) = (self, other) + let lhs = a.numerator * b.denominator + if a.numerator != 0L && lhs / a.numerator != b.denominator { + return None + } + let rhs = b.numerator * a.denominator + if b.numerator != 0L && rhs / b.numerator != a.denominator { + return None + } + let num = lhs + rhs + if (lhs ^ rhs) >= 0L && (lhs ^ num) < 0L { + return None + } + let den = a.denominator * b.denominator + if a.denominator != 0L && den / a.denominator != b.denominator { + return None + } + new(num, den) +} + ///| pub impl Sub for T with op_sub(self : T, other : T) -> T { new_unchecked( @@ -366,6 +390,26 @@ test "op_add" { assert_eq(c.denominator, 1L) } +///| +test "checked_add" { + // 1/2 + 1/3 = 5/6 + let a = new_unchecked(1L, 2L) + let b = new_unchecked(1L, 3L) + let c = a.checked_add(b) + assert_eq(c, Some(new_unchecked(5L, 6L))) + + // -1/2 + 1/2 = 0 + let a = new_unchecked(-1L, 2L) + let b = new_unchecked(1L, 2L) + let c = a.checked_add(b) + assert_eq(c, Some(new_unchecked(0L, 1L))) + + // Large overflow case: MAX + MAX + let big = new_unchecked(@int64.max_value, 1L) + let c = big.checked_add(big) + assert_eq(c, None) +} + ///| test "op_sub" { // 1/2 - 1/3 = 1/6 diff --git a/rational/rational.mbti b/rational/rational.mbti index a81fd00dd..df4e10797 100644 --- a/rational/rational.mbti +++ b/rational/rational.mbti @@ -31,6 +31,7 @@ impl Show for RationalError type T fn T::abs(Self) -> Self fn T::ceil(Self) -> Int64 +fn T::checked_add(Self, Self) -> Self? fn T::floor(Self) -> Int64 fn T::fract(Self) -> Self fn T::is_integer(Self) -> Bool