Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failing test cases for bit-shifting #105

Merged
merged 16 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ else() # GCC/Clang warning option
enable_cxx_compiler_flag_if_supported("-Wno-error=unused-parameter")
enable_cxx_compiler_flag_if_supported("-Wno-error=unused-private-field")
enable_cxx_compiler_flag_if_supported("-Wno-error=unused-but-set-variable")
# Clang's now introduced a "helpful" analysis of for-loops that increment a variable twice
enable_cxx_compiler_flag_if_supported("-Wno-error=for-loop-analysis")
endif()
endif()

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ arby is designed with the following goals in mind:
- cast to/from `uintmax_t` and `long double`
- conversion to/from decimal, octal and hexadecimal string
- bitwise operators
- bit-shift operators

### What will be provided in future?

- Additions to **`Nat`**:
- bit-shift operators
- Arbitrary-precision signed integers (the [Integers](https://en.wikipedia.org/wiki/Integer)) via class **`Int`**
- All operations supported by **`Nat`** with the exception of bitwise ones
- sign change
Expand Down
2 changes: 1 addition & 1 deletion arby/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ include(GNUInstallDirs)
CPMFindPackage(
NAME Codlili
GIT_REPOSITORY https://github.com/saxbophone/codlili.git
GIT_TAG v0.4.0
GIT_TAG v0.5.1
)

add_library(arby STATIC)
Expand Down
110 changes: 88 additions & 22 deletions arby/include/arby/Nat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace com::saxbophone::arby {
/*
* uses compile-time template logic to pick StorageType and OverflowType:
* - picks unsigned int if its range is less than that of uintmax_t
* - otherwise, picks the next type smaller than uintmax_t (very unlikely)
* - otherwise, picks the next type smaller than unsigned int/uintmax_t (very unlikely)
*/
struct StorageTraits {
using StorageType = std::conditional<
Expand Down Expand Up @@ -152,6 +152,7 @@ namespace com::saxbophone::arby {
*/
using OverflowType = PRIVATE::StorageTraits::OverflowType;
private:
static constexpr std::size_t BITS_PER_DIGIT = std::numeric_limits<StorageType>::digits;
static constexpr std::size_t BITS_BETWEEN = std::numeric_limits<OverflowType>::digits - std::numeric_limits<StorageType>::digits;
// validates the digits array
constexpr void _validate_digits() const {
Expand Down Expand Up @@ -837,27 +838,92 @@ namespace com::saxbophone::arby {
result._validate_digits();
return result;
}
// XXX: unimplemented shift operators commented out until implemented
// // left-shift-assignment
// constexpr Nat& operator<<=(const Nat& n) {
// // TODO: implement
// return *this;
// }
// // left-shift
// friend constexpr Nat operator<<(Nat lhs, const Nat& rhs) {
// lhs <<= rhs; // reuse compound assignment
// return lhs; // return the result by value (uses move constructor)
// }
// // right-shift-assignment
// constexpr Nat& operator>>=(const Nat& n) {
// // TODO: implement
// return *this;
// }
// // right-shift
// friend constexpr Nat operator>>(Nat lhs, const Nat& rhs) {
// lhs <<= rhs; // reuse compound assignment
// return lhs; // return the result by value (uses move constructor)
// }
/**
* @brief bitwise left-shift assignment
* @details Bits are never shifted out, instead the object is enlargened
* to make them fit.
* @note Complexity: @f$ \mathcal{O(n)} @f$
*/
constexpr Nat& operator<<=(uintmax_t n) {
// break the shift up into whole-digit and part-digit shifts
auto wholes = n / BITS_PER_DIGIT;
auto parts = n % BITS_PER_DIGIT;
// shift up by whole number of digits first
_digits.push_back(wholes, 0);
// handle the sub-digit shift next
if (parts > 0) {
// add another digit at the top end to accommodate the shift
_digits.push_front(0);
// shift up each digit into a bucket twice the size (to not lose top bits)
for (auto it = ++_digits.begin(); it != _digits.end(); it++) { // second element
OverflowType bucket = *it;
bucket <<= parts; // do the shift into bucket
*it = (StorageType)bucket; // overwrite original value with lower bits in bucket
// write upper part of the bucket
bucket >>= BITS_PER_DIGIT;
it--;
*it |= bucket; // OR is to make sure we preserve any already-written bits
it++;
}
}
// XXX: why do we require this? Shift operation isn't supposed to leave any leading zeroes...
if (_digits.front() == 0) {
_digits.pop_front();
}
_validate_digits(); // TODO: remove when satisfied not required
return *this;
}
/**
* @brief bitwise left-shift for Nat
* @details Bits are never shifted out, instead the object is enlargened
* to make them fit.
* @note Complexity: @f$ \mathcal{O(n)} @f$
*/
friend constexpr Nat operator<<(Nat lhs, uintmax_t rhs) {
lhs <<= rhs; // reuse compound assignment
return lhs; // return the result by value (uses move constructor)
}
/**
* @brief bitwise right-shift assignment
* @details Bits are shifted out rightwards and the object may be shrunk
* @note Complexity: @f$ \mathcal{O(n)} @f$
*/
constexpr Nat& operator>>=(uintmax_t n) {
// cap n to be no more than total bits in number
if (n > this->bit_length()) { n = this->bit_length(); }
// break the shift up into whole-digit and part-digit shifts
auto wholes = n / BITS_PER_DIGIT;
auto parts = n % BITS_PER_DIGIT;
// shift down by whole number of digits first
for (uintmax_t i = 0; i < wholes; i++) {
_digits.pop_back();
}
// handle the sub-digit shift next
if (parts > 0) {
for (auto it = _digits.rbegin(); it != _digits.rend(); ) {
*it >>= parts;
auto prev = it++;
if (it != _digits.rend()) {
*prev |= (*it << (BITS_PER_DIGIT - parts));
}
}
}
// replace digits array with zero if empty
if (_digits.empty()) {
_digits = {0};
}
_validate_digits(); // TODO: remove when satisfied not required
return *this;
}
/**
* @brief bitwise right-shift for Nat
* @details Bits are shifted out rightwards and the object may be shrunk
* @note Complexity: @f$ \mathcal{O(n)} @f$
*/
friend constexpr Nat operator>>(Nat lhs, uintmax_t rhs) {
lhs >>= rhs; // reuse compound assignment
return lhs; // return the result by value (uses move constructor)
}
/**
* @brief contextual conversion to bool (behaves same way as int)
* @returns `false` when value is `0`, otherwise `true`
Expand Down
1 change: 1 addition & 0 deletions tests/Nat/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
add_library(
Nat OBJECT
basic_arithmetic.cpp
bit_shifting.cpp
bitwise.cpp
casting.cpp
digits.cpp
Expand Down
88 changes: 88 additions & 0 deletions tests/Nat/bit_shifting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <cstdint>

#include <limits>

#include <catch2/catch.hpp>

#include <arby/Nat.hpp>

using namespace com::saxbophone;
using namespace com::saxbophone::arby::literals;

TEST_CASE("arby::Nat left bit-shift", "[bit-shifting]") {
auto [lhs, rhs, result] = GENERATE(
table<arby::Nat, uintmax_t, arby::Nat>(
{
{0b1110101_nat, 23, 0b111010100000000000000000000000_nat},
{0b0_nat, 13, 0b0_nat},
{0b1101_nat, 1, 0b11010_nat},
{0b10001011_nat, 0, 0b10001011_nat},
{0b10101110001_nat, 4, 0b101011100010000_nat},
{0b1_nat, 70, 0b10000000000000000000000000000000000000000000000000000000000000000000000_nat},
}
)
);

arby::Nat shifted = lhs << rhs;

CHECK(shifted == result);
}

TEST_CASE("arby::Nat left bit-shift assignment", "[bit-shifting]") {
auto [lhs, rhs, result] = GENERATE(
table<arby::Nat, uintmax_t, arby::Nat>(
{
{0b1110101_nat, 23, 0b111010100000000000000000000000_nat},
{0b0_nat, 13, 0b0_nat},
{0b1101_nat, 1, 0b11010_nat},
{0b10001011_nat, 0, 0b10001011_nat},
{0b10101110001_nat, 4, 0b101011100010000_nat},
{0b1_nat, 70, 0b10000000000000000000000000000000000000000000000000000000000000000000000_nat},
}
)
);

lhs <<= rhs;

CHECK(lhs == result);
}

TEST_CASE("arby::Nat right bit-shift", "[bit-shifting]") {
auto [lhs, rhs, result] = GENERATE(
table<arby::Nat, uintmax_t, arby::Nat>(
{
{0b10001101_nat, 3, 0b10001_nat},
{0b111011001001001010101110101010_nat, 19, 0b11101100100_nat},
{0b10000000110100000000011101101000_nat, 54, 0b0_nat},
{0b10011001010_nat, 0, 0b10011001010_nat},
{0b1101011000011000_nat, 8, 0b11010110_nat},
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat}
}
)
);
CAPTURE(lhs, rhs, result);

arby::Nat shifted = lhs >> rhs;

CHECK(shifted == result);
}

TEST_CASE("arby::Nat right bit-shift assignment", "[bit-shifting]") {
auto [lhs, rhs, result] = GENERATE(
table<arby::Nat, uintmax_t, arby::Nat>(
{
{0b10001101_nat, 3, 0b10001_nat},
{0b111011001001001010101110101010_nat, 19, 0b11101100100_nat},
{0b10000000110100000000011101101000_nat, 54, 0b0_nat},
{0b10011001010_nat, 0, 0b10011001010_nat},
{0b1101011000011000_nat, 8, 0b11010110_nat},
{0b11111111111111111111111111111111111111111111111111111111111111111111111111111111_nat, 70, 0b1111111111_nat}
}
)
);
CAPTURE(lhs, rhs, result);

lhs >>= rhs;

CHECK(lhs == result);
}