This was mostly just for fun, motivated by questions about a student programming problem. There are plenty of arbitrary- and extended-precision arithmetic libraries out there, and there are plenty of great references describing the algorithms involved. But there can be a lot of added complexity in the translation from pseudo-code to actual lines of working code. My goal here was to provide the latter, while maintaining the readability of the former. I’m not sure I succeeded. But it was still fun.
The end result is about 500 lines of code in a single C++ header file, defining an unsigned (i.e., non-negative) integer type with overloading of all sensible arithmetic, bitwise, and relational operators (basically everything except bitwise one’s complement), and implicit conversions to/from a decimal string representation. The code follows the conventions and variable names of Knuth’s TAOCP Volume 2 very closely throughout, although the division algorithm is slightly simplified.
[Edit 2018-09-02: See this more recent post extending this implementation to also support signed integer and rational arithmetic.]
Following is the source code, which is free, unencumbered, released into the public domain; do what you want with it.
#ifndef MATH_UNSIGNED_H
#define MATH_UNSIGNED_H
#include <cstdint>
#include <vector>
#include <iosfwd>
#include <stdexcept>
#include <algorithm>
#include <sstream>
#include <cctype>
using std::size_t;
namespace math
{
class Unsigned
{
public:
typedef std::uint32_t Digit;
typedef std::uint64_t Wigit;
static const unsigned BITS = 32;
Unsigned(Digit u = 0) :
digits(1, u)
{
// empty
}
Unsigned(const std::string& s) :
digits(1, 0)
{
std::istringstream iss(s);
iss >> *this;
if (iss.fail() || !iss.eof())
{
throw std::runtime_error("Error: Unsigned::string");
}
}
Unsigned(const Unsigned& copy) :
digits(copy.digits)
{
// empty
}
Unsigned& operator= (const Unsigned& rhs)
{
digits = rhs.digits;
return *this;
}
Unsigned operator++ (int)
{
Unsigned w(*this);
++(*this);
return w;
}
Unsigned& operator++ ()
{
for (size_t j = 0; j < digits.size() && ++digits[j] == 0; ++j);
if (digits.back() == 0)
{
digits.push_back(1);
}
return *this;
}
Unsigned operator-- (int)
{
Unsigned w(*this);
--(*this);
return w;
}
Unsigned& operator-- ()
{
if (digits.back() == 0)
{
throw std::underflow_error("Error: Unsigned::underflow");
}
for (size_t j = 0; j < digits.size() && digits[j]-- == 0; ++j);
trim();
return *this;
}
friend Unsigned operator+ (Unsigned u, const Unsigned& v)
{
u += v;
return u;
}
Unsigned& operator+= (const Unsigned& rhs)
{
const size_t n = rhs.digits.size();
if (digits.size() < n)
{
digits.resize(n, 0);
}
size_t j = 0;
Wigit k = 0;
for (; j < n; ++j)
{
k = k + digits[j] + rhs.digits[j];
digits[j] = static_cast<Digit>(k);
k >>= BITS;
}
for (; k != 0 && j < digits.size(); ++j)
{
k += digits[j];
digits[j] = static_cast<Digit>(k);
k >>= BITS;
}
if (k != 0)
{
digits.push_back(1);
}
return *this;
}
friend Unsigned operator- (Unsigned u, const Unsigned& v)
{
u -= v;
return u;
}
Unsigned& operator-= (const Unsigned& rhs)
{
if ((*this) < rhs)
{
throw std::underflow_error("Error: Unsigned::underflow");
}
size_t j = 0;
Wigit k = 0;
for (; j < rhs.digits.size(); ++j)
{
k = k + digits[j] - rhs.digits[j];
digits[j] = static_cast<Digit>(k);
k = ((k >> BITS) ? -1 : 0);
}
for (; k != 0 && j < digits.size(); ++j)
{
k += digits[j];
digits[j] = static_cast<Digit>(k);
k = ((k >> BITS) ? -1 : 0);
}
trim();
return *this;
}
friend Unsigned operator* (const Unsigned& u, const Unsigned& v)
{
const size_t m = u.digits.size();
const size_t n = v.digits.size();
Unsigned w;
w.digits.resize(m + n, 0);
for (size_t j = 0; j < n; ++j)
{
Wigit k = 0;
for (size_t i = 0; i < m; ++i)
{
k += static_cast<Wigit>(u.digits[i]) * v.digits[j] +
w.digits[i + j];
w.digits[i + j] = static_cast<Digit>(k);
k >>= BITS;
}
w.digits[j + m] = static_cast<Digit>(k);
}
w.trim();
return w;
}
Unsigned& operator*= (const Unsigned& rhs)
{
*this = (*this) * rhs;
return *this;
}
friend Unsigned operator/ (const Unsigned& u, const Unsigned& v)
{
Unsigned q, r;
u.divide(v, q, r);
return q;
}
Unsigned& operator/= (const Unsigned& rhs)
{
Unsigned r;
divide(rhs, *this, r);
return *this;
}
friend Unsigned operator% (const Unsigned& u, const Unsigned& v)
{
Unsigned q, r;
u.divide(v, q, r);
return r;
}
Unsigned& operator%= (const Unsigned& rhs)
{
Unsigned q;
divide(rhs, q, *this);
return *this;
}
void divide(Unsigned v, Unsigned& q, Unsigned& r) const
{
// Handle special cases (m < n).
if (v.digits.back() == 0)
{
throw std::overflow_error("Error: Unsigned::overflow");
}
r.digits = digits;
const size_t n = v.digits.size();
if (digits.size() < n)
{
q.digits.assign(1, 0);
return;
}
// Normalize divisor (v[n-1] >= BASE/2).
unsigned d = BITS;
for (Digit vn = v.digits.back(); vn != 0; vn >>= 1, --d);
v <<= d;
r <<= d;
const Digit vn = v.digits.back();
// Ensure first single-digit quotient (u[m-1] < v[n-1]).
r.digits.push_back(0);
const size_t m = r.digits.size();
q.digits.resize(m - n);
Unsigned w;
w.digits.resize(n + 1);
const Wigit MAX_DIGIT = (static_cast<Wigit>(1) << BITS) - 1;
for (size_t j = m - n; j-- != 0;)
{
// Estimate quotient digit.
Wigit qhat = std::min(MAX_DIGIT,
(static_cast<Wigit>(r.digits[j + n]) << BITS |
r.digits[j + n - 1]) / vn);
// Compute partial product (w = qhat * v).
Wigit k = 0;
for (size_t i = 0; i < n; ++i)
{
k += qhat * v.digits[i];
w.digits[i] = static_cast<Digit>(k);
k >>= BITS;
}
w.digits[n] = static_cast<Digit>(k);
// Check if qhat is too large (u - w < 0).
bool is_trial = true;
while (is_trial)
{
size_t i = n;
for (; i != 0 && r.digits[j + i] == w.digits[i]; --i);
if ((is_trial = (r.digits[j + i] < w.digits[i])))
{
// Adjust partial product (w -= v).
--qhat;
k = 0;
for (size_t i = 0; i < n; ++i)
{
k = k + w.digits[i] - v.digits[i];
w.digits[i] = static_cast<Digit>(k);
k = ((k >> BITS) ? -1 : 0);
}
w.digits[n] = static_cast<Digit>(k + w.digits[n]);
}
}
q.digits[j] = static_cast<Digit>(qhat);
// Compute partial remainder (u -= w).
k = 0;
for (size_t i = 0; i < n; ++i)
{
k = k + r.digits[j + i] - w.digits[i];
r.digits[j + i] = static_cast<Digit>(k);
k = ((k >> BITS) ? -1 : 0);
}
}
// Denormalize remainder.
q.trim();
r.digits.resize(n);
r >>= d;
}
friend Unsigned operator<< (Unsigned u, size_t v)
{
u <<= v;
return u;
}
Unsigned& operator<<= (size_t rhs)
{
if (digits.back() != 0 && rhs != 0)
{
const size_t n = rhs / BITS;
digits.insert(digits.begin(), n, 0);
rhs -= n * BITS;
Wigit k = 0;
for (size_t j = n; j < digits.size(); ++j)
{
k |= static_cast<Wigit>(digits[j]) << rhs;
digits[j] = static_cast<Digit>(k);
k >>= BITS;
}
if (k != 0)
{
digits.push_back(static_cast<Digit>(k));
}
}
return *this;
}
friend Unsigned operator>> (Unsigned u, size_t v)
{
u >>= v;
return u;
}
Unsigned& operator>>= (size_t rhs)
{
const size_t n = rhs / BITS;
if (n >= digits.size())
{
digits.assign(1, 0);
}
else
{
digits.erase(digits.begin(), digits.begin() + n);
rhs -= n * BITS;
Wigit k = 0;
for (size_t j = digits.size(); j-- != 0;)
{
k = k << BITS | digits[j];
digits[j] = static_cast<Digit>(k >> rhs);
k = static_cast<Digit>(k);
}
trim();
}
return *this;
}
friend Unsigned operator& (Unsigned u, const Unsigned& v)
{
u &= v;
return u;
}
Unsigned& operator&= (const Unsigned& rhs)
{
const size_t n = rhs.digits.size();
if (digits.size() > n)
{
digits.resize(n);
}
for (size_t j = 0; j < digits.size(); ++j)
{
digits[j] &= rhs.digits[j];
}
trim();
return *this;
}
Unsigned and_not(const Unsigned& v) const
{
Unsigned u(*this);
const size_t n = v.digits.size();
if (u.digits.size() > n)
{
u.digits.resize(n);
}
for (size_t j = 0; j < u.digits.size(); ++j)
{
u.digits[j] &= ~v.digits[j];
}
u.trim();
return u;
}
friend Unsigned operator^ (Unsigned u, const Unsigned& v)
{
u ^= v;
return u;
}
Unsigned& operator^= (const Unsigned& rhs)
{
const size_t n = rhs.digits.size();
if (digits.size() < n)
{
digits.resize(n, 0);
}
for (size_t j = 0; j < n; ++j)
{
digits[j] ^= rhs.digits[j];
}
trim();
return *this;
}
friend Unsigned operator| (Unsigned u, const Unsigned& v)
{
u |= v;
return u;
}
Unsigned& operator|= (const Unsigned& rhs)
{
const size_t n = rhs.digits.size();
if (digits.size() < n)
{
digits.resize(n, 0);
}
for (size_t j = 0; j < n; ++j)
{
digits[j] |= rhs.digits[j];
}
return *this;
}
friend bool operator< (const Unsigned& u, const Unsigned& v)
{
const size_t m = u.digits.size();
size_t n = v.digits.size();
if (m != n)
{
return (m < n);
}
for (--n; n != 0 && u.digits[n] == v.digits[n]; --n);
return (u.digits[n] < v.digits[n]);
}
friend bool operator> (const Unsigned& u, const Unsigned& v)
{
return (v < u);
}
friend bool operator<= (const Unsigned& u, const Unsigned& v)
{
return !(v < u);
}
friend bool operator>= (const Unsigned& u, const Unsigned& v)
{
return !(u < v);
}
friend bool operator== (const Unsigned& u, const Unsigned& v)
{
return (u.digits == v.digits);
}
friend bool operator!= (const Unsigned& u, const Unsigned& v)
{
return (u.digits != v.digits);
}
// Return 1 + floor(log2(u)), or 0 for u == 0.
int bits() const
{
size_t count = (digits.size() - 1) * BITS;
for (Digit u = digits.back(); u != 0; u >>= 1, ++count);
return static_cast<int>(count);
}
Digit to_uint() const
{
return digits[0];
}
std::string to_string() const
{
std::ostringstream oss;
Unsigned q(*this), r;
do
{
q.divide(10, q, r);
oss << r.digits[0];
} while (q.digits.back() != 0);
std::string s(oss.str());
std::reverse(s.begin(), s.end());
return s;
}
friend std::ostream& operator<< (std::ostream& os, const Unsigned& u)
{
os << u.to_string();
return os;
}
friend std::istream& operator>> (std::istream& is, Unsigned& u)
{
char digit = '\0';
is >> digit;
if (is.good() && std::isdigit(digit))
{
u = digit - '0';
while (std::isdigit(is.peek()))
{
is >> digit;
u = 10 * u + (digit - '0');
}
}
else
{
is.setstate(std::ios_base::failbit);
}
return is;
}
private:
std::vector<Digit> digits;
void trim()
{
while (digits.back() == 0 && digits.size() > 1)
{
digits.pop_back();
}
}
};
} // namespace math
#endif // MATH_UNSIGNED_H