mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-02 19:22:33 +00:00
Merge branch 'master' of https://github.com/odin-lang/Odin
This commit is contained in:
154
core/math/big/api.odin
Normal file
154
core/math/big/api.odin
Normal 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
8
core/math/big/build.bat
Normal 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
206
core/math/big/common.odin
Normal 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
246
core/math/big/example.odin
Normal 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
806
core/math/big/helpers.odin
Normal 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
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
144
core/math/big/logical.odin
Normal 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
33
core/math/big/prime.odin
Normal 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
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
559
core/math/big/public.odin
Normal 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
485
core/math/big/radix.odin
Normal 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
369
core/math/big/test.odin
Normal 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
668
core/math/big/test.py
Normal 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
80
core/math/big/tune.odin
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user