bigint: Fast paths for radix code.

This commit is contained in:
Jeroen van Rijn
2021-07-18 00:58:19 +02:00
parent 767948ab46
commit e3d8ac559d
3 changed files with 154 additions and 25 deletions

View File

@@ -46,37 +46,35 @@ demo :: proc() {
as, bs, cs: string;
err: Error;
a, err = init(512);
a, err = init(4);
defer destroy(a);
as, err = itoa(a, 10);
as, err = itoa(a);
fmt.printf("a: %v, err: %v\n\n", as, err);
delete(as);
b, err = init(42);
b, err = init(4);
defer destroy(b);
bs, err = itoa(b, 10);
fmt.printf("b: %v, err: %v\n\n", bs, err);
bs, err = itoa(b);
fmt.printf("b: %s, err: %v\n\n", bs, err);
delete(bs);
c, err = init();
defer destroy(c);
cs, err = itoa(c, 10);
fmt.printf("c: %v\n", cs);
cs, err = itoa(c);
fmt.printf("b: %s, err: %v\n\n", cs, err);
delete(cs);
fmt.println("=== Add ===");
err = sub(c, a, b);
fmt.printf("Error: %v\n", err);
as, err = itoa(a, 10);
bs, err = itoa(b, 10);
cs, err = itoa(c, 10);
as, err = itoa(a);
bs, err = itoa(b);
cs, err = itoa(c);
fmt.printf("a: %v, bits: %v\n", as, count_bits(a));
fmt.printf("b: %v, bits: %v\n", bs, count_bits(b));
fmt.printf("c: %v, bits: %v\n", cs, count_bits(c));
delete(as); delete(bs); delete(cs);
fmt.println("radix_size:", radix_size(a, 10));
}
main :: proc() {

View File

@@ -11,7 +11,7 @@ package bigint
import "core:fmt"
log_n_int :: proc(a: ^Int, base: int) -> (log: int, err: Error) {
log_n_int :: proc(a: ^Int, base: DIGIT) -> (log: int, err: Error) {
assert_initialized(a);
if is_neg(a) || is_zero(a) || base < 2 || DIGIT(base) > _DIGIT_MAX {
return -1, .Invalid_Input;
@@ -20,7 +20,7 @@ log_n_int :: proc(a: ^Int, base: int) -> (log: int, err: Error) {
/*
Fast path for bases that are a power of two.
*/
if is_power_of_two(base) {
if is_power_of_two(int(base)) {
return _log_power_of_two(a, base), .OK;
}
@@ -44,7 +44,7 @@ log_n :: proc{log_n_int, log_n_digit};
Returns the log2 of an `Int`, provided `base` is a power of two.
Don't call it if it isn't.
*/
_log_power_of_two :: proc(a: ^Int, base: int) -> (log: int) {
_log_power_of_two :: proc(a: ^Int, base: DIGIT) -> (log: int) {
base := base;
y: int;
for y = 0; base & 1 == 0; {

View File

@@ -15,23 +15,60 @@ import "core:mem"
import "core:intrinsics"
import "core:fmt"
import "core:strings"
import "core:slice"
itoa :: proc(a: ^Int, radix: int, allocator := context.allocator) -> (res: string, err: Error) {
/*
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) {
assert_initialized(a);
/*
Radix defaults to 10.
*/
radix := radix if radix > 0 else 10;
if radix < 2 || radix > 64 {
return strings.clone("", allocator), .Invalid_Input;
/*
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.
*/
size: int;
size, err = radix_size(a, radix);
if zero_terminate {
size += 1;
}
/*
Fast path for radixes that are a power of two.
Exit if calculating the size returned an error.
*/
if is_power_of_two(radix) {
if err != .OK {
if zero_terminate {
return string(cstring("")), err;
}
return "", err;
}
/*
Allocate the buffer we need.
*/
buffer := make([]u8, size);
/*
Write the digits out into the buffer.
*/
written: int;
written, err = itoa_raw(a, radix, buffer, zero_terminate);
/*
For now, delete the buffer and fall back to the below on failure.
*/
if err == .OK {
return string(buffer[:written]), .OK;
}
delete(buffer);
fallback :: proc(a: ^Int, print_raw := false) -> string {
if print_raw {
@@ -48,12 +85,91 @@ itoa :: proc(a: ^Int, radix: int, allocator := context.allocator) -> (res: strin
return strings.clone(fallback(a), allocator), .Unimplemented;
}
int_to_string :: itoa;
/*
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) {
assert_initialized(a);
/*
Radix defaults to 10.
*/
radix := radix if radix > 0 else 10;
s: string;
s, err = itoa_string(a, radix, true, allocator);
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.
`written` includes the sign if negative, and the zero terminator if asked for.
*/
itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false) -> (written: int, err: Error) {
assert_initialized(a);
/*
Radix defaults to 10.
*/
radix := radix if radix > 0 else 10;
/*
Early exit if we were given an empty buffer.
*/
available := len(buffer);
if available == 0 {
return 0, .Buffer_Overflow;
}
/*
Early exit if `Int` == 0 or the entire `Int` fits in a single radix digit.
*/
if is_zero(a) || (a.used == 1 && a.digit[0] < DIGIT(radix)) {
needed := 2 if is_neg(a) else 1;
needed += 1 if zero_terminate else 0;
if available < needed {
return 0, .Buffer_Overflow;
}
if is_neg(a) {
buffer[written] = '-';
written += 1;
}
buffer[written] = RADIX_TABLE[a.digit[0]];
written += 1;
if zero_terminate {
buffer[written] = 0;
written += 1;
}
return written, .OK;
}
/*
Fast path for radixes that are a power of two.
*/
if is_power_of_two(int(radix)) {
return len(buffer), .OK;
}
return -1, .Unimplemented;
}
itoa :: proc{itoa_string, itoa_raw};
int_to_string :: itoa;
int_to_cstring :: itoa_cstring;
/*
We size for `string`, not `cstring`.
*/
radix_size :: proc(a: ^Int, radix: int) -> (size: int, err: Error) {
radix_size :: proc(a: ^Int, radix: i8) -> (size: int, err: Error) {
t := a;
if radix < 2 || radix > 64 {
@@ -67,7 +183,7 @@ radix_size :: proc(a: ^Int, radix: int) -> (size: int, err: Error) {
t.sign = .Zero_or_Positive;
log: int;
log, err = log_n(t, radix);
log, err = log_n(t, DIGIT(radix));
if err != .OK {
return log, err;
}
@@ -80,4 +196,19 @@ radix_size :: proc(a: ^Int, radix: int) -> (size: int, err: Error) {
} else {
return log + 1, .OK;
}
}
}
/*
Characters used in radix conversions.
*/
RADIX_TABLE := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
RADIX_TABLE_REVERSE := [80]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 */
};