Initial BigInt implementation #1
@ -11,6 +11,39 @@ project(
|
||||
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
|
||||
file(GLOB LIB_SRCS "common/**.cpp")
|
||||
|
||||
add_library(
|
||||
common STATIC
|
||||
${LIB_SRCS}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
common PUBLIC
|
||||
common/
|
||||
)
|
||||
|
||||
file(GLOB LIB_TESTS "common-tests/src/**.cpp")
|
||||
|
||||
foreach(FILE IN LISTS LIB_TESTS)
|
||||
cmake_path(GET FILE STEM TEST)
|
||||
|
||||
add_executable("common-tests-${TEST}" "${FILE}")
|
||||
|
||||
target_link_libraries(
|
||||
"common-tests-${TEST}" PRIVATE
|
||||
common
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
"common-tests-${TEST}" PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/common-tests/bin"
|
||||
RUNTIME_OUTPUT_NAME "test-${TEST}"
|
||||
SUFFIX ""
|
||||
)
|
||||
endforeach()
|
||||
|
||||
file(GLOB SOURCES "problem-*/")
|
||||
|
||||
foreach(FILE IN LISTS SOURCES)
|
||||
@ -37,24 +70,24 @@ foreach(FILE IN LISTS SOURCES)
|
||||
|
||||
target_link_libraries(
|
||||
"${PROBLEM}" PRIVATE
|
||||
common
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
"test-${PROBLEM}" PRIVATE
|
||||
common
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
"${PROBLEM}" PRIVATE
|
||||
"${FILE}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common"
|
||||
)
|
||||
|
||||
|
||||
target_include_directories(
|
||||
"test-${PROBLEM}" PRIVATE
|
||||
"${FILE}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common"
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
|
||||
229
common-tests/src/bigint.cpp
Normal file
229
common-tests/src/bigint.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Project Euler Solutions - Test File
|
||||
* ------------------------------------
|
||||
* Problem : Testing
|
||||
* URL : https://projecteuler.net/problem=test
|
||||
*
|
||||
* Author : erick-alcachofa
|
||||
* Created : Friday, September 05 2025
|
||||
*
|
||||
* Notes:
|
||||
* -
|
||||
*
|
||||
* License : GNU Affero General Public License v3.0 (AGPLv3)
|
||||
* https://www.gnu.org/licenses/agpl-3.0.html
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "bigint.hpp"
|
||||
|
||||
// Helper macro for creating BigInt from string, fails test on error
|
||||
#define BI(str) BigInt::fromString(str).value()
|
||||
|
||||
TEST(FromString, HandlesPositiveNumbers) {
|
||||
EXPECT_EQ(BI("12345").toString(), "12345");
|
||||
EXPECT_EQ(BI("0").toString(), "0");
|
||||
EXPECT_EQ(BI("99").toString(), "99");
|
||||
EXPECT_EQ(BI("100").toString(), "100");
|
||||
}
|
||||
|
||||
TEST(FromString, HandlesNegativeNumbers) {
|
||||
EXPECT_EQ(BI("-12345").toString(), "-12345");
|
||||
EXPECT_EQ(BI("-99").toString(), "-99");
|
||||
}
|
||||
|
||||
TEST(FromString, HandlesZero) {
|
||||
EXPECT_EQ(BI("0").toString(), "0");
|
||||
EXPECT_EQ(BI("-0").toString(), "0"); // Canonical zero
|
||||
}
|
||||
|
||||
TEST(FromString, HandlesWhitespaceAndSigns) {
|
||||
EXPECT_EQ(BI(" 123 ").toString(), "123");
|
||||
EXPECT_EQ(BI("+456").toString(), "456");
|
||||
EXPECT_EQ(BI(" -789 ").toString(), "-789");
|
||||
}
|
||||
|
||||
TEST(FromString, HandlesInvalidInput) {
|
||||
EXPECT_FALSE(BigInt::fromString("").has_value());
|
||||
EXPECT_FALSE(BigInt::fromString("-").has_value());
|
||||
EXPECT_FALSE(BigInt::fromString("12a34").has_value());
|
||||
EXPECT_FALSE(BigInt::fromString("abc").has_value());
|
||||
}
|
||||
|
||||
TEST(Addition, PositivePlusPositive) {
|
||||
EXPECT_EQ((BI("123") + BI("456")).toString(), "579");
|
||||
EXPECT_EQ((BI("99") + BI("1")).toString(), "100"); // With carry
|
||||
EXPECT_EQ((BI("9999") + BI("1")).toString(), "10000");
|
||||
}
|
||||
|
||||
TEST(Addition, NegativePlusNegative) {
|
||||
EXPECT_EQ((BI("-123") + BI("-456")).toString(), "-579");
|
||||
EXPECT_EQ((BI("-99") + BI("-1")).toString(), "-100");
|
||||
}
|
||||
|
||||
TEST(Addition, MixedSigns) {
|
||||
EXPECT_EQ((BI("500") + BI("-200")).toString(), "300");
|
||||
EXPECT_EQ((BI("200") + BI("-500")).toString(), "-300");
|
||||
EXPECT_EQ((BI("-500") + BI("200")).toString(), "-300");
|
||||
EXPECT_EQ((BI("-200") + BI("500")).toString(), "300");
|
||||
EXPECT_EQ((BI("200") + BI("-200")).toString(), "0");
|
||||
}
|
||||
|
||||
TEST(Subtraction, PositiveMinusPositive) {
|
||||
EXPECT_EQ((BI("500") - BI("200")).toString(), "300");
|
||||
EXPECT_EQ((BI("200") - BI("500")).toString(), "-300");
|
||||
EXPECT_EQ((BI("100") - BI("1")).toString(), "99"); // With borrow
|
||||
}
|
||||
|
||||
TEST(Subtraction, MixedSigns) {
|
||||
EXPECT_EQ((BI("200") - BI("-300")).toString(), "500");
|
||||
EXPECT_EQ((BI("-200") - BI("300")).toString(), "-500");
|
||||
EXPECT_EQ((BI("-500") - BI("-200")).toString(), "-300");
|
||||
}
|
||||
|
||||
TEST(Subtraction, ResultingInZero) {
|
||||
EXPECT_EQ((BI("200") - BI("200")).toString(), "0");
|
||||
EXPECT_EQ((BI("-200") - BI("-200")).toString(), "0");
|
||||
}
|
||||
|
||||
TEST(Multiplication, BasicCases) {
|
||||
EXPECT_EQ((BI("12") * BI("10")).toString(), "120");
|
||||
EXPECT_EQ((BI("123") * BI("456")).toString(), "56088");
|
||||
EXPECT_EQ((BI("99") * BI("99")).toString(), "9801");
|
||||
}
|
||||
|
||||
TEST(Multiplication, WithZero) {
|
||||
EXPECT_EQ((BI("123") * BI("0")).toString(), "0");
|
||||
EXPECT_EQ((BI("0") * BI("123")).toString(), "0");
|
||||
EXPECT_EQ((BI("-123") * BI("0")).toString(), "0");
|
||||
}
|
||||
|
||||
TEST(Multiplication, WithNegativeNumbers) {
|
||||
EXPECT_EQ((BI("-12") * BI("10")).toString(), "-120");
|
||||
EXPECT_EQ((BI("12") * BI("-10")).toString(), "-120");
|
||||
EXPECT_EQ((BI("-12") * BI("-10")).toString(), "120");
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, DivRemPositive) {
|
||||
auto result = BigInt::divmod(BI("12345"), BI("54"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "228");
|
||||
EXPECT_EQ(result->remainder.toString(), "33");
|
||||
|
||||
result = BigInt::divmod(BI("100"), BI("10"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "10");
|
||||
EXPECT_EQ(result->remainder.toString(), "0");
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, DivRemNegative) {
|
||||
// -12345 / 54 -> Q = -228, R = -33
|
||||
auto result = BigInt::divmod(BI("-12345"), BI("54"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "-228");
|
||||
EXPECT_EQ(result->remainder.toString(), "-33");
|
||||
|
||||
// 12345 / -54 -> Q = -228, R = 33
|
||||
result = BigInt::divmod(BI("12345"), BI("-54"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "-228");
|
||||
EXPECT_EQ(result->remainder.toString(), "33");
|
||||
|
||||
// -12345 / -54 -> Q = 228, R = -33
|
||||
result = BigInt::divmod(BI("-12345"), BI("-54"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "228");
|
||||
EXPECT_EQ(result->remainder.toString(), "-33");
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, QuotientOnly) {
|
||||
auto q = BigInt::quotient(BI("56088"), BI("123"));
|
||||
ASSERT_TRUE(q.has_value());
|
||||
EXPECT_EQ(q->toString(), "456");
|
||||
|
||||
q = BigInt::quotient(BI("-100"), BI("9"));
|
||||
ASSERT_TRUE(q.has_value());
|
||||
EXPECT_EQ(q->toString(), "-11");
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, RemainderOnly) {
|
||||
auto r_pos = BigInt::remainder(BI("100"), BI("9"));
|
||||
ASSERT_TRUE(r_pos.has_value());
|
||||
EXPECT_EQ(r_pos->toString(), "1");
|
||||
|
||||
auto r_neg = BigInt::remainder(BI("-100"), BI("9"));
|
||||
ASSERT_TRUE(r_neg.has_value());
|
||||
EXPECT_EQ(r_neg->toString(), "-1");
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, DivisionByZero) {
|
||||
EXPECT_FALSE(BigInt::divmod(BI("100"), BI("0")).has_value());
|
||||
EXPECT_FALSE(BigInt::quotient(BI("100"), BI("0")).has_value());
|
||||
EXPECT_FALSE(BigInt::remainder(BI("100"), BI("0")).has_value());
|
||||
}
|
||||
|
||||
TEST(DivisionAndRemainder, DividendSmallerThanDivisor) {
|
||||
auto result = BigInt::divmod(BI("10"), BI("100"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "0");
|
||||
EXPECT_EQ(result->remainder.toString(), "10");
|
||||
|
||||
result = BigInt::divmod(BI("-10"), BI("100"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "0");
|
||||
EXPECT_EQ(result->remainder.toString(), "-10");
|
||||
}
|
||||
|
||||
TEST(Comparison, AllOperators) {
|
||||
EXPECT_TRUE(BI("100") > BI("99"));
|
||||
EXPECT_TRUE(BI("99") < BI("100"));
|
||||
EXPECT_TRUE(BI("-99") > BI("-100"));
|
||||
EXPECT_TRUE(BI("-100") < BI("-99"));
|
||||
EXPECT_TRUE(BI("100") >= BI("100"));
|
||||
EXPECT_TRUE(BI("100") <= BI("100"));
|
||||
EXPECT_TRUE(BI("100") == BI("100"));
|
||||
EXPECT_TRUE(BI("100") != BI("101"));
|
||||
EXPECT_TRUE(BI("100") > BI("-100"));
|
||||
EXPECT_TRUE(BI("-100") < BI("100"));
|
||||
}
|
||||
|
||||
TEST(LargeValues, HandlesValuesExceedingULLONG_MAX) {
|
||||
// ULLONG_MAX is 18,446,744,073,709,551,615. These numbers are larger.
|
||||
const auto big_num1 = BI("20000000000000000000"); // 2 * 10^19
|
||||
const auto big_num2 = BI("10000000000000000000"); // 1 * 10^19
|
||||
const auto neg_big_num1 = BI("-20000000000000000000");
|
||||
|
||||
// Addition
|
||||
EXPECT_EQ((big_num1 + big_num2).toString(), "30000000000000000000");
|
||||
|
||||
// Subtraction
|
||||
EXPECT_EQ((big_num1 - big_num2).toString(), "10000000000000000000");
|
||||
|
||||
// Multiplication
|
||||
EXPECT_EQ((big_num1 * big_num2).toString(),
|
||||
"200000000000000000000000000000000000000");
|
||||
|
||||
// Division and Remainder
|
||||
auto result = BigInt::divmod(big_num1, big_num2);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(), "2");
|
||||
EXPECT_EQ(result->remainder.toString(), "0");
|
||||
|
||||
// Comparisons
|
||||
EXPECT_TRUE(big_num1 > big_num2);
|
||||
EXPECT_TRUE(big_num2 < big_num1);
|
||||
EXPECT_TRUE(big_num1 > neg_big_num1);
|
||||
EXPECT_TRUE(neg_big_num1 < big_num2);
|
||||
|
||||
EXPECT_EQ((BI("7284792756374829238474859") * BI("28347299847928374918237"))
|
||||
.toString(),
|
||||
"206504204594973924026042468998840507198705103583");
|
||||
|
||||
result = BigInt::divmod(
|
||||
BI("206504204594973924026042468998840507198705103583"), BI("38294791"));
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result->quotient.toString(),
|
||||
"5392488095704032541293735458664352214344");
|
||||
EXPECT_EQ(result->remainder.toString(), "14421479");
|
||||
}
|
||||
420
common/bigint.cpp
Normal file
420
common/bigint.cpp
Normal file
@ -0,0 +1,420 @@
|
||||
#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);
|
||||
}
|
||||
@ -1,303 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <strings.hpp>
|
||||
#include <types.hpp>
|
||||
|
||||
struct BigInt {
|
||||
static constexpr uint32_t Base = 100;
|
||||
static constexpr u32 Base = 100;
|
||||
|
||||
bool isNegative = false;
|
||||
std::vector<u8> digits;
|
||||
|
||||
struct DivisionResult;
|
||||
|
||||
friend BigInt operator+(const BigInt &, const BigInt &);
|
||||
friend BigInt operator-(const BigInt &, const BigInt &);
|
||||
friend BigInt operator*(const BigInt &, const BigInt &);
|
||||
friend BigInt operator/(const BigInt &, const BigInt &);
|
||||
friend std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
static std::expected<BigInt, std::string> fromString(std::string_view value) {
|
||||
BigInt bi = {};
|
||||
uint8_t cval = 0;
|
||||
static std::expected<BigInt, std::string>
|
||||
fromString(std::string_view value);
|
||||
|
||||
value = trim(value, isSpace);
|
||||
|
||||
if (value.empty()) {
|
||||
return std::unexpected{"Invalid value, no digits found"};
|
||||
}
|
||||
|
||||
if (value.front() == '-') {
|
||||
value.remove_prefix(1);
|
||||
bi.isNegative = true;
|
||||
} else if (value.front() == '+') {
|
||||
value.remove_prefix(1);
|
||||
}
|
||||
|
||||
if (value.empty()) {
|
||||
return std::unexpected{"Invalid value, no digits found"};
|
||||
}
|
||||
|
||||
if (std::ranges::any_of(
|
||||
value, [](uint8_t ch) -> bool { return !std::isdigit(ch); })) {
|
||||
return std::unexpected{"Invalid value, contains non-digits"};
|
||||
}
|
||||
|
||||
bi.digits.reserve((value.size() / 2) + 1);
|
||||
|
||||
auto it = value.begin();
|
||||
|
||||
if ((value.size() % 2) == 1) {
|
||||
bi.digits.push_back(uint8_t(*it++ - '0'));
|
||||
}
|
||||
|
||||
while (it != value.end()) {
|
||||
cval = uint8_t((*it++ - '0') * 10);
|
||||
cval += uint8_t(*it++ - '0');
|
||||
bi.digits.push_back(cval);
|
||||
}
|
||||
|
||||
std::ranges::reverse(bi.digits);
|
||||
|
||||
bi.trimLeadingZeroes();
|
||||
|
||||
if (bi.isZero()) {
|
||||
bi.isNegative = false;
|
||||
}
|
||||
|
||||
return bi;
|
||||
static std::expected<BigInt, std::string>
|
||||
fromInteger(std::integral auto value) {
|
||||
auto str = std::to_string(value);
|
||||
return BigInt::fromString(str);
|
||||
}
|
||||
|
||||
bool isZero() noexcept { return digits.empty(); }
|
||||
std::string toString() const;
|
||||
|
||||
BigInt() : isNegative(false), digits() {}
|
||||
inline bool isZero() const noexcept {
|
||||
return digits.empty();
|
||||
}
|
||||
|
||||
BigInt() = default;
|
||||
BigInt(BigInt &&) = default;
|
||||
BigInt(const BigInt &) = default;
|
||||
|
||||
BigInt &operator=(BigInt &&) = default;
|
||||
BigInt &operator=(const BigInt &) = default;
|
||||
|
||||
~BigInt() = default;
|
||||
|
||||
bool isNegative;
|
||||
std::vector<uint8_t> digits;
|
||||
static std::expected<DivisionResult, std::string> divmod(const BigInt &lhs,
|
||||
const BigInt &rhs);
|
||||
|
||||
static std::expected<BigInt, std::string> quotient(const BigInt &lhs,
|
||||
const BigInt &rhs);
|
||||
|
||||
static std::expected<BigInt, std::string> remainder(const BigInt &lhs,
|
||||
const BigInt &rhs);
|
||||
|
||||
private:
|
||||
void trimLeadingZeroes() noexcept {
|
||||
while (!this->digits.empty() && this->digits.back() == 0) {
|
||||
this->digits.pop_back();
|
||||
}
|
||||
void trimLeadingZeroes() noexcept;
|
||||
|
||||
inline u64 size() const noexcept {
|
||||
return this->digits.size();
|
||||
}
|
||||
|
||||
static BigInt addMagnitudes(const BigInt &lhs, const BigInt &rhs) {
|
||||
BigInt res = {};
|
||||
static BigInt addMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
size_t it = 0;
|
||||
uint8_t carry = 0;
|
||||
static BigInt subMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
auto minL = std::min(lhs.digits.size(), rhs.digits.size());
|
||||
auto maxL = std::max(lhs.digits.size(), rhs.digits.size());
|
||||
|
||||
const auto &maxC = [&] -> const BigInt & {
|
||||
if (lhs.digits.size() > rhs.digits.size()) {
|
||||
return lhs;
|
||||
}
|
||||
return rhs;
|
||||
}();
|
||||
|
||||
res.digits.reserve(maxL + 1);
|
||||
|
||||
for (it = 0; it < minL; ++it) {
|
||||
uint8_t csum = lhs.digits[it] + rhs.digits[it] + carry;
|
||||
|
||||
carry = csum / Base;
|
||||
csum %= Base;
|
||||
|
||||
res.digits.push_back(csum);
|
||||
}
|
||||
|
||||
for (; it < maxL; ++it) {
|
||||
uint8_t csum = maxC.digits[it] + carry;
|
||||
|
||||
carry = csum / Base;
|
||||
csum %= Base;
|
||||
|
||||
res.digits.push_back(csum);
|
||||
}
|
||||
|
||||
if (carry != 0) {
|
||||
res.digits.push_back(carry);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static BigInt subMagnitudes(const BigInt &lhs, const BigInt &rhs) {
|
||||
BigInt res = {};
|
||||
|
||||
size_t it = 0;
|
||||
uint8_t borrow = 0;
|
||||
|
||||
res.digits.reserve(lhs.digits.size() + 1);
|
||||
|
||||
for (it = 0; it < rhs.digits.size(); ++it) {
|
||||
int8_t csub = lhs.digits[it] - rhs.digits[it] - borrow;
|
||||
|
||||
if (csub < 0) {
|
||||
csub += Base;
|
||||
borrow = 1;
|
||||
} else {
|
||||
borrow = 0;
|
||||
}
|
||||
|
||||
res.digits.push_back(uint8_t(csub));
|
||||
}
|
||||
|
||||
for (; it < lhs.digits.size(); ++it) {
|
||||
int8_t csub = lhs.digits[it] - borrow;
|
||||
|
||||
if (csub < 0) {
|
||||
csub += Base;
|
||||
borrow = 1;
|
||||
} else {
|
||||
borrow = 0;
|
||||
}
|
||||
|
||||
res.digits.push_back(uint8_t(csub));
|
||||
}
|
||||
|
||||
res.trimLeadingZeroes();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static BigInt multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs) {
|
||||
const auto &maxC = [&] -> const BigInt & {
|
||||
if (lhs.digits.size() > rhs.digits.size()) {
|
||||
return lhs;
|
||||
}
|
||||
return rhs;
|
||||
}();
|
||||
|
||||
const auto &minC = [&] -> const BigInt & {
|
||||
if (lhs.digits.size() <= rhs.digits.size()) {
|
||||
return lhs;
|
||||
}
|
||||
return rhs;
|
||||
}();
|
||||
|
||||
std::vector<uint32_t> accs(lhs.digits.size() + rhs.digits.size() + 1, 0);
|
||||
|
||||
for (const auto &[bi, b] : std::views::enumerate(minC.digits)) {
|
||||
uint32_t carry = 0;
|
||||
|
||||
for (const auto &[ti, t] : std::views::enumerate(maxC.digits)) {
|
||||
uint32_t cmul = b * t + carry;
|
||||
|
||||
carry = cmul / Base;
|
||||
cmul %= Base;
|
||||
|
||||
accs[size_t(ti + bi)] += cmul;
|
||||
}
|
||||
|
||||
if (carry != 0) {
|
||||
accs[maxC.digits.size() + size_t(bi)] += carry;
|
||||
}
|
||||
}
|
||||
|
||||
BigInt res{};
|
||||
uint32_t carry = 0;
|
||||
|
||||
res.digits.reserve(accs.size() + 2);
|
||||
|
||||
for (auto &d : accs) {
|
||||
d += carry;
|
||||
carry = d / Base;
|
||||
res.digits.push_back(d % Base);
|
||||
}
|
||||
|
||||
while (carry != 0) {
|
||||
res.digits.push_back(carry % Base);
|
||||
carry /= Base;
|
||||
}
|
||||
|
||||
res.trimLeadingZeroes();
|
||||
|
||||
return res;
|
||||
}
|
||||
static BigInt multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
static std::strong_ordering compareMagnitudes(const BigInt &lhs,
|
||||
const BigInt &rhs) noexcept {
|
||||
namespace vw = std::views;
|
||||
if (lhs.digits.size() > rhs.digits.size()) {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
if (rhs.digits.size() > lhs.digits.size()) {
|
||||
return std::strong_ordering::less;
|
||||
}
|
||||
|
||||
for (const auto &[l, r] : vw::zip(lhs.digits, rhs.digits) | vw::reverse) {
|
||||
if (l > r) {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
|
||||
if (r > l) {
|
||||
return std::strong_ordering::less;
|
||||
}
|
||||
}
|
||||
|
||||
return std::strong_ordering::equal;
|
||||
}
|
||||
const BigInt &rhs) noexcept;
|
||||
};
|
||||
|
||||
inline BigInt operator+(const BigInt &lhs, const BigInt &rhs) {
|
||||
if (lhs.isNegative == rhs.isNegative) {
|
||||
BigInt res = BigInt::addMagnitudes(lhs, rhs);
|
||||
res.isNegative = lhs.isNegative;
|
||||
return res;
|
||||
}
|
||||
struct BigInt::DivisionResult {
|
||||
BigInt quotient;
|
||||
BigInt remainder;
|
||||
};
|
||||
|
||||
auto comp = BigInt::compareMagnitudes(lhs, rhs);
|
||||
BigInt operator+(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
if (comp == std::strong_ordering::greater) {
|
||||
BigInt res = BigInt::subMagnitudes(lhs, rhs);
|
||||
res.isNegative = lhs.isNegative;
|
||||
return res;
|
||||
}
|
||||
BigInt operator-(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
if (comp == std::strong_ordering::less) {
|
||||
BigInt res = BigInt::subMagnitudes(rhs, lhs);
|
||||
res.isNegative = rhs.isNegative;
|
||||
return res;
|
||||
}
|
||||
BigInt operator*(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
return BigInt{};
|
||||
}
|
||||
std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
inline BigInt operator-(const BigInt &lhs, const BigInt &rhs) {
|
||||
BigInt res = rhs;
|
||||
|
||||
if (!res.isZero()) {
|
||||
res.isNegative = !res.isNegative;
|
||||
}
|
||||
|
||||
return lhs + res;
|
||||
}
|
||||
|
||||
inline BigInt operator*(const BigInt &lhs, const BigInt &rhs) {
|
||||
BigInt res = BigInt::multiplyMagnitudes(lhs, rhs);
|
||||
res.isNegative = lhs.isNegative ^ rhs.isNegative;
|
||||
|
||||
if (res.isZero()) {
|
||||
res.isNegative = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
inline BigInt operator/(const BigInt &lhs, const BigInt &rhs) {
|
||||
BigInt res = {};
|
||||
return res;
|
||||
}
|
||||
bool operator==(const BigInt &lhs, const BigInt &rhs);
|
||||
bool operator!=(const BigInt &lhs, const BigInt &rhs);
|
||||
|
||||
20
common/types.hpp
Normal file
20
common/types.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ranges>
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
namespace rg = std::ranges;
|
||||
namespace vw = std::views;
|
||||
Loading…
x
Reference in New Issue
Block a user