Initial BigInt implementation #1
@ -11,6 +11,39 @@ project(
|
|||||||
|
|
||||||
find_package(GTest CONFIG REQUIRED)
|
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-*/")
|
file(GLOB SOURCES "problem-*/")
|
||||||
|
|
||||||
foreach(FILE IN LISTS SOURCES)
|
foreach(FILE IN LISTS SOURCES)
|
||||||
@ -37,24 +70,24 @@ foreach(FILE IN LISTS SOURCES)
|
|||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
"${PROBLEM}" PRIVATE
|
"${PROBLEM}" PRIVATE
|
||||||
|
common
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
"test-${PROBLEM}" PRIVATE
|
"test-${PROBLEM}" PRIVATE
|
||||||
|
common
|
||||||
GTest::gtest_main
|
GTest::gtest_main
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
"${PROBLEM}" PRIVATE
|
"${PROBLEM}" PRIVATE
|
||||||
"${FILE}/src"
|
"${FILE}/src"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
"test-${PROBLEM}" PRIVATE
|
"test-${PROBLEM}" PRIVATE
|
||||||
"${FILE}/src"
|
"${FILE}/src"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(
|
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);
|
||||||
|
}
|
||||||
86
common/bigint.hpp
Normal file
86
common/bigint.hpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <strings.hpp>
|
||||||
|
#include <types.hpp>
|
||||||
|
|
||||||
|
struct BigInt {
|
||||||
|
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 std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
static std::expected<BigInt, std::string>
|
||||||
|
fromString(std::string_view value);
|
||||||
|
|
||||||
|
static std::expected<BigInt, std::string>
|
||||||
|
fromInteger(std::integral auto value) {
|
||||||
|
auto str = std::to_string(value);
|
||||||
|
return BigInt::fromString(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString() const;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
inline u64 size() const noexcept {
|
||||||
|
return this->digits.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static BigInt addMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
static BigInt subMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
static BigInt multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
static std::strong_ordering compareMagnitudes(const BigInt &lhs,
|
||||||
|
const BigInt &rhs) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BigInt::DivisionResult {
|
||||||
|
BigInt quotient;
|
||||||
|
BigInt remainder;
|
||||||
|
};
|
||||||
|
|
||||||
|
BigInt operator+(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
BigInt operator-(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
BigInt operator*(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
|
||||||
|
bool operator==(const BigInt &lhs, const BigInt &rhs);
|
||||||
|
bool operator!=(const BigInt &lhs, const BigInt &rhs);
|
||||||
44
common/strings.hpp
Normal file
44
common/strings.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <functional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
static inline bool isSpace(char ch) {
|
||||||
|
return std::isspace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view ltrim(std::string_view str,
|
||||||
|
std::function<bool(char)> 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<bool(char)> 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<bool(char)> filter = isSpace) {
|
||||||
|
return ltrim(rtrim(str, filter), filter);
|
||||||
|
}
|
||||||
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