This is my infinite precision integer implemented in C++20. It supports negative numbers. I have implemented addition, subtraction, multiplication and binary integer division, which returns quotient and remainder, and all bitwise operators.
Addition is done by base-256 addition with carry, subtraction is done by subtraction with borrow in base-256. Multiplication is base-256 long multiplication, division is done by binary long division using bits.
I have implemented all comparison operators, all arithmetic operators, left-shift, right-shift, bitwise-AND, bitwise-OR, bitwise-XOR, bitwise-NOT, modulus, unary minus, and all in place modification operators, including (pre, post) * (decrement, increment).
And I have overloaded all the operators so that they work with built-in integral types as well. Left-shift and right-shift require the shift amount to be of unsigned integral type, all bitwise operators require both operands to be nonnegative.
I have fixed all the bugs I can find, all the tests I conducted give correct results, but there might be some bugs I haven't found, though there might be no bugs at all.
This is done as a programming challenge, it is for research purposes, though I do wish to make it as efficient as possible.
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
using bytes = std::vector<uint8_t>;
using std::string;
class BigInt {
public:
bytes value;
bool sign = false;
size_t length;
static const BigInt ZERO;
static const BigInt ONE;
static const BigInt MINUS_ONE;
static const BigInt TEN;
BigInt() : value{ 0 }, sign(false), length(1) {}
template<std::integral T>
BigInt(T n) {
using U = std::make_unsigned_t<T>;
U num;
if (std::is_signed_v<T> && n < 0) {
sign = true;
num = static_cast<U>(-n);
}
else {
sign = false;
num = n;
}
uint8_t size = sizeof(U);
value.resize(size);
std::memcpy(value.data(), &num, size);
trim();
}
explicit BigInt(const string& repr) {
sign = false;
value = { 0 };
length = 1;
size_t i = 0;
size_t size = repr.size();
if (size && repr[0] == '-') {
sign = true;
i = 1;
}
for (; i < size; ++i) {
if (!std::isdigit(repr[i]))
throw std::invalid_argument("Invalid decimal digit in input string");
*this = *this * BigInt::TEN + BigInt(repr[i] - '0');
}
trim();
}
constexpr inline explicit operator bool() const {
return std::any_of(value.begin(), value.end(), [](uint8_t b) { return b != 0; });
}
string to_string() const {
if (!*this) return "0";
BigInt n = *this;
n.sign = false;
string s;
while (n) {
auto [q, r] = n.divmod(BigInt::TEN);
s.push_back("0123456789"[r.value[0]]);
n = q;
}
if (sign) s.push_back('-');
std::reverse(s.begin(), s.end());
return s;
}
friend std::ostream& operator << (std::ostream& os, const BigInt& n) {
os << n.to_string();
return os;
}
const inline BigInt copy() const {
BigInt result;
result.value = value;
result.sign = sign;
result.length = length;
return result;
}
constexpr inline bool operator == (const BigInt& rhs) const { return sign == rhs.sign && value == rhs.value; }
constexpr inline bool operator != (const BigInt& rhs) const { return !(*this == rhs); }
constexpr inline bool operator < (const BigInt& rhs) const {
int8_t diff;
if (diff = int8_t(sign) - rhs.sign) return diff < 0;
return abs_compare(rhs) == -1 + sign * 2;
}
constexpr inline bool operator > (const BigInt& rhs) const {
int8_t diff;
if (diff = int8_t(sign) - rhs.sign) return diff > 0;
return -abs_compare(rhs) == -1 + sign * 2;
}
constexpr inline bool operator >= (const BigInt& rhs) const { return !(*this < rhs); }
constexpr inline bool operator <= (const BigInt& rhs) const { return !(*this > rhs); }
const inline BigInt operator + (const BigInt& rhs) const {
BigInt result;
if (sign == rhs.sign) {
length >= rhs.length ? abs_add(value, rhs.value, result) : abs_add(rhs.value, value, result);
result.sign = sign;
return result;
}
BigInt minuend, subtrahend;
if (sign) {
minuend.value = rhs.value;
subtrahend.value = value;
}
else {
minuend.value = value;
subtrahend.value = rhs.value;
}
return minuend - subtrahend;
}
const inline BigInt operator - (const BigInt& rhs) const {
BigInt result;
int8_t cmp = abs_compare(rhs);
if (sign != rhs.sign) {
cmp == 1 ? abs_add(value, rhs.value, result) : abs_add(rhs.value, value, result);
result.sign = sign;
return result;
}
if (!cmp) {
return BigInt::ZERO;
}
else if (cmp == 1) {
abs_sub(value, rhs.value, result);
result.sign = sign;
}
else {
abs_sub(rhs.value, value, result);
result.sign = !sign;
}
return result;
}
const inline BigInt operator << (const size_t& shift) const {
if (sign) throw std::invalid_argument("Left shift on negative number");
if (!shift) return *this;
BigInt result;
size_t byte_shift = shift >> 3;
uint8_t bit_shift = shift & 7;
size_t end = byte_shift + length;
if (!bit_shift) {
bytes number(end, 0);
std::memcpy(&number[byte_shift], &value[0], length);
result.value = number;
result.trim();
return result;
}
bytes number(end + 1, 0);
uint8_t hi_bit = 8 - bit_shift, lo_mask = (1 << hi_bit) - 1, hi = 0, byte;
for (size_t i = 0, j = byte_shift; i < length; i++, j++) {
byte = value[i];
number[j] = (byte & lo_mask) << bit_shift | hi;
hi = byte >> hi_bit;
}
if (hi) number[end] = hi;
result.value = number;
result.trim();
return result;
}
const inline BigInt operator >> (const size_t& shift) const {
if (sign) throw std::invalid_argument("Right shift on negative number");
if (!shift) return *this;
BigInt result;
size_t byte_shift = shift >> 3;
uint8_t bit_shift = shift & 7;
if (byte_shift >= length) return result;
size_t shorth = length - byte_shift;
bytes number(shorth, 0);
if (!bit_shift) {
std::memcpy(&number[0], &value[byte_shift], shorth);
}
else {
uint8_t hi_bit = 8 - bit_shift, hi = 0, byte;
for (size_t i = shorth, j = length - 1; i-- > 0; j--) {
byte = value[j];
number[i] = (byte >> bit_shift) | hi;
hi = byte << hi_bit;
}
}
result.value = number;
result.trim();
return result;
}
const inline BigInt operator & (const BigInt& rhs) const {
if (sign || rhs.sign)
throw std::invalid_argument("Bitwise AND on negative number");
size_t shorth = std::min(value.size(), rhs.value.size());
BigInt result;
bytes number(shorth);
for (size_t i = 0; i < shorth; i++) number[i] = value[i] & rhs.value[i];
result.value = number;
result.trim();
return result;
}
const inline BigInt operator | (const BigInt& rhs) const {
if (sign || rhs.sign)
throw std::invalid_argument("Bitwise OR on negative number");
BigInt result;
const bytes* longer_ptr = &value, * shorter_ptr = &rhs.value;
if (length < rhs.length) std::swap(longer_ptr, shorter_ptr);
const bytes longer = *longer_ptr, shorter = *shorter_ptr;
size_t longth = longer.size(), shorth = shorter.size();
bytes number(longth);
size_t i = 0;
for (; i < shorth; i++) number[i] = longer[i] | shorter[i];
std::memcpy(&number[shorth], &value[shorth], longth - shorth);
result.value = number;
result.trim();
return result;
}
const inline BigInt operator ^ (const BigInt& rhs) const {
if (sign || rhs.sign)
throw std::invalid_argument("Bitwise OR on negative number");
BigInt result;
const bytes* longer_ptr = &value, * shorter_ptr = &rhs.value;
if (length < rhs.length) std::swap(longer_ptr, shorter_ptr);
const bytes longer = *longer_ptr, shorter = *shorter_ptr;
size_t longth = longer.size(), shorth = shorter.size();
bytes number(longth);
size_t i = 0;
for (; i < shorth; i++) number[i] = longer[i] ^ shorter[i];
std::memcpy(&number[shorth], &value[shorth], longth - shorth);
result.value = number;
result.trim();
return result;
}
const inline BigInt operator ~ () const {
if (sign)
throw std::invalid_argument("Bitwise NOT on negative number");
BigInt result = *this;
for (auto& b : result.value) b ^= 255;
result.trim();
return result;
}
const inline BigInt operator * (const BigInt& rhs) const {
BigInt result;
size_t longth = rhs.length;
uint16_t carry, prod;
bytes number(length + longth, 0);
size_t k;
for (size_t i = 0; i < length; i++) {
carry = 0;
for (size_t j = 0; j < longth; j++) {
prod = uint16_t(value[i]) * rhs.value[j] + number[k = i + j] + carry;
number[k] = prod & 0xFF;
carry = prod >> 8;
}
number[i + longth] = carry;
}
result.sign = sign != rhs.sign;
result.value = number;
result.trim();
return result;
}
const inline BigInt operator / (const BigInt& rhs) const {
return divmod(rhs).first;
}
const inline BigInt operator % (const BigInt& rhs) const {
return divmod(rhs).second;
}
const inline BigInt operator - () const {
BigInt result = *this;
if (*this != BigInt::ZERO) result.sign = !sign;
return result;
}
const inline BigInt pow(const BigInt& rhs) const {
if (rhs.sign)
throw std::invalid_argument("Negative exponents are not supported");
bool odd = rhs.value[0] & 1;
BigInt product = 1;
BigInt square = this->copy();
BigInt exponent = rhs;
square.sign = false;
while (exponent) {
if (exponent.value[0] & 1) product = product * square;
square = square * square;
exponent = exponent >> 1;
}
product.sign = sign && odd;
return product;
}
template <std::integral T>
const inline BigInt pow(T exp) const {
if (exp < 0)
throw std::invalid_argument("Negative exponents are not supported");
bool odd = exp & 1;
BigInt product = 1;
BigInt square = this->copy();
square.sign = false;
while (exp) {
if (exp & 1) product = product * square;
square = square * square;
exp >>= 1;
}
product.sign = sign && odd;
return product;
}
friend inline BigInt pow(const BigInt& lhs, const BigInt& rhs) { return lhs.pow(rhs); }
template <std::integral T>
friend inline BigInt pow(const BigInt& lhs, T exp) { return lhs.pow(exp); }
inline BigInt& operator <<= (const size_t& rhs) { *this = *this << rhs; return *this; }
inline BigInt& operator >>= (const size_t& rhs) { *this = *this >> rhs; return *this; }
inline BigInt& operator += (const BigInt& rhs) { *this = *this + rhs; return *this; }
inline BigInt& operator -= (const BigInt& rhs) { *this = *this - rhs; return *this; }
inline BigInt& operator *= (const BigInt& rhs) { *this = *this * rhs; return *this; }
inline BigInt& operator /= (const BigInt& rhs) { *this = *this / rhs; return *this; }
inline BigInt& operator %= (const BigInt& rhs) { *this = *this % rhs; return *this; }
inline BigInt& operator &= (const BigInt& rhs) { *this = *this & rhs; return *this; }
inline BigInt& operator |= (const BigInt& rhs) { *this = *this | rhs; return *this; }
inline BigInt& operator ^= (const BigInt& rhs) { *this = *this ^ rhs; return *this; }
inline BigInt& operator++() { *this += BigInt::ONE; return *this; }
inline BigInt operator++(int) { BigInt temp = *this; ++(*this); return temp; }
inline BigInt& operator--() { *this -= BigInt::ONE; return *this; }
inline BigInt operator--(int) { BigInt temp = *this; --(*this); return temp; }
template <std::integral T>
inline BigInt operator + (T rhs) const { return *this + BigInt(rhs); }
template <std::integral T>
inline BigInt operator - (T rhs) const { return *this - BigInt(rhs); }
template <std::integral T>
inline BigInt operator * (T rhs) const { return *this * BigInt(rhs); }
template <std::integral T>
inline BigInt operator / (T rhs) const { return *this / BigInt(rhs); }
template <std::integral T>
inline BigInt operator % (T rhs) const { return *this % BigInt(rhs); }
template <std::integral T>
inline BigInt operator & (T rhs) const { return *this & BigInt(rhs); }
template <std::integral T>
inline BigInt operator | (T rhs) const { return *this | BigInt(rhs); }
template <std::integral T>
inline BigInt operator ^ (T rhs) const { return *this ^ BigInt(rhs); }
template <std::integral T>
inline BigInt& operator += (T rhs) { *this = *this + BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator -= (T rhs) { *this = *this - BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator *= (T rhs) { *this = *this * BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator /= (T rhs) { *this = *this / BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator %= (T rhs) { *this = *this % BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator &= (T rhs) { *this = *this & BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator |= (T rhs) { *this = *this | BigInt(rhs); return *this; }
template <std::integral T>
inline BigInt& operator ^= (T rhs) { *this = *this ^ BigInt(rhs); return *this; }
template <std::integral T>
friend inline BigInt operator + (T lhs, const BigInt& rhs) { return BigInt(lhs) + rhs; }
template <std::integral T>
friend inline BigInt operator - (T lhs, const BigInt& rhs) { return BigInt(lhs) - rhs; }
template <std::integral T>
friend inline BigInt operator * (T lhs, const BigInt& rhs) { return BigInt(lhs) * rhs; }
template <std::integral T>
friend inline BigInt operator / (T lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; }
template <std::integral T>
friend inline BigInt operator % (T lhs, const BigInt& rhs) { return BigInt(lhs) % rhs; }
template <std::integral T>
friend inline BigInt operator & (T lhs, const BigInt& rhs) { return BigInt(lhs) & rhs; }
template <std::integral T>
friend inline BigInt operator | (T lhs, const BigInt& rhs) { return BigInt(lhs) | rhs; }
template <std::integral T>
friend inline BigInt operator ^ (T lhs, const BigInt& rhs) { return BigInt(lhs) ^ rhs; }
template <std::integral T>
inline bool operator == (T rhs) const { return *this == BigInt(rhs); }
template <std::integral T>
friend inline bool operator == (T lhs, const BigInt& rhs) { return BigInt(lhs) == rhs; }
template <std::integral T>
inline bool operator != (T rhs) const { return *this != BigInt(rhs); }
template <std::integral T>
friend inline bool operator != (T lhs, const BigInt& rhs) { return BigInt(lhs) != rhs; }
template <std::integral T>
inline bool operator > (T rhs) const { return *this > BigInt(rhs); }
template <std::integral T>
friend inline bool operator > (T lhs, const BigInt& rhs) { return BigInt(lhs) > rhs; }
template <std::integral T>
inline bool operator >= (T rhs) const { return *this >= BigInt(rhs); }
template <std::integral T>
friend inline bool operator >= (T lhs, const BigInt& rhs) { return BigInt(lhs) >= rhs; }
template <std::integral T>
inline bool operator < (T rhs) const { return *this < BigInt(rhs); }
template <std::integral T>
friend inline bool operator < (T lhs, const BigInt& rhs) { return BigInt(lhs) < rhs; }
template <std::integral T>
inline bool operator <= (T rhs) const { return *this <= BigInt(rhs); }
template <std::integral T>
friend inline bool operator <= (T lhs, const BigInt& rhs) { return BigInt(lhs) <= rhs; }
private:
inline void trim() {
auto rit = std::find_if(value.rbegin(), value.rend(), [](uint8_t v) { return v != 0; });
value.erase(rit.base(), value.end());
if (value.empty()) {
value.push_back(0);
}
length = value.size();
}
constexpr inline int8_t abs_compare(const BigInt& rhs) const {
size_t longth = rhs.length;
if (length != longth) return length < longth ? -1 : 1;
int16_t diff;
for (size_t i = length; i-- > 0;) {
if (diff = int16_t(value[i]) - rhs.value[i]) {
return -1 + (diff > 0) * 2;
}
}
return 0;
}
inline void abs_add(const bytes& longer, const bytes& shorter, BigInt& result) const {
size_t longth = longer.size(), shorth = shorter.size();
bytes value(longth, 0);
uint16_t carry = 0;
size_t i = 0;
for (; i < shorth; i++) {
uint16_t sum = uint16_t(longer[i]) + shorter[i] + carry;
value[i] = sum & 255;
carry = sum >> 8;
}
for (; i < longth; i++) {
uint16_t sum = uint16_t(longer[i]) + carry;
value[i] = sum & 255;
carry = sum >> 8;
}
if (carry) value.push_back(carry);
result.value = value;
result.trim();
}
inline void abs_sub(const bytes& large, const bytes& small, BigInt& result) const {
size_t longth = large.size(), shorth = small.size();
bytes value(longth, 0);
uint16_t borrow = 0;
size_t i = 0;
for (; i < shorth; i++) {
int16_t diff = int16_t(large[i]) - small[i] - borrow;
borrow = diff < 0;
value[i] = static_cast<uint8_t>(diff + (borrow << 8));
}
for (; i < longth; i++) {
int16_t diff = int16_t(large[i]) - borrow;
borrow = diff < 0;
value[i] = static_cast<uint8_t>(diff + (borrow << 8));
}
result.value = value;
result.trim();
}
inline std::pair<BigInt, BigInt> divmod(const BigInt& rhs) const {
if (!rhs) throw std::invalid_argument("Division by zero");
int8_t cmp;
if ((cmp = abs_compare(rhs)) == -1) {
if (sign == rhs.sign) {
return { BigInt::ZERO, this->copy() };
}
BigInt minuend = rhs.copy(), subtrahend = this->copy(), modulus;
minuend.sign = subtrahend.sign = false;
modulus = minuend - subtrahend;
modulus.sign = rhs.sign;
return { BigInt::MINUS_ONE, modulus };
}
if (!cmp) return {BigInt::ONE, BigInt::ZERO};
bytes quotient(length);
BigInt remainder, dividend = this->copy(), divisor = rhs.copy();
dividend.sign = divisor.sign = false;
uint8_t byte;
for (size_t i = length; i-- > 0;) {
byte = dividend.value[i];
for (uint8_t j = 8; j-- > 0;) {
remainder = remainder << 1;
remainder.value[0] |= (byte >> j) & 1;
if (remainder.abs_compare(divisor) >= 0) {
remainder = remainder - divisor;
quotient[i] |= 1 << j;
}
}
}
BigInt numerator;
numerator.value = quotient;
numerator.trim();
if (sign != rhs.sign) {
numerator = numerator + BigInt::ONE;
numerator.sign = true;
remainder = rhs.sign ? remainder + rhs : rhs - remainder;
}
remainder.sign = rhs.sign;
return { numerator, remainder };
}
};
const BigInt BigInt::MINUS_ONE = BigInt(-1);
const BigInt BigInt::ZERO = BigInt(0);
const BigInt BigInt::ONE = BigInt(1);
const BigInt BigInt::TEN = BigInt(10);
int main() {
using std::cout;
BigInt a("12200160415121876738"), b(46368);
cout << "a: " << a << ", b: " << b << "\n";
cout << "a + b: " << a + b << "\n";
cout << "b + a: " << b + a << "\n";
cout << "a - b: " << a - b << "\n";
cout << "a * b: " << a * b << "\n";
cout << "b * a: " << b * a << "\n";
cout << "a / b: " << a / b << "\n";
cout << "b / a: " << b / a << "\n";
cout << "a > b: " << (a > b) << "\n";
cout << "a >= b: " << (a >= b) << "\n";
cout << "a < b: " << (a < b) << "\n";
cout << "a <= b: " << (a <= b) << "\n";
cout << "b > a: " << (b > a) << "\n";
cout << "b >= a: " << (b >= a) << "\n";
cout << "b < a: " << (b < a) << "\n";
cout << "b <= a: " << (b <= a) << "\n";
cout << "b - a: " << b - a << "\n";
cout << "b - (-a): " << b - (-a) << "\n";
cout << "(-b) - (-a): " << (-b) - (-a) << "\n";
cout << "(-b) - a: " << (-b) - a << "\n";
cout << "a - (-b): " << a - (-b) << "\n";
cout << "(-a) - (-b): " << (-a) - (-b) << "\n";
cout << "(-a) - b: " << (-a) - b << "\n";
cout << "b << 25: " << (b << 25) << "\n";
cout << "a >> 37: " << (a >> 37) << "\n";
cout << "pow(a, 9): " << pow(a, 9) << "\n";
cout << "pow(a, BigInt(9)): " << pow(a, BigInt(9)) << "\n";
}
Compiled with:
g++ -std=c++20 -march=native -O3 -flto -funroll-loops -fomit-frame-pointer -fstrict-aliasing -m64 -o "D:\CliExec\BigInt.exe" "D:\MyScript\BigInt.cpp"
Output:
PS C:\Users\xenig> D:\CliExec\BigInt.exe
a: 12200160415121876738, b: 46368
a + b: 12200160415121923106
b + a: 12200160415121923106
a - b: 12200160415121830370
a * b: 565697038128371180587584
b * a: 565697038128371180587584
a / b: 263115950981752
b / a: 0
a > b: 1
a >= b: 1
a < b: 0
a <= b: 0
b > a: 0
b >= a: 0
b < a: 1
b <= a: 1
b - a: -12200160415121830370
b - (-a): 12200160415121923106
(-b) - (-a): 12200160415121830370
(-b) - a: -12200160415121923106
a - (-b): 12200160415121923106
(-a) - (-b): -12200160415121830370
(-a) - b: -12200160415121923106
b << 25: 1555851902976
a >> 37: 88767850
pow(a, 9): 5988111380203749302281405938825593794758074921697104588146061703693668620265526517158513989265970683614253963908860200687001079849212560161352806929414971396504240019931648
pow(a, BigInt(9)): 5988111380203749302281405938825593794758074921697104588146061703693668620265526517158513989265970683614253963908860200687001079849212560161352806929414971396504240019931648
How can I improve the performance of my program? I want to reduce the execution time.
signas a bool is a bit confusing. Istrue"positive" or "negative"? \$\endgroup\$size_tneeds to have a specific width and a specific SIZE_MAX, which means any concrete C++ implementation has finite object sizes. \$\endgroup\$value,signandlengthpublic? That's how invariants get broken. \$\endgroup\$