bigint: refactor to big.Int instead of bigint.Int.

This commit is contained in:
Jeroen van Rijn
2021-08-11 20:59:50 +02:00
parent baef0c291d
commit 9dba17cf87
11 changed files with 834 additions and 715 deletions
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -13,7 +13,6 @@ package bigint
import "core:mem" import "core:mem"
import "core:intrinsics" import "core:intrinsics"
import "core:fmt"
/* /*
=========================== ===========================
@@ -56,7 +55,7 @@ add_two_ints :: proc(dest, a, b: ^Int) -> (err: Error) {
dest = a + digit; dest = a + digit;
*/ */
add_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error) { add_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error) {
dest := dest; x := a; digit := digit; dest := dest; digit := digit;
assert_initialized(dest); assert_initialized(a); assert_initialized(dest); assert_initialized(a);
/* /*
@@ -212,7 +211,7 @@ sub_two_ints :: proc(dest, number, decrease: ^Int) -> (err: Error) {
dest = a - digit; dest = a - digit;
*/ */
sub_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error) { sub_digit :: proc(dest, a: ^Int, digit: DIGIT) -> (err: Error) {
dest := dest; x := a; digit := digit; dest := dest; digit := digit;
assert_initialized(dest); assert_initialized(a); assert_initialized(dest); assert_initialized(a);
/* /*
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -35,7 +35,13 @@ _DEFAULT_SQR_KARATSUBA_CUTOFF :: 120;
_DEFAULT_MUL_TOOM_CUTOFF :: 350; _DEFAULT_MUL_TOOM_CUTOFF :: 350;
_DEFAULT_SQR_TOOM_CUTOFF :: 400; _DEFAULT_SQR_TOOM_CUTOFF :: 400;
/*
TODO(Jeroen): Decide whether to turn `Sign` into `Flags :: bit_set{Flag; u8}`.
This would hold the sign and float class, as appropriate, and would allow us
to set an `Int` to +/- Inf, or NaN.
The operations would need to be updated to propagate these as expected.
*/
Sign :: enum u8 { Sign :: enum u8 {
Zero_or_Positive = 0, Zero_or_Positive = 0,
Negative = 1, Negative = 1,
@@ -44,8 +50,8 @@ Sign :: enum u8 {
Int :: struct { Int :: struct {
used: int, used: int,
allocated: int, allocated: int,
sign: Sign,
digit: [dynamic]DIGIT, digit: [dynamic]DIGIT,
sign: Sign,
}; };
Comparison_Flag :: enum i8 { Comparison_Flag :: enum i8 {
@@ -72,7 +78,7 @@ Error :: enum i8 {
Primality_Flag :: enum u8 { Primality_Flag :: enum u8 {
Blum_Blum_Shub = 0, /* BBS style prime */ Blum_Blum_Shub = 0, /* BBS style prime */
Safe = 1, /* Safe prime (p-1)/2 == prime */ Safe = 1, /* Safe prime (p-1)/2 == prime */
Second_MSB_On = 3, /* force 2nd MSB to 1 */ Second_MSB_On = 3, /* force 2nd MSB to 1 */
}; };
Primality_Flags :: bit_set[Primality_Flag; u8]; Primality_Flags :: bit_set[Primality_Flag; u8];
+2
View File
@@ -0,0 +1,2 @@
@echo off
odin run . -vet
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -1,5 +1,5 @@
//+ignore //+ignore
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -60,9 +60,9 @@ demo :: proc() {
defer destroy(b); defer destroy(b);
defer destroy(c); defer destroy(c);
a, err = init(1+4+16+64); a, err = init(512);
b, err = init(1+2+8+32+128); b, err = init(a);
c, err = init(-4); c, err = init(-4);
@@ -108,4 +108,10 @@ main :: proc() {
fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location); 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);
}
}
} }
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -11,7 +11,6 @@ package bigint
import "core:mem" import "core:mem"
import "core:intrinsics" import "core:intrinsics"
import "core:fmt"
/* /*
Deallocates the backing memory of an Int. Deallocates the backing memory of an Int.
@@ -62,7 +61,7 @@ init_new :: proc(allocator_zeroes := true, allocator := context.allocator, size
Initialize from a signed or unsigned integer. Initialize from a signed or unsigned integer.
Inits a new `Int` and then calls the appropriate `set` routine. Inits a new `Int` and then calls the appropriate `set` routine.
*/ */
init_new_integer :: proc(u: $T, minimize := false, allocator_zeroes := true, allocator := context.allocator) -> (a: ^Int, err: Error) where intrinsics.type_is_integer(T) { init_from_integer :: proc(src: $T, minimize := false, allocator_zeroes := true, allocator := context.allocator) -> (a: ^Int, err: Error) where intrinsics.type_is_integer(T) {
n := _DEFAULT_DIGIT_COUNT; n := _DEFAULT_DIGIT_COUNT;
if minimize { if minimize {
@@ -71,12 +70,27 @@ init_new_integer :: proc(u: $T, minimize := false, allocator_zeroes := true, all
a, err = init_new(allocator_zeroes, allocator, n); a, err = init_new(allocator_zeroes, allocator, n);
if err == .OK { if err == .OK {
set(a, u, minimize); set(a, src, minimize);
} }
return; return;
} }
init :: proc{init_new, init_new_integer}; /*
Initialize an `Int` as a copy from another `Int`.
*/
init_copy :: proc(src: ^Int, minimize := false, allocator_zeroes := true, allocator := context.allocator) -> (a: ^Int, err: Error) {
if !is_initialized(src) {
return nil, .Invalid_Input;
}
a, err = init_new(allocator_zeroes, allocator, src.used);
if err == .OK {
copy(a, src);
}
return;
}
init :: proc{init_new, init_from_integer, init_copy};
/* /*
Helpers to set an `Int` to a specific value. Helpers to set an `Int` to a specific value.
@@ -123,7 +137,7 @@ copy :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
/* /*
Grow `dest` to fit `src`. Grow `dest` to fit `src`.
*/ */
if err = grow(dest, min(src.used, _DEFAULT_DIGIT_COUNT)); err != .OK { if err = grow(dest, src.used); err != .OK {
return err; return err;
} }
@@ -226,6 +240,9 @@ extract_bit :: proc(a: ^Int, bit_offset: int) -> (bit: DIGIT, err: Error) {
return 1 if ((a.digit[limb] & i) != 0) else 0, .OK; return 1 if ((a.digit[limb] & i) != 0) else 0, .OK;
} }
/*
TODO: Optimize.
*/
extract_bits :: proc(a: ^Int, offset, count: int) -> (res: _WORD, err: Error) { extract_bits :: proc(a: ^Int, offset, count: int) -> (res: _WORD, err: Error) {
if count > _WORD_BITS || count < 1 { if count > _WORD_BITS || count < 1 {
return 0, .Invalid_Input; return 0, .Invalid_Input;
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -9,8 +9,6 @@ package bigint
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
*/ */
import "core:fmt"
log_n_int :: proc(a: ^Int, base: DIGIT) -> (log: int, err: Error) { log_n_int :: proc(a: ^Int, base: DIGIT) -> (log: int, err: Error) {
assert_initialized(a); assert_initialized(a);
if is_neg(a) || is_zero(a) || base < 2 || DIGIT(base) > _DIGIT_MAX { if is_neg(a) || is_zero(a) || base < 2 || DIGIT(base) > _DIGIT_MAX {
+206
View File
@@ -0,0 +1,206 @@
package 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.
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.
*/
/*
2's complement `and`, returns `dest = a & b;`
*/
and :: proc(dest, a, b: ^Int) -> (err: Error) {
assert_initialized(dest); assert_initialized(a); assert_initialized(b);
used := max(a.used, b.used) + 1;
neg: bool;
neg = is_neg(a) && is_neg(b);
ac, bc, cc := DIGIT(1), DIGIT(1), DIGIT(1);
/*
Grow the destination to accomodate the result.
*/
if err = grow(dest, used); err != .OK {
return err;
}
for i := 0; i < used; i += 1 {
x, y: DIGIT;
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
ac += _MASK if i >= a.used else (~a.digit[i] & _MASK);
x = ac & _MASK;
ac >>= _DIGIT_BITS;
} else {
x = 0 if i >= a.used else a.digit[i];
}
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
bc += _MASK if i >= b.used else (~b.digit[i] & _MASK);
y = bc & _MASK;
bc >>= _DIGIT_BITS;
} else {
y = 0 if i >= b.used else b.digit[i];
}
dest.digit[i] = x & y;
/*
Convert to to sign-magnitude if negative.
*/
if neg {
cc += ~dest.digit[i] & _MASK;
dest.digit[i] = cc & _MASK;
cc >>= _DIGIT_BITS;
}
}
dest.used = used;
dest.sign = .Negative if neg else .Zero_or_Positive;
clamp(dest);
return .OK;
}
/*
2's complement `or`, returns `dest = a | b;`
*/
or :: proc(dest, a, b: ^Int) -> (err: Error) {
assert_initialized(dest); assert_initialized(a); assert_initialized(b);
used := max(a.used, b.used) + 1;
neg: bool;
neg = is_neg(a) || is_neg(b);
ac, bc, cc := DIGIT(1), DIGIT(1), DIGIT(1);
/*
Grow the destination to accomodate the result.
*/
if err = grow(dest, used); err != .OK {
return err;
}
for i := 0; i < used; i += 1 {
x, y: DIGIT;
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
ac += _MASK if i >= a.used else (~a.digit[i] & _MASK);
x = ac & _MASK;
ac >>= _DIGIT_BITS;
} else {
x = 0 if i >= a.used else a.digit[i];
}
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
bc += _MASK if i >= b.used else (~b.digit[i] & _MASK);
y = bc & _MASK;
bc >>= _DIGIT_BITS;
} else {
y = 0 if i >= b.used else b.digit[i];
}
dest.digit[i] = x | y;
/*
Convert to to sign-magnitude if negative.
*/
if neg {
cc += ~dest.digit[i] & _MASK;
dest.digit[i] = cc & _MASK;
cc >>= _DIGIT_BITS;
}
}
dest.used = used;
dest.sign = .Negative if neg else .Zero_or_Positive;
clamp(dest);
return .OK;
}
/*
2's complement `xor`, returns `dest = a ~ b;`
*/
xor :: proc(dest, a, b: ^Int) -> (err: Error) {
assert_initialized(dest); assert_initialized(a); assert_initialized(b);
used := max(a.used, b.used) + 1;
neg: bool;
neg = is_neg(a) != is_neg(b);
ac, bc, cc := DIGIT(1), DIGIT(1), DIGIT(1);
/*
Grow the destination to accomodate the result.
*/
if err = grow(dest, used); err != .OK {
return err;
}
for i := 0; i < used; i += 1 {
x, y: DIGIT;
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
ac += _MASK if i >= a.used else (~a.digit[i] & _MASK);
x = ac & _MASK;
ac >>= _DIGIT_BITS;
} else {
x = 0 if i >= a.used else a.digit[i];
}
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
bc += _MASK if i >= b.used else (~b.digit[i] & _MASK);
y = bc & _MASK;
bc >>= _DIGIT_BITS;
} else {
y = 0 if i >= b.used else b.digit[i];
}
dest.digit[i] = x ~ y;
/*
Convert to to sign-magnitude if negative.
*/
if neg {
cc += ~dest.digit[i] & _MASK;
dest.digit[i] = cc & _MASK;
cc >>= _DIGIT_BITS;
}
}
dest.used = used;
dest.sign = .Negative if neg else .Zero_or_Positive;
clamp(dest);
return .OK;
}
@@ -1,4 +1,4 @@
package bigint package big
/* /*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
@@ -11,21 +11,20 @@ package bigint
This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa). This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa).
*/ */
import "core:mem"
import "core:intrinsics" import "core:intrinsics"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:slice"
/* /*
This version of `itoa` allocates one behalf of the caller. The caller must free the string. This version of `itoa` allocates one behalf of the caller. The caller must free the string.
*/ */
itoa_string :: proc(a: ^Int, radix := i8(-1), zero_terminate := false, allocator := context.allocator) -> (res: string, err: Error) { itoa_string :: proc(a: ^Int, radix := i8(-1), zero_terminate := false, allocator := context.allocator) -> (res: string, err: Error) {
radix := radix;
assert_initialized(a); assert_initialized(a);
/* /*
Radix defaults to 10. Radix defaults to 10.
*/ */
radix := radix if radix > 0 else 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. TODO: If we want to write a prefix for some of the radixes, we can oversize the buffer.
@@ -87,11 +86,12 @@ itoa_string :: proc(a: ^Int, radix := i8(-1), zero_terminate := false, allocator
This version of `itoa` allocates one behalf of the caller. The caller must free the string. This version of `itoa` allocates one behalf of the caller. The caller must free the string.
*/ */
itoa_cstring :: proc(a: ^Int, radix := i8(-1), allocator := context.allocator) -> (res: cstring, err: Error) { itoa_cstring :: proc(a: ^Int, radix := i8(-1), allocator := context.allocator) -> (res: cstring, err: Error) {
radix := radix;
assert_initialized(a); assert_initialized(a);
/* /*
Radix defaults to 10. Radix defaults to 10.
*/ */
radix := radix if radix > 0 else 10; radix = radix if radix > 0 else 10;
s: string; s: string;
s, err = itoa_string(a, radix, true, allocator); s, err = itoa_string(a, radix, true, allocator);
@@ -119,11 +119,12 @@ itoa_cstring :: proc(a: ^Int, radix := i8(-1), allocator := context.allocator) -
and having to perform a buffer overflow check each character. and having to perform a buffer overflow check each character.
*/ */
itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_terminate := false) -> (written: int, err: Error) { itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_terminate := false) -> (written: int, err: Error) {
radix := radix;
assert_initialized(a); size := size; assert_initialized(a); size := size;
/* /*
Radix defaults to 10. Radix defaults to 10.
*/ */
radix := radix if radix > 0 else 10; radix = radix if radix > 0 else 10;
if radix < 2 || radix > 64 { if radix < 2 || radix > 64 {
return 0, .Invalid_Input; return 0, .Invalid_Input;
} }
@@ -197,10 +198,10 @@ itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_termina
buffer[available] = 0; buffer[available] = 0;
} }
mask := _WORD(radix - 1); // mask := _WORD(radix - 1);
shift := int(log_n(DIGIT(radix), 2)); shift := int(log_n(DIGIT(radix), 2));
count := int(count_bits(a)); count := int(count_bits(a));
digit: _WORD; // digit: _WORD;
for offset := 0; offset < count; offset += 4 { for offset := 0; offset < count; offset += 4 {
bits_to_get := int(min(count - offset, shift)); bits_to_get := int(min(count - offset, shift));
-3
View File
@@ -1,3 +0,0 @@
@echo off
odin run .
rem -vet
-113
View File
@@ -1,113 +0,0 @@
package bigint
/*
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.
This file contains logical operations like `and`, `or` and `xor`.
*/
import "core:fmt"
@private
Operator :: enum u8 {
And = 1,
Or = 2,
Xor = 3,
}
/*
2's complement `and`, returns `dest = a & b;`
*/
_binary_op :: proc(dest, a, b: ^Int, op: Operator) -> (err: Error) {
assert_initialized(dest); assert_initialized(a); assert_initialized(b);
used := max(a.used, b.used) + 1;
neg: bool;
switch(op) {
case .And:
neg = is_neg(a) && is_neg(b);
case .Or:
neg = is_neg(a) || is_neg(b);
case .Xor:
neg = is_neg(a) != is_neg(b);
case:
return .Invalid_Input;
}
ac, bc, cc := DIGIT(1), DIGIT(1), DIGIT(1);
/*
Grow the destination to accomodate the result.
*/
if err = grow(dest, used); err != .OK {
return err;
}
for i := 0; i < used; i += 1 {
x, y: DIGIT;
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
ac += _MASK if i >= a.used else (~a.digit[i] & _MASK);
x = ac & _MASK;
ac >>= _DIGIT_BITS;
} else {
x = 0 if i >= a.used else a.digit[i];
}
/*
Convert to 2's complement if negative.
*/
if is_neg(a) {
bc += _MASK if i >= b.used else (~b.digit[i] & _MASK);
y = bc & _MASK;
bc >>= _DIGIT_BITS;
} else {
y = 0 if i >= b.used else b.digit[i];
}
switch(op) {
case .And:
dest.digit[i] = x & y;
case .Or:
dest.digit[i] = x | y;
case .Xor:
dest.digit[i] = x ~ y;
}
/*
Convert to to sign-magnitude if negative.
*/
if neg {
cc += ~dest.digit[i] & _MASK;
dest.digit[i] = cc & _MASK;
cc >>= _DIGIT_BITS;
}
}
dest.used = used;
dest.sign = .Negative if neg else .Zero_or_Positive;
clamp(dest);
return .OK;
}
and :: proc(dest, a, b: ^Int) -> (err: Error) {
return _binary_op(dest, a, b, .And);
}
or :: proc(dest, a, b: ^Int) -> (err: Error) {
return _binary_op(dest, a, b, .Or);
}
xor :: proc(dest, a, b: ^Int) -> (err: Error) {
return _binary_op(dest, a, b, .Xor);
}