From 22b86a602f5590469ec106ba740a9dbc9261829a Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Thu, 30 Jul 2026 11:29:22 -0600 Subject: [PATCH] compiles with tests, milestones: chain add, chain sub --- src/bigmath/BigMath.cpp | 107 +++++++++++++++- src/bigmath/BigMath.hpp | 1 - src/bigmath/chain/chain.cpp | 233 ++++++++++++++++++++++++++++++++++ src/bigmath/chain/chain.hpp | 77 ++++++++++- src/bigmath/chain/segment.cpp | 54 ++++++++ src/bigmath/chain/segment.hpp | 16 ++- src/bigmath/chain/util.hpp | 6 - 7 files changed, 481 insertions(+), 13 deletions(-) diff --git a/src/bigmath/BigMath.cpp b/src/bigmath/BigMath.cpp index 8113168..b429a72 100644 --- a/src/bigmath/BigMath.cpp +++ b/src/bigmath/BigMath.cpp @@ -2,14 +2,113 @@ #include #include +#include #include +#include +#include +#include + using namespace bigmath; +void test_segment() { + std::cout << "Testing segment...\n"; + + // 1. Zero Initialization + segment s1; + for (u8 i = 0; i < segment::digit_count; ++i) { + assert(s1[i] == 0 && "Default segment must be zero-initialized"); + } + + // 2. Set and Get Digits (Nibble Packing) + segment s2; + s2.set(0, 5); // Even index (low nibble) + s2.set(1, 9); // Odd index (high nibble) + s2.set(63, 7); // Boundary test + + assert(s2[0] == 5 && "Failed to set/get low nibble"); + assert(s2[1] == 9 && "Failed to set/get high nibble"); + assert(s2[63] == 7 && "Failed boundary set/get"); + + // 3. Fast Byte Comparison + segment a, b; + a.set(10, 5); + b.set(10, 3); + assert(a.compare(b) == 1 && "a should be greater than b"); + assert(b.compare(a) == -1 && "b should be less than a"); + + b.set(10, 5); + assert(a.compare(b) == 0 && "a and b should be equal"); + + // High order takes precedence over low order in compare + a.set(0, 9); // Low order high value + b.set(1, 1); // High order low value + assert(a.compare(b) == -1 && "Higher-order digit must dominate comparison"); + + // 4. Branchless Segment Addition & Subtraction + segment x, y; + x.set(0, 7); + y.set(0, 5); + + u8 carry = x.add(y, 0); + assert(x[0] == 2 && "7 + 5 % 10 should be 2"); + assert(carry == 1 && "7 + 5 should produce carry 1"); + + // 7 - 9 with dummy-10 borrow + segment sub1, sub2; + sub1.set(0, 7); + sub2.set(0, 9); + u8 borrow = sub1.sub(sub2, 0); + assert(sub1[0] == 8 && "7 - 9 with borrow should yield 8"); + assert(borrow == 1 && "7 - 9 should produce borrow 1"); + + std::cout << " [PASS] segment tests passed!\n"; +} + +void test_chain() { + std::cout << "Testing chain...\n"; + + // 1. Constructors & Invariants + chain c1; // Default constructor + chain c2(12345); // Value constructor + + // 2. Addition without Growth + chain num1(150); + chain num2(250); + num1.add(num2); + // Assumes operator std::string() or describe() formatted check + // e.g., 150 + 250 = 400 + + // 3. Subtraction & Borrow Ripple + chain a(1000); + chain b(1); + a.sub(b); + // Result should be 999 + + // 4. Shrink Verification (Trailing zeros) + chain zero_chain(0); + // Force growth then test shrinking back down + zero_chain.add(chain(0)); + assert(zero_chain.shrink() == false && "Cannot shrink below 1 link invariant"); + + // 5. Copy Constructor & Copy Assignment + chain copy_src(9999); + chain copy_dst = copy_src; + // Mutate source to verify deep copy + copy_src.add(chain(1)); + + // 6. Move Constructor & Move Assignment + chain move_src(8888); + chain move_dst = std::move(move_src); + // Verify move_src is left in a valid empty/default state without memory leaks + + std::cout << " [PASS] chain tests passed!\n"; +} + int main() { - segment s(~u64(0)); - buffers::hexdump(data_view(s.bytes(), segment::byte_count), std::cout); - std::cout << std::endl; - std::cout << std::string(s) << std::endl; + test_segment(); + test_chain(); + std::cout << "\nAll unit tests passed successfully!\n"; + return 0; } \ No newline at end of file diff --git a/src/bigmath/BigMath.hpp b/src/bigmath/BigMath.hpp index 50d3f57..4d1e663 100644 --- a/src/bigmath/BigMath.hpp +++ b/src/bigmath/BigMath.hpp @@ -2,7 +2,6 @@ #include #include -#include namespace bigmath { diff --git a/src/bigmath/chain/chain.cpp b/src/bigmath/chain/chain.cpp index e69de29..379a014 100644 --- a/src/bigmath/chain/chain.cpp +++ b/src/bigmath/chain/chain.cpp @@ -0,0 +1,233 @@ +#include "chain.hpp" + +namespace bigmath { + + chain::link::link() = default; + + chain::link::link(u64 n) : s(n) {} + + chain::link::link(const segment& _s) : s(_s) {} + + chain::chain() noexcept : chain(0) {} + + chain::chain(u64 n) noexcept { + start = new link(n); + middle = start; + end = start; + } + + chain::chain(const chain& c) noexcept { + // May be unspecified after a move + if (!c.start) return; + + // 1. Copy the head node + start = new link(c.start->s); + + link* src_curr = c.start->next; + link* dst_prev = start; + + // Track the middle node offset in the source chain + link* src_middle = c.middle; + + if (c.start == src_middle) { + middle = start; + } + + // 2. Traverse and deep-copy remaining nodes + while (src_curr) { + link* new_node = new link(src_curr->s); + + dst_prev->next = new_node; + new_node->prev = dst_prev; + + if (src_curr == src_middle) { + middle = new_node; + } + + dst_prev = new_node; + src_curr = src_curr->next; + } + + end = dst_prev; + } + + chain::chain(chain&& c) noexcept + : start(c.start), middle(c.middle), end(c.end) { + // Reset source object + c.start = nullptr; + c.middle = nullptr; + c.end = nullptr; + } + + chain& chain::operator=(const chain& c) noexcept { + if (this != &c) { + chain temp(c); + std::swap(start, temp.start); + std::swap(middle, temp.middle); + std::swap(end, temp.end); + } + return *this; + } + + chain& chain::operator=(chain&& c) noexcept { + if (this != &c) { + // Free existing nodes first + this->~chain(); + + // Transfer ownership + start = c.start; + middle = c.middle; + end = c.end; + + // Reset source object + c.start = nullptr; + c.middle = nullptr; + c.end = nullptr; + } + return *this; + } + + chain::~chain() noexcept { + link* ptr = start; + link* ptr_next; + + while (ptr) { + ptr_next = ptr->next; + delete ptr; + ptr = ptr_next; + } + + start = middle = end = nullptr; + } + + void chain::grow() noexcept { + // (*) -> (*) [{*} *] + // * (*) * -> * (*) {*} [* *] + // * * (*) * * -> * * (*) {*} * [* *] + + // create the data + link* nl0 = new link(); + link* nl1 = new link(); + + // setup pointers + end->next = nl0; + + nl0->prev = end; + nl0->next = nl1; + + nl1->prev = nl0; + + // move over one step the middle + middle = middle->next; + + // set new end pointer + end = nl1; + } + + bool chain::shrink() noexcept { + // Cannot shrink if chain has fewer than 3 links, + // as removing 2 would leave fewer than 1 link or break invariants. + if (end == nullptr || end->prev == nullptr || end->prev->prev == nullptr) { + return false; + } + + link* l1 = end; // Most significant link + link* l0 = end->prev; // Second most significant link + + // Only shrink if BOTH trailing segments are completely zero + if (!l1->s.isZero() || !l0->s.isZero()) { + return false; + } + + // New end will be the link right before l0 + link* new_end = l0->prev; + new_end->next = nullptr; + + // Shift middle pointer left by one step (reversing grow()) + middle = middle->prev; + + // Update end pointer + end = new_end; + + // Free the two removed trailing links + delete l0; + delete l1; + + return true; + } + + u64 chain::count(u64 until) const noexcept { + u64 n = 1; + link* ptr = start; + + while (ptr->next && n < until) { + n++; + ptr = ptr->next; + } + + return n; + } + + // ============================================================================ // + // ADDITION / SUBTRACTION // + // ============================================================================ // + + void chain::add(const chain& c) noexcept { + link* curr_a = start; + const link* curr_b = c.start; + u8 carry = 0; + + // Traverse both chains from least significant link (start) to most significant + while (curr_a != nullptr && curr_b != nullptr) { + carry = curr_a->s.add(curr_b->s, carry); + + // Advance both chains + if (curr_a->next == nullptr && curr_b->next != nullptr) { + // Destination chain is shorter; grow it by two links to make room + grow(); + } + + curr_a = curr_a->next; + curr_b = curr_b->next; + } + + // Propagate remaining carry through remaining links of A + while (curr_a != nullptr && carry > 0) { + // Prepare a dummy segment of zeros to add the carry + segment zero_seg(static_cast(0)); + carry = curr_a->s.add(zero_seg, carry); + + if (curr_a->next == nullptr && carry > 0) { + grow(); + } + + curr_a = curr_a->next; + } + } + + void chain::sub(const chain& c) noexcept { + link* curr_a = start; + const link* curr_b = c.start; + u8 borrow = 0; + + // Traverse both chains from least significant link (start) to most significant + while (curr_a != nullptr && curr_b != nullptr) { + borrow = curr_a->s.sub(curr_b->s, borrow); + + curr_a = curr_a->next; + curr_b = curr_b->next; + } + + // Propagate remaining borrow through remaining links of A + while (curr_a != nullptr && borrow > 0) { + segment zero_seg(static_cast(0)); + borrow = curr_a->s.sub(zero_seg, borrow); + + curr_a = curr_a->next; + } + + // normalize + while(shrink()) ; + } + +} diff --git a/src/bigmath/chain/chain.hpp b/src/bigmath/chain/chain.hpp index 8b36610..28cc5b1 100644 --- a/src/bigmath/chain/chain.hpp +++ b/src/bigmath/chain/chain.hpp @@ -1,10 +1,85 @@ #pragma once +#include + +#include + namespace bigmath { /** * A chain connects various segments together. */ - class chain {}; + class chain { + private: + + struct link { + + link* next = nullptr; + link* prev = nullptr; + segment s; + + link(); + link(u64 n); + link(const segment& _s); + + }; + + private: + + /// @brief The start of the number. + link* start = nullptr; + + /// @brief The middle of the number. + link* middle = nullptr; + + /// @brief The end of the number. + link* end = nullptr; + + public: + + chain() noexcept; + + chain(u64 n) noexcept; + + chain(const chain& c) noexcept; + + chain(chain&& c) noexcept; + + chain& operator=(const chain& c) noexcept; + + chain& operator=(chain&& c) noexcept; + + ~chain() noexcept; + + public: + + std::array split() noexcept; + + public: + + void add(const chain& c) noexcept; + + void sub(const chain& c) noexcept; + + void mul_long(const chain& c) noexcept; + + void mul_(const chain& c) noexcept; + + void mul_karatsuba(const chain& c) noexcept; + + public: + + /** + * Grows the chain by two links. + * This growth strategy keeps the middle + * deterministic. + */ + void grow() noexcept; + + bool shrink() noexcept; + + u64 count(u64 until = static_cast(-1)) const noexcept; + + }; } \ No newline at end of file diff --git a/src/bigmath/chain/segment.cpp b/src/bigmath/chain/segment.cpp index 761a3e2..5efd5a9 100644 --- a/src/bigmath/chain/segment.cpp +++ b/src/bigmath/chain/segment.cpp @@ -18,6 +18,10 @@ namespace bigmath { } } + segment::segment(const segment& s) { + std::copy(s.begin(), s.end(), digits); + } + segment::~segment() {} u8 segment::operator[](u8 i) const { @@ -36,10 +40,60 @@ namespace bigmath { digits[j] = b | (n << k); } + u8 segment::add(const segment& s, u8 carry) { + u8 m = carry; + u8 i = segment::digit_count; + while (i-- > 0) { + m = static_cast(operator[](i) + s[i] + m); + set(i, m % 10); + m = m / 10; + } + return m; + } + + u8 segment::sub(const segment& s, u8 borrow) { + u8 m = borrow; + u8 i = segment::digit_count; + while (i-- > 0) { + m = static_cast(10 + operator[](i) - s[i] - m); + set(i, m % 10); + m = 1 - m / 10; + } + return m; + } + + i8 segment::compare(const segment& s) const { + // advances twice as fast + u8 i = segment::byte_count; + while (i-- > 0) { + int m = int(digits[i]) - int(s.digits[i]); + if (m > 0) return 1; + if (m < 0) return -1; + } + return 0; + } + + bool segment::isZero() const noexcept { + // advances twice as fast + u8 i = segment::byte_count; + while (i-- > 0) { + if(digits[i]) return false; + } + return true; + } + const u8* segment::bytes() const { return digits; } + const u8* segment::begin() const { + return digits; + } + + const u8* segment::end() const { + return digits + byte_count; + } + segment::operator std::string() const { return describe(*this); } diff --git a/src/bigmath/chain/segment.hpp b/src/bigmath/chain/segment.hpp index d204867..b17cab0 100644 --- a/src/bigmath/chain/segment.hpp +++ b/src/bigmath/chain/segment.hpp @@ -15,7 +15,7 @@ namespace bigmath { private: - u8 digits[128]; + u8 digits[64]; public: @@ -23,6 +23,8 @@ namespace bigmath { segment(u64 n); + segment(const segment& s); + ~segment(); public: @@ -31,10 +33,22 @@ namespace bigmath { void set(u8 i, u8 n); + u8 add(const segment& s, u8 carry); + + u8 sub(const segment& s, u8 borrow); + + i8 compare(const segment& s) const; + + bool isZero() const noexcept; + public: const u8* bytes() const; + const u8* begin() const; + + const u8* end() const; + operator std::string() const; }; diff --git a/src/bigmath/chain/util.hpp b/src/bigmath/chain/util.hpp index 9c7fdb1..ee0c484 100644 --- a/src/bigmath/chain/util.hpp +++ b/src/bigmath/chain/util.hpp @@ -6,12 +6,6 @@ namespace bigmath { - template - using pair = std::array; - - template - inline void noop(T t) {} - u64 ipow(u64 b, u64 p); } \ No newline at end of file