421 lines
9.0 KiB
C++
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 "ient = 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);
|
|
}
|