erick-alcachofa 32fe4db4f2
BigInt implementation
Signed-off-by: erick-alcachofa <erick@artichoke.dev>
2025-09-09 03:29:44 +00:00

421 lines
9.0 KiB
C++

#include "bigint.hpp"
#include <algorithm>
#include <ranges>
#include <format>
#include <print>
#include <sstream>
std::expected<BigInt, std::string>
BigInt::fromString(std::string_view value) {
BigInt result;
value = trim(value, isSpace);
if (value.empty()) {
return std::unexpected{"Invalid value: input string is empty."};
}
if (value.front() == '-') {
result.isNegative = true;
value.remove_prefix(1);
} else if (value.front() == '+') {
value.remove_prefix(1);
}
if (value.empty()) {
return std::unexpected{"Invalid value: string contains only a sign."};
}
if (rg::any_of(value, [](u8 ch) { return !std::isdigit(ch); })) {
return std::unexpected{"Invalid value: contains non-digit characters."};
}
result.digits.reserve(value.size() / 2 + 1);
auto it = value.begin();
if ((value.size() % 2) == 1) {
result.digits.push_back(u8(*it++ - '0'));
}
while (it != value.end()) {
u8 cval = u8((*it++ - '0') * 10);
cval += u8((*it++ - '0'));
result.digits.push_back(cval);
}
rg::reverse(result.digits);
result.trimLeadingZeroes();
if (result.isZero()) {
result.isNegative = false;
}
return result;
}
std::string BigInt::toString() const {
if (this->isZero()) {
return "0";
}
std::stringstream ss;
if (isNegative) {
ss << "-";
}
ss << i32(digits.back());
for (const auto &d : this->digits | vw::reverse | vw::drop(1)) {
ss << std::format("{:02}", i32(d));
}
return ss.str();
}
void BigInt::trimLeadingZeroes() noexcept {
while (!digits.empty() && digits.back() == 0) {
digits.pop_back();
}
}
BigInt BigInt::addMagnitudes(const BigInt &lhs, const BigInt &rhs) {
BigInt result;
u16 carry = 0;
const auto &longer = [&] -> const BigInt & {
if (lhs.size() > rhs.size()) {
return lhs;
}
return rhs;
}();
const auto &shorter = [&] -> const BigInt & {
if (lhs.size() <= rhs.size()) {
return lhs;
}
return rhs;
}();
result.digits.reserve(longer.size() + 1);
for (u64 i = 0; i < shorter.size(); ++i) {
u16 sum = shorter.digits[i] + longer.digits[i] + carry;
carry = sum / Base;
sum %= Base;
result.digits.push_back(sum);
}
for (u64 i = shorter.size(); i < longer.size(); ++i) {
u16 sum = longer.digits[i] + carry;
carry = sum / Base;
sum %= Base;
result.digits.push_back(sum);
}
if (carry != 0) {
result.digits.push_back(carry);
}
return result;
}
BigInt BigInt::subMagnitudes(const BigInt &lhs, const BigInt &rhs) {
BigInt result;
i16 borrow = 0;
result.digits.reserve(lhs.size() + 1);
for (u64 i = 0; i < rhs.size(); ++i) {
i16 diff = lhs.digits[i] - rhs.digits[i] - borrow;
if (diff < 0) {
diff += Base;
borrow = 1;
} else {
borrow = 0;
}
result.digits.push_back(u8(diff));
}
for (u64 i = rhs.size(); i < lhs.size(); ++i) {
i16 diff = lhs.digits[i] - borrow;
if (diff < 0) {
diff += Base;
borrow = 1;
} else {
borrow = 0;
}
result.digits.push_back(u8(diff));
}
result.trimLeadingZeroes();
return result;
}
BigInt BigInt::multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs) {
if (lhs.isZero() || rhs.isZero()) {
return BigInt{};
}
std::vector<u32> accumulator(lhs.size() + rhs.size() + 1, 0);
const auto &longer = [&] -> const BigInt & {
if (lhs.size() > rhs.size()) {
return lhs;
}
return rhs;
}();
const auto &shorter = [&] -> const BigInt & {
if (lhs.size() <= rhs.size()) {
return lhs;
}
return rhs;
}();
for (const auto &[bottomIdx, bottom] : vw::enumerate(shorter.digits)) {
u32 carry = 0;
for (const auto &[topIdx, top] : vw::enumerate(longer.digits)) {
u32 prod = bottom * top + carry;
carry = prod / Base;
prod %= Base;
accumulator[bottomIdx + topIdx] += prod;
}
if (carry != 0) {
accumulator[longer.size() + bottomIdx] += carry;
}
}
BigInt result;
u32 carry = 0;
result.digits.reserve(accumulator.size() + 2);
for (auto &prod : accumulator) {
prod += carry;
carry = prod / Base;
prod %= Base;
result.digits.push_back(prod);
}
while (carry != 0) {
result.digits.push_back(carry % Base);
carry /= Base;
}
result.trimLeadingZeroes();
return result;
}
std::strong_ordering BigInt::compareMagnitudes(const BigInt &lhs,
const BigInt &rhs) noexcept {
if (lhs.size() > rhs.size())
return std::strong_ordering::greater;
if (lhs.size() < rhs.size())
return std::strong_ordering::less;
for (const auto &[left, right] :
vw::zip(lhs.digits, rhs.digits) | vw::reverse) {
if (left > right)
return std::strong_ordering::greater;
if (left < right)
return std::strong_ordering::less;
}
return std::strong_ordering::equal;
}
BigInt operator+(const BigInt &lhs, const BigInt &rhs) {
if (lhs.isNegative == rhs.isNegative) {
BigInt result = BigInt::addMagnitudes(lhs, rhs);
result.isNegative = lhs.isNegative;
return result;
}
auto comparison = BigInt::compareMagnitudes(lhs, rhs);
if (comparison == std::strong_ordering::greater) {
BigInt result = BigInt::subMagnitudes(lhs, rhs);
result.isNegative = lhs.isNegative;
return result;
}
if (comparison == std::strong_ordering::less) {
BigInt result = BigInt::subMagnitudes(rhs, lhs);
result.isNegative = rhs.isNegative;
return result;
}
return BigInt{};
}
BigInt operator-(const BigInt &lhs, const BigInt &rhs) {
BigInt negated_rhs = rhs;
if (!negated_rhs.isZero()) {
negated_rhs.isNegative = !rhs.isNegative;
}
return lhs + negated_rhs;
}
BigInt operator*(const BigInt &lhs, const BigInt &rhs) {
BigInt result = BigInt::multiplyMagnitudes(lhs, rhs);
result.isNegative = lhs.isNegative ^ rhs.isNegative;
if (result.isZero()) {
result.isNegative = false;
}
return result;
}
std::expected<BigInt::DivisionResult, std::string>
BigInt::divmod(const BigInt &lhs, const BigInt &rhs) {
DivisionResult result{};
if (rhs.isZero()) {
return std::unexpected{"Division by zero"};
}
if (lhs.isZero()) {
return result;
}
if (BigInt::compareMagnitudes(lhs, rhs) == std::strong_ordering::less) {
result.remainder = lhs;
return result;
}
auto &quotient = result.quotient;
auto &remainder = result.remainder;
remainder.digits.reserve(rhs.size());
for (const auto &digit : vw::reverse(lhs.digits)) {
remainder.digits.insert(remainder.digits.begin(), digit);
remainder.trimLeadingZeroes();
if (BigInt::compareMagnitudes(remainder, rhs) !=
std::strong_ordering::less) {
i16 quotDInt = 0;
BigInt quotD{};
for (i16 iter = Base - 1; iter >= 0; --iter) {
auto bigIter = BigInt::fromInteger(iter);
if (!bigIter) {
return std::unexpected{bigIter.error()};
}
auto mult = BigInt::multiplyMagnitudes(rhs, bigIter.value());
if (BigInt::compareMagnitudes(mult, remainder) !=
std::strong_ordering::greater) {
quotDInt = iter;
quotD = std::move(mult);
break;
}
}
remainder = BigInt::subMagnitudes(remainder, quotD);
quotient.digits.push_back(quotDInt);
}
}
rg::reverse(quotient.digits);
quotient.trimLeadingZeroes();
if (!quotient.isZero()) {
quotient.isNegative = lhs.isNegative ^ rhs.isNegative;
}
if (!remainder.isZero()) {
remainder.isNegative = lhs.isNegative;
}
return result;
}
std::expected<BigInt, std::string> BigInt::quotient(const BigInt &lhs,
const BigInt &rhs) {
auto result = BigInt::divmod(lhs, rhs);
if (result) {
return result->quotient;
}
return std::unexpected(result.error());
}
std::expected<BigInt, std::string> BigInt::remainder(const BigInt &lhs,
const BigInt &rhs) {
auto result = BigInt::divmod(lhs, rhs);
if (result) {
return result->remainder;
}
return std::unexpected(result.error());
}
std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs) {
if (lhs.isZero() && rhs.isZero()) {
return std::strong_ordering::equal;
}
if (lhs.isNegative && !rhs.isNegative) {
return std::strong_ordering::less;
}
if (!lhs.isNegative && rhs.isNegative) {
return std::strong_ordering::greater;
}
auto magnitude_comparison = BigInt::compareMagnitudes(lhs, rhs);
// If both are negative, the ordering is the reverse of their magnitude.
// e.g., magnitude of -100 > magnitude of -90, but -100 < -90.
if (lhs.isNegative) {
if (magnitude_comparison == std::strong_ordering::less)
return std::strong_ordering::greater;
if (magnitude_comparison == std::strong_ordering::greater)
return std::strong_ordering::less;
return std::strong_ordering::equal;
}
return magnitude_comparison;
}
bool operator==(const BigInt &lhs, const BigInt &rhs) {
return (lhs <=> rhs) == std::strong_ordering::equal;
}
bool operator!=(const BigInt &lhs, const BigInt &rhs) {
return !(lhs == rhs);
}