The class below implements an arbitrarily sized whole number C++ class.
It is wrapped around a bitvector
class, which does most the binary heavy lifting.
The basic mathematical operators are implemented using whole word manipulation, inspired from Modern Computer Arithmetic - Paul Zimmermann.
I was unable to understand the pseudo-code and math notation for division from that book. So, I came up with my own algorithm. Feedback on that is very welcomed. I would be surprised if it couldn’t be optimized in some way.
A design change was made from the previous bitvector
class, to remove the std::string
and std::stringstream
aliases as recommended here. After thinking about it if I make the changes now and cascade them up as I revise the rest of the library, it will be more manageable, than later going top to bottom.
This class is designed to abstract the arithmetic for arbitrary precision integrals, which will using this class in their design. Hence the boolean error variable in one of the constructors.
whole_number.h
#include "../support_files/string_support.h"
#include "../BitVector/bitvector.h"
namespace Olly {
namespace MPA {
/********************************************************************************************/
//
// 'whole_number' Class Declaration
//
/********************************************************************************************/
class whole_number {
using Word = bitvector::word_t;
using Double_Word = bitvector::double_word_t;
public:
whole_number();
whole_number(Word value);
whole_number(const std::string& value, Word base = 10);
whole_number(const std::string& value, Word base, bool& error);
virtual ~whole_number();
whole_number(whole_number&& obj) = default;
whole_number(const whole_number& obj) = default;
whole_number& operator=(whole_number&& obj) = default;
whole_number& operator=(const whole_number& obj) = default;
friend void swap(whole_number& first, whole_number& second);
operator bool() const;
bool is() const;
bool is_odd() const;
bool is_even() const;
bool operator==(const whole_number& b) const;
bool operator!=(const whole_number& b) const;
std::partial_ordering operator<=>(const whole_number& b) const;
whole_number& operator&=(const whole_number& other);
whole_number& operator|=(const whole_number& other);
whole_number& operator^=(const whole_number& other);
whole_number& operator<<=(std::size_t index);
whole_number& operator>>=(std::size_t index);
whole_number bin_comp() const;
whole_number operator&(const whole_number& b) const;
whole_number operator|(const whole_number& b) const;
whole_number operator^(const whole_number& b) const;
whole_number operator~() const;
whole_number operator<<(std::size_t index) const;
whole_number operator>>(std::size_t index) const;
whole_number& operator+=(const whole_number& other);
whole_number& operator-=(const whole_number& other);
whole_number& operator*=(const whole_number& other);
whole_number& operator/=(const whole_number& other);
whole_number& operator%=(const whole_number& other);
whole_number operator+(const whole_number& b) const;
whole_number operator-(const whole_number& b) const;
whole_number operator*(const whole_number& b) const;
whole_number operator/(const whole_number& b) const;
whole_number operator%(const whole_number& b) const;
friend whole_number operator&(whole_number&& a, const whole_number& b);
friend whole_number operator|(whole_number&& a, const whole_number& b);
friend whole_number operator^(whole_number&& a, const whole_number& b);
friend whole_number operator<<(whole_number&& a, std::size_t index);
friend whole_number operator>>(whole_number&& a, std::size_t index);
friend whole_number operator+(whole_number&& a, const whole_number& b);
friend whole_number operator-(whole_number&& a, const whole_number& b);
friend whole_number operator*(whole_number&& a, const whole_number& b);
friend whole_number operator/(whole_number&& a, const whole_number& b);
friend whole_number operator%(whole_number&& a, const whole_number& b);
whole_number& operator++();
whole_number operator++(int);
whole_number& operator--();
whole_number operator--(int);
void div_rem(const whole_number& other, whole_number& qot, whole_number& rem) const;
whole_number pow(std::size_t b) const;
whole_number sqrt() const;
whole_number root(const whole_number& b) const;
std::string to_string() const;
std::string to_string(std::size_t base) const;
template<typename N>
N to_integral() const;
const bitvector& get_bitvector() const;
private:
bitvector _bitvec;
whole_number(const bitvector& bitvec);
void trim();
void divide_remainder(Word d, whole_number& q, whole_number& r, int stop) const;
bool set_numeric_value(const std::string& text, const Word& base);
};
template<typename N>
inline N whole_number::to_integral() const {
return _bitvec.to_integral<N>();
}
}
}
whole_number.cpp
#include "whole_number.h"
namespace Olly {
namespace MPA {
whole_number::whole_number() : _bitvec() {
}
whole_number::whole_number(Word value) : _bitvec(1, value) {
}
whole_number::whole_number(const std::string& value, Word base) : _bitvec() {
set_numeric_value(value, base);
}
whole_number::whole_number(const std::string& value, Word base, bool& error) : _bitvec() {
error = set_numeric_value(value, base);
}
whole_number::whole_number(const bitvector& bitvec) : _bitvec(bitvec) {
}
whole_number::~whole_number() {
}
void swap(whole_number& left, whole_number& right) {
whole_number temp = std::move(left);
left = std::move(right);
right = std::move(temp);
}
whole_number::operator bool() const {
return is();
}
bool whole_number::is() const {
return _bitvec.is();
}
bool whole_number::is_odd() const {
return _bitvec.last_word() & 1;
}
bool whole_number::is_even() const {
return !is_odd();
}
bool whole_number::operator==(const whole_number& b) const {
return operator<=>(b) == std::partial_ordering::equivalent;
}
bool whole_number::operator!=(const whole_number& b) const {
return operator<=>(b) != std::partial_ordering::equivalent;
}
std::partial_ordering whole_number::operator<=>(const whole_number& b) const {
return _bitvec <=> b._bitvec;
}
whole_number& whole_number::operator&=(const whole_number& other) {
_bitvec &= other._bitvec;
trim();
return *this;
}
whole_number& whole_number::operator|=(const whole_number& other) {
_bitvec |= other._bitvec;
trim();
return *this;
}
whole_number& whole_number::operator^=(const whole_number& other) {
_bitvec ^= other._bitvec;
trim();
return *this;
}
whole_number& whole_number::operator<<=(std::size_t index) {
_bitvec <<= index;
trim();
return *this;
}
whole_number& whole_number::operator>>=(std::size_t index) {
_bitvec >>= index;
trim();
return *this;
}
whole_number whole_number::bin_comp() const {
whole_number n;
n._bitvec = _bitvec.bin_comp();
return n;
}
whole_number whole_number::operator&(const whole_number& b) const {
whole_number a = *this;
a &= b;
return a;
}
whole_number whole_number::operator|(const whole_number& b) const {
whole_number a = *this;
a |= b;
return a;
}
whole_number whole_number::operator^(const whole_number& b) const {
whole_number a = *this;
a ^= b;
return a;
}
whole_number whole_number::operator~() const {
whole_number a;
a._bitvec = ~_bitvec;
return a;
}
whole_number whole_number::operator<<(std::size_t index) const {
whole_number a = *this;
a <<= index;
return a;
}
whole_number whole_number::operator>>(std::size_t index) const {
whole_number a = *this;
a >>= index;
return a;
}
whole_number& whole_number::operator+=(const whole_number& other) {
std::size_t limit = _bitvec.size_words() > other._bitvec.size_words() ? _bitvec.size_words() : other._bitvec.size_words();
Double_Word n = 0;
for (std::size_t i = 0; i < limit; i += 1) {
n = n + _bitvec.at_word(i) + other._bitvec.at_word(i);
_bitvec.at_word(i) = static_cast<Word>(n);
n >>= _bitvec.value_type;
}
if (n != 0) {
_bitvec.at_word(limit) = static_cast<Word>(n);
}
trim();
return *this;
}
whole_number& whole_number::operator-=(const whole_number& other) {
if (other > *this) {
*this = whole_number();
return *this;
}
std::size_t limit = _bitvec.size_words() > other._bitvec.size_words() ? _bitvec.size_words() : other._bitvec.size_words();
Double_Word n = 0;
for (std::size_t i = 0; i < limit; i += 1) {
n = n + _bitvec.at_word(i) - other._bitvec.at_word(i);
_bitvec.at_word(i) = static_cast<Word>(n);
n = ((n >> _bitvec.value_type) ? -1 : 0);
}
trim();
return *this;
}
whole_number& whole_number::operator*=(const whole_number& other) {
*this = *this * other;
return *this;
}
whole_number& whole_number::operator/=(const whole_number& other) {
*this = *this / other;
return *this;
}
whole_number& whole_number::operator%=(const whole_number& other) {
*this = *this % other;
return *this;
}
whole_number whole_number::operator+(const whole_number& b) const {
whole_number a = *this;
a += b;
return a;
}
whole_number whole_number::operator-(const whole_number& b) const {
whole_number a = *this;
a -= b;
return a;
}
whole_number whole_number::operator*(const whole_number& b) const {
std::size_t size_a = _bitvec.size_words();
std::size_t size_b = b._bitvec.size_words();
bitvector r((size_a + size_b + 1), 0);
for (std::size_t j = 0; j < size_b; j += 1) {
Double_Word n = 0;
for (std::size_t i = 0; i < size_a; i += 1) {
std::size_t k = i + j;
n += static_cast<Double_Word>(_bitvec.at_word(i)) * b._bitvec.at_word(j) + r.at_word(k);
r.at_word(k) = static_cast<Word>(n);
n >>= _bitvec.value_type;
}
r.at_word(j + size_a) = static_cast<Word>(n);
}
r.trim();
return r;
}
whole_number whole_number::operator/(const whole_number& b) const {
whole_number q;
whole_number r;
div_rem(b, q, r);
return q;
}
whole_number whole_number::operator%(const whole_number& b) const {
whole_number q;
whole_number r;
div_rem(b, q, r);
return r;
}
whole_number operator&(whole_number&& a, const whole_number& b) {
return a &= b;
}
whole_number operator|(whole_number&& a, const whole_number& b) {
return a |= b;
}
whole_number operator^(whole_number&& a, const whole_number& b) {
return a ^= b;
}
whole_number operator<<(whole_number&& a, std::size_t index) {
return a <<= index;
}
whole_number operator>>(whole_number&& a, std::size_t index) {
return a >>= index;
}
whole_number operator+(whole_number&& a, const whole_number& b) {
return a += b;
}
whole_number operator-(whole_number&& a, const whole_number& b) {
return a -= b;
}
whole_number operator*(whole_number&& a, const whole_number& b) {
return a *= b;
}
whole_number operator/(whole_number&& a, const whole_number& b) {
return a /= b;
}
whole_number operator%(whole_number&& a, const whole_number& b) {
return a %= b;
}
whole_number& whole_number::operator++() {
++_bitvec;
trim();
return *this;
}
whole_number whole_number::operator++(int) {
whole_number a(*this);
operator++();
return a;
}
whole_number& whole_number::operator--() {
--_bitvec;
trim();
return *this;
}
whole_number whole_number::operator--(int) {
whole_number a(*this);
operator--();
return a;
}
void whole_number::div_rem(const whole_number& other, whole_number& qot, whole_number& rem) const {
// Ensure both the qotient and remander are initalized to zero.
qot = whole_number();
rem = whole_number();
if (!other.is()) {
// Division by zero.
return;
}
if (other > *this) {
// Division by a greater value.
qot = whole_number();
rem = *this;
return;
}
if (*this > other) {
if (other._bitvec.size_words() == 1) {
divide_remainder(other._bitvec.at_word(0), qot, rem, 0);
return;
}
int stop = static_cast<int>(other._bitvec.size_words() - 1);
auto d = other._bitvec.at_word(stop);
for (int i = stop - 1; i >= 0; i -= 1) {
// Add one to 'd' if any other digits are defined.
if (other._bitvec.at_word(i) != 0) {
d += 1;
break;
}
}
// Perform long division.
whole_number n = *this;
whole_number q = whole_number();
whole_number guard = whole_number();
while (n >= other && n != guard) {
n.divide_remainder(d, q, rem, stop);
qot += q;
guard = n;
n -= (other * q);
q = whole_number();
}
// Confirm the qoutent is correct.
q = other * qot;
while (q < *this) {
qot += whole_number(1);
q += other;
}
while (q > *this) {
qot -= whole_number(1);
q -= other;
}
// Determine the remainder.
rem = *this - q;
return;
}
// Return division by two equal values.
qot = whole_number(1);
rem = whole_number();
}
whole_number whole_number::pow(std::size_t b) const {
if (b == 2) {
whole_number a = *this;
return a * a;
}
if (b == 1) {
return *this;
}
if (b == 0) {
return 1;
}
whole_number a = *this;
whole_number res = 1;
while (b) {
if (b & 1) {
res *= a;
}
b >>= 1;
if (b) {
a *= a;
}
}
return res;
}
whole_number whole_number::sqrt() const {
return static_cast<Word>(_bitvec.lead_bit() - 1);
}
whole_number whole_number::root(const whole_number& b) const {
std::size_t n = b.to_integral<std::size_t>();
whole_number low = 0;
whole_number high = 1;
whole_number ONE = 1;
whole_number TWO = 2;
while (high.pow(n) <= *this) {
low = high;
high *= TWO;
}
while (low != high - ONE) {
whole_number step = (high - low) / TWO;
whole_number candidate = low + step;
auto value = candidate.pow(n);
if (value == *this) {
return candidate;
}
if (value < *this) {
low = candidate;
}
else {
high = candidate;
}
}
return low;
}
std::string whole_number::to_string() const {
return to_string(10);
}
std::string whole_number::to_string(std::size_t base) const {
if (base == 10 || base == 0 || base == 2 || base == 8 || base == 16) {
std::string result = "";
if (!is()) {
return "0";
}
whole_number radix = static_cast<Word>(base != 0 ? base : 10);
whole_number n = *this;
std::stringstream stream;
int count = 0;
while (n.is()) {
whole_number q;
whole_number r = n;
n.div_rem(radix, q, r);
n = q;
if (base == 16) {
stream << std::hex << r._bitvec.at_word(0);
}
else if (base == 8) {
stream << std::oct << r._bitvec.at_word(0);
}
else {
stream << r._bitvec.at_word(0);
}
if (base == 10) {
count += 1;
if (count == 3) {
stream << ',';
count = 0;
}
}
}
std::string res = stream.str();
if (res.back() == ',') {
res.pop_back();
}
for (auto i = res.crbegin(); i != res.crend(); ++i) {
result += *i;
}
return result;
}
return "";
}
const bitvector& whole_number::get_bitvector() const {
return _bitvec;
}
void whole_number::divide_remainder(Word d, whole_number& q, whole_number& r, int stop) const {
Double_Word n(0);
for (int i = static_cast<int>(_bitvec.size_words() - 1); i >= stop; i -= 1) {
n += _bitvec.at_word(i);
q._bitvec.at_word(static_cast<std::size_t>(i) - stop) = static_cast<Word>(n / d);
n %= d;
n <<= _bitvec.value_type;
}
r._bitvec.at_word(0) = n >> _bitvec.value_type;
return;
}
bool whole_number::set_numeric_value(const std::string& text, const Word& base) {
if (base == 10) { // Parse a decimal number.
Word x = 0;
for (const auto n : text) {
if (!std::isspace(n) && n != ',') {
x = n - '0';
if (x >= 0 && x < base) {
operator*=(base);
operator+=(x);
}
else {
_bitvec = bitvector();
return true;
}
}
}
}
else if (base == 16) { // Parse a hexidecimal number.
Word x;
for (const auto n : text) {
if (!std::isspace(n)) {
switch (n) {
case('a'):
case('A'):
x = 10;
break;
case('b'):
case('B'):
x = 11;
break;
case('c'):
case('C'):
x = 12;
break;
case('d'):
case('D'):
x = 13;
break;
case('e'):
case('E'):
x = 14;
break;
case('f'):
case('F'):
x = 15;
break;
default:
x = (n - '0');
}
if (x >= 0 && x < base) {
operator*=(base);
operator+=(x);
}
else {
_bitvec = bitvector();
return true;
}
}
}
}
else if (base == 8) { // Parse an octal number.
Word x;
for (const auto n : text) {
if (!std::isspace(n)) {
x = n - '0';
if (x >= 0 && x < base) {
operator*=(base);
operator+=(x);
}
else {
_bitvec = bitvector();
return true;
}
}
}
}
else if (base == 2) { // Parse a binary number.
for (const auto n : text) {
operator<<=(1);
if (n == '1') {
operator+=(1);
}
else if (n != '0') {
_bitvec = bitvector();
return true;
}
}
}
return false;
}
void whole_number::trim() {
_bitvec.trim();
}
}
}