zmodn

A simple C++ library for integers modulo N
git clone https://git.tronto.net/zmodn
Download | Log | Files | Refs | README

commit 38efc7d2df030d37e3f20efbce71fadad1294cef
parent 8ca27427af98bd3a82e8c5f4a199513295747930
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Tue, 14 Jan 2025 21:32:09 +0100

Added BigInt

Diffstat:
MREADME.md | 9++++++++-
Abigint.h | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest | 349++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mzmodn.h | 25++++++++++++++++++++-----
4 files changed, 621 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md @@ -1,10 +1,17 @@ # ZmodN - A simple library for modular arithmetic +## ZmodN + Usage: 1. `#include "zmodn.h"` in your project 2. enjoy -# Development +## BigInt + +This repo contains also a class for big integer. Performance are not great, +but you have a look at it for educational purposes. + +## Development Run `chmod +x test` and then `./test` to run tests. diff --git a/bigint.h b/bigint.h @@ -0,0 +1,245 @@ +#ifndef BIGUNSIGNED_H +#define BIGUNSIGNED_H + +#include <cstdint> +#include <iostream> +#include <string_view> + +constexpr uint64_t abs64(int64_t); +constexpr uint64_t pow10(uint64_t); + +// Big integer class for numbers of at most N decimal digits. +// The number E is used to tune the size of each digit, mostly for +// testing purposes. + +template<uint64_t N = 50, uint64_t E = 9> +requires (E < 10) +class BigInt { +public: + // The member variables sign and digits are declared public so that + // BigInt becomes a structural type and can be used in templates. + + static constexpr uint64_t M = pow10(E); + static constexpr uint64_t D = (N / E) + 1; + + bool sign; + uint64_t digits[D]; + + constexpr BigInt() : sign{true} { + std::fill(digits, digits+D, 0); + } + + constexpr BigInt(int64_t n) : sign{n >= 0} { + std::fill(digits, digits+D, 0); + digits[0] = abs64(n); + carryover(); + } + + constexpr BigInt(const std::string_view s) : sign{true} { + std::fill(digits, digits+D, 0); + if (s.size() == 0) + return; + for (int i = s.size()-1, j = 0; i >= 0; i--, j++) { + if (s[i] == '\'') + continue; + if (i == 0 && s[i] == '-') { + sign = false; + break; + } + digits[j/E] += (pow10(j % E)) + * static_cast<uint64_t>(s[i] - '0'); + } + } + + constexpr auto operator<=>(const BigInt& other) const { + if (sign != other.sign) + return sign <=> other.sign; + + for (int i = D-1; i >= 0; i--) + if (digits[i] != other.digits[i]) + return sign ? + digits[i] <=> other.digits[i] : + other.digits[i] <=> digits[i]; + + return 0 <=> 0; + } + + constexpr bool operator==(const BigInt& other) const = default; + + constexpr BigInt abs() const { + BigInt ret = *this; + ret.sign = true; + return ret; + } + + constexpr BigInt operator-() const { + if (*this == 0) + return 0; + BigInt ret = *this; + ret.sign = !ret.sign; + return ret; + } + + constexpr BigInt operator+(const BigInt& z) const { + if (sign && z.sign) + return positive_sum(*this, z); + else if (sign && !z.sign) + return positive_diff(*this, -z); + else if (!sign && z.sign) + return positive_diff(z, -*this); + else + return -positive_sum(-*this, -z); + } + + constexpr BigInt operator-(const BigInt& z) const { + return *this + (-z); + } + + constexpr BigInt operator*(const BigInt& z) const { + BigInt ret; + ret.sign = !(sign ^ z.sign); + for (int i = 0; i < D; i++) + for (int j = 0; i+j < D; j++) + ret.digits[i+j] += digits[i] * z.digits[j]; + ret.carryover(); + return ret; + } + + constexpr BigInt operator/(const BigInt& z) const { + auto [q, r] = euclidean_division(*this, z); + return q; + } + + constexpr BigInt operator%(const BigInt& z) const { + auto [q, r] = euclidean_division(*this, z); + return r; + } + + constexpr BigInt operator+=(const BigInt& z) { return *this = *this + z; } + constexpr BigInt operator++() { return *this += 1; } + constexpr BigInt operator-=(const BigInt& z) { return *this = *this - z; } + constexpr BigInt operator--() { return *this -= 1; } + constexpr BigInt operator*=(const BigInt& z) { return *this = *this * z; } + constexpr BigInt operator/=(const BigInt& z) { return *this = *this / z; } + constexpr BigInt operator%=(const BigInt& z) { return *this = *this % z; } + + friend std::ostream& operator<<(std::ostream& os, const BigInt<N, E>& z) { + bool fl = false; + if (!z.sign) + os << "-"; + for (int i = z.D-1; i >= 0; i--) + if (fl = fl || z.digits[i] != 0; fl) + os << z.digits[i]; + if (z == 0) + os << "0"; + return os; + } + +private: + constexpr void carryover() { + for (int i = 1; i < D; i++) { + auto c = digits[i-1] / M; + digits[i-1] -= c * M; + digits[i] += c; + } + } + + constexpr BigInt half() const { + BigInt ret; + uint64_t carry = 0; + for (int i = D-1; i >= 0; i--) { + ret.digits[i] += (digits[i] + M * carry) / 2; + carry = digits[i] % 2; + } + return ret; + } + + static constexpr BigInt powM(uint64_t e) { + BigInt ret; + ret.digits[e] = 1; + return ret; + } + + // Sum of non-negative integers + static constexpr BigInt positive_sum(const BigInt& x, const BigInt& y) { + BigInt ret; + for (int i = 0; i < D; i++) + ret.digits[i] = x.digits[i] + y.digits[i]; + ret.carryover(); + return ret; + } + + // Difference of non-negative integers (result may be negative) + static constexpr BigInt positive_diff(const BigInt& x, const BigInt& y) { + if (y > x) + return -positive_diff(y, x); + + BigInt ret; + uint64_t carry = 0; + for (int i = 0; i < D; i++) { + uint64_t oldcarry = carry; + if (x.digits[i] < y.digits[i] + oldcarry) { + ret.digits[i] = M; + carry = 1; + } else { + carry = 0; + } + ret.digits[i] += x.digits[i]; + ret.digits[i] -= y.digits[i] + oldcarry; + } + ret.carryover(); + return ret; + } + + // Division with remainder, UB if y == 0 + static constexpr std::pair<BigInt, BigInt> + euclidean_division(const BigInt& x, const BigInt& y) { + auto [q, r] = positive_div(x.abs(), y.abs()); + if (x.sign && y.sign) + return std::pair(q, r); + else if (x.sign && !y.sign) + return r == 0 ? std::pair(-q, 0) : std::pair(-q-1, y+r); + else if (!x.sign && y.sign) + return r == 0 ? std::pair(-q, r) : std::pair(-q-1, y-r); + else + return std::pair(q, -r); + } + + // Division with remainder of non-negative integers, UB if y == 0 + // This method is inefficient (O(log(x/y)) BigInt multiplications) + static constexpr std::pair<BigInt, BigInt> + positive_div(const BigInt& x, const BigInt& y) { + BigInt q = 0; + BigInt r = x; + + if (y > x) + return std::pair(q, r); + + BigInt lb = 0; + BigInt ub = x; + while (true) { + BigInt q = (ub + lb).half(); + BigInt r = x - y*q; + + if (r < 0) + ub = q; + else if (r >= y) + lb = q+1; + else + return std::pair(q, r); + } + } +}; + +constexpr uint64_t abs64(int64_t x) { + return static_cast<uint64_t>(x > 0 ? x : -x); +} + +constexpr uint64_t pow10(uint64_t e) { + if (e == 0) + return 1; + else + return 10 * pow10(e-1); +} + +#endif diff --git a/test b/test @@ -2,13 +2,16 @@ cc=${CC:-g++} bin="$(mktemp)" -${cc} -x c++ -std=c++20 -o "$bin" "$(realpath $0)" +${cc} -x c++ -std=c++20 -o "$bin" -g -O0 "$(realpath $0)" +echo "Running $bin" "$bin" exit 0 #endif #include "zmodn.h" +#include "bigint.h" + #include <concepts> #include <functional> #include <iostream> @@ -141,6 +144,350 @@ public: assert_equal(p, 92); } }, +{ + .name = "BigInt constructor zero", + .f = []() { + BigInt x; + BigInt y(0); + + assert_equal(x, y); + } +}, +{ + .name = "BigInt constructor one digit", + .f = []() { + BigInt x(12345); + BigInt y("12345"); + + assert_equal(x, y); + } +}, +{ + .name = "BigInt constructor many small digits", + .f = []() { + BigInt<20, 2> x(123456789); + BigInt<20, 2> y("123456789"); + + assert_equal(x, y); + } +}, +{ + .name = "BigInt constructor negative many small digits", + .f = []() { + BigInt<20, 2> x(-123456789); + BigInt<20, 2> y("-123456789"); + + assert_equal(x, y); + } +}, +{ + .name = "BigInt operator==", + .f = []() { + BigInt<20, 2> x(123456789); + BigInt<20, 2> y("123456789"); + BigInt<20, 2> z("12456789"); + + bool eq = (x == y); + bool diff = (x == z); + + assert_equal(eq, true); + assert_equal(diff, false); + }, +}, +{ + .name = "BigInt operator== negative", + .f = []() { + BigInt<20, 2> x("-123456789"); + BigInt<20, 2> z("123456789"); + + bool diff = (x == z); + + assert_equal(diff, false); + }, +}, +{ + .name = "BigInt operator!= true", + .f = []() { + BigInt<20, 2> x(12345678); + BigInt<20, 2> y("123456789"); + BigInt<20, 2> z("123456789"); + + bool diff = (x != y); + bool eq = (y != z); + + assert_equal(diff, true); + assert_equal(eq, false); + }, +}, +{ + .name = "BigInt operator< and operator>", + .f = []() { + BigInt<20, 2> x(7891); + BigInt<20, 2> y(7881); + + bool t = (y < x); + bool f = (x < y); + + assert_equal(t, true); + assert_equal(f, false); + } +}, +{ + .name = "BigInt operator< both negative", + .f = []() { + BigInt<20, 2> x(-7891); + BigInt<20, 2> y(-7881); + + bool cmp = (x < y); + + assert_equal(cmp, true); + } +}, +{ + .name = "BigInt operator< different sign", + .f = []() { + BigInt<20, 2> x(-7); + BigInt<20, 2> y(7); + + bool cmp = (x < y); + + assert_equal(cmp, true); + } +}, +{ + .name = "BigInt abs", + .f = []() { + BigInt<20, 2> x(-1234567); + BigInt<20, 2> y(7654321); + + assert_equal(x.abs(), BigInt<20, 2>(1234567)); + assert_equal(y.abs(), y); + } +}, +{ + .name = "BigInt opposite", + .f = []() { + BigInt<20, 2> x(-1234567); + BigInt<20, 2> y(7654321); + + assert_equal(-x, BigInt<20, 2>(1234567)); + assert_equal(-y, BigInt<20, 2>(-7654321)); + } +}, +{ + .name = "BigInt -0 == 0", + .f = []() { + BigInt z(0); + + assert_equal(-z, z); + } +}, +{ + .name = "BigInt sum", + .f = []() { + BigInt<20, 2> x("987608548588589"); + BigInt<20, 2> y("6793564545455289"); + BigInt<20, 2> z("7781173094043878"); + + assert_equal(x+y, z); + } +}, +{ + .name = "BigInt sum both negative", + .f = []() { + BigInt<20, 2> x("-987608548588589"); + BigInt<20, 2> y("-6793564545455289"); + BigInt<20, 2> z("-7781173094043878"); + + assert_equal(x+y, z); + } +}, +{ + .name = "BigInt sum negative + positive, result positive", + .f = []() { + BigInt<20, 2> x("-987608548588589"); + BigInt<20, 2> y("6793564545455289"); + BigInt<20, 2> z("5805955996866700"); + + assert_equal(x+y, z); + } +}, +{ + .name = "BigInt sum positive + negative, result negative", + .f = []() { + BigInt<20, 2> x("987608548588589"); + BigInt<20, 2> y("-6793564545455289"); + BigInt<20, 2> z("-5805955996866700"); + + assert_equal(x+y, z); + } +}, +{ + .name = "BigInt difference", + .f = []() { + BigInt<20, 2> x("2342442323434134"); + BigInt<20, 2> y("2524342523342342"); + BigInt<20, 2> z("-181900199908208"); + + assert_equal(x-y, z); + } +}, +{ + .name = "BigInt product", + .f = []() { + BigInt<100, 3> x("134142345244134"); + BigInt<100, 3> y("-56543047058245"); + BigInt<100, 3> z("-7584816939642416135042584830"); + + assert_equal(x*y, z); + } +}, +{ + .name = "BigInt operator+=", + .f = []() { + BigInt<20, 2> x("987608548588589"); + BigInt<20, 2> y("6793564545455289"); + BigInt<20, 2> z("7781173094043878"); + + x += y; + + assert_equal(x, z); + } +}, +{ + .name = "BigInt 14 / 3", + .f = []() { + BigInt x(14); + BigInt y(3); + + assert_equal(x / y, 4); + } +}, +{ + .name = "BigInt 14 % 3", + .f = []() { + BigInt x(14); + BigInt y(3); + + assert_equal(x % y, 2); + } +}, +{ + .name = "BigInt 14 / -3", + .f = []() { + BigInt x(14); + BigInt y(-3); + + assert_equal(x / y, -5); + } +}, +{ + .name = "BigInt 14 % -3", + .f = []() { + BigInt x(14); + BigInt y(-3); + + assert_equal(x % y, -1); + } +}, +{ + .name = "BigInt -14 / 3", + .f = []() { + BigInt x(-14); + BigInt y(3); + + assert_equal(x / y, -5); + } +}, +{ + .name = "BigInt -14 % 3", + .f = []() { + BigInt x(-14); + BigInt y(3); + + assert_equal(x % y, 1); + } +}, +{ + .name = "BigInt -14 / -3", + .f = []() { + BigInt x(-14); + BigInt y(-3); + + assert_equal(x / y, 4); + } +}, +{ + .name = "BigInt -14 % -3", + .f = []() { + BigInt x(-14); + BigInt y(-3); + + assert_equal(x % y, -2); + } +}, +{ + .name = "BigInt division large numbers, quotient = 0", + .f = []() { + BigInt<50, 3> x("4534435234134244242"); + BigInt<50, 3> y("7832478748237487343"); + + assert_equal(x / y, 0); + } +}, +{ + .name = "BigInt division large numbers", + .f = []() { + BigInt<50, 3> x("12344534435234134244242"); + BigInt<50, 3> y("7832478748237487343"); + BigInt<50, 3> z(1576); + + assert_equal(x / y, z); + } +}, +{ + .name = "BigInt modulo large numbers", + .f = []() { + BigInt<50, 3> x("12344534435234134244242"); + BigInt<50, 3> y("7832478748237487343"); + BigInt<50, 3> z("547928011854191674"); + + assert_equal(x % y, z); + } +}, +{ + .name = "Zmod with BigInt constructor", + .f = []() { + constexpr BigInt<50, 3> N("78923471"); + constexpr BigInt<50, 3> x("145452451"); + Zmod<N> xmodN(x); + + assert_equal(xmodN.toint(), x % N); + } +}, +{ + .name = "Zmod with BigInt big inverse", + .f = []() { + constexpr BigInt<50, 3> N("7520824651249795349285"); + constexpr BigInt<50, 3> x("234589234599896924596"); + constexpr BigInt<50, 3> expected("5901181270843786267351"); + Zmod<N> xmodN(x); + + auto inv = xmodN.inverse(); + + assert_equal(inv.has_value(), true); + assert_equal(inv.value().toint(), expected); + } +}, +/* +{ + .name = "This does not compile", + .f = []() { + constexpr double N = 1.2; + Zmod<N> x; + } +} +*/ }; int main() { diff --git a/zmodn.h b/zmodn.h @@ -7,16 +7,31 @@ #include <tuple> #include <type_traits> -template<typename INT> -requires std::is_integral_v<INT> -std::tuple<INT, INT, INT> extended_gcd(INT a, INT b) { +template<typename T> +concept Integer = requires(T a, T b, int i, std::ostream& os) { + {T(i)}; + + {a + b} -> std::same_as<T>; + {a - b} -> std::same_as<T>; + {a * b} -> std::same_as<T>; + {a / b} -> std::same_as<T>; + {a % b} -> std::same_as<T>; + + {a == b} -> std::same_as<bool>; + {a != b} -> std::same_as<bool>; + + {os << a} -> std::same_as<std::ostream&>; +}; + +template<Integer T> +std::tuple<T, T, T> extended_gcd(T a, T b) { if (b == 0) return {a, 1, 0}; auto [g, x, y] = extended_gcd(b, a%b); return {g, y, x - y*(a/b)}; } -template<auto N> -requires(N > 1) && std::is_integral_v<decltype(N)> +template<Integer auto N> +requires(N > 1) class Zmod { public: Zmod(decltype(N) z) : value{(z%N + N) % N} {}