From 4e3d53ecd48763ad1ee470ffb0ec7a189f8bb913 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 21 Aug 2021 01:51:33 +0200 Subject: [PATCH] abacus: bignum: add division & modulo Like the C language, the `%` operator is rather the remainder, such that `(a/b)*b + (a%b) = a`. I still call it modulo though... --- src/bignum/bignum.cc | 63 ++++++++++++++++++++++++++++++++++++++++++++ src/bignum/bignum.hh | 27 +++++++++++++++++++ tests/unit/bignum.cc | 61 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/src/bignum/bignum.cc b/src/bignum/bignum.cc index 2d99095..a8ff26c 100644 --- a/src/bignum/bignum.cc +++ b/src/bignum/bignum.cc @@ -92,6 +92,37 @@ digits_type do_multiplication(digits_type const& lhs, digits_type const& rhs) { return res; } +std::pair do_div_mod(digits_type const& lhs, + digits_type const& rhs) { + if (rhs.size() == 0) { + throw std::invalid_argument("attempt to divide by zero"); + } + + digits_type quotient; + digits_type remainder = lhs; + + while (!do_less_than(remainder, rhs)) { + digits_type multiple = rhs; + digits_type rank; + rank.push_back(1); + + digits_type prev_multiple = multiple; + digits_type prev_rank = rank; + + while (!do_less_than(remainder, multiple)) { + prev_multiple = multiple; + prev_rank = rank; + multiple = do_addition(multiple, multiple); + rank = do_addition(rank, rank); + } + + quotient = do_addition(quotient, prev_rank); + remainder = do_substraction(remainder, prev_multiple); + } + + return std::make_pair(quotient, remainder); +} + } // namespace BigNum::BigNum(std::int64_t number) { @@ -226,6 +257,14 @@ void BigNum::multiply(BigNum const& rhs) { canonicalize(); } +void BigNum::divide(BigNum const& rhs) { + std::tie(*this, std::ignore) = div_mod(*this, rhs); +} + +void BigNum::modulo(BigNum const& rhs) { + std::tie(std::ignore, *this) = div_mod(*this, rhs); +} + bool BigNum::equal(BigNum const& rhs) const { assert(is_canonicalized()); assert(rhs.is_canonicalized()); @@ -295,4 +334,28 @@ bool BigNum::is_negative() const { return sign_ <= 0; } +std::pair div_mod(BigNum const& lhs, BigNum const& rhs) { + assert(lhs.is_canonicalized()); + assert(rhs.is_canonicalized()); + + if (lhs.is_zero()) { + return std::make_pair(BigNum(), BigNum()); + } + + BigNum quotient; + BigNum remainder; + + std::tie(quotient.digits_, remainder.digits_) + = do_div_mod(lhs.digits_, rhs.digits_); + + // Respect the identity `(a/b)*b + (a%b) = a` + quotient.sign_ = lhs.sign_ * rhs.sign_; + remainder.sign_ = lhs.sign_; + + quotient.canonicalize(); + remainder.canonicalize(); + + return std::make_pair(quotient, remainder); +} + } // namespace abacus::bignum diff --git a/src/bignum/bignum.hh b/src/bignum/bignum.hh index f23edd4..3ef6ba3 100644 --- a/src/bignum/bignum.hh +++ b/src/bignum/bignum.hh @@ -62,6 +62,31 @@ public: return ret; } + friend BigNum& operator/=(BigNum& lhs, BigNum const& rhs) { + lhs.divide(rhs); + return lhs; + } + + friend BigNum operator/(BigNum const& lhs, BigNum const& rhs) { + auto ret = lhs; + ret /= rhs; + return ret; + } + + friend BigNum& operator%=(BigNum& lhs, BigNum const& rhs) { + lhs.modulo(rhs); + return lhs; + } + + friend BigNum operator%(BigNum const& lhs, BigNum const& rhs) { + auto ret = lhs; + ret %= rhs; + return ret; + } + + friend std::pair div_mod(BigNum const& lhs, + BigNum const& rhs); + friend bool operator==(BigNum const& lhs, BigNum const& rhs) { return lhs.equal(rhs); } @@ -98,6 +123,8 @@ private: void add(BigNum const& rhs); void substract(BigNum const& rhs); void multiply(BigNum const& rhs); + void divide(BigNum const& rhs); + void modulo(BigNum const& rhs); bool equal(BigNum const& rhs) const; bool less_than(BigNum const& rhs) const; diff --git a/tests/unit/bignum.cc b/tests/unit/bignum.cc index 31cf03a..0fc4418 100644 --- a/tests/unit/bignum.cc +++ b/tests/unit/bignum.cc @@ -206,3 +206,64 @@ TEST(BigNum, multiplication_mixed_signs) { EXPECT_EQ(minus_two * two, minus_four); EXPECT_EQ(minus_two * minus_two, four); } + +TEST(BigNum, division) { + auto const zero = BigNum(0); + auto const one = BigNum(1); + auto const two = BigNum(2); + + EXPECT_EQ(one / one, one); + EXPECT_EQ(one / two, zero); + EXPECT_EQ(two / one, two); +} + +TEST(BigNum, division_negative) { + auto const zero = BigNum(0); + auto const one = BigNum(1); + auto const minus_one = BigNum(-1); + auto const two = BigNum(2); + auto const minus_two = BigNum(-2); + + EXPECT_EQ(one / minus_one, minus_one); + EXPECT_EQ(minus_one / one, minus_one); + EXPECT_EQ(minus_one / minus_one, one); + EXPECT_EQ(two / minus_two, minus_one); + EXPECT_EQ(minus_two / two, minus_one); + EXPECT_EQ(minus_two / minus_two, one); + + EXPECT_EQ(one / minus_two, zero); + EXPECT_EQ(minus_one / two, zero); + EXPECT_EQ(one / minus_two, zero); + EXPECT_EQ(minus_one / minus_two, zero); + EXPECT_EQ(two / minus_one, minus_two); + EXPECT_EQ(minus_two / minus_one, two); + EXPECT_EQ(minus_two / one, minus_two); +} + +TEST(BigNum, division_truncate) { + auto const one = BigNum(1); + auto const minus_one = BigNum(-1); + auto const three = BigNum(3); + auto const minus_three = BigNum(-3); + auto const five = BigNum(5); + auto const minus_five = BigNum(-5); + + EXPECT_EQ(five / three, one); + EXPECT_EQ(five / minus_three, minus_one); + EXPECT_EQ(minus_five / three, minus_one); + EXPECT_EQ(minus_five / minus_three, one); +} + +TEST(BigNum, div_mod_identity) { + auto const three = BigNum(3); + auto const minus_three = BigNum(-3); + auto const five = BigNum(5); + auto const minus_five = BigNum(-5); + + EXPECT_EQ((five / three) * three + (five % three), five); + EXPECT_EQ((five / minus_three) * minus_three + (five % minus_three), five); + EXPECT_EQ((minus_five / three) * three + (minus_five % three), minus_five); + EXPECT_EQ((minus_five / minus_three) * minus_three + + (minus_five % minus_three), + minus_five); +}