From 185fcd058b3bccb7331eb8246055350939d1887a Mon Sep 17 00:00:00 2001 From: erick-alcachofa Date: Sun, 7 Sep 2025 00:14:38 -0600 Subject: [PATCH 1/2] Initial big int implementation Signed-off-by: erick-alcachofa --- common/bigint.hpp | 303 +++++++++++++++++++++++++++++++++++++++++++++ common/strings.hpp | 44 +++++++ 2 files changed, 347 insertions(+) create mode 100644 common/bigint.hpp create mode 100644 common/strings.hpp diff --git a/common/bigint.hpp b/common/bigint.hpp new file mode 100644 index 0000000..359df8f --- /dev/null +++ b/common/bigint.hpp @@ -0,0 +1,303 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct BigInt { + static constexpr uint32_t Base = 100; + + 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 &); + + static std::expected fromString(std::string_view value) { + BigInt bi = {}; + uint8_t cval = 0; + + 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; + } + + bool isZero() noexcept { return digits.empty(); } + + BigInt() : isNegative(false), digits() {} + + BigInt(BigInt &&) = default; + BigInt(const BigInt &) = default; + + BigInt &operator=(BigInt &&) = default; + BigInt &operator=(const BigInt &) = default; + + ~BigInt() = default; + + bool isNegative; + std::vector digits; + +private: + void trimLeadingZeroes() noexcept { + while (!this->digits.empty() && this->digits.back() == 0) { + this->digits.pop_back(); + } + } + + static BigInt addMagnitudes(const BigInt &lhs, const BigInt &rhs) { + BigInt res = {}; + + size_t it = 0; + uint8_t carry = 0; + + 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 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 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; + } +}; + +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; + } + + auto comp = BigInt::compareMagnitudes(lhs, rhs); + + if (comp == std::strong_ordering::greater) { + BigInt res = BigInt::subMagnitudes(lhs, rhs); + res.isNegative = lhs.isNegative; + return res; + } + + if (comp == std::strong_ordering::less) { + BigInt res = BigInt::subMagnitudes(rhs, lhs); + res.isNegative = rhs.isNegative; + return res; + } + + return BigInt{}; +} + +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; +} diff --git a/common/strings.hpp b/common/strings.hpp new file mode 100644 index 0000000..272fc29 --- /dev/null +++ b/common/strings.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +static inline bool isSpace(char ch) { + return std::isspace(ch); +} + +static std::string_view ltrim(std::string_view str, + std::function filter = isSpace) { + auto it = str.begin(); + + while (it != str.end()) { + if (!filter(*it)) { + break; + } + + ++it; + } + + return {it, str.end()}; +} + +static std::string_view rtrim(std::string_view str, + std::function filter = isSpace) { + auto it = str.rbegin(); + + while (it != str.rend()) { + if (!filter(*it)) { + break; + } + + ++it; + } + + return {str.begin(), it.base()}; +} + +static std::string_view trim(std::string_view str, + std::function filter = isSpace) { + return ltrim(rtrim(str, filter), filter); +} -- 2.52.0 From 32fe4db4f2c536315403a7b9e801b1a9e60a5957 Mon Sep 17 00:00:00 2001 From: erick-alcachofa Date: Tue, 9 Sep 2025 03:29:44 +0000 Subject: [PATCH 2/2] BigInt implementation Signed-off-by: erick-alcachofa --- CMakeLists.txt | 37 +++- common-tests/src/bigint.cpp | 229 ++++++++++++++++++++ common/bigint.cpp | 420 ++++++++++++++++++++++++++++++++++++ common/bigint.hpp | 307 ++++---------------------- common/types.hpp | 20 ++ 5 files changed, 749 insertions(+), 264 deletions(-) create mode 100644 common-tests/src/bigint.cpp create mode 100644 common/bigint.cpp create mode 100644 common/types.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b412f..ea5fbfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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( diff --git a/common-tests/src/bigint.cpp b/common-tests/src/bigint.cpp new file mode 100644 index 0000000..12d9d3f --- /dev/null +++ b/common-tests/src/bigint.cpp @@ -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 + +#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"); +} diff --git a/common/bigint.cpp b/common/bigint.cpp new file mode 100644 index 0000000..3e6c55e --- /dev/null +++ b/common/bigint.cpp @@ -0,0 +1,420 @@ +#include "bigint.hpp" + +#include +#include +#include +#include +#include + +std::expected +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 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::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::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::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); +} diff --git a/common/bigint.hpp b/common/bigint.hpp index 359df8f..bb6c57d 100644 --- a/common/bigint.hpp +++ b/common/bigint.hpp @@ -1,303 +1,86 @@ #pragma once -#include -#include #include -#include #include #include #include #include +#include struct BigInt { - static constexpr uint32_t Base = 100; + static constexpr u32 Base = 100; + + bool isNegative = false; + std::vector 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 fromString(std::string_view value) { - BigInt bi = {}; - uint8_t cval = 0; + static std::expected + 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 + 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 digits; + static std::expected divmod(const BigInt &lhs, + const BigInt &rhs); + + static std::expected quotient(const BigInt &lhs, + const BigInt &rhs); + + static std::expected 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 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); diff --git a/common/types.hpp b/common/types.hpp new file mode 100644 index 0000000..a1aa224 --- /dev/null +++ b/common/types.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +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; -- 2.52.0