aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2021-08-02 17:47:07 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2021-08-11 20:59:52 +0200
commit491e4ecc740a361bd726f8caf92f8eed078ed333 (patch)
tree216f38088769fa48759ebe0561c777192f1b95d3
parentcd0ce7b76e800ac308d6df02ddad4da221fbb90a (diff)
big: Add binary split factorial.
-rw-r--r--core/math/big/basic.odin57
-rw-r--r--core/math/big/build.bat1
-rw-r--r--core/math/big/common.odin12
-rw-r--r--core/math/big/example.odin10
-rw-r--r--core/math/big/helpers.odin16
-rw-r--r--core/math/big/logical.odin61
6 files changed, 103 insertions, 54 deletions
diff --git a/core/math/big/basic.odin b/core/math/big/basic.odin
index 4129f44d5..f403d853f 100644
--- a/core/math/big/basic.odin
+++ b/core/math/big/basic.odin
@@ -751,12 +751,15 @@ sqrmod :: proc { int_sqrmod, };
int_factorial :: proc(res: ^Int, n: DIGIT) -> (err: Error) {
- if n < 0 { return .Invalid_Argument; }
+ if n < 0 || n > _FACTORIAL_MAX_N || res == nil { return .Invalid_Argument; }
i := DIGIT(len(_factorial_table));
if n < i {
return set(res, _factorial_table[n]);
}
+ if n >= _FACTORIAL_BINARY_SPLIT_CUTOFF {
+ return int_factorial_binary_split(res, n);
+ }
a := &Int{};
defer destroy(a);
@@ -771,6 +774,58 @@ int_factorial :: proc(res: ^Int, n: DIGIT) -> (err: Error) {
return .None;
}
+
+_int_recursive_product :: proc(res: ^Int, start, stop: DIGIT, level := int(0)) -> (err: Error) {
+ t1, t2 := &Int{}, &Int{};
+ defer destroy(t1, t2);
+
+ if level > _FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS { return .Max_Iterations_Reached; }
+
+ num_factors := (stop - start) >> 1;
+ if num_factors == 2 {
+ if err = set(t1, start); err != .None { return err; }
+ if err = add(t2, t1, 2); err != .None { return err; }
+ return mul(res, t1, t2);
+ }
+
+ if num_factors > 1 {
+ mid := (start + num_factors) | 1;
+ if err = _int_recursive_product(t1, start, mid, level + 1); err != .None { return err; }
+ if err = _int_recursive_product(t2, mid, stop, level + 1); err != .None { return err; }
+ return mul(res, t1, t2);
+ }
+
+ if num_factors == 1 { return set(res, start); }
+
+ return one(res);
+}
+
+/*
+ Binary split factorial algo due to: http://www.luschny.de/math/factorial/binarysplitfact.html
+*/
+int_factorial_binary_split :: proc(res: ^Int, n: DIGIT) -> (err: Error) {
+ if n < 0 || n > _FACTORIAL_MAX_N || res == nil { return .Invalid_Argument; }
+
+ inner, outer, start, stop, temp := &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
+ defer destroy(inner, outer, start, stop, temp);
+
+ if err = one(inner); err != .None { return err; }
+ if err = one(outer); err != .None { return err; }
+
+ bits_used := int(_DIGIT_TYPE_BITS - intrinsics.count_leading_zeros(n));
+
+ for i := bits_used; i >= 0; i -= 1 {
+ start := (n >> (uint(i) + 1)) + 1 | 1;
+ stop := (n >> uint(i)) + 1 | 1;
+ if err = _int_recursive_product(temp, start, stop); err != .None { return err; }
+ if err = mul(inner, inner, temp); err != .None { return err; }
+ if err = mul(outer, outer, inner); err != .None { return err; }
+ }
+ shift := n - intrinsics.count_ones(n);
+
+ return shl(res, outer, int(shift));
+}
+
factorial :: proc { int_factorial, };
/*
diff --git a/core/math/big/build.bat b/core/math/big/build.bat
index 8e69389e9..2c1edfcec 100644
--- a/core/math/big/build.bat
+++ b/core/math/big/build.bat
@@ -1,5 +1,6 @@
@echo off
odin run . -vet
+: -o:size -no-bounds-check
:odin build . -build-mode:shared -show-timings -o:minimal -use-separate-modules
:odin build . -build-mode:shared -show-timings -o:size -use-separate-modules -no-bounds-check
:odin build . -build-mode:shared -show-timings -o:size -use-separate-modules
diff --git a/core/math/big/common.odin b/core/math/big/common.odin
index 5bdc0139f..bcda658b0 100644
--- a/core/math/big/common.odin
+++ b/core/math/big/common.odin
@@ -26,7 +26,6 @@ when _LOW_MEMORY {
_DEFAULT_DIGIT_COUNT :: 32;
}
-
_MUL_KARATSUBA_CUTOFF :: #config(MUL_KARATSUBA_CUTOFF, _DEFAULT_MUL_KARATSUBA_CUTOFF);
_SQR_KARATSUBA_CUTOFF :: #config(SQR_KARATSUBA_CUTOFF, _DEFAULT_SQR_KARATSUBA_CUTOFF);
_MUL_TOOM_CUTOFF :: #config(MUL_TOOM_CUTOFF, _DEFAULT_MUL_TOOM_CUTOFF);
@@ -43,6 +42,17 @@ _DEFAULT_SQR_TOOM_CUTOFF :: 400;
_MAX_ITERATIONS_ROOT_N :: 500;
+/*
+ Largest `N` for which we'll compute `N!`
+*/
+_FACTORIAL_MAX_N :: 100_000;
+
+/*
+ Cutoff to switch to int_factorial_binary_split, and its max recursion level.
+*/
+_FACTORIAL_BINARY_SPLIT_CUTOFF :: 6100;
+_FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS :: 100;
+
Sign :: enum u8 {
Zero_or_Positive = 0,
Negative = 1,
diff --git a/core/math/big/example.odin b/core/math/big/example.odin
index 14531a184..f7ba61034 100644
--- a/core/math/big/example.odin
+++ b/core/math/big/example.odin
@@ -42,7 +42,7 @@ _SQR_TOOM_CUTOFF,
}
print_timings :: proc() {
- fmt.printf("\nTimings:\n");
+ fmt.printf("Timings:\n");
for v, i in Timings {
if v.c > 0 {
avg := time.Duration(f64(v.t) / f64(v.c));
@@ -76,6 +76,7 @@ Category :: enum {
itoa,
atoi,
factorial,
+ factorial_bin,
choose,
lsb,
ctz,
@@ -116,10 +117,11 @@ demo :: proc() {
defer destroy(a, b, c, d, e, f);
s := time.tick_now();
- err = choose(a, 65535, 255);
+ err = choose(a, 1024, 255);
Timings[.choose].t += time.tick_since(s); Timings[.choose].c += 1;
- print("choose", a);
- fmt.println(err);
+
+ print("1024 choose 255", a, 10, true, true, true);
+ fmt.printf("Error: %v\n", err);
}
main :: proc() {
diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin
index dc846b4b9..a3b4f13ba 100644
--- a/core/math/big/helpers.odin
+++ b/core/math/big/helpers.odin
@@ -337,9 +337,7 @@ zero :: clear;
Set the `Int` to 1 and optionally shrink it to the minimum backing size.
*/
int_one :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
- if err = clear(a, minimize, allocator); err != .None {
- return err;
- }
+ if err = clear(a, minimize, allocator); err != .None { return err; }
a.used = 1;
a.digit[0] = 1;
@@ -429,9 +427,7 @@ get_i32 :: proc { int_get_i32, };
and maybe return max(T), .Integer_Overflow if not?
*/
int_get :: proc(a: ^Int, $T: typeid) -> (res: T, err: Error) where intrinsics.type_is_integer(T) {
- if err = clear_if_uninitialized(a); err != .None {
- return 0, err;
- }
+ if err = clear_if_uninitialized(a); err != .None { return 0, err; }
size_in_bits := int(size_of(T) * 8);
i := int((size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS);
@@ -656,4 +652,10 @@ clamp :: proc(a: ^Int) -> (err: Error) {
a.sign = .Zero_or_Positive;
}
return .None;
-} \ No newline at end of file
+}
+
+
+_STATIC_ZERO := &Int{
+ used = 0,
+ sign = .Zero_or_Positive,
+};
diff --git a/core/math/big/logical.odin b/core/math/big/logical.odin
index e721db603..1e11f1a35 100644
--- a/core/math/big/logical.odin
+++ b/core/math/big/logical.odin
@@ -16,25 +16,21 @@ import "core:mem"
/*
The `and`, `or` and `xor` binops differ in two lines only.
We could handle those with a switch, but that adds overhead.
+
+ TODO: Implement versions that take a DIGIT immediate.
*/
/*
2's complement `and`, returns `dest = a & b;`
*/
-and :: proc(dest, a, b: ^Int) -> (err: Error) {
- if err = clear_if_uninitialized(a); err != .None {
- return err;
- }
- if err = clear_if_uninitialized(b); err != .None {
- return err;
- }
+int_and :: proc(dest, a, b: ^Int) -> (err: Error) {
+ if err = clear_if_uninitialized(a, b); err != .None { return err; }
+
used := max(a.used, b.used) + 1;
/*
Grow the destination to accomodate the result.
*/
- if err = grow(dest, used); err != .None {
- return err;
- }
+ if err = grow(dest, used); err != .None { return err; }
neg_a, _ := is_neg(a);
neg_b, _ := is_neg(b);
@@ -83,24 +79,18 @@ and :: proc(dest, a, b: ^Int) -> (err: Error) {
dest.sign = .Negative if neg else .Zero_or_Positive;
return clamp(dest);
}
+and :: proc { int_add, };
/*
2's complement `or`, returns `dest = a | b;`
*/
-or :: proc(dest, a, b: ^Int) -> (err: Error) {
- if err = clear_if_uninitialized(a); err != .None {
- return err;
- }
- if err = clear_if_uninitialized(b); err != .None {
- return err;
- }
+int_or :: proc(dest, a, b: ^Int) -> (err: Error) {
+ if err = clear_if_uninitialized(a, b); err != .None { return err; }
used := max(a.used, b.used) + 1;
/*
Grow the destination to accomodate the result.
*/
- if err = grow(dest, used); err != .None {
- return err;
- }
+ if err = grow(dest, used); err != .None { return err; }
neg_a, _ := is_neg(a);
neg_b, _ := is_neg(b);
@@ -149,24 +139,19 @@ or :: proc(dest, a, b: ^Int) -> (err: Error) {
dest.sign = .Negative if neg else .Zero_or_Positive;
return clamp(dest);
}
+or :: proc { int_or, };
/*
2's complement `xor`, returns `dest = a ~ b;`
*/
-xor :: proc(dest, a, b: ^Int) -> (err: Error) {
- if err = clear_if_uninitialized(a); err != .None {
- return err;
- }
- if err = clear_if_uninitialized(b); err != .None {
- return err;
- }
+int_xor :: proc(dest, a, b: ^Int) -> (err: Error) {
+ if err = clear_if_uninitialized(a, b); err != .None { return err; }
+
used := max(a.used, b.used) + 1;
/*
Grow the destination to accomodate the result.
*/
- if err = grow(dest, used); err != .None {
- return err;
- }
+ if err = grow(dest, used); err != .None { return err; }
neg_a, _ := is_neg(a);
neg_b, _ := is_neg(b);
@@ -215,20 +200,16 @@ xor :: proc(dest, a, b: ^Int) -> (err: Error) {
dest.sign = .Negative if neg else .Zero_or_Positive;
return clamp(dest);
}
+xor :: proc { int_xor, };
/*
dest = ~src
*/
int_complement :: proc(dest, src: ^Int) -> (err: Error) {
/*
- Check that src and dest are usable.
+ Check that src is usable. Dest will get checked by `sub`.
*/
- if err = clear_if_uninitialized(src); err != .None {
- return err;
- }
- if err = clear_if_uninitialized(dest); err != .None {
- return err;
- }
+ if err = clear_if_uninitialized(src); err != .None { return err; }
/*
Temporarily fix sign.
@@ -254,8 +235,7 @@ complement :: proc { int_complement, };
*/
int_shrmod :: proc(quotient, remainder, numerator: ^Int, bits: int) -> (err: Error) {
bits := bits;
- if err = clear_if_uninitialized(quotient); err != .None { return err; }
- if err = clear_if_uninitialized(numerator); err != .None { return err; }
+ if err = clear_if_uninitialized(quotient, numerator); err != .None { return err; }
if bits < 0 { return .Invalid_Argument; }
@@ -371,8 +351,7 @@ shr_signed :: proc { int_shr_signed, };
*/
int_shl :: proc(dest, src: ^Int, bits: int) -> (err: Error) {
bits := bits;
- if err = clear_if_uninitialized(src); err != .None { return err; }
- if err = clear_if_uninitialized(dest); err != .None { return err; }
+ if err = clear_if_uninitialized(src, dest); err != .None { return err; }
if bits < 0 {
return .Invalid_Argument;