From de5d897b5c11097b34b6c594dac59db475601e91 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 6 Sep 2021 12:57:48 +0200 Subject: [PATCH] Add `internal_int_(pack, unpack)`. --- core/math/big/common.odin | 7 ++- core/math/big/example.odin | 28 ++++++---- core/math/big/helpers.odin | 14 ++--- core/math/big/radix.odin | 111 +++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 19 deletions(-) diff --git a/core/math/big/common.odin b/core/math/big/common.odin index 11810d144..d8dbdcd95 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -186,6 +186,10 @@ Error_String :: #partial [Error]string{ .Division_by_Zero = "Division by zero", .Math_Domain_Error = "Math domain error", + .Cannot_Open_File = "Cannot_Open_File", + .Cannot_Read_File = "Cannot_Read_File", + .Cannot_Write_File = "Cannot_Write_File", + .Unimplemented = "Unimplemented", }; @@ -231,7 +235,8 @@ when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) { _DIGIT_TYPE_BITS :: 8 * size_of(DIGIT); _WORD_TYPE_BITS :: 8 * size_of(_WORD); -_DIGIT_BITS :: _DIGIT_TYPE_BITS - 4; +_DIGIT_NAILS :: 4; +_DIGIT_BITS :: _DIGIT_TYPE_BITS - _DIGIT_NAILS; _WORD_BITS :: 2 * _DIGIT_BITS; _MASK :: (DIGIT(1) << DIGIT(_DIGIT_BITS)) - DIGIT(1); diff --git a/core/math/big/example.odin b/core/math/big/example.odin index dfc49bc15..c18ce0cbf 100644 --- a/core/math/big/example.odin +++ b/core/math/big/example.odin @@ -86,13 +86,13 @@ print :: proc(name: string, a: ^Int, base := i8(10), print_name := true, newline } } -printf :: fmt.printf; +// printf :: fmt.printf; demo :: proc() { a, b, c, d, e, f, res := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}; defer destroy(a, b, c, d, e, f, res); - bits := 64; + bits := 111; trials := -1; flags := Primality_Flags{}; @@ -108,17 +108,23 @@ demo :: proc() { fmt.printf("err: %v\n", err); fmt.printf("RANDOM_PRIME_ITERATIONS_USED: %v\n", RANDOM_PRIME_ITERATIONS_USED); - // err = internal_int_write_to_ascii_file(a, "a.txt"); - // if err != nil { - // fmt.printf("internal_int_write_to_ascii_file returned %v\n", err); - // } + nails := 0; - // err = internal_int_read_from_ascii_file(b, "a.txt"); - // if err != nil { - // fmt.printf("internal_int_read_from_ascii_file returned %v\n", err); - // } + count := internal_int_pack_count(a, u8, nails); + buf := make([]u8, count); + defer delete(buf); - // print("b: ", b); + written: int; + order := Order.LSB_First; + + fmt.printf("\na.digit: %v\n", a.digit[:a.used]); + written, err = internal_int_pack(a, buf, nails, order); + fmt.printf("\nPacked into buf: %v | err: %v | written: %v\n", buf, err, written); + + err = internal_int_unpack(b, buf, nails, order); + print("\nUnpacked into b: ", b); + fmt.printf("err: %v\n", err); + fmt.printf("b.digit: %v\n", b.digit[:b.used]); } main :: proc() { diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index ccd00615f..352367a23 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -495,7 +495,7 @@ int_to_bytes_little :: proc(a: ^Int, buf: []u8, signed := false, allocator := co if signed { buf[l - 1] = 1 if a.sign == .Negative else 0; } - for offset := 0; offset < size_in_bits; offset += 8 { + #no_bounds_check for offset := 0; offset < size_in_bits; offset += 8 { bits, _ := internal_int_bitfield_extract(a, offset, 8); buf[i] = u8(bits & 255); i += 1; } @@ -519,7 +519,7 @@ int_to_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := conte if signed { buf[0] = 1 if a.sign == .Negative else 0; } - for offset := 0; offset < size_in_bits; offset += 8 { + #no_bounds_check for offset := 0; offset < size_in_bits; offset += 8 { bits, _ := internal_int_bitfield_extract(a, offset, 8); buf[i] = u8(bits & 255); i -= 1; } @@ -546,7 +546,7 @@ int_to_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocato size_in_bits := internal_count_bits(t); i := 0; - for offset := 0; offset < size_in_bits; offset += 8 { + #no_bounds_check 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; } @@ -554,7 +554,7 @@ int_to_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, allocato } else { size_in_bits := internal_count_bits(a); i := 0; - for offset := 0; offset < size_in_bits; offset += 8 { + #no_bounds_check for offset := 0; offset < size_in_bits; offset += 8 { bits, _ := internal_int_bitfield_extract(a, offset, 8); buf[i] = u8(bits & 255); i += 1; } @@ -583,7 +583,7 @@ int_to_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator : size_in_bits := internal_count_bits(t); i := l - 1; - for offset := 0; offset < size_in_bits; offset += 8 { + #no_bounds_check 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; } @@ -620,7 +620,7 @@ int_from_bytes_big :: proc(a: ^Int, buf: []u8, signed := false, allocator := con buf = buf[1:]; } - for v in buf { + #no_bounds_check for v in buf { internal_shl(a, a, 8) or_return; a.digit[0] |= DIGIT(v); } @@ -657,7 +657,7 @@ int_from_bytes_big_python :: proc(a: ^Int, buf: []u8, signed := false, allocator buf = buf[1:]; } - for v in buf { + #no_bounds_check for v in buf { internal_shl(a, a, 8) or_return; if signed && sign == .Negative { a.digit[0] |= DIGIT(255 - v); diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index fb1af250b..f3a9b9d43 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -431,6 +431,117 @@ internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8( return nil if ok else .Cannot_Write_File; } +/* + Calculate the size needed for `internal_int_pack`. + + See https://gmplib.org/manual/Integer-Import-and-Export.html +*/ +internal_int_pack_count :: proc(a: ^Int, $T: typeid, nails := 0) -> (size_needed: int) { + assert(nails >= 0 && nails < (size_of(T) * 8)); + + bits := internal_count_bits(a); + size := size_of(T); + + size_needed = bits / ((size * 8) - nails); + size_needed += 1 if (bits % ((size * 8) - nails)) != 0 else 0; + + return size_needed; +} + +/* + Based on gmp's mpz_export. + See https://gmplib.org/manual/Integer-Import-and-Export.html + + `buf` is a pre-allocated slice of type `T` "words", which must be an unsigned integer of some description. + Use `internal_int_pack_count(a, T, nails)` to calculate the necessary size. + The library internally uses `DIGIT` as the type, which is u64 or u32 depending on the platform. + You are of course welcome to export to []u8, []u32be, and so forth. + After this you can use `mem.slice_data_cast` to interpret the buffer as bytes if you so choose. + + `nails` are the number of top bits the output "word" reserves. + To mimic the internals of this library, this would be 4. + + To use the minimum amount of output bytes, set `nails` to 0 and pass a `[]u8`. + IMPORTANT: `pack` serializes the magnitude of an Int, that is, the output is unsigned. + + Assumes `a` not to be `nil` and to have been initialized. +*/ +internal_int_pack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First) -> (written: int, err: Error) + where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 { + + assert(nails >= 0 && nails < (size_of(T) * 8)); + + type_size := size_of(T); + type_bits := (type_size * 8) - nails; + + word_count := internal_int_pack_count(a, T, nails); + bit_count := internal_count_bits(a); + + if len(buf) < word_count { + return 0, .Buffer_Overflow; + } + + bit_offset := 0; + word_offset := 0; + + #no_bounds_check for i := 0; i < word_count; i += 1 { + bit_offset = i * type_bits; + if order == .MSB_First { + word_offset = word_count - i - 1; + } else { + word_offset = i; + } + + bits_to_get := min(type_bits, bit_count - bit_offset); + W := internal_int_bitfield_extract(a, bit_offset, bits_to_get) or_return; + buf[word_offset] = T(W); + } + + return word_count, nil; +} + + + +internal_int_unpack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First, allocator := context.allocator) -> (err: Error) + where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 { + assert(nails >= 0 && nails < (size_of(T) * 8)); + context.allocator = allocator; + + type_size := size_of(T); + type_bits := (type_size * 8) - nails; + type_mask := T(1 << uint(type_bits)) - 1; + + if len(buf) == 0 { + return .Invalid_Argument; + } + + bit_count := type_bits * len(buf); + digit_count := (bit_count / _DIGIT_BITS) + min(1, bit_count % _DIGIT_BITS); + + /* + Pre-size output Int. + */ + internal_grow(a, digit_count) or_return; + + t := &Int{}; + defer internal_destroy(t); + + if order == .LSB_First { + for W, i in buf { + internal_set(t, W & type_mask) or_return; + internal_shl(t, t, type_bits * i) or_return; + internal_add(a, a, t) or_return; + } + } else { + for W in buf { + internal_set(t, W & type_mask) or_return; + internal_shl(a, a, type_bits) or_return; + internal_add(a, a, t) or_return; + } + } + + return internal_clamp(a); +} /* Overestimate the size needed for the bigint to string conversion by a very small amount.