Skip to content

Commit 8d8387a

Browse files
committed
Optimised divmod with bit-shifting
Doesn't seem to make much difference currently but probably makes a bigger difference for large sizes
1 parent f29fbed commit 8d8387a

File tree

3 files changed

+26
-6
lines changed

3 files changed

+26
-6
lines changed

arby/include/arby/Nat.hpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ namespace com::saxbophone::arby {
554554
}
555555
private: // private helper methods for multiplication operator
556556
constexpr bool is_power_of_2() const {
557-
return *this == arby::Nat(1) << (bit_length() - 1);
557+
return *this == Nat(1) << (bit_length() - 1);
558558
}
559559
public:
560560
/**
@@ -570,8 +570,7 @@ namespace com::saxbophone::arby {
570570
if (lhs._digits.front() == 0 or rhs._digits.front() == 0) {
571571
return product;
572572
}
573-
// TODO: optimise this using bitshifts when either operand is a binary power
574-
// NOTE: you will need a "is a power of two?" private method to do it: check if this == 1 << (bitlength - 1)
573+
// optimisation using bitshifting when multiplying by binary powers
575574
if (rhs.is_power_of_2()) {
576575
return lhs << (rhs.bit_length() - 1);
577576
} else if (lhs.is_power_of_2()) {
@@ -604,6 +603,7 @@ namespace com::saxbophone::arby {
604603
}
605604
private: // private helper methods for Nat::divmod()
606605
// function that shifts up rhs to be just big enough to be smaller than lhs
606+
// TODO: rewrite this to use bit-shifting for speed
607607
static constexpr Nat get_max_shift(const Nat& lhs, const Nat& rhs) {
608608
// how many places can we shift rhs left until it's the same width as lhs?
609609
std::size_t wiggle_room = lhs._digits.size() - rhs._digits.size();
@@ -651,6 +651,18 @@ namespace com::saxbophone::arby {
651651
if (rhs._digits.front() == 0) {
652652
throw std::domain_error("division by zero");
653653
}
654+
if (lhs._digits.front() == 0) { return {lhs, lhs}; } // zero shortcut
655+
// optimisation using bitshifting when dividing by binary powers
656+
if (rhs.is_power_of_2()) {
657+
auto width = rhs.bit_length();
658+
// the remainder is the digits that are shifted out, so bitmask for them
659+
auto bitmask = (Nat(1) << (width - 1)) - 1;
660+
Nat quotient = lhs >> (width - 1);
661+
Nat remainder = lhs & bitmask;
662+
quotient._validate_digits();
663+
remainder._validate_digits();
664+
return {quotient, remainder};
665+
}
654666
// this will gradually accumulate the calculated quotient
655667
Nat quotient;
656668
// this will gradually decrement with each subtraction
@@ -925,7 +937,9 @@ namespace com::saxbophone::arby {
925937
if (_digits.empty()) {
926938
_digits = {0};
927939
}
928-
_validate_digits(); // TODO: remove when satisfied not required
940+
// needed in some cases, probably when the intial whole-digit shift leaves a small value which then turns 0
941+
_remove_leading_zeroes();
942+
_validate_digits();
929943
return *this;
930944
}
931945
/**

tests/Nat/bit_shifting.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ TEST_CASE("arby::Nat left bit-shift", "[bit-shifting]") {
1919
{0b10001011_nat, 0, 0b10001011_nat},
2020
{0b10101110001_nat, 4, 0b101011100010000_nat},
2121
{0b1_nat, 70, 0b10000000000000000000000000000000000000000000000000000000000000000000000_nat},
22+
{0xfeed_nat, 32, 0xfeed00000000_nat},
2223
}
2324
)
2425
);
@@ -38,6 +39,7 @@ TEST_CASE("arby::Nat left bit-shift assignment", "[bit-shifting]") {
3839
{0b10001011_nat, 0, 0b10001011_nat},
3940
{0b10101110001_nat, 4, 0b101011100010000_nat},
4041
{0b1_nat, 70, 0b10000000000000000000000000000000000000000000000000000000000000000000000_nat},
42+
{0xfeed_nat, 32, 0xfeed00000000_nat},
4143
}
4244
)
4345
);
@@ -56,7 +58,8 @@ TEST_CASE("arby::Nat right bit-shift", "[bit-shifting]") {
5658
{0b10000000110100000000011101101000_nat, 54, 0b0_nat},
5759
{0b10011001010_nat, 0, 0b10011001010_nat},
5860
{0b1101011000011000_nat, 8, 0b11010110_nat},
59-
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat}
61+
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat},
62+
{0xfeedface1_nat, 32, 0xf_nat},
6063
}
6164
)
6265
);
@@ -76,7 +79,8 @@ TEST_CASE("arby::Nat right bit-shift assignment", "[bit-shifting]") {
7679
{0b10000000110100000000011101101000_nat, 54, 0b0_nat},
7780
{0b10011001010_nat, 0, 0b10011001010_nat},
7881
{0b1101011000011000_nat, 8, 0b11010110_nat},
79-
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat}
82+
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat},
83+
{0xfeedface1_nat, 32, 0xf_nat},
8084
}
8185
)
8286
);

tests/Nat/divmod.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ TEST_CASE("divmod of arby::Nat by a power of two", "[divmod]") {
230230
uintmax_t denominator = integer_pow(2, power);
231231
uintmax_t numerator = GENERATE_COPY(take(100, random(denominator, std::numeric_limits<uintmax_t>::max())));
232232

233+
CAPTURE(numerator, denominator);
234+
233235
auto [quotient, remainder] = arby::Nat::divmod(numerator, denominator);
234236

235237
CAPTURE(numerator, denominator, quotient, remainder);

0 commit comments

Comments
 (0)