From e3d8ac559de102a7600f54eacb8aba4b54b0e428 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 18 Jul 2021 00:58:19 +0200 Subject: [PATCH] bigint: Fast paths for radix code. --- core/math/bigint/example.odin | 22 +++-- core/math/bigint/log.odin | 6 +- core/math/bigint/radix.odin | 151 +++++++++++++++++++++++++++++++--- 3 files changed, 154 insertions(+), 25 deletions(-) diff --git a/core/math/bigint/example.odin b/core/math/bigint/example.odin index e21925f09..166088b4e 100644 --- a/core/math/bigint/example.odin +++ b/core/math/bigint/example.odin @@ -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() { diff --git a/core/math/bigint/log.odin b/core/math/bigint/log.odin index 347e60d99..980b5a7cb 100644 --- a/core/math/bigint/log.odin +++ b/core/math/bigint/log.odin @@ -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; { diff --git a/core/math/bigint/radix.odin b/core/math/bigint/radix.odin index 64118f70b..5c2a8aef2 100644 --- a/core/math/bigint/radix.odin +++ b/core/math/bigint/radix.odin @@ -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; } -} \ No newline at end of file +} + +/* + 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 */ +}; \ No newline at end of file