From 9fe57ae019271ab88dfd7b78cd330ea1e6b739f2 Mon Sep 17 00:00:00 2001 From: failfmi Date: Wed, 27 Sep 2023 12:43:01 +0300 Subject: [PATCH 1/2] feat(u128): saturating add and sub --- math.go | 20 ++++++++++++++++++++ math_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ u128.go | 20 -------------------- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/math.go b/math.go index 8e9aff9..486bbe6 100644 --- a/math.go +++ b/math.go @@ -102,6 +102,16 @@ func SaturatingAddU64(a, b U64) U64 { return U64(sum) } +func SaturatingAddU128(a, b U128) U128 { + sumLow, carry := bits.Add64(uint64(a[0]), uint64(b[0]), 0) + sumHigh, overflow := bits.Add64(uint64(a[1]), uint64(b[1]), carry) + // check for overflow + if overflow == 1 || (carry == 1 && sumHigh <= uint64(a[1]) && sumHigh <= uint64(b[1])) { + return MaxU128() + } + return U128{U64(sumLow), U64(sumHigh)} +} + func SaturatingSubU64(a, b U64) U64 { diff, borrow := bits.Sub64(uint64(a), uint64(b), 0) if borrow != 0 { @@ -110,6 +120,16 @@ func SaturatingSubU64(a, b U64) U64 { return U64(diff) } +func SaturatingSubU128(a, b U128) U128 { + low, borrow := bits.Sub64(uint64(a[0]), uint64(b[0]), 0) + high, _ := bits.Sub64(uint64(a[1]), uint64(b[1]), borrow) + // check for underflow + if borrow == 1 || high > uint64(a[1]) { + return U128{0, 0} + } + return U128{U64(low), U64(high)} +} + func SaturatingMulU64(a, b U64) U64 { if a == 0 || b == 0 { return U64(0) diff --git a/math_test.go b/math_test.go index df07a06..04606af 100644 --- a/math_test.go +++ b/math_test.go @@ -173,6 +173,26 @@ func Test_SaturatingAddU64(t *testing.T) { } } +func Test_SaturatingAddU128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"2 + 1", NewU128(2), NewU128(1), NewU128(3)}, + {"MaxU128+1", MaxU128(), NewU128(1), MaxU128()}, + {"MaxU128+MaxU128", MaxU128(), MaxU128(), MaxU128()}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingAddU128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + func Test_SaturatingSubU64(t *testing.T) { testExamples := []struct { label string @@ -192,6 +212,26 @@ func Test_SaturatingSubU64(t *testing.T) { } } +func Test_SaturatingSubU128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"2-1", NewU128(2), NewU128(1), NewU128(1)}, + {"0-1", NewU128(0), NewU128(1), NewU128(0)}, + {"0-MaxU128", NewU128(0), MaxU128(), NewU128(0)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingSubU128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + func Test_SaturatingMul(t *testing.T) { testExamples := []struct { label string diff --git a/u128.go b/u128.go index 286b0b3..fb3aa65 100644 --- a/u128.go +++ b/u128.go @@ -101,26 +101,6 @@ func (n U128) Gte(other U128) bool { return n.ToBigInt().Cmp(other.ToBigInt()) >= 0 } -//func (n U128) SaturatingAdd(other U128) U128 { -// sumLow, carry := bits.Add64(uint64(n[0]), uint64(other[0]), 0) -// sumHigh, overflow := bits.Add64(uint64(n[1]), uint64(other[1]), carry) -// // check for overflow -// if overflow == 1 || (carry == 1 && sumHigh <= uint64(n[1]) && sumHigh <= uint64(other[1])) { -// return MaxU128() -// } -// return U128{U64(sumLow), U64(sumHigh)} -//} -// -//func (n U128) SaturatingSub(other U128) U128 { -// low, borrow := bits.Sub64(uint64(n[0]), uint64(other[0]), 0) -// high, _ := bits.Sub64(uint64(n[1]), uint64(other[1]), borrow) -// // check for underflow -// if borrow == 1 || high > uint64(n[1]) { -// return U128{0, 0} -// } -// return U128{U64(low), U64(high)} -//} -// //func (n U128) SaturatingMul(other U128) U128 { // result := new(big.Int).Mul(n.ToBigInt(), other.ToBigInt()) // // check for overflow From e0b1a09d0563fd7ca547640105e27b4ba99e43df Mon Sep 17 00:00:00 2001 From: failfmi Date: Thu, 28 Sep 2023 08:55:59 +0300 Subject: [PATCH 2/2] feat(u128): checked add and sub --- math.go | 22 ++++++++++++++++++++- math_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/math.go b/math.go index 486bbe6..d308546 100644 --- a/math.go +++ b/math.go @@ -125,7 +125,7 @@ func SaturatingSubU128(a, b U128) U128 { high, _ := bits.Sub64(uint64(a[1]), uint64(b[1]), borrow) // check for underflow if borrow == 1 || high > uint64(a[1]) { - return U128{0, 0} + return U128{} } return U128{U64(low), U64(high)} } @@ -157,3 +157,23 @@ func CheckedAddU64(a, b U64) (U64, error) { } return U64(sum), nil } + +func CheckedAddU128(a, b U128) (U128, error) { + sumLow, carry := bits.Add64(uint64(a[0]), uint64(b[0]), 0) + sumHigh, overflow := bits.Add64(uint64(a[1]), uint64(b[1]), carry) + // check for overflow + if overflow == 1 || (carry == 1 && sumHigh <= uint64(a[1]) && sumHigh <= uint64(b[1])) { + return U128{}, errOverflow + } + return U128{U64(sumLow), U64(sumHigh)}, nil +} + +func CheckedSubU128(a, b U128) (U128, error) { + low, borrow := bits.Sub64(uint64(a[0]), uint64(b[0]), 0) + high, _ := bits.Sub64(uint64(a[1]), uint64(b[1]), borrow) + // check for underflow + if borrow == 1 || high > uint64(a[1]) { + return U128{0, 0}, errUnderflow + } + return U128{U64(low), U64(high)}, nil +} diff --git a/math_test.go b/math_test.go index 04606af..d9a5c1c 100644 --- a/math_test.go +++ b/math_test.go @@ -308,3 +308,57 @@ func Test_CheckedAddU64(t *testing.T) { }) } } + +func Test_CheckedAddU128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + hasExpectErr bool + }{ + {"2 + 1", NewU128(2), NewU128(1), NewU128(3), false}, + {"MaxU128+1", MaxU128(), NewU128(1), NewU128(0), true}, + {"MaxU128+MaxU128", MaxU128(), MaxU128(), NewU128(0), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result, err := CheckedAddU128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + + if testExample.hasExpectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_CheckedSubU128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + hasExpectErr bool + }{ + {"2-1", NewU128(2), NewU128(1), NewU128(1), false}, + {"0-1", NewU128(0), NewU128(1), NewU128(0), true}, + {"0-MaxU128", NewU128(0), MaxU128(), NewU128(0), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result, err := CheckedSubU128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + + if testExample.hasExpectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +}