This commit is contained in:
gingerBill
2021-08-13 10:45:35 +01:00
15 changed files with 7612 additions and 1 deletions

154
core/math/big/api.odin Normal file
View File

@@ -0,0 +1,154 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file collects public proc maps and their aliases.
/*
*/
=== === === === === === === === === === === === === === === === === === === === === === === ===
Basic arithmetic.
See `public.odin`.
=== === === === === === === === === === === === === === === === === === === === === === === ===
*/
/*
High-level addition. Handles sign.
*/
add :: proc {
/*
int_add :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error)
*/
int_add,
/*
Adds the unsigned `DIGIT` immediate to an `Int`, such that the
`DIGIT` doesn't have to be turned into an `Int` first.
int_add_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error)
*/
int_add_digit,
};
/*
err = sub(dest, a, b);
*/
sub :: proc {
/*
int_sub :: proc(dest, a, b: ^Int) -> (err: Error)
*/
int_sub,
/*
int_sub_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error)
*/
int_sub_digit,
};
/*
=== === === === === === === === === === === === === === === === === === === === === === === ===
Comparisons.
See `compare.odin`.
=== === === === === === === === === === === === === === === === === === === === === === === ===
*/
is_initialized :: proc {
/*
int_is_initialized :: proc(a: ^Int) -> bool
*/
int_is_initialized,
};
is_zero :: proc {
/*
int_is_zero :: proc(a: ^Int) -> bool
*/
int_is_zero,
};
is_positive :: proc {
/*
int_is_positive :: proc(a: ^Int) -> bool
*/
int_is_positive,
};
is_pos :: is_positive;
is_negative :: proc {
/*
int_is_negative :: proc(a: ^Int) -> bool
*/
int_is_negative,
};
is_neg :: is_negative;
is_even :: proc {
/*
int_is_even :: proc(a: ^Int) -> bool
*/
int_is_even,
};
is_odd :: proc {
/*
int_is_odd :: proc(a: ^Int) -> bool
*/
int_is_odd,
};
is_power_of_two :: proc {
/*
platform_int_is_power_of_two :: proc(a: int) -> bool
*/
platform_int_is_power_of_two,
/*
int_is_power_of_two :: proc(a: ^Int) -> (res: bool)
*/
int_is_power_of_two,
};
compare :: proc {
/*
Compare two `Int`s, signed.
int_compare :: proc(a, b: ^Int) -> Comparison_Flag
*/
int_compare,
/*
Compare an `Int` to an unsigned number upto the size of the backing type.
int_compare_digit :: proc(a: ^Int, u: DIGIT) -> Comparison_Flag
*/
int_compare_digit,
};
cmp :: compare;
compare_magnitude :: proc {
/*
Compare the magnitude of two `Int`s, unsigned.
*/
int_compare_magnitude,
};
cmp_mag :: compare_magnitude;
/*
=== === === === === === === === === === === === === === === === === === === === === === === ===
Initialization and other helpers.
See `helpers.odin`.
=== === === === === === === === === === === === === === === === === === === === === === === ===
*/
destroy :: proc {
/*
Clears one or more `Int`s and dellocates their backing memory.
int_destroy :: proc(integers: ..^Int)
*/
int_destroy,
};

8
core/math/big/build.bat Normal file
View File

@@ -0,0 +1,8 @@
@echo off
odin run . -vet
: -o:size
:odin build . -build-mode:shared -show-timings -o:minimal -no-bounds-check && python test.py -fast-tests
:odin build . -build-mode:shared -show-timings -o:size -no-bounds-check && python test.py -fast-tests
:odin build . -build-mode:shared -show-timings -o:size && python test.py -fast-tests
:odin build . -build-mode:shared -show-timings -o:speed -no-bounds-check && python test.py -fast-tests
:odin build . -build-mode:shared -show-timings -o:speed && python test.py -fast-tests

206
core/math/big/common.odin Normal file
View File

@@ -0,0 +1,206 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
*/
import "core:intrinsics"
/*
TODO: Make the tunables runtime adjustable where practical.
This allows to benchmark and/or setting optimized values for a certain CPU without recompiling.
*/
/*
========================== TUNABLES ==========================
`initialize_constants` returns `#config(MUL_KARATSUBA_CUTOFF, _DEFAULT_MUL_KARATSUBA_CUTOFF)`
and we initialize this cutoff that way so that the procedure is used and called,
because it handles initializing the constants ONE, ZERO, MINUS_ONE, NAN and INF.
`initialize_constants` also replaces the other `_DEFAULT_*` cutoffs with custom compile-time values if so `#config`ured.
*/
MUL_KARATSUBA_CUTOFF := initialize_constants();
SQR_KARATSUBA_CUTOFF := _DEFAULT_SQR_KARATSUBA_CUTOFF;
MUL_TOOM_CUTOFF := _DEFAULT_MUL_TOOM_CUTOFF;
SQR_TOOM_CUTOFF := _DEFAULT_SQR_TOOM_CUTOFF;
/*
These defaults were tuned on an AMD A8-6600K (64-bit) using libTomMath's `make tune`.
TODO(Jeroen): Port this tuning algorithm and tune them for more modern processors.
It would also be cool if we collected some data across various processor families.
This would let uss set reasonable defaults at runtime as this library initializes
itself by using `cpuid` or the ARM equivalent.
IMPORTANT: The 32_BIT path has largely gone untested. It needs to be tested and
debugged where necessary.
*/
_DEFAULT_MUL_KARATSUBA_CUTOFF :: #config(MUL_KARATSUBA_CUTOFF, 80);
_DEFAULT_SQR_KARATSUBA_CUTOFF :: #config(SQR_KARATSUBA_CUTOFF, 120);
_DEFAULT_MUL_TOOM_CUTOFF :: #config(MUL_TOOM_CUTOFF, 350);
_DEFAULT_SQR_TOOM_CUTOFF :: #config(SQR_TOOM_CUTOFF, 400);
MAX_ITERATIONS_ROOT_N := 500;
/*
Largest `N` for which we'll compute `N!`
*/
FACTORIAL_MAX_N := 1_000_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;
/*
We don't allow these to be switched at runtime for two reasons:
1) 32-bit and 64-bit versions of procedures use different types for their storage,
so we'd have to double the number of procedures, and they couldn't interact.
2) Optimizations thanks to precomputed masks wouldn't work.
*/
MATH_BIG_FORCE_64_BIT :: #config(MATH_BIG_FORCE_64_BIT, false);
MATH_BIG_FORCE_32_BIT :: #config(MATH_BIG_FORCE_32_BIT, false);
when (MATH_BIG_FORCE_32_BIT && MATH_BIG_FORCE_64_BIT) { #panic("Cannot force 32-bit and 64-bit big backend simultaneously."); };
_LOW_MEMORY :: #config(BIGINT_SMALL_MEMORY, false);
when _LOW_MEMORY {
_DEFAULT_DIGIT_COUNT :: 8;
} else {
_DEFAULT_DIGIT_COUNT :: 32;
}
/*
======================= END OF TUNABLES =======================
*/
Sign :: enum u8 {
Zero_or_Positive = 0,
Negative = 1,
};
Int :: struct {
used: int,
digit: [dynamic]DIGIT,
sign: Sign,
flags: Flags,
};
Flag :: enum u8 {
NaN,
Inf,
Immutable,
};
Flags :: bit_set[Flag; u8];
/*
Errors are a strict superset of runtime.Allocation_Error.
*/
Error :: enum int {
Okay = 0,
Out_Of_Memory = 1,
Invalid_Pointer = 2,
Invalid_Argument = 3,
Assignment_To_Immutable = 4,
Max_Iterations_Reached = 5,
Buffer_Overflow = 6,
Integer_Overflow = 7,
Division_by_Zero = 8,
Math_Domain_Error = 9,
Unimplemented = 127,
};
Error_String :: #partial [Error]string{
.Out_Of_Memory = "Out of memory",
.Invalid_Pointer = "Invalid pointer",
.Invalid_Argument = "Invalid argument",
.Assignment_To_Immutable = "Assignment to immutable",
.Max_Iterations_Reached = "Max iterations reached",
.Buffer_Overflow = "Buffer overflow",
.Integer_Overflow = "Integer overflow",
.Division_by_Zero = "Division by zero",
.Math_Domain_Error = "Math domain error",
.Unimplemented = "Unimplemented",
};
Primality_Flag :: enum u8 {
Blum_Blum_Shub = 0, /* BBS style prime */
Safe = 1, /* Safe prime (p-1)/2 == prime */
Second_MSB_On = 3, /* force 2nd MSB to 1 */
};
Primality_Flags :: bit_set[Primality_Flag; u8];
/*
How do we store the Ints?
Minimum number of available digits in `Int`, `_DEFAULT_DIGIT_COUNT` >= `_MIN_DIGIT_COUNT`
- Must be at least 3 for `_div_school`.
- Must be large enough such that `init_integer` can store `u128` in the `Int` without growing.
*/
_MIN_DIGIT_COUNT :: max(3, ((size_of(u128) + _DIGIT_BITS) - 1) / _DIGIT_BITS);
#assert(_DEFAULT_DIGIT_COUNT >= _MIN_DIGIT_COUNT);
/*
Maximum number of digits.
- Must be small enough such that `_bit_count` does not overflow.
- Must be small enough such that `_radix_size` for base 2 does not overflow.
`_radix_size` needs two additional bytes for zero termination and sign.
*/
_MAX_BIT_COUNT :: (max(int) - 2);
_MAX_DIGIT_COUNT :: _MAX_BIT_COUNT / _DIGIT_BITS;
when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) {
/*
We can use u128 as an intermediary.
*/
DIGIT :: distinct u64;
_WORD :: distinct u128;
} else {
DIGIT :: distinct u32;
_WORD :: distinct u64;
}
#assert(size_of(_WORD) == 2 * size_of(DIGIT));
_DIGIT_TYPE_BITS :: 8 * size_of(DIGIT);
_WORD_TYPE_BITS :: 8 * size_of(_WORD);
_DIGIT_BITS :: _DIGIT_TYPE_BITS - 4;
_WORD_BITS :: 2 * _DIGIT_BITS;
_MASK :: (DIGIT(1) << DIGIT(_DIGIT_BITS)) - DIGIT(1);
_DIGIT_MAX :: _MASK;
_MAX_COMBA :: 1 << (_WORD_TYPE_BITS - (2 * _DIGIT_BITS)) ;
_WARRAY :: 1 << ((_WORD_TYPE_BITS - (2 * _DIGIT_BITS)) + 1);
Order :: enum i8 {
LSB_First = -1,
MSB_First = 1,
};
Endianness :: enum i8 {
Little = -1,
Platform = 0,
Big = 1,
};

246
core/math/big/example.odin Normal file
View File

@@ -0,0 +1,246 @@
//+ignore
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
A BigInt implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
*/
import "core:fmt"
import "core:mem"
print_configation :: proc() {
fmt.printf(
`
Configuration:
_DIGIT_BITS %v
_MIN_DIGIT_COUNT %v
_MAX_DIGIT_COUNT %v
_DEFAULT_DIGIT_COUNT %v
_MAX_COMBA %v
_WARRAY %v
Runtime tunable:
MUL_KARATSUBA_CUTOFF %v
SQR_KARATSUBA_CUTOFF %v
MUL_TOOM_CUTOFF %v
SQR_TOOM_CUTOFF %v
MAX_ITERATIONS_ROOT_N %v
FACTORIAL_MAX_N %v
FACTORIAL_BINARY_SPLIT_CUTOFF %v
FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS %v
`, _DIGIT_BITS,
_MIN_DIGIT_COUNT,
_MAX_DIGIT_COUNT,
_DEFAULT_DIGIT_COUNT,
_MAX_COMBA,
_WARRAY,
MUL_KARATSUBA_CUTOFF,
SQR_KARATSUBA_CUTOFF,
MUL_TOOM_CUTOFF,
SQR_TOOM_CUTOFF,
MAX_ITERATIONS_ROOT_N,
FACTORIAL_MAX_N,
FACTORIAL_BINARY_SPLIT_CUTOFF,
FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS,
);
}
print :: proc(name: string, a: ^Int, base := i8(10), print_name := true, newline := true, print_extra_info := false) {
assert_if_nil(a);
as, err := itoa(a, base);
defer delete(as);
cb := internal_count_bits(a);
if print_name {
fmt.printf("%v", name);
}
if err != nil {
fmt.printf("%v (error: %v | %v)", name, err, a);
}
fmt.printf("%v", as);
if print_extra_info {
fmt.printf(" (base: %v, bits: %v (digits: %v), flags: %v)", base, cb, a.used, a.flags);
}
if newline {
fmt.println();
}
}
int_to_byte :: proc(v: ^Int) {
err: Error;
size: int;
print("v: ", v);
fmt.println();
t := &Int{};
defer destroy(t);
if size, err = int_to_bytes_size(v); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b1 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_big(v, b1);
int_from_bytes_big(t, b1);
fmt.printf("big: %v | err: %v\n", b1, err);
int_from_bytes_big(t, b1);
if internal_cmp_mag(t, v) != 0 {
print("\tError parsing t: ", t);
}
if size, err = int_to_bytes_size(v); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b2 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_big_python(v, b2);
fmt.printf("big python: %v | err: %v\n", b2, err);
if err == nil {
int_from_bytes_big_python(t, b2);
if internal_cmp_mag(t, v) != 0 {
print("\tError parsing t: ", t);
}
}
if size, err = int_to_bytes_size(v, true); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b3 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_big(v, b3, true);
fmt.printf("big signed: %v | err: %v\n", b3, err);
int_from_bytes_big(t, b3, true);
if internal_cmp(t, v) != 0 {
print("\tError parsing t: ", t);
}
if size, err = int_to_bytes_size(v, true); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b4 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_big_python(v, b4, true);
fmt.printf("big signed python: %v | err: %v\n", b4, err);
int_from_bytes_big_python(t, b4, true);
if internal_cmp(t, v) != 0 {
print("\tError parsing t: ", t);
}
}
int_to_byte_little :: proc(v: ^Int) {
err: Error;
size: int;
print("v: ", v);
fmt.println();
t := &Int{};
defer destroy(t);
if size, err = int_to_bytes_size(v); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b1 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_little(v, b1);
fmt.printf("little: %v | err: %v\n", b1, err);
int_from_bytes_little(t, b1);
if internal_cmp_mag(t, v) != 0 {
print("\tError parsing t: ", t);
}
if size, err = int_to_bytes_size(v); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b2 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_little_python(v, b2);
fmt.printf("little python: %v | err: %v\n", b2, err);
if err == nil {
int_from_bytes_little_python(t, b2);
if internal_cmp_mag(t, v) != 0 {
print("\tError parsing t: ", t);
}
}
if size, err = int_to_bytes_size(v, true); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b3 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_little(v, b3, true);
fmt.printf("little signed: %v | err: %v\n", b3, err);
int_from_bytes_little(t, b3, true);
if internal_cmp(t, v) != 0 {
print("\tError parsing t: ", t);
}
if size, err = int_to_bytes_size(v, true); err != nil {
fmt.printf("int_to_bytes_size returned: %v\n", err);
return;
}
b4 := make([]u8, size, context.temp_allocator);
err = int_to_bytes_little_python(v, b4, true);
fmt.printf("little signed python: %v | err: %v\n", b4, err);
int_from_bytes_little_python(t, b4, true);
if internal_cmp(t, v) != 0 {
print("\tError parsing t: ", t);
}
}
demo :: proc() {
a, b, c, d, e, f := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
defer destroy(a, b, c, d, e, f);
set(a, 64336);
fmt.println("--- --- --- ---");
int_to_byte(a);
fmt.println("--- --- --- ---");
int_to_byte_little(a);
fmt.println("--- --- --- ---");
set(b, -64336);
fmt.println("--- --- --- ---");
int_to_byte(b);
fmt.println("--- --- --- ---");
int_to_byte_little(b);
fmt.println("--- --- --- ---");
}
main :: proc() {
ta := mem.Tracking_Allocator{};
mem.tracking_allocator_init(&ta, context.allocator);
context.allocator = mem.tracking_allocator(&ta);
demo();
print_configation();
print_timings();
if len(ta.allocation_map) > 0 {
for _, v in ta.allocation_map {
fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location);
}
}
if len(ta.bad_free_array) > 0 {
fmt.println("Bad frees:");
for v in ta.bad_free_array {
fmt.println(v);
}
}
}

806
core/math/big/helpers.odin Normal file
View File

@@ -0,0 +1,806 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
*/
import "core:intrinsics"
import rnd "core:math/rand"
// import "core:fmt"
/*
TODO: Int.flags and Constants like ONE, NAN, etc, are not yet properly handled everywhere.
*/
/*
Deallocates the backing memory of one or more `Int`s.
*/
int_destroy :: proc(integers: ..^Int) {
integers := integers;
for a in &integers {
assert_if_nil(a);
}
#force_inline internal_int_destroy(..integers);
}
/*
Helpers to set an `Int` to a specific value.
*/
int_set_from_integer :: proc(dest: ^Int, src: $T, minimize := false, allocator := context.allocator) -> (err: Error)
where intrinsics.type_is_integer(T) {
context.allocator = allocator;
src := src;
/*
Check that `src` is usable and `dest` isn't immutable.
*/
assert_if_nil(dest);
if err = #force_inline internal_error_if_immutable(dest); err != nil { return err; }
return #force_inline internal_int_set_from_integer(dest, src, minimize);
}
set :: proc { int_set_from_integer, int_copy, int_atoi, };
/*
Copy one `Int` to another.
*/
int_copy :: proc(dest, src: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
If dest == src, do nothing
*/
if (dest == src) { return nil; }
/*
Check that `src` is usable and `dest` isn't immutable.
*/
assert_if_nil(dest, src);
context.allocator = allocator;
if err = #force_inline internal_clear_if_uninitialized(src); err != nil { return err; }
if err = #force_inline internal_error_if_immutable(dest); err != nil { return err; }
return #force_inline internal_int_copy(dest, src, minimize);
}
copy :: proc { int_copy, };
/*
In normal code, you can also write `a, b = b, a`.
However, that only swaps within the current scope.
This helper swaps completely.
*/
int_swap :: proc(a, b: ^Int) {
assert_if_nil(a, b);
#force_inline internal_swap(a, b);
}
swap :: proc { int_swap, };
/*
Set `dest` to |`src`|.
*/
int_abs :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
/*
Check that `src` is usable and `dest` isn't immutable.
*/
assert_if_nil(dest, src);
context.allocator = allocator;
if err = #force_inline internal_clear_if_uninitialized(src); err != nil { return err; }
if err = #force_inline internal_error_if_immutable(dest); err != nil { return err; }
return #force_inline internal_int_abs(dest, src);
}
platform_abs :: proc(n: $T) -> T where intrinsics.type_is_integer(T) {
return n if n >= 0 else -n;
}
abs :: proc{ int_abs, platform_abs, };
/*
Set `dest` to `-src`.
*/
int_neg :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
/*
Check that `src` is usable and `dest` isn't immutable.
*/
assert_if_nil(dest, src);
context.allocator = allocator;
if err = #force_inline internal_clear_if_uninitialized(src); err != nil { return err; }
if err = #force_inline internal_error_if_immutable(dest); err != nil { return err; }
return #force_inline internal_int_neg(dest, src);
}
neg :: proc { int_neg, };
/*
Helpers to extract values from the `Int`.
*/
int_bitfield_extract_single :: proc(a: ^Int, offset: int, allocator := context.allocator) -> (bit: _WORD, err: Error) {
return #force_inline int_bitfield_extract(a, offset, 1, allocator);
}
int_bitfield_extract :: proc(a: ^Int, offset, count: int, allocator := context.allocator) -> (res: _WORD, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
context.allocator = allocator;
if err = #force_inline internal_clear_if_uninitialized(a); err != nil { return {}, err; }
return #force_inline internal_int_bitfield_extract(a, offset, count);
}
/*
Resize backing store.
*/
shrink :: proc(a: ^Int, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
context.allocator = allocator;
if err = #force_inline internal_clear_if_uninitialized(a); err != nil { return err; }
return #force_inline internal_shrink(a);
}
int_grow :: proc(a: ^Int, digits: int, allow_shrink := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_int_grow(a, digits, allow_shrink, allocator);
}
grow :: proc { int_grow, };
/*
Clear `Int` and resize it to the default size.
*/
int_clear :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_int_clear(a, minimize, allocator);
}
clear :: proc { int_clear, };
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) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_one(a, minimize, allocator);
}
one :: proc { int_one, };
/*
Set the `Int` to -1 and optionally shrink it to the minimum backing size.
*/
int_minus_one :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_minus_one(a, minimize, allocator);
}
minus_one :: proc { int_minus_one, };
/*
Set the `Int` to Inf and optionally shrink it to the minimum backing size.
*/
int_inf :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_inf(a, minimize, allocator);
}
inf :: proc { int_inf, };
/*
Set the `Int` to -Inf and optionally shrink it to the minimum backing size.
*/
int_minus_inf :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_minus_inf(a, minimize, allocator);
}
minus_inf :: proc { int_inf, };
/*
Set the `Int` to NaN and optionally shrink it to the minimum backing size.
*/
int_nan :: proc(a: ^Int, minimize := false, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_nan(a, minimize, allocator);
}
nan :: proc { int_nan, };
power_of_two :: proc(a: ^Int, power: int, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return #force_inline internal_int_power_of_two(a, power, allocator);
}
int_get_u128 :: proc(a: ^Int, allocator := context.allocator) -> (res: u128, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, u128, allocator);
}
get_u128 :: proc { int_get_u128, };
int_get_i128 :: proc(a: ^Int, allocator := context.allocator) -> (res: i128, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, i128, allocator);
}
get_i128 :: proc { int_get_i128, };
int_get_u64 :: proc(a: ^Int, allocator := context.allocator) -> (res: u64, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, u64, allocator);
}
get_u64 :: proc { int_get_u64, };
int_get_i64 :: proc(a: ^Int, allocator := context.allocator) -> (res: i64, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, i64, allocator);
}
get_i64 :: proc { int_get_i64, };
int_get_u32 :: proc(a: ^Int, allocator := context.allocator) -> (res: u32, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, u32, allocator);
}
get_u32 :: proc { int_get_u32, };
int_get_i32 :: proc(a: ^Int, allocator := context.allocator) -> (res: i32, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
return int_get(a, i32, allocator);
}
get_i32 :: proc { int_get_i32, };
/*
TODO: Think about using `count_bits` to check if the value could be returned completely,
and maybe return max(T), .Integer_Overflow if not?
*/
int_get :: proc(a: ^Int, $T: typeid, allocator := context.allocator) -> (res: T, err: Error) where intrinsics.type_is_integer(T) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return T{}, err; }
return #force_inline internal_int_get(a, T);
}
get :: proc { int_get, };
int_get_float :: proc(a: ^Int, allocator := context.allocator) -> (res: f64, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return 0, err; }
return #force_inline internal_int_get_float(a);
}
/*
Count bits in an `Int`.
*/
count_bits :: proc(a: ^Int, allocator := context.allocator) -> (count: int, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return 0, err; }
return #force_inline internal_count_bits(a), nil;
}
/*
Returns the number of trailing zeroes before the first one.
Differs from regular `ctz` in that 0 returns 0.
*/
int_count_lsb :: proc(a: ^Int, allocator := context.allocator) -> (count: int, err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return 0, err; }
return #force_inline internal_int_count_lsb(a);
}
platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) {
return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0;
}
count_lsb :: proc { int_count_lsb, platform_count_lsb, };
int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
when _DIGIT_BITS == 60 { // DIGIT = u64
return DIGIT(rnd.uint64(r)) & _MASK;
} else when _DIGIT_BITS == 28 { // DIGIT = u32
return DIGIT(rnd.uint32(r)) & _MASK;
} else {
panic("Unsupported DIGIT size.");
}
return 0; // We shouldn't get here.
}
int_rand :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(dest);
return #force_inline internal_int_rand(dest, bits, r, allocator);
}
rand :: proc { int_rand, };
/*
Internal helpers.
*/
assert_initialized :: proc(a: ^Int, loc := #caller_location) {
assert_if_nil(a);
assert(is_initialized(a), "`Int` was not properly initialized.", loc);
}
zero_unused :: proc(dest: ^Int, old_used := -1) {
assert_if_nil(dest);
if ! #force_inline is_initialized(dest) { return; }
#force_inline internal_zero_unused(dest, old_used);
}
clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(arg);
return #force_inline internal_clear_if_uninitialized_single(arg, allocator);
}
clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) {
args := args;
assert_if_nil(..args);
for i in &args {
if err = #force_inline internal_clear_if_uninitialized_single(i, allocator); err != nil { return nil; }
}
return err;
}
clear_if_uninitialized :: proc {clear_if_uninitialized_single, clear_if_uninitialized_multi, };
error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) {
if arg != nil && .Immutable in arg.flags { return .Assignment_To_Immutable; }
return nil;
}
error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) {
for i in args {
if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable; }
}
return nil;
}
error_if_immutable :: proc {error_if_immutable_single, error_if_immutable_multi, };
/*
Allocates several `Int`s at once.
*/
int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(..integers);
integers := integers;
for a in &integers {
if err = #force_inline internal_clear(a, true, allocator); err != nil { return err; }
}
return nil;
}
init_multi :: proc { int_init_multi, };
copy_digits :: proc(dest, src: ^Int, digits: int, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator;
digits := digits;
/*
Check that `src` is usable and `dest` isn't immutable.
*/
assert_if_nil(dest, src);
if err = #force_inline internal_clear_if_uninitialized(src); err != nil { return err; }
digits = min(digits, len(src.digit), len(dest.digit));
return #force_inline internal_copy_digits(dest, src, digits);
}
/*
Trim unused digits.
This is used to ensure that leading zero digits are trimmed and the leading "used" digit will be non-zero.
Typically very fast. Also fixes the sign if there are no more leading digits.
*/
clamp :: proc(a: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return err; }
for a.used > 0 && a.digit[a.used - 1] == 0 {
a.used -= 1;
}
if z, _ := is_zero(a); z {
a.sign = .Zero_or_Positive;
}
return nil;
}
/*
Size binary representation
*/
int_to_bytes_size :: proc(a: ^Int, signed := false, allocator := context.allocator) -> (size_in_bytes: int, err: Error) {
assert_if_nil(a);
if err = #force_inline internal_clear_if_uninitialized(a, allocator); err != nil { return {}, err; }
size_in_bits := internal_count_bits(a);
size_in_bytes = (size_in_bits / 8);
size_in_bytes += 0 if size_in_bits % 8 == 0 else 1;
size_in_bytes += 1 if signed else 0;
return;
}
/*
Return Little Endian binary representation of `a`, either signed or unsigned.
If `a` is negative and we ask for the default unsigned representation, we return abs(a).
*/
int_to_bytes_little :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
size_in_bytes: int;
if size_in_bytes, err = int_to_bytes_size(a, signed, allocator); err != nil { return err; }
l := len(buf);
if size_in_bytes > l { return .Buffer_Overflow; }
size_in_bits := internal_count_bits(a);
i := 0;
if signed {
buf[l - 1] = 1 if a.sign == .Negative else 0;
}
for offset := 0; offset < size_in_bits; offset += 8 {
bits, _ := internal_int_bitfield_extract(a, offset, 8);
buf[i] = u8(bits & 255); i += 1;
}
return;
}
/*
Return Big Endian binary representation of `a`, either signed or unsigned.
If `a` is negative and we ask for the default unsigned representation, we return abs(a).
*/
int_to_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
size_in_bytes: int;
if size_in_bytes, err = int_to_bytes_size(a, signed, allocator); err != nil { return err; }
l := len(buf);
if size_in_bytes > l { return .Buffer_Overflow; }
size_in_bits := internal_count_bits(a);
i := l - 1;
if signed {
buf[0] = 1 if a.sign == .Negative else 0;
}
for offset := 0; offset < size_in_bits; offset += 8 {
bits, _ := internal_int_bitfield_extract(a, offset, 8);
buf[i] = u8(bits & 255); i -= 1;
}
return;
}
/*
Return Python 3.x compatible Little Endian binary representation of `a`, either signed or unsigned.
If `a` is negative when asking for an unsigned number, we return an error like Python does.
*/
int_to_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
size_in_bytes: int;
if !signed && a.sign == .Negative { return .Invalid_Argument; }
l := len(buf);
if size_in_bytes, err = int_to_bytes_size(a, signed, allocator); err != nil { return err; }
if size_in_bytes > l { return .Buffer_Overflow; }
if a.sign == .Negative {
t := &Int{};
defer destroy(t);
if err = internal_complement(t, a, allocator); err != nil { return err; }
size_in_bits := internal_count_bits(t);
i := 0;
for offset := 0; offset < size_in_bits; offset += 8 {
bits, _ := internal_int_bitfield_extract(t, offset, 8);
buf[i] = 255 - u8(bits & 255); i += 1;
}
buf[l-1] = 255;
} else {
size_in_bits := internal_count_bits(a);
i := 0;
for offset := 0; offset < size_in_bits; offset += 8 {
bits, _ := internal_int_bitfield_extract(a, offset, 8);
buf[i] = u8(bits & 255); i += 1;
}
}
return;
}
/*
Return Python 3.x compatible Big Endian binary representation of `a`, either signed or unsigned.
If `a` is negative when asking for an unsigned number, we return an error like Python does.
*/
int_to_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
size_in_bytes: int;
if !signed && a.sign == .Negative { return .Invalid_Argument; }
if a.sign == .Zero_or_Positive { return int_to_bytes_big(a, buf, signed, allocator); }
l := len(buf);
if size_in_bytes, err = int_to_bytes_size(a, signed, allocator); err != nil { return err; }
if size_in_bytes > l { return .Buffer_Overflow; }
t := &Int{};
defer destroy(t);
if err = internal_complement(t, a, allocator); err != nil { return err; }
size_in_bits := internal_count_bits(t);
i := l - 1;
for offset := 0; offset < size_in_bits; offset += 8 {
bits, _ := internal_int_bitfield_extract(t, offset, 8);
buf[i] = 255 - u8(bits & 255); i -= 1;
}
buf[0] = 255;
return;
}
/*
Read `Int` from a Big Endian binary representation.
Sign is detected from the first byte if `signed` is true.
*/
int_from_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
buf := buf;
l := len(buf);
if l == 0 { return .Invalid_Argument; }
sign: Sign;
size_in_bits := l * 8;
if signed {
/*
First byte denotes the sign.
*/
size_in_bits -= 8;
}
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
if err = internal_zero(a, false, allocator); err != nil { return err; }
if err = internal_grow(a, size_in_digits, false, allocator); err != nil { return err; }
if signed {
sign = .Zero_or_Positive if buf[0] == 0 else .Negative;
buf = buf[1:];
}
for v in buf {
if err = internal_shl(a, a, 8); err != nil { return err; }
a.digit[0] |= DIGIT(v);
}
a.sign = sign;
a.used = size_in_digits;
return internal_clamp(a);
}
/*
Read `Int` from a Big Endian Python binary representation.
Sign is detected from the first byte if `signed` is true.
*/
int_from_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
buf := buf;
l := len(buf);
if l == 0 { return .Invalid_Argument; }
sign: Sign;
size_in_bits := l * 8;
if signed {
/*
First byte denotes the sign.
*/
size_in_bits -= 8;
}
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
if err = internal_zero(a, false, allocator); err != nil { return err; }
if err = internal_grow(a, size_in_digits, false, allocator); err != nil { return err; }
if signed {
sign = .Zero_or_Positive if buf[0] == 0 else .Negative;
buf = buf[1:];
}
for v in buf {
if err = internal_shl(a, a, 8); err != nil { return err; }
if signed && sign == .Negative {
a.digit[0] |= DIGIT(255 - v);
} else {
a.digit[0] |= DIGIT(v);
}
}
a.sign = sign;
a.used = size_in_digits;
if err = internal_clamp(a); err != nil { return err; }
if signed && sign == .Negative {
return internal_sub(a, a, 1);
}
return nil;
}
/*
Read `Int` from a Little Endian binary representation.
Sign is detected from the last byte if `signed` is true.
*/
int_from_bytes_little :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
buf := buf;
l := len(buf);
if l == 0 { return .Invalid_Argument; }
sign: Sign;
size_in_bits := l * 8;
if signed {
/*
First byte denotes the sign.
*/
size_in_bits -= 8;
}
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
if err = internal_zero(a, false, allocator); err != nil { return err; }
if err = internal_grow(a, size_in_digits, false, allocator); err != nil { return err; }
if signed {
sign = .Zero_or_Positive if buf[l-1] == 0 else .Negative;
buf = buf[:l-1];
l -= 1;
}
for _, i in buf {
if err = internal_shl(a, a, 8); err != nil { return err; }
a.digit[0] |= DIGIT(buf[l-i-1]);
}
a.sign = sign;
a.used = size_in_digits;
return internal_clamp(a);
}
/*
Read `Int` from a Little Endian Python binary representation.
Sign is detected from the first byte if `signed` is true.
*/
int_from_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocator := context.allocator) -> (err: Error) {
assert_if_nil(a);
buf := buf;
l := len(buf);
if l == 0 { return .Invalid_Argument; }
sign: Sign;
size_in_bits := l * 8;
if signed {
/*
First byte denotes the sign.
*/
size_in_bits -= 8;
}
size_in_digits := (size_in_bits + _DIGIT_BITS - 1) / _DIGIT_BITS;
size_in_digits += 0 if size_in_bits % 8 == 0 else 1;
if err = internal_zero(a, false, allocator); err != nil { return err; }
if err = internal_grow(a, size_in_digits, false, allocator); err != nil { return err; }
if signed {
sign = .Zero_or_Positive if buf[l-1] == 0 else .Negative;
buf = buf[:l-1];
l -= 1;
}
for _, i in buf {
if err = internal_shl(a, a, 8); err != nil { return err; }
if signed && sign == .Negative {
a.digit[0] |= DIGIT(255 - buf[l-i-1]);
} else {
a.digit[0] |= DIGIT(buf[l-i-1]);
}
}
a.sign = sign;
a.used = size_in_digits;
if err = internal_clamp(a); err != nil { return err; }
if signed && sign == .Negative {
return internal_sub(a, a, 1);
}
return nil;
}
/*
Initialize constants.
*/
INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
initialize_constants :: proc() -> (res: int) {
internal_set( INT_ZERO, 0); INT_ZERO.flags = {.Immutable};
internal_set( INT_ONE, 1); INT_ONE.flags = {.Immutable};
internal_set(INT_MINUS_ONE, -1); INT_MINUS_ONE.flags = {.Immutable};
/*
We set these special values to -1 or 1 so they don't get mistake for zero accidentally.
This allows for shortcut tests of is_zero as .used == 0.
*/
internal_set( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN};
internal_set( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf};
internal_set( INT_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf};
return _DEFAULT_MUL_KARATSUBA_CUTOFF;
}
/*
Destroy constants.
Optional for an EXE, as this would be called at the very end of a process.
*/
destroy_constants :: proc() {
internal_destroy(INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN);
}
assert_if_nil :: #force_inline proc(integers: ..^Int, loc := #caller_location) {
integers := integers;
for i in &integers {
assert(i != nil, "(nil)", loc);
}
}

2606
core/math/big/internal.odin Normal file

File diff suppressed because it is too large Load Diff

144
core/math/big/logical.odin Normal file
View File

@@ -0,0 +1,144 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file contains logical operations like `and`, `or` and `xor`.
*/
/*
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;`
*/
int_and :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return err; }
return #force_inline internal_int_and(dest, a, b);
}
and :: proc { int_and, };
/*
2's complement `or`, returns `dest = a | b;`
*/
int_or :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return err; }
return #force_inline internal_int_or(dest, a, b);
}
or :: proc { int_or, };
/*
2's complement `xor`, returns `dest = a ^ b;`
*/
int_xor :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return err; }
return #force_inline internal_int_xor(dest, a, b);
}
xor :: proc { int_xor, };
/*
dest = ~src
*/
int_complement :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
/*
Check that `src` and `dest` are usable.
*/
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
return #force_inline internal_int_complement(dest, src);
}
complement :: proc { int_complement, };
/*
quotient, remainder := numerator >> bits;
`remainder` is allowed to be passed a `nil`, in which case `mod` won't be computed.
*/
int_shrmod :: proc(quotient, remainder, numerator: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(quotient, numerator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(quotient, numerator); err != nil { return err; }
return #force_inline internal_int_shrmod(quotient, remainder, numerator, bits);
}
shrmod :: proc { int_shrmod, };
int_shr :: proc(dest, source: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
return #force_inline shrmod(dest, nil, source, bits, allocator);
}
shr :: proc { int_shr, };
/*
Shift right by `digits` * _DIGIT_BITS bits.
*/
int_shr_digit :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) {
/*
Check that `quotient` is usable.
*/
assert_if_nil(quotient);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(quotient); err != nil { return err; }
return #force_inline internal_int_shr_digit(quotient, digits);
}
shr_digit :: proc { int_shr_digit, };
/*
Shift right by a certain bit count with sign extension.
*/
int_shr_signed :: proc(dest, src: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
return #force_inline internal_int_shr_signed(dest, src, bits);
}
shr_signed :: proc { int_shr_signed, };
/*
Shift left by a certain bit count.
*/
int_shl :: proc(dest, src: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
return #force_inline internal_int_shl(dest, src, bits);
}
shl :: proc { int_shl, };
/*
Shift left by `digits` * _DIGIT_BITS bits.
*/
int_shl_digit :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) {
/*
Check that `quotient` is usable.
*/
assert_if_nil(quotient);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(quotient); err != nil { return err; }
return #force_inline internal_int_shl_digit(quotient, digits);
}
shl_digit :: proc { int_shl_digit, };

33
core/math/big/prime.odin Normal file
View File

@@ -0,0 +1,33 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file contains prime finding operations.
*/
/*
Determines if an Integer is divisible by one of the _PRIME_TABLE primes.
Returns true if it is, false if not.
*/
int_prime_is_divisible :: proc(a: ^Int, allocator := context.allocator) -> (res: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return {}, err; }
rem: DIGIT;
for prime in _private_prime_table {
if rem, err = #force_inline int_mod_digit(a, prime); err != nil { return false, err; }
if rem == 0 { return true, nil; }
}
/*
Default to not divisible.
*/
return false, nil;
}

1247
core/math/big/private.odin Normal file

File diff suppressed because it is too large Load Diff

559
core/math/big/public.odin Normal file
View File

@@ -0,0 +1,559 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file contains basic arithmetic operations like `add`, `sub`, `mul`, `div`, ...
*/
/*
===========================
User-level routines
===========================
*/
/*
High-level addition. Handles sign.
*/
int_add :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, a, b); err != nil { return err; }
/*
All parameters have been initialized.
*/
return #force_inline internal_int_add_signed(dest, a, b);
}
/*
Adds the unsigned `DIGIT` immediate to an `Int`,
such that the `DIGIT` doesn't have to be turned into an `Int` first.
dest = a + digit;
*/
int_add_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return err; }
/*
Grow destination as required.
*/
if err = grow(dest, a.used + 1); err != nil { return err; }
/*
All parameters have been initialized.
*/
return #force_inline internal_int_add_digit(dest, a, digit);
}
/*
High-level subtraction, dest = number - decrease. Handles signs.
*/
int_sub :: proc(dest, number, decrease: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, number, decrease);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, number, decrease); err != nil { return err; }
/*
All parameters have been initialized.
*/
return #force_inline internal_int_sub_signed(dest, number, decrease);
}
/*
Adds the unsigned `DIGIT` immediate to an `Int`,
such that the `DIGIT` doesn't have to be turned into an `Int` first.
dest = a - digit;
*/
int_sub_digit :: proc(dest, a: ^Int, digit: DIGIT, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return err; }
/*
Grow destination as required.
*/
if err = grow(dest, a.used + 1); err != nil { return err; }
/*
All parameters have been initialized.
*/
return #force_inline internal_int_sub_digit(dest, a, digit);
}
/*
dest = src / 2
dest = src >> 1
*/
int_halve :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
/*
Grow destination as required.
*/
if dest != src { if err = grow(dest, src.used + 1); err != nil { return err; } }
return #force_inline internal_int_shr1(dest, src);
}
halve :: proc { int_halve, };
shr1 :: halve;
/*
dest = src * 2
dest = src << 1
*/
int_double :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
/*
Grow destination as required.
*/
if dest != src { if err = grow(dest, src.used + 1); err != nil { return err; } }
return #force_inline internal_int_shl1(dest, src);
}
double :: proc { int_double, };
shl1 :: double;
/*
Multiply by a DIGIT.
*/
int_mul_digit :: proc(dest, src: ^Int, multiplier: DIGIT, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(src, dest); err != nil { return err; }
return #force_inline internal_int_mul_digit(dest, src, multiplier);
}
/*
High level multiplication (handles sign).
*/
int_mul :: proc(dest, src, multiplier: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src, multiplier);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src, multiplier); err != nil { return err; }
return #force_inline internal_int_mul(dest, src, multiplier);
}
mul :: proc { int_mul, int_mul_digit, };
sqr :: proc(dest, src: ^Int) -> (err: Error) { return mul(dest, src, src); }
/*
divmod.
Both the quotient and remainder are optional and may be passed a nil.
*/
int_divmod :: proc(quotient, remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator;
/*
Early out if neither of the results is wanted.
*/
if quotient == nil && remainder == nil { return nil; }
if err = internal_clear_if_uninitialized(numerator, denominator); err != nil { return err; }
return #force_inline internal_divmod(quotient, remainder, numerator, denominator);
}
int_divmod_digit :: proc(quotient, numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (remainder: DIGIT, err: Error) {
assert_if_nil(quotient, numerator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(numerator); err != nil { return 0, err; }
return #force_inline internal_divmod(quotient, numerator, denominator);
}
divmod :: proc{ int_divmod, int_divmod_digit, };
int_div :: proc(quotient, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(quotient, numerator, denominator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(numerator, denominator); err != nil { return err; }
return #force_inline internal_divmod(quotient, nil, numerator, denominator);
}
int_div_digit :: proc(quotient, numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (err: Error) {
assert_if_nil(quotient, numerator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(numerator); err != nil { return err; }
remainder: DIGIT;
remainder, err = #force_inline internal_divmod(quotient, numerator, denominator);
return err;
}
div :: proc { int_div, int_div_digit, };
/*
remainder = numerator % denominator.
0 <= remainder < denominator if denominator > 0
denominator < remainder <= 0 if denominator < 0
*/
int_mod :: proc(remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, numerator, denominator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(numerator, denominator); err != nil { return err; }
return #force_inline internal_int_mod(remainder, numerator, denominator);
}
int_mod_digit :: proc(numerator: ^Int, denominator: DIGIT, allocator := context.allocator) -> (remainder: DIGIT, err: Error) {
return #force_inline internal_divmod(nil, numerator, denominator, allocator);
}
mod :: proc { int_mod, int_mod_digit, };
/*
remainder = (number + addend) % modulus.
*/
int_addmod :: proc(remainder, number, addend, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, number, addend);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(number, addend, modulus); err != nil { return err; }
return #force_inline internal_addmod(remainder, number, addend, modulus);
}
addmod :: proc { int_addmod, };
/*
remainder = (number - decrease) % modulus.
*/
int_submod :: proc(remainder, number, decrease, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, number, decrease);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(number, decrease, modulus); err != nil { return err; }
return #force_inline internal_submod(remainder, number, decrease, modulus);
}
submod :: proc { int_submod, };
/*
remainder = (number * multiplicand) % modulus.
*/
int_mulmod :: proc(remainder, number, multiplicand, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, number, multiplicand);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(number, multiplicand, modulus); err != nil { return err; }
return #force_inline internal_mulmod(remainder, number, multiplicand, modulus);
}
mulmod :: proc { int_mulmod, };
/*
remainder = (number * number) % modulus.
*/
int_sqrmod :: proc(remainder, number, modulus: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, number, modulus);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(number, modulus); err != nil { return err; }
return #force_inline internal_sqrmod(remainder, number, modulus);
}
sqrmod :: proc { int_sqrmod, };
int_factorial :: proc(res: ^Int, n: int, allocator := context.allocator) -> (err: Error) {
if n < 0 || n > FACTORIAL_MAX_N { return .Invalid_Argument; }
assert_if_nil(res);
return #force_inline internal_int_factorial(res, n, allocator);
}
factorial :: proc { int_factorial, };
/*
Number of ways to choose `k` items from `n` items.
Also known as the binomial coefficient.
TODO: Speed up.
Could be done faster by reusing code from factorial and reusing the common "prefix" results for n!, k! and n-k!
We know that n >= k, otherwise we early out with res = 0.
So:
n-k, keep result
n, start from previous result
k, start from previous result
*/
int_choose_digit :: proc(res: ^Int, n, k: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(res);
context.allocator = allocator;
if n < 0 || n > FACTORIAL_MAX_N { return .Invalid_Argument; }
if k > n { return internal_zero(res); }
/*
res = n! / (k! * (n - k)!)
*/
n_fac, k_fac, n_minus_k_fac := &Int{}, &Int{}, &Int{};
defer internal_destroy(n_fac, k_fac, n_minus_k_fac);
if err = #force_inline internal_int_factorial(n_minus_k_fac, n - k); err != nil { return err; }
if err = #force_inline internal_int_factorial(k_fac, k); err != nil { return err; }
if err = #force_inline internal_mul(k_fac, k_fac, n_minus_k_fac); err != nil { return err; }
if err = #force_inline internal_int_factorial(n_fac, n); err != nil { return err; }
if err = #force_inline internal_div(res, n_fac, k_fac); err != nil { return err; }
return err;
}
choose :: proc { int_choose_digit, };
/*
Function computing both GCD and (if target isn't `nil`) also LCM.
*/
int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
if res_gcd == nil && res_lcm == nil { return nil; }
assert_if_nil(a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return err; }
return #force_inline internal_int_gcd_lcm(res_gcd, res_lcm, a, b);
}
gcd_lcm :: proc { int_gcd_lcm, };
/*
Greatest Common Divisor.
*/
int_gcd :: proc(res, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
return #force_inline int_gcd_lcm(res, nil, a, b, allocator);
}
gcd :: proc { int_gcd, };
/*
Least Common Multiple.
*/
int_lcm :: proc(res, a, b: ^Int, allocator := context.allocator) -> (err: Error) {
return #force_inline int_gcd_lcm(nil, res, a, b, allocator);
}
lcm :: proc { int_lcm, };
/*
remainder = numerator % (1 << bits)
*/
int_mod_bits :: proc(remainder, numerator: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(remainder, numerator);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(remainder, numerator); err != nil { return err; }
if bits < 0 { return .Invalid_Argument; }
return #force_inline internal_int_mod_bits(remainder, numerator, bits);
}
mod_bits :: proc { int_mod_bits, };
/*
Logs and roots and such.
*/
int_log :: proc(a: ^Int, base: DIGIT, allocator := context.allocator) -> (res: int, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return 0, err; }
return #force_inline internal_int_log(a, base);
}
digit_log :: proc(a: DIGIT, base: DIGIT) -> (log: int, err: Error) {
return #force_inline internal_digit_log(a, base);
}
log :: proc { int_log, digit_log, };
/*
Calculate `dest = base^power` using a square-multiply algorithm.
*/
int_pow :: proc(dest, base: ^Int, power: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, base);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, base); err != nil { return err; }
return #force_inline internal_int_pow(dest, base, power);
}
/*
Calculate `dest = base^power` using a square-multiply algorithm.
*/
int_pow_int :: proc(dest: ^Int, base, power: int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest);
return #force_inline internal_pow(dest, base, power, allocator);
}
pow :: proc { int_pow, int_pow_int, small_pow, };
exp :: pow;
small_pow :: proc(base: _WORD, exponent: _WORD) -> (result: _WORD) {
return #force_inline internal_small_pow(base, exponent);
}
/*
This function is less generic than `root_n`, simpler and faster.
*/
int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
assert_if_nil(dest, src);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
return #force_inline internal_int_sqrt(dest, src);
}
sqrt :: proc { int_sqrt, };
/*
Find the nth root of an Integer.
Result found such that `(dest)**n <= src` and `(dest+1)**n > src`
This algorithm uses Newton's approximation `x[i+1] = x[i] - f(x[i])/f'(x[i])`,
which will find the root in `log(n)` time where each step involves a fair bit.
*/
int_root_n :: proc(dest, src: ^Int, n: int, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator;
/*
Fast path for n == 2.
*/
if n == 2 { return sqrt(dest, src); }
assert_if_nil(dest, src);
/*
Initialize dest + src if needed.
*/
if err = internal_clear_if_uninitialized(dest, src); err != nil { return err; }
return #force_inline internal_int_root_n(dest, src, n);
}
root_n :: proc { int_root_n, };
/*
Comparison routines.
*/
int_is_initialized :: proc(a: ^Int) -> bool {
if a == nil { return false; }
return #force_inline internal_int_is_initialized(a);
}
int_is_zero :: proc(a: ^Int, allocator := context.allocator) -> (zero: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_zero(a), nil;
}
int_is_positive :: proc(a: ^Int, allocator := context.allocator) -> (positive: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_positive(a), nil;
}
int_is_negative :: proc(a: ^Int, allocator := context.allocator) -> (negative: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_negative(a), nil;
}
int_is_even :: proc(a: ^Int, allocator := context.allocator) -> (even: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_even(a), nil;
}
int_is_odd :: proc(a: ^Int, allocator := context.allocator) -> (odd: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_odd(a), nil;
}
platform_int_is_power_of_two :: #force_inline proc(a: int) -> bool {
return ((a) != 0) && (((a) & ((a) - 1)) == 0);
}
int_is_power_of_two :: proc(a: ^Int, allocator := context.allocator) -> (res: bool, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return false, err; }
return #force_inline internal_is_power_of_two(a), nil;
}
/*
Compare two `Int`s, signed.
*/
int_compare :: proc(a, b: ^Int, allocator := context.allocator) -> (comparison: int, err: Error) {
assert_if_nil(a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return 0, err; }
return #force_inline internal_cmp(a, b), nil;
}
int_cmp :: int_compare;
/*
Compare an `Int` to an unsigned number upto the size of the backing type.
*/
int_compare_digit :: proc(a: ^Int, b: DIGIT, allocator := context.allocator) -> (comparison: int, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a); err != nil { return 0, err; }
return #force_inline internal_cmp_digit(a, b), nil;
}
int_cmp_digit :: int_compare_digit;
/*
Compare the magnitude of two `Int`s, unsigned.
*/
int_compare_magnitude :: proc(a, b: ^Int, allocator := context.allocator) -> (res: int, err: Error) {
assert_if_nil(a, b);
context.allocator = allocator;
if err = internal_clear_if_uninitialized(a, b); err != nil { return 0, err; }
return #force_inline internal_cmp_mag(a, b), nil;
}

485
core/math/big/radix.odin Normal file
View File

@@ -0,0 +1,485 @@
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa).
TODO:
- Use Barrett reduction for non-powers-of-two.
- Also look at extracting and splatting several digits at once.
*/
import "core:intrinsics"
import "core:mem"
/*
This version of `itoa` allocates one behalf of the caller. The caller must free the string.
*/
int_itoa_string :: proc(a: ^Int, radix := i8(-1), zero_terminate := false, allocator := context.allocator) -> (res: string, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
a := a; radix := radix;
if err = clear_if_uninitialized(a); err != nil { return "", err; }
/*
Radix defaults to 10.
*/
radix = radix if radix > 0 else 10;
/*
TODO: If we want to write a prefix for some of the radixes, we can oversize the buffer.
Then after the digits are written and the string is reversed
*/
/*
Calculate the size of the buffer we need, and
*/
size: int;
/*
Exit if calculating the size returned an error.
*/
if size, err = radix_size(a, radix, zero_terminate); err != nil {
return "", err;
}
/*
Allocate the buffer we need.
*/
buffer := make([]u8, size);
/*
Write the digits out into the buffer.
*/
written: int;
written, err = int_itoa_raw(a, radix, buffer, size, zero_terminate);
return string(buffer[:written]), err;
}
/*
This version of `itoa` allocates one behalf of the caller. The caller must free the string.
*/
int_itoa_cstring :: proc(a: ^Int, radix := i8(-1), allocator := context.allocator) -> (res: cstring, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
a := a; radix := radix;
if err = clear_if_uninitialized(a); err != nil { return "", err; }
/*
Radix defaults to 10.
*/
radix = radix if radix > 0 else 10;
s: string;
s, err = int_itoa_string(a, radix, true);
return cstring(raw_data(s)), err;
}
/*
A low-level `itoa` using a caller-provided buffer. `itoa_string` and `itoa_cstring` use this.
You can use also use it if you want to pre-allocate a buffer and optionally reuse it.
Use `radix_size` or `radix_size_estimate` to determine a buffer size big enough.
You can pass the output of `radix_size` to `size` if you've previously called it to size
the output buffer. If you haven't, this routine will call it. This way it knows if the buffer
is the appropriate size, and we can write directly in place without a reverse step at the end.
=== === === IMPORTANT === === ===
If you determined the buffer size using `radix_size_estimate`, or have a buffer
that you reuse that you know is large enough, don't pass this size unless you know what you are doing,
because we will always write backwards starting at last byte of the buffer.
Keep in mind that if you set `size` yourself and it's smaller than the buffer,
it'll result in buffer overflows, as we use it to avoid reversing at the end
and having to perform a buffer overflow check each character.
*/
int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_terminate := false) -> (written: int, err: Error) {
assert_if_nil(a);
a := a; radix := radix; size := size;
if err = clear_if_uninitialized(a); err != nil { return 0, err; }
/*
Radix defaults to 10.
*/
radix = radix if radix > 0 else 10;
if radix < 2 || radix > 64 {
return 0, .Invalid_Argument;
}
/*
We weren't given a size. Let's compute it.
*/
if size == -1 {
if size, err = radix_size(a, radix, zero_terminate); err != nil {
return 0, err;
}
}
/*
Early exit if the buffer we were given is too small.
*/
available := len(buffer);
if available < size {
return 0, .Buffer_Overflow;
}
/*
Fast path for when `Int` == 0 or the entire `Int` fits in a single radix digit.
*/
z, _ := is_zero(a);
if z || (a.used == 1 && a.digit[0] < DIGIT(radix)) {
if zero_terminate {
available -= 1;
buffer[available] = 0;
}
available -= 1;
buffer[available] = RADIX_TABLE[a.digit[0]];
if n, _ := is_neg(a); n {
available -= 1;
buffer[available] = '-';
}
/*
If we overestimated the size, we need to move the buffer left.
*/
written = len(buffer) - available;
if written < size {
diff := size - written;
mem.copy(&buffer[0], &buffer[diff], written);
}
return written, nil;
}
/*
Fast path for when `Int` fits within a `_WORD`.
*/
if a.used == 1 || a.used == 2 {
if zero_terminate {
available -= 1;
buffer[available] = 0;
}
val := _WORD(a.digit[1]) << _DIGIT_BITS + _WORD(a.digit[0]);
for val > 0 {
q := val / _WORD(radix);
available -= 1;
buffer[available] = RADIX_TABLE[val - (q * _WORD(radix))];
val = q;
}
if n, _ := is_neg(a); n {
available -= 1;
buffer[available] = '-';
}
/*
If we overestimated the size, we need to move the buffer left.
*/
written = len(buffer) - available;
if written < size {
diff := size - written;
mem.copy(&buffer[0], &buffer[diff], written);
}
return written, nil;
}
/*
Fast path for radixes that are a power of two.
*/
if is_power_of_two(int(radix)) {
if zero_terminate {
available -= 1;
buffer[available] = 0;
}
shift, count: int;
// mask := _WORD(radix - 1);
shift, err = log(DIGIT(radix), 2);
count, err = count_bits(a);
digit: _WORD;
for offset := 0; offset < count; offset += shift {
bits_to_get := int(min(count - offset, shift));
digit, err = int_bitfield_extract(a, offset, bits_to_get);
if err != nil {
return len(buffer) - available, .Invalid_Argument;
}
available -= 1;
buffer[available] = RADIX_TABLE[digit];
}
if n, _ := is_neg(a); n {
available -= 1;
buffer[available] = '-';
}
/*
If we overestimated the size, we need to move the buffer left.
*/
written = len(buffer) - available;
if written < size {
diff := size - written;
mem.copy(&buffer[0], &buffer[diff], written);
}
return written, nil;
}
return _itoa_raw_full(a, radix, buffer, zero_terminate);
}
itoa :: proc{int_itoa_string, int_itoa_raw};
int_to_string :: int_itoa_string;
int_to_cstring :: int_itoa_cstring;
/*
Read a string [ASCII] in a given radix.
*/
int_atoi :: proc(res: ^Int, input: string, radix: i8, allocator := context.allocator) -> (err: Error) {
assert_if_nil(res);
input := input;
context.allocator = allocator;
/*
Make sure the radix is ok.
*/
if radix < 2 || radix > 64 { return .Invalid_Argument; }
/*
Set the integer to the default of zero.
*/
if err = internal_zero(res); err != nil { return err; }
/*
We'll interpret an empty string as zero.
*/
if len(input) == 0 { return nil; }
/*
If the leading digit is a minus set the sign to negative.
Given the above early out, the length should be at least 1.
*/
sign := Sign.Zero_or_Positive;
if input[0] == '-' {
input = input[1:];
sign = .Negative;
}
/*
Process each digit of the string.
*/
ch: rune;
for len(input) > 0 {
/* if the radix <= 36 the conversion is case insensitive
* this allows numbers like 1AB and 1ab to represent the same value
* [e.g. in hex]
*/
ch = rune(input[0]);
if radix <= 36 && ch >= 'a' && ch <= 'z' {
ch -= 32; // 'a' - 'A'
}
pos := ch - '+';
if RADIX_TABLE_REVERSE_SIZE <= pos {
break;
}
y := RADIX_TABLE_REVERSE[pos];
/* if the char was found in the map
* and is less than the given radix add it
* to the number, otherwise exit the loop.
*/
if y >= u8(radix) {
break;
}
if err = internal_mul(res, res, DIGIT(radix)); err != nil { return err; }
if err = internal_add(res, res, DIGIT(y)); err != nil { return err; }
input = input[1:];
}
/*
If an illegal character was found, fail.
*/
if len(input) > 0 && ch != 0 && ch != '\r' && ch != '\n' {
return .Invalid_Argument;
}
/*
Set the sign only if res != 0.
*/
if res.used > 0 {
res.sign = sign;
}
return nil;
}
atoi :: proc { int_atoi, };
/*
We size for `string` by default.
*/
radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false, allocator := context.allocator) -> (size: int, err: Error) {
a := a;
assert_if_nil(a);
if radix < 2 || radix > 64 { return -1, .Invalid_Argument; }
if err = clear_if_uninitialized(a); err != nil { return {}, err; }
if internal_is_zero(a) {
if zero_terminate {
return 2, nil;
}
return 1, nil;
}
if internal_is_power_of_two(a) {
/*
Calculate `log` on a temporary "copy" with its sign set to positive.
*/
t := &Int{
used = a.used,
sign = .Zero_or_Positive,
digit = a.digit,
};
if size, err = internal_log(t, DIGIT(radix)); err != nil { return {}, err; }
} else {
la, k := &Int{}, &Int{};
defer internal_destroy(la, k);
/* la = floor(log_2(a)) + 1 */
bit_count := internal_count_bits(a);
if err = internal_set(la, bit_count); err != nil { return {}, err; }
/* k = floor(2^29/log_2(radix)) + 1 */
lb := _log_bases;
if err = internal_set(k, lb[radix]); err != nil { return {}, err; }
/* n = floor((la * k) / 2^29) + 1 */
if err = internal_mul(k, la, k); err != nil { return 0, err; }
if err = internal_shr(k, k, _RADIX_SIZE_SCALE); err != nil { return {}, err; }
/* The "+1" here is the "+1" in "floor((la * k) / 2^29) + 1" */
/* n = n + 1 + EOS + sign */
size_, _ := internal_get(k, u128);
size = int(size_);
}
/*
log truncates to zero, so we need to add one more, and one for `-` if negative.
*/
size += 2 if a.sign == .Negative else 1;
size += 1 if zero_terminate else 0;
return size, nil;
}
/*
Overestimate the size needed for the bigint to string conversion by a very small amount.
The error is about 10^-8; it will overestimate the result by at most 11 elements for
a number of the size 2^(2^31)-1 which is currently the largest possible in this library.
Some short tests gave no results larger than 5 (plus 2 for sign and EOS).
*/
/*
Table of {0, INT(log_2([1..64])*2^p)+1 } where p is the scale
factor defined in MP_RADIX_SIZE_SCALE and INT() extracts the integer part (truncating).
Good for 32 bit "int". Set MP_RADIX_SIZE_SCALE = 61 and recompute values
for 64 bit "int".
*/
_RADIX_SIZE_SCALE :: 29;
_log_bases :: [65]u32{
0, 0, 0x20000001, 0x14309399, 0x10000001,
0xdc81a35, 0xc611924, 0xb660c9e, 0xaaaaaab, 0xa1849cd,
0x9a209a9, 0x94004e1, 0x8ed19c2, 0x8a5ca7d, 0x867a000,
0x830cee3, 0x8000001, 0x7d42d60, 0x7ac8b32, 0x7887847,
0x7677349, 0x749131f, 0x72d0163, 0x712f657, 0x6fab5db,
0x6e40d1b, 0x6ced0d0, 0x6badbde, 0x6a80e3b, 0x6964c19,
0x6857d31, 0x6758c38, 0x6666667, 0x657fb21, 0x64a3b9f,
0x63d1ab4, 0x6308c92, 0x624869e, 0x618ff47, 0x60dedea,
0x6034ab0, 0x5f90e7b, 0x5ef32cb, 0x5e5b1b2, 0x5dc85c3,
0x5d3aa02, 0x5cb19d9, 0x5c2d10f, 0x5bacbbf, 0x5b3064f,
0x5ab7d68, 0x5a42df0, 0x59d1506, 0x5962ffe, 0x58f7c57,
0x588f7bc, 0x582a000, 0x57c7319, 0x5766f1d, 0x5709243,
0x56adad9, 0x565474d, 0x55fd61f, 0x55a85e8, 0x5555556,
};
/*
Characters used in radix conversions.
*/
RADIX_TABLE := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
RADIX_TABLE_REVERSE := [RADIX_TABLE_REVERSE_SIZE]u8{
0x3e, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x01, 0x02, 0x03, 0x04, /* +,-./01234 */
0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56789:;<=> */
0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, /* ?@ABCDEFGH */
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, /* IJKLMNOPQR */
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0xff, 0xff, /* STUVWXYZ[\ */
0xff, 0xff, 0xff, 0xff, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, /* ]^_`abcdef */
0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, /* ghijklmnop */
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, /* qrstuvwxyz */
};
RADIX_TABLE_REVERSE_SIZE :: 80;
/*
Stores a bignum as a ASCII string in a given radix (2..64)
The buffer must be appropriately sized. This routine doesn't check.
*/
_itoa_raw_full :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false, allocator := context.allocator) -> (written: int, err: Error) {
assert_if_nil(a);
context.allocator = allocator;
temp, denominator := &Int{}, &Int{};
if err = internal_copy(temp, a); err != nil { return 0, err; }
if err = internal_set(denominator, radix); err != nil { return 0, err; }
available := len(buffer);
if zero_terminate {
available -= 1;
buffer[available] = 0;
}
if a.sign == .Negative {
temp.sign = .Zero_or_Positive;
}
remainder: DIGIT;
for {
if remainder, err = #force_inline internal_divmod(temp, temp, DIGIT(radix)); err != nil {
internal_destroy(temp, denominator);
return len(buffer) - available, err;
}
available -= 1;
buffer[available] = RADIX_TABLE[remainder];
if temp.used == 0 {
break;
}
}
if a.sign == .Negative {
available -= 1;
buffer[available] = '-';
}
internal_destroy(temp, denominator);
/*
If we overestimated the size, we need to move the buffer left.
*/
written = len(buffer) - available;
if written < len(buffer) {
diff := len(buffer) - written;
mem.copy(&buffer[0], &buffer[diff], written);
}
return written, nil;
}

369
core/math/big/test.odin Normal file
View File

@@ -0,0 +1,369 @@
//+ignore
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file exports procedures for use with the test.py test suite.
*/
/*
TODO: Write tests for `internal_*` and test reusing parameters with the public implementations.
*/
import "core:runtime"
import "core:strings"
PyRes :: struct {
res: cstring,
err: Error,
}
@export test_initialize_constants :: proc "c" () -> (res: u64) {
context = runtime.default_context();
return u64(initialize_constants());
}
@export test_error_string :: proc "c" (err: Error) -> (res: cstring) {
context = runtime.default_context();
es := Error_String;
return strings.clone_to_cstring(es[err], context.temp_allocator);
}
@export test_add :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
aa, bb, sum := &Int{}, &Int{}, &Int{};
defer internal_destroy(aa, bb, sum);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":add:atoi(a):", err=err}; }
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":add:atoi(b):", err=err}; }
if bb.used == 1 {
if err = #force_inline internal_add(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err}; }
} else {
if err = #force_inline internal_add(sum, aa, bb); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err}; }
}
r: cstring;
r, err = int_itoa_cstring(sum, 16, context.temp_allocator);
if err != nil { return PyRes{res=":add:itoa(sum):", err=err}; }
return PyRes{res = r, err = nil};
}
@export test_sub :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
aa, bb, sum := &Int{}, &Int{}, &Int{};
defer internal_destroy(aa, bb, sum);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":sub:atoi(a):", err=err}; }
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":sub:atoi(b):", err=err}; }
if bb.used == 1 {
if err = #force_inline internal_sub(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err}; }
} else {
if err = #force_inline internal_sub(sum, aa, bb); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err}; }
}
r: cstring;
r, err = int_itoa_cstring(sum, 16, context.temp_allocator);
if err != nil { return PyRes{res=":sub:itoa(sum):", err=err}; }
return PyRes{res = r, err = nil};
}
@export test_mul :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
aa, bb, product := &Int{}, &Int{}, &Int{};
defer internal_destroy(aa, bb, product);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":mul:atoi(a):", err=err}; }
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":mul:atoi(b):", err=err}; }
if err = #force_inline internal_mul(product, aa, bb); err != nil { return PyRes{res=":mul:mul(product,a,b):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(product, 16, context.temp_allocator);
if err != nil { return PyRes{res=":mul:itoa(product):", err=err}; }
return PyRes{res = r, err = nil};
}
@export test_sqr :: proc "c" (a: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
aa, square := &Int{}, &Int{};
defer internal_destroy(aa, square);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":sqr:atoi(a):", err=err}; }
if err = #force_inline internal_sqr(square, aa); err != nil { return PyRes{res=":sqr:sqr(square,a):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(square, 16, context.temp_allocator);
if err != nil { return PyRes{res=":sqr:itoa(square):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
NOTE(Jeroen): For simplicity, we don't return the quotient and the remainder, just the quotient.
*/
@export test_div :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
aa, bb, quotient := &Int{}, &Int{}, &Int{};
defer internal_destroy(aa, bb, quotient);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":div:atoi(a):", err=err}; }
if err = atoi(bb, string(b), 16); err != nil { return PyRes{res=":div:atoi(b):", err=err}; }
if err = #force_inline internal_div(quotient, aa, bb); err != nil { return PyRes{res=":div:div(quotient,a,b):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(quotient, 16, context.temp_allocator);
if err != nil { return PyRes{res=":div:itoa(quotient):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
res = log(a, base)
*/
@export test_log :: proc "c" (a: cstring, base := DIGIT(2)) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
l: int;
aa := &Int{};
defer internal_destroy(aa);
if err = atoi(aa, string(a), 16); err != nil { return PyRes{res=":log:atoi(a):", err=err}; }
if l, err = #force_inline internal_log(aa, base); err != nil { return PyRes{res=":log:log(a, base):", err=err}; }
#force_inline internal_zero(aa);
aa.digit[0] = DIGIT(l) & _MASK;
aa.digit[1] = DIGIT(l) >> _DIGIT_BITS;
aa.used = 2;
clamp(aa);
r: cstring;
r, err = int_itoa_cstring(aa, 16, context.temp_allocator);
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = base^power
*/
@export test_pow :: proc "c" (base: cstring, power := int(2)) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
dest, bb := &Int{}, &Int{};
defer internal_destroy(dest, bb);
if err = atoi(bb, string(base), 16); err != nil { return PyRes{res=":pow:atoi(base):", err=err}; }
if err = #force_inline internal_pow(dest, bb, power); err != nil { return PyRes{res=":pow:pow(dest, base, power):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = sqrt(src)
*/
@export test_sqrt :: proc "c" (source: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":sqrt:atoi(src):", err=err}; }
if err = #force_inline internal_sqrt(src, src); err != nil { return PyRes{res=":sqrt:sqrt(src):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":log:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = root_n(src, power)
*/
@export test_root_n :: proc "c" (source: cstring, power: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":root_n:atoi(src):", err=err}; }
if err = #force_inline internal_root_n(src, src, power); err != nil { return PyRes{res=":root_n:root_n(src):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":root_n:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = shr_digit(src, digits)
*/
@export test_shr_digit :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_digit:atoi(src):", err=err}; }
if err = #force_inline internal_shr_digit(src, digits); err != nil { return PyRes{res=":shr_digit:shr_digit(src):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":shr_digit:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = shl_digit(src, digits)
*/
@export test_shl_digit :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shl_digit:atoi(src):", err=err}; }
if err = #force_inline internal_shl_digit(src, digits); err != nil { return PyRes{res=":shl_digit:shr_digit(src):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":shl_digit:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = shr(src, bits)
*/
@export test_shr :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr:atoi(src):", err=err}; }
if err = #force_inline internal_shr(src, src, bits); err != nil { return PyRes{res=":shr:shr(src, bits):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":shr:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = shr_signed(src, bits)
*/
@export test_shr_signed :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_signed:atoi(src):", err=err}; }
if err = #force_inline internal_shr_signed(src, src, bits); err != nil { return PyRes{res=":shr_signed:shr_signed(src, bits):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":shr_signed:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = shl(src, bits)
*/
@export test_shl :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
src := &Int{};
defer internal_destroy(src);
if err = atoi(src, string(source), 16); err != nil { return PyRes{res=":shl:atoi(src):", err=err}; }
if err = #force_inline internal_shl(src, src, bits); err != nil { return PyRes{res=":shl:shl(src, bits):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(src, 16, context.temp_allocator);
if err != nil { return PyRes{res=":shl:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = factorial(n)
*/
@export test_factorial :: proc "c" (n: int) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
dest := &Int{};
defer internal_destroy(dest);
if err = #force_inline internal_int_factorial(dest, n); err != nil { return PyRes{res=":factorial:factorial(n):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
if err != nil { return PyRes{res=":factorial:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = gcd(a, b)
*/
@export test_gcd :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
ai, bi, dest := &Int{}, &Int{}, &Int{};
defer internal_destroy(ai, bi, dest);
if err = atoi(ai, string(a), 16); err != nil { return PyRes{res=":gcd:atoi(a):", err=err}; }
if err = atoi(bi, string(b), 16); err != nil { return PyRes{res=":gcd:atoi(b):", err=err}; }
if err = #force_inline internal_int_gcd_lcm(dest, nil, ai, bi); err != nil { return PyRes{res=":gcd:gcd(a, b):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
if err != nil { return PyRes{res=":gcd:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}
/*
dest = lcm(a, b)
*/
@export test_lcm :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context();
err: Error;
ai, bi, dest := &Int{}, &Int{}, &Int{};
defer internal_destroy(ai, bi, dest);
if err = atoi(ai, string(a), 16); err != nil { return PyRes{res=":lcm:atoi(a):", err=err}; }
if err = atoi(bi, string(b), 16); err != nil { return PyRes{res=":lcm:atoi(b):", err=err}; }
if err = #force_inline internal_int_gcd_lcm(nil, dest, ai, bi); err != nil { return PyRes{res=":lcm:lcm(a, b):", err=err}; }
r: cstring;
r, err = int_itoa_cstring(dest, 16, context.temp_allocator);
if err != nil { return PyRes{res=":lcm:itoa(res):", err=err}; }
return PyRes{res = r, err = nil};
}

668
core/math/big/test.py Normal file
View File

@@ -0,0 +1,668 @@
from ctypes import *
from random import *
import math
import os
import platform
import time
import gc
from enum import Enum
import argparse
parser = argparse.ArgumentParser(
description = "Odin core:math/big test suite",
epilog = "By default we run regression and random tests with preset parameters.",
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
)
#
# Normally, we report the number of passes and fails. With this option set, we exit at first fail.
#
parser.add_argument(
"-exit-on-fail",
help = "Exit when a test fails",
action = "store_true",
)
#
# We skip randomized tests altogether if this is set.
#
no_random = parser.add_mutually_exclusive_group()
no_random.add_argument(
"-no-random",
help = "No random tests",
action = "store_true",
)
#
# Normally we run a given number of cycles on each test.
# Timed tests budget 1 second per 20_000 bits instead.
#
# For timed tests we budget a second per `n` bits and iterate until we hit that time.
#
timed_or_fast = no_random.add_mutually_exclusive_group()
timed_or_fast.add_argument(
"-timed",
type = bool,
default = False,
help = "Timed tests instead of a preset number of iterations.",
)
parser.add_argument(
"-timed-bits",
type = int,
metavar = "BITS",
default = 20_000,
help = "Timed tests. Every `BITS` worth of input is given a second of running time.",
)
#
# For normal tests (non-timed), `-fast-tests` cuts down on the number of iterations.
#
timed_or_fast.add_argument(
"-fast-tests",
help = "Cut down on the number of iterations of each test",
action = "store_true",
)
args = parser.parse_args()
#
# How many iterations of each random test do we want to run?
#
BITS_AND_ITERATIONS = [
( 120, 10_000),
( 1_200, 1_000),
( 4_096, 100),
(12_000, 10),
]
if args.fast_tests:
for k in range(len(BITS_AND_ITERATIONS)):
b, i = BITS_AND_ITERATIONS[k]
BITS_AND_ITERATIONS[k] = (b, i // 10 if i >= 100 else 5)
if args.no_random:
BITS_AND_ITERATIONS = []
#
# Where is the DLL? If missing, build using: `odin build . -build-mode:shared`
#
if platform.system() == "Windows":
LIB_PATH = os.getcwd() + os.sep + "big.dll"
elif platform.system() == "Linux":
LIB_PATH = os.getcwd() + os.sep + "big.so"
elif platform.system() == "Darwin":
LIB_PATH = os.getcwd() + os.sep + "big.dylib"
else:
print("Platform is unsupported.")
exit(1)
TOTAL_TIME = 0
UNTIL_TIME = 0
UNTIL_ITERS = 0
def we_iterate():
if args.timed:
return TOTAL_TIME < UNTIL_TIME
else:
global UNTIL_ITERS
UNTIL_ITERS -= 1
return UNTIL_ITERS != -1
#
# Error enum values
#
class Error(Enum):
Okay = 0
Out_Of_Memory = 1
Invalid_Pointer = 2
Invalid_Argument = 3
Unknown_Error = 4
Max_Iterations_Reached = 5
Buffer_Overflow = 6
Integer_Overflow = 7
Division_by_Zero = 8
Math_Domain_Error = 9
Unimplemented = 127
#
# Disable garbage collection
#
gc.disable()
#
# Set up exported procedures
#
try:
l = cdll.LoadLibrary(LIB_PATH)
except:
print("Couldn't find or load " + LIB_PATH + ".")
exit(1)
def load(export_name, args, res):
export_name.argtypes = args
export_name.restype = res
return export_name
#
# Result values will be passed in a struct { res: cstring, err: Error }
#
class Res(Structure):
_fields_ = [("res", c_char_p), ("err", c_uint64)]
initialize_constants = load(l.test_initialize_constants, [], c_uint64)
initialize_constants()
error_string = load(l.test_error_string, [c_byte], c_char_p)
add = load(l.test_add, [c_char_p, c_char_p], Res)
sub = load(l.test_sub, [c_char_p, c_char_p], Res)
mul = load(l.test_mul, [c_char_p, c_char_p], Res)
sqr = load(l.test_sqr, [c_char_p ], Res)
div = load(l.test_div, [c_char_p, c_char_p], Res)
# Powers and such
int_log = load(l.test_log, [c_char_p, c_longlong], Res)
int_pow = load(l.test_pow, [c_char_p, c_longlong], Res)
int_sqrt = load(l.test_sqrt, [c_char_p ], Res)
int_root_n = load(l.test_root_n, [c_char_p, c_longlong], Res)
# Logical operations
int_shl_digit = load(l.test_shl_digit, [c_char_p, c_longlong], Res)
int_shr_digit = load(l.test_shr_digit, [c_char_p, c_longlong], Res)
int_shl = load(l.test_shl, [c_char_p, c_longlong], Res)
int_shr = load(l.test_shr, [c_char_p, c_longlong], Res)
int_shr_signed = load(l.test_shr_signed, [c_char_p, c_longlong], Res)
int_factorial = load(l.test_factorial, [c_uint64], Res)
int_gcd = load(l.test_gcd, [c_char_p, c_char_p], Res)
int_lcm = load(l.test_lcm, [c_char_p, c_char_p], Res)
def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = "", radix=16):
passed = True
r = None
err = Error(res.err)
if err != expected_error:
error_loc = res.res.decode('utf-8')
error = "{}: {} in '{}'".format(test_name, err, error_loc)
if len(param):
error += " with params {}".format(param)
print(error, flush=True)
passed = False
elif err == Error.Okay:
r = None
try:
r = res.res.decode('utf-8')
r = int(res.res, radix)
except:
pass
if r != expected_result:
error = "{}: Result was '{}', expected '{}'".format(test_name, r, expected_result)
if len(param):
error += " with params {}".format(param)
print(error, flush=True)
passed = False
if args.exit_on_fail and not passed: exit(res.err)
return passed
def arg_to_odin(a):
if a >= 0:
s = hex(a)[2:]
else:
s = '-' + hex(a)[3:]
return s.encode('utf-8')
def test_add(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
res = add(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = a + b
return test("test_add", res, [a, b], expected_error, expected_result)
def test_sub(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
res = sub(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = a - b
return test("test_sub", res, [a, b], expected_error, expected_result)
def test_mul(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
try:
res = mul(*args)
except OSError as e:
print("{} while trying to multiply {} x {}.".format(e, a, b))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None
if expected_error == Error.Okay:
expected_result = a * b
return test("test_mul", res, [a, b], expected_error, expected_result)
def test_sqr(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a)]
try:
res = sqr(*args)
except OSError as e:
print("{} while trying to square {} x {}.".format(e, a))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None
if expected_error == Error.Okay:
expected_result = a * a
return test("test_sqr", res, [a], expected_error, expected_result)
def test_div(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
res = div(*args)
expected_result = None
if expected_error == Error.Okay:
#
# We don't round the division results, so if one component is negative, we're off by one.
#
if a < 0 and b > 0:
expected_result = int(-(abs(a) // b))
elif b < 0 and a > 0:
expected_result = int(-(a // abs((b))))
else:
expected_result = a // b if b != 0 else None
return test("test_div", res, [a, b], expected_error, expected_result)
def test_log(a = 0, base = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), base]
res = int_log(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = int(math.log(a, base))
return test("test_log", res, [a, base], expected_error, expected_result)
def test_pow(base = 0, power = 0, expected_error = Error.Okay):
args = [arg_to_odin(base), power]
res = int_pow(*args)
expected_result = None
if expected_error == Error.Okay:
if power < 0:
expected_result = 0
else:
# NOTE(Jeroen): Don't use `math.pow`, it's a floating point approximation.
# Use built-in `pow` or `a**b` instead.
expected_result = pow(base, power)
return test("test_pow", res, [base, power], expected_error, expected_result)
def test_sqrt(number = 0, expected_error = Error.Okay):
args = [arg_to_odin(number)]
try:
res = int_sqrt(*args)
except OSError as e:
print("{} while trying to sqrt {}.".format(e, number))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None
if expected_error == Error.Okay:
if number < 0:
expected_result = 0
else:
expected_result = int(math.isqrt(number))
return test("test_sqrt", res, [number], expected_error, expected_result)
def root_n(number, root):
u, s = number, number + 1
while u < s:
s = u
t = (root-1) * s + number // pow(s, root - 1)
u = t // root
return s
def test_root_n(number = 0, root = 0, expected_error = Error.Okay):
args = [arg_to_odin(number), root]
res = int_root_n(*args)
expected_result = None
if expected_error == Error.Okay:
if number < 0:
expected_result = 0
else:
expected_result = root_n(number, root)
return test("test_root_n", res, [number, root], expected_error, expected_result)
def test_shl_digit(a = 0, digits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), digits]
res = int_shl_digit(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = a << (digits * 60)
return test("test_shl_digit", res, [a, digits], expected_error, expected_result)
def test_shr_digit(a = 0, digits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), digits]
res = int_shr_digit(*args)
expected_result = None
if expected_error == Error.Okay:
if a < 0:
# Don't pass negative numbers. We have a shr_signed.
return False
else:
expected_result = a >> (digits * 60)
return test("test_shr_digit", res, [a, digits], expected_error, expected_result)
def test_shl(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits]
res = int_shl(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = a << bits
return test("test_shl", res, [a, bits], expected_error, expected_result)
def test_shr(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits]
res = int_shr(*args)
expected_result = None
if expected_error == Error.Okay:
if a < 0:
# Don't pass negative numbers. We have a shr_signed.
return False
else:
expected_result = a >> bits
return test("test_shr", res, [a, bits], expected_error, expected_result)
def test_shr_signed(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits]
res = int_shr_signed(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = a >> bits
return test("test_shr_signed", res, [a, bits], expected_error, expected_result)
def test_factorial(n = 0, expected_error = Error.Okay):
args = [n]
res = int_factorial(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = math.factorial(n)
return test("test_factorial", res, [n], expected_error, expected_result)
def test_gcd(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
res = int_gcd(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = math.gcd(a, b)
return test("test_gcd", res, [a, b], expected_error, expected_result)
def test_lcm(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)]
res = int_lcm(*args)
expected_result = None
if expected_error == Error.Okay:
expected_result = math.lcm(a, b)
return test("test_lcm", res, [a, b], expected_error, expected_result)
# TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on.
#
# The last two arguments in tests are the expected error and expected result.
#
# The expected error defaults to None.
# By default the Odin implementation will be tested against the Python one.
# You can override that by supplying an expected result as the last argument instead.
TESTS = {
test_add: [
[ 1234, 5432],
],
test_sub: [
[ 1234, 5432],
],
test_mul: [
[ 1234, 5432],
[ 0xd3b4e926aaba3040e1c12b5ea553b5, 0x1a821e41257ed9281bee5bc7789ea7 ],
],
test_sqr: [
[ 5432],
[ 0xd3b4e926aaba3040e1c12b5ea553b5 ],
],
test_div: [
[ 54321, 12345],
[ 55431, 0, Error.Division_by_Zero],
[ 12980742146337069150589594264770969721, 4611686018427387904 ],
[ 831956404029821402159719858789932422, 243087903122332132 ],
],
test_log: [
[ 3192, 1, Error.Invalid_Argument],
[ -1234, 2, Error.Math_Domain_Error],
[ 0, 2, Error.Math_Domain_Error],
[ 1024, 2],
],
test_pow: [
[ 0, -1, Error.Math_Domain_Error ], # Math
[ 0, 0 ], # 1
[ 0, 2 ], # 0
[ 42, -1,], # 0
[ 42, 1 ], # 1
[ 42, 0 ], # 42
[ 42, 2 ], # 42*42
],
test_sqrt: [
[ -1, Error.Invalid_Argument, ],
[ 42, Error.Okay, ],
[ 12345678901234567890, Error.Okay, ],
[ 1298074214633706907132624082305024, Error.Okay, ],
[ 686885735734829009541949746871140768343076607029752932751182108475420900392874228486622313727012705619148037570309621219533087263900443932890792804879473795673302686046941536636874184361869252299636701671980034458333859202703255467709267777184095435235980845369829397344182319113372092844648570818726316581751114346501124871729572474923695509057166373026411194094493240101036672016770945150422252961487398124677567028263059046193391737576836378376192651849283925197438927999526058932679219572030021792914065825542626400207956134072247020690107136531852625253942429167557531123651471221455967386267137846791963149859804549891438562641323068751514370656287452006867713758971418043865298618635213551059471668293725548570452377976322899027050925842868079489675596835389444833567439058609775325447891875359487104691935576723532407937236505941186660707032433807075470656782452889754501872408562496805517394619388777930253411467941214807849472083814447498068636264021405175653742244368865090604940094889189800007448083930490871954101880815781177612910234741529950538835837693870921008635195545246771593130784786737543736434086434015200264933536294884482218945403958647118802574342840790536176272341586020230110889699633073513016344826709214, Error.Okay, ],
],
test_root_n: [
[ 1298074214633706907132624082305024, 2, Error.Okay, ],
],
test_shl_digit: [
[ 3192, 1 ],
[ 1298074214633706907132624082305024, 2 ],
[ 1024, 3 ],
],
test_shr_digit: [
[ 3680125442705055547392, 1 ],
[ 1725436586697640946858688965569256363112777243042596638790631055949824, 2 ],
[ 219504133884436710204395031992179571, 2 ],
],
test_shl: [
[ 3192, 1 ],
[ 1298074214633706907132624082305024, 2 ],
[ 1024, 3 ],
],
test_shr: [
[ 3680125442705055547392, 1 ],
[ 1725436586697640946858688965569256363112777243042596638790631055949824, 2 ],
[ 219504133884436710204395031992179571, 2 ],
],
test_shr_signed: [
[ -611105530635358368578155082258244262, 12 ],
[ -149195686190273039203651143129455, 12 ],
[ 611105530635358368578155082258244262, 12 ],
[ 149195686190273039203651143129455, 12 ],
],
test_factorial: [
[ 6_000 ], # Regular factorial, see cutoff in common.odin.
[ 12_345 ], # Binary split factorial
],
test_gcd: [
[ 23, 25, ],
[ 125, 25, ],
[ 125, 0, ],
[ 0, 0, ],
[ 0, 125,],
],
test_lcm: [
[ 23, 25,],
[ 125, 25, ],
[ 125, 0, ],
[ 0, 0, ],
[ 0, 125,],
],
}
if not args.fast_tests:
TESTS[test_factorial].append(
# This one on its own takes around 800ms, so we exclude it for FAST_TESTS
[ 100_000 ],
)
total_passes = 0
total_failures = 0
#
# test_shr_signed also tests shr, so we're not going to test shr randomly.
#
RANDOM_TESTS = [
test_add, test_sub, test_mul, test_sqr, test_div,
test_log, test_pow, test_sqrt, test_root_n,
test_shl_digit, test_shr_digit, test_shl, test_shr_signed,
test_gcd, test_lcm,
]
SKIP_LARGE = [
test_pow, test_root_n, # test_gcd,
]
SKIP_LARGEST = []
# Untimed warmup.
for test_proc in TESTS:
for t in TESTS[test_proc]:
res = test_proc(*t)
if __name__ == '__main__':
print("\n---- math/big tests ----")
print()
for test_proc in TESTS:
count_pass = 0
count_fail = 0
TIMINGS = {}
for t in TESTS[test_proc]:
start = time.perf_counter()
res = test_proc(*t)
diff = time.perf_counter() - start
TOTAL_TIME += diff
if test_proc not in TIMINGS:
TIMINGS[test_proc] = diff
else:
TIMINGS[test_proc] += diff
if res:
count_pass += 1
total_passes += 1
else:
count_fail += 1
total_failures += 1
print("{name}: {count_pass:,} passes and {count_fail:,} failures in {timing:.3f} ms.".format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
for BITS, ITERATIONS in BITS_AND_ITERATIONS:
print()
print("---- math/big with two random {bits:,} bit numbers ----".format(bits=BITS))
print()
#
# We've already tested up to the 10th root.
#
TEST_ROOT_N_PARAMS = [2, 3, 4, 5, 6]
for test_proc in RANDOM_TESTS:
if BITS > 1_200 and test_proc in SKIP_LARGE: continue
if BITS > 4_096 and test_proc in SKIP_LARGEST: continue
count_pass = 0
count_fail = 0
TIMINGS = {}
UNTIL_ITERS = ITERATIONS
if test_proc == test_root_n and BITS == 1_200:
UNTIL_ITERS /= 10
UNTIL_TIME = TOTAL_TIME + BITS / args.timed_bits
# We run each test for a second per 20k bits
index = 0
while we_iterate():
a = randint(-(1 << BITS), 1 << BITS)
b = randint(-(1 << BITS), 1 << BITS)
if test_proc == test_div:
# We've already tested division by zero above.
bits = int(BITS * 0.6)
b = randint(-(1 << bits), 1 << bits)
if b == 0:
b == 42
elif test_proc == test_log:
# We've already tested log's domain errors.
a = randint(1, 1 << BITS)
b = randint(2, 1 << 60)
elif test_proc == test_pow:
b = randint(1, 10)
elif test_proc == test_sqrt:
a = randint(1, 1 << BITS)
b = Error.Okay
elif test_proc == test_root_n:
a = randint(1, 1 << BITS)
b = TEST_ROOT_N_PARAMS[index]
index = (index + 1) % len(TEST_ROOT_N_PARAMS)
elif test_proc == test_shl_digit:
b = randint(0, 10);
elif test_proc == test_shr_digit:
a = abs(a)
b = randint(0, 10);
elif test_proc == test_shl:
b = randint(0, min(BITS, 120));
elif test_proc == test_shr_signed:
b = randint(0, min(BITS, 120));
else:
b = randint(0, 1 << BITS)
res = None
start = time.perf_counter()
res = test_proc(a, b)
diff = time.perf_counter() - start
TOTAL_TIME += diff
if test_proc not in TIMINGS:
TIMINGS[test_proc] = diff
else:
TIMINGS[test_proc] += diff
if res:
count_pass += 1; total_passes += 1
else:
count_fail += 1; total_failures += 1
print("{name}: {count_pass:,} passes and {count_fail:,} failures in {timing:.3f} ms.".format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
print()
print("---- THE END ----")
print()
print("total: {count_pass:,} passes and {count_fail:,} failures in {timing:.3f} ms.".format(count_pass=total_passes, count_fail=total_failures, timing=TOTAL_TIME * 1_000))
if total_failures:
exit(1)

80
core/math/big/tune.odin Normal file
View File

@@ -0,0 +1,80 @@
//+ignore
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
A BigInt implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
*/
import "core:fmt"
import "core:time"
Category :: enum {
itoa,
atoi,
factorial,
factorial_bin,
choose,
lsb,
ctz,
sqr,
bitfield_extract,
};
Event :: struct {
ticks: time.Duration,
count: int,
cycles: u64,
}
Timings := [Category]Event{};
print_timings :: proc() {
duration :: proc(d: time.Duration) -> (res: string) {
switch {
case d < time.Microsecond:
return fmt.tprintf("%v ns", time.duration_nanoseconds(d));
case d < time.Millisecond:
return fmt.tprintf("%v µs", time.duration_microseconds(d));
case:
return fmt.tprintf("%v ms", time.duration_milliseconds(d));
}
}
for v in Timings {
if v.count > 0 {
fmt.println("\nTimings:");
break;
}
}
for v, i in Timings {
if v.count > 0 {
avg_ticks := time.Duration(f64(v.ticks) / f64(v.count));
avg_cycles := f64(v.cycles) / f64(v.count);
fmt.printf("\t%v: %s / %v cycles (avg), %s / %v cycles (total, %v calls)\n", i, duration(avg_ticks), avg_cycles, duration(v.ticks), v.cycles, v.count);
}
}
}
@(deferred_in_out=_SCOPE_END)
SCOPED_TIMING :: #force_inline proc(c: Category) -> (ticks: time.Tick, cycles: u64) {
cycles = time.read_cycle_counter();
ticks = time.tick_now();
return;
}
_SCOPE_END :: #force_inline proc(c: Category, ticks: time.Tick, cycles: u64) {
cycles_now := time.read_cycle_counter();
ticks_now := time.tick_now();
Timings[c].ticks = time.tick_diff(ticks, ticks_now);
Timings[c].cycles = cycles_now - cycles;
Timings[c].count += 1;
}
SCOPED_COUNT_ADD :: #force_inline proc(c: Category, count: int) {
Timings[c].count += count;
}

View File

@@ -95,7 +95,7 @@ int63_max :: proc(n: i64, r: ^Rand = nil) -> i64 {
int127_max :: proc(n: i128, r: ^Rand = nil) -> i128 {
if n <= 0 {
panic("Invalid argument to int63_max");
panic("Invalid argument to int127_max");
}
if n&(n-1) == 0 {
return int127(r) & (n-1);