aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano@tronto.net>2025-01-14 21:32:09 +0100
committerSebastiano Tronto <sebastiano@tronto.net>2025-01-14 21:32:09 +0100
commit38efc7d2df030d37e3f20efbce71fadad1294cef (patch)
treebbe8a7bbb6acbeb2b7ed54c189d1e719a7459427
parent8ca27427af98bd3a82e8c5f4a199513295747930 (diff)
downloadzmodn-38efc7d2df030d37e3f20efbce71fadad1294cef.tar.gz
zmodn-38efc7d2df030d37e3f20efbce71fadad1294cef.zip
Added BigInt
-rw-r--r--README.md9
-rw-r--r--bigint.h245
-rwxr-xr-xtest349
-rw-r--r--zmodn.h25
4 files changed, 621 insertions, 7 deletions
diff --git a/README.md b/README.md
index 900a296..043b626 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,17 @@
1# ZmodN - A simple library for modular arithmetic 1# ZmodN - A simple library for modular arithmetic
2 2
3## ZmodN
4
3Usage: 5Usage:
4 6
51. `#include "zmodn.h"` in your project 71. `#include "zmodn.h"` in your project
62. enjoy 82. enjoy
7 9
8# Development 10## BigInt
11
12This repo contains also a class for big integer. Performance are not great,
13but you have a look at it for educational purposes.
14
15## Development
9 16
10Run `chmod +x test` and then `./test` to run tests. 17Run `chmod +x test` and then `./test` to run tests.
diff --git a/bigint.h b/bigint.h
new file mode 100644
index 0000000..a8199b9
--- /dev/null
+++ b/bigint.h
@@ -0,0 +1,245 @@
1#ifndef BIGUNSIGNED_H
2#define BIGUNSIGNED_H
3
4#include <cstdint>
5#include <iostream>
6#include <string_view>
7
8constexpr uint64_t abs64(int64_t);
9constexpr uint64_t pow10(uint64_t);
10
11// Big integer class for numbers of at most N decimal digits.
12// The number E is used to tune the size of each digit, mostly for
13// testing purposes.
14
15template<uint64_t N = 50, uint64_t E = 9>
16requires (E < 10)
17class BigInt {
18public:
19 // The member variables sign and digits are declared public so that
20 // BigInt becomes a structural type and can be used in templates.
21
22 static constexpr uint64_t M = pow10(E);
23 static constexpr uint64_t D = (N / E) + 1;
24
25 bool sign;
26 uint64_t digits[D];
27
28 constexpr BigInt() : sign{true} {
29 std::fill(digits, digits+D, 0);
30 }
31
32 constexpr BigInt(int64_t n) : sign{n >= 0} {
33 std::fill(digits, digits+D, 0);
34 digits[0] = abs64(n);
35 carryover();
36 }
37
38 constexpr BigInt(const std::string_view s) : sign{true} {
39 std::fill(digits, digits+D, 0);
40 if (s.size() == 0)
41 return;
42 for (int i = s.size()-1, j = 0; i >= 0; i--, j++) {
43 if (s[i] == '\'')
44 continue;
45 if (i == 0 && s[i] == '-') {
46 sign = false;
47 break;
48 }
49 digits[j/E] += (pow10(j % E))
50 * static_cast<uint64_t>(s[i] - '0');
51 }
52 }
53
54 constexpr auto operator<=>(const BigInt& other) const {
55 if (sign != other.sign)
56 return sign <=> other.sign;
57
58 for (int i = D-1; i >= 0; i--)
59 if (digits[i] != other.digits[i])
60 return sign ?
61 digits[i] <=> other.digits[i] :
62 other.digits[i] <=> digits[i];
63
64 return 0 <=> 0;
65 }
66
67 constexpr bool operator==(const BigInt& other) const = default;
68
69 constexpr BigInt abs() const {
70 BigInt ret = *this;
71 ret.sign = true;
72 return ret;
73 }
74
75 constexpr BigInt operator-() const {
76 if (*this == 0)
77 return 0;
78 BigInt ret = *this;
79 ret.sign = !ret.sign;
80 return ret;
81 }
82
83 constexpr BigInt operator+(const BigInt& z) const {
84 if (sign && z.sign)
85 return positive_sum(*this, z);
86 else if (sign && !z.sign)
87 return positive_diff(*this, -z);
88 else if (!sign && z.sign)
89 return positive_diff(z, -*this);
90 else
91 return -positive_sum(-*this, -z);
92 }
93
94 constexpr BigInt operator-(const BigInt& z) const {
95 return *this + (-z);
96 }
97
98 constexpr BigInt operator*(const BigInt& z) const {
99 BigInt ret;
100 ret.sign = !(sign ^ z.sign);
101 for (int i = 0; i < D; i++)
102 for (int j = 0; i+j < D; j++)
103 ret.digits[i+j] += digits[i] * z.digits[j];
104 ret.carryover();
105 return ret;
106 }
107
108 constexpr BigInt operator/(const BigInt& z) const {
109 auto [q, r] = euclidean_division(*this, z);
110 return q;
111 }
112
113 constexpr BigInt operator%(const BigInt& z) const {
114 auto [q, r] = euclidean_division(*this, z);
115 return r;
116 }
117
118 constexpr BigInt operator+=(const BigInt& z) { return *this = *this + z; }
119 constexpr BigInt operator++() { return *this += 1; }
120 constexpr BigInt operator-=(const BigInt& z) { return *this = *this - z; }
121 constexpr BigInt operator--() { return *this -= 1; }
122 constexpr BigInt operator*=(const BigInt& z) { return *this = *this * z; }
123 constexpr BigInt operator/=(const BigInt& z) { return *this = *this / z; }
124 constexpr BigInt operator%=(const BigInt& z) { return *this = *this % z; }
125
126 friend std::ostream& operator<<(std::ostream& os, const BigInt<N, E>& z) {
127 bool fl = false;
128 if (!z.sign)
129 os << "-";
130 for (int i = z.D-1; i >= 0; i--)
131 if (fl = fl || z.digits[i] != 0; fl)
132 os << z.digits[i];
133 if (z == 0)
134 os << "0";
135 return os;
136 }
137
138private:
139 constexpr void carryover() {
140 for (int i = 1; i < D; i++) {
141 auto c = digits[i-1] / M;
142 digits[i-1] -= c * M;
143 digits[i] += c;
144 }
145 }
146
147 constexpr BigInt half() const {
148 BigInt ret;
149 uint64_t carry = 0;
150 for (int i = D-1; i >= 0; i--) {
151 ret.digits[i] += (digits[i] + M * carry) / 2;
152 carry = digits[i] % 2;
153 }
154 return ret;
155 }
156
157 static constexpr BigInt powM(uint64_t e) {
158 BigInt ret;
159 ret.digits[e] = 1;
160 return ret;
161 }
162
163 // Sum of non-negative integers
164 static constexpr BigInt positive_sum(const BigInt& x, const BigInt& y) {
165 BigInt ret;
166 for (int i = 0; i < D; i++)
167 ret.digits[i] = x.digits[i] + y.digits[i];
168 ret.carryover();
169 return ret;
170 }
171
172 // Difference of non-negative integers (result may be negative)
173 static constexpr BigInt positive_diff(const BigInt& x, const BigInt& y) {
174 if (y > x)
175 return -positive_diff(y, x);
176
177 BigInt ret;
178 uint64_t carry = 0;
179 for (int i = 0; i < D; i++) {
180 uint64_t oldcarry = carry;
181 if (x.digits[i] < y.digits[i] + oldcarry) {
182 ret.digits[i] = M;
183 carry = 1;
184 } else {
185 carry = 0;
186 }
187 ret.digits[i] += x.digits[i];
188 ret.digits[i] -= y.digits[i] + oldcarry;
189 }
190 ret.carryover();
191 return ret;
192 }
193
194 // Division with remainder, UB if y == 0
195 static constexpr std::pair<BigInt, BigInt>
196 euclidean_division(const BigInt& x, const BigInt& y) {
197 auto [q, r] = positive_div(x.abs(), y.abs());
198 if (x.sign && y.sign)
199 return std::pair(q, r);
200 else if (x.sign && !y.sign)
201 return r == 0 ? std::pair(-q, 0) : std::pair(-q-1, y+r);
202 else if (!x.sign && y.sign)
203 return r == 0 ? std::pair(-q, r) : std::pair(-q-1, y-r);
204 else
205 return std::pair(q, -r);
206 }
207
208 // Division with remainder of non-negative integers, UB if y == 0
209 // This method is inefficient (O(log(x/y)) BigInt multiplications)
210 static constexpr std::pair<BigInt, BigInt>
211 positive_div(const BigInt& x, const BigInt& y) {
212 BigInt q = 0;
213 BigInt r = x;
214
215 if (y > x)
216 return std::pair(q, r);
217
218 BigInt lb = 0;
219 BigInt ub = x;
220 while (true) {
221 BigInt q = (ub + lb).half();
222 BigInt r = x - y*q;
223
224 if (r < 0)
225 ub = q;
226 else if (r >= y)
227 lb = q+1;
228 else
229 return std::pair(q, r);
230 }
231 }
232};
233
234constexpr uint64_t abs64(int64_t x) {
235 return static_cast<uint64_t>(x > 0 ? x : -x);
236}
237
238constexpr uint64_t pow10(uint64_t e) {
239 if (e == 0)
240 return 1;
241 else
242 return 10 * pow10(e-1);
243}
244
245#endif
diff --git a/test b/test
index 4b426c2..46ed7c3 100755
--- a/test
+++ b/test
@@ -2,13 +2,16 @@
2 2
3cc=${CC:-g++} 3cc=${CC:-g++}
4bin="$(mktemp)" 4bin="$(mktemp)"
5${cc} -x c++ -std=c++20 -o "$bin" "$(realpath $0)" 5${cc} -x c++ -std=c++20 -o "$bin" -g -O0 "$(realpath $0)"
6echo "Running $bin"
6"$bin" 7"$bin"
7 8
8exit 0 9exit 0
9#endif 10#endif
10 11
11#include "zmodn.h" 12#include "zmodn.h"
13#include "bigint.h"
14
12#include <concepts> 15#include <concepts>
13#include <functional> 16#include <functional>
14#include <iostream> 17#include <iostream>
@@ -141,6 +144,350 @@ public:
141 assert_equal(p, 92); 144 assert_equal(p, 92);
142 } 145 }
143}, 146},
147{
148 .name = "BigInt constructor zero",
149 .f = []() {
150 BigInt x;
151 BigInt y(0);
152
153 assert_equal(x, y);
154 }
155},
156{
157 .name = "BigInt constructor one digit",
158 .f = []() {
159 BigInt x(12345);
160 BigInt y("12345");
161
162 assert_equal(x, y);
163 }
164},
165{
166 .name = "BigInt constructor many small digits",
167 .f = []() {
168 BigInt<20, 2> x(123456789);
169 BigInt<20, 2> y("123456789");
170
171 assert_equal(x, y);
172 }
173},
174{
175 .name = "BigInt constructor negative many small digits",
176 .f = []() {
177 BigInt<20, 2> x(-123456789);
178 BigInt<20, 2> y("-123456789");
179
180 assert_equal(x, y);
181 }
182},
183{
184 .name = "BigInt operator==",
185 .f = []() {
186 BigInt<20, 2> x(123456789);
187 BigInt<20, 2> y("123456789");
188 BigInt<20, 2> z("12456789");
189
190 bool eq = (x == y);
191 bool diff = (x == z);
192
193 assert_equal(eq, true);
194 assert_equal(diff, false);
195 },
196},
197{
198 .name = "BigInt operator== negative",
199 .f = []() {
200 BigInt<20, 2> x("-123456789");
201 BigInt<20, 2> z("123456789");
202
203 bool diff = (x == z);
204
205 assert_equal(diff, false);
206 },
207},
208{
209 .name = "BigInt operator!= true",
210 .f = []() {
211 BigInt<20, 2> x(12345678);
212 BigInt<20, 2> y("123456789");
213 BigInt<20, 2> z("123456789");
214
215 bool diff = (x != y);
216 bool eq = (y != z);
217
218 assert_equal(diff, true);
219 assert_equal(eq, false);
220 },
221},
222{
223 .name = "BigInt operator< and operator>",
224 .f = []() {
225 BigInt<20, 2> x(7891);
226 BigInt<20, 2> y(7881);
227
228 bool t = (y < x);
229 bool f = (x < y);
230
231 assert_equal(t, true);
232 assert_equal(f, false);
233 }
234},
235{
236 .name = "BigInt operator< both negative",
237 .f = []() {
238 BigInt<20, 2> x(-7891);
239 BigInt<20, 2> y(-7881);
240
241 bool cmp = (x < y);
242
243 assert_equal(cmp, true);
244 }
245},
246{
247 .name = "BigInt operator< different sign",
248 .f = []() {
249 BigInt<20, 2> x(-7);
250 BigInt<20, 2> y(7);
251
252 bool cmp = (x < y);
253
254 assert_equal(cmp, true);
255 }
256},
257{
258 .name = "BigInt abs",
259 .f = []() {
260 BigInt<20, 2> x(-1234567);
261 BigInt<20, 2> y(7654321);
262
263 assert_equal(x.abs(), BigInt<20, 2>(1234567));
264 assert_equal(y.abs(), y);
265 }
266},
267{
268 .name = "BigInt opposite",
269 .f = []() {
270 BigInt<20, 2> x(-1234567);
271 BigInt<20, 2> y(7654321);
272
273 assert_equal(-x, BigInt<20, 2>(1234567));
274 assert_equal(-y, BigInt<20, 2>(-7654321));
275 }
276},
277{
278 .name = "BigInt -0 == 0",
279 .f = []() {
280 BigInt z(0);
281
282 assert_equal(-z, z);
283 }
284},
285{
286 .name = "BigInt sum",
287 .f = []() {
288 BigInt<20, 2> x("987608548588589");
289 BigInt<20, 2> y("6793564545455289");
290 BigInt<20, 2> z("7781173094043878");
291
292 assert_equal(x+y, z);
293 }
294},
295{
296 .name = "BigInt sum both negative",
297 .f = []() {
298 BigInt<20, 2> x("-987608548588589");
299 BigInt<20, 2> y("-6793564545455289");
300 BigInt<20, 2> z("-7781173094043878");
301
302 assert_equal(x+y, z);
303 }
304},
305{
306 .name = "BigInt sum negative + positive, result positive",
307 .f = []() {
308 BigInt<20, 2> x("-987608548588589");
309 BigInt<20, 2> y("6793564545455289");
310 BigInt<20, 2> z("5805955996866700");
311
312 assert_equal(x+y, z);
313 }
314},
315{
316 .name = "BigInt sum positive + negative, result negative",
317 .f = []() {
318 BigInt<20, 2> x("987608548588589");
319 BigInt<20, 2> y("-6793564545455289");
320 BigInt<20, 2> z("-5805955996866700");
321
322 assert_equal(x+y, z);
323 }
324},
325{
326 .name = "BigInt difference",
327 .f = []() {
328 BigInt<20, 2> x("2342442323434134");
329 BigInt<20, 2> y("2524342523342342");
330 BigInt<20, 2> z("-181900199908208");
331
332 assert_equal(x-y, z);
333 }
334},
335{
336 .name = "BigInt product",
337 .f = []() {
338 BigInt<100, 3> x("134142345244134");
339 BigInt<100, 3> y("-56543047058245");
340 BigInt<100, 3> z("-7584816939642416135042584830");
341
342 assert_equal(x*y, z);
343 }
344},
345{
346 .name = "BigInt operator+=",
347 .f = []() {
348 BigInt<20, 2> x("987608548588589");
349 BigInt<20, 2> y("6793564545455289");
350 BigInt<20, 2> z("7781173094043878");
351
352 x += y;
353
354 assert_equal(x, z);
355 }
356},
357{
358 .name = "BigInt 14 / 3",
359 .f = []() {
360 BigInt x(14);
361 BigInt y(3);
362
363 assert_equal(x / y, 4);
364 }
365},
366{
367 .name = "BigInt 14 % 3",
368 .f = []() {
369 BigInt x(14);
370 BigInt y(3);
371
372 assert_equal(x % y, 2);
373 }
374},
375{
376 .name = "BigInt 14 / -3",
377 .f = []() {
378 BigInt x(14);
379 BigInt y(-3);
380
381 assert_equal(x / y, -5);
382 }
383},
384{
385 .name = "BigInt 14 % -3",
386 .f = []() {
387 BigInt x(14);
388 BigInt y(-3);
389
390 assert_equal(x % y, -1);
391 }
392},
393{
394 .name = "BigInt -14 / 3",
395 .f = []() {
396 BigInt x(-14);
397 BigInt y(3);
398
399 assert_equal(x / y, -5);
400 }
401},
402{
403 .name = "BigInt -14 % 3",
404 .f = []() {
405 BigInt x(-14);
406 BigInt y(3);
407
408 assert_equal(x % y, 1);
409 }
410},
411{
412 .name = "BigInt -14 / -3",
413 .f = []() {
414 BigInt x(-14);
415 BigInt y(-3);
416
417 assert_equal(x / y, 4);
418 }
419},
420{
421 .name = "BigInt -14 % -3",
422 .f = []() {
423 BigInt x(-14);
424 BigInt y(-3);
425
426 assert_equal(x % y, -2);
427 }
428},
429{
430 .name = "BigInt division large numbers, quotient = 0",
431 .f = []() {
432 BigInt<50, 3> x("4534435234134244242");
433 BigInt<50, 3> y("7832478748237487343");
434
435 assert_equal(x / y, 0);
436 }
437},
438{
439 .name = "BigInt division large numbers",
440 .f = []() {
441 BigInt<50, 3> x("12344534435234134244242");
442 BigInt<50, 3> y("7832478748237487343");
443 BigInt<50, 3> z(1576);
444
445 assert_equal(x / y, z);
446 }
447},
448{
449 .name = "BigInt modulo large numbers",
450 .f = []() {
451 BigInt<50, 3> x("12344534435234134244242");
452 BigInt<50, 3> y("7832478748237487343");
453 BigInt<50, 3> z("547928011854191674");
454
455 assert_equal(x % y, z);
456 }
457},
458{
459 .name = "Zmod with BigInt constructor",
460 .f = []() {
461 constexpr BigInt<50, 3> N("78923471");
462 constexpr BigInt<50, 3> x("145452451");
463 Zmod<N> xmodN(x);
464
465 assert_equal(xmodN.toint(), x % N);
466 }
467},
468{
469 .name = "Zmod with BigInt big inverse",
470 .f = []() {
471 constexpr BigInt<50, 3> N("7520824651249795349285");
472 constexpr BigInt<50, 3> x("234589234599896924596");
473 constexpr BigInt<50, 3> expected("5901181270843786267351");
474 Zmod<N> xmodN(x);
475
476 auto inv = xmodN.inverse();
477
478 assert_equal(inv.has_value(), true);
479 assert_equal(inv.value().toint(), expected);
480 }
481},
482/*
483{
484 .name = "This does not compile",
485 .f = []() {
486 constexpr double N = 1.2;
487 Zmod<N> x;
488 }
489}
490*/
144}; 491};
145 492
146int main() { 493int main() {
diff --git a/zmodn.h b/zmodn.h
index fdb1ee6..a24f293 100644
--- a/zmodn.h
+++ b/zmodn.h
@@ -7,16 +7,31 @@
7#include <tuple> 7#include <tuple>
8#include <type_traits> 8#include <type_traits>
9 9
10template<typename INT> 10template<typename T>
11requires std::is_integral_v<INT> 11concept Integer = requires(T a, T b, int i, std::ostream& os) {
12std::tuple<INT, INT, INT> extended_gcd(INT a, INT b) { 12 {T(i)};
13
14 {a + b} -> std::same_as<T>;
15 {a - b} -> std::same_as<T>;
16 {a * b} -> std::same_as<T>;
17 {a / b} -> std::same_as<T>;
18 {a % b} -> std::same_as<T>;
19
20 {a == b} -> std::same_as<bool>;
21 {a != b} -> std::same_as<bool>;
22
23 {os << a} -> std::same_as<std::ostream&>;
24};
25
26template<Integer T>
27std::tuple<T, T, T> extended_gcd(T a, T b) {
13 if (b == 0) return {a, 1, 0}; 28 if (b == 0) return {a, 1, 0};
14 auto [g, x, y] = extended_gcd(b, a%b); 29 auto [g, x, y] = extended_gcd(b, a%b);
15 return {g, y, x - y*(a/b)}; 30 return {g, y, x - y*(a/b)};
16} 31}
17 32
18template<auto N> 33template<Integer auto N>
19requires(N > 1) && std::is_integral_v<decltype(N)> 34requires(N > 1)
20class Zmod { 35class Zmod {
21public: 36public:
22 Zmod(decltype(N) z) : value{(z%N + N) % N} {} 37 Zmod(decltype(N) z) : value{(z%N + N) % N} {}