Browse Source

big: Add `int_to_bytes_{big, little}` + Python compatible variants.

Jeroen van Rijn 4 years ago
parent
commit
12f9b6db63

+ 6 - 6
core/math/big/build.bat

@@ -1,8 +1,8 @@
 @echo off
-:odin run . -vet
+odin run . -vet
 : -o:size
-:odin build . -build-mode:shared -show-timings -o:minimal -no-bounds-check && python test.py
-:odin build . -build-mode:shared -show-timings -o:size -no-bounds-check  && python test.py
-:odin build . -build-mode:shared -show-timings -o:size  && python test.py
-odin build . -build-mode:shared -show-timings -o:speed -no-bounds-check && python test.py
-:odin build . -build-mode:shared -show-timings -o:speed && python test.py
+: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

+ 2 - 2
core/math/big/common.odin

@@ -114,7 +114,7 @@ Flags :: bit_set[Flag; u8];
 Error :: enum int {
 	Okay                    = 0,
 	Out_Of_Memory           = 1,
-	// Invalid_Pointer         = 2,
+	Invalid_Pointer         = 2,
 	Invalid_Argument        = 3,
 
 	Assignment_To_Immutable = 4,
@@ -130,7 +130,7 @@ Error :: enum int {
 
 Error_String :: #partial [Error]string{
 	.Out_Of_Memory           = "Out of memory",
-	// .Invalid_Pointer         = "Invalid pointer",
+	.Invalid_Pointer         = "Invalid pointer",
 	.Invalid_Argument        = "Invalid argument",
 
 	.Assignment_To_Immutable = "Assignment to immutable",

File diff suppressed because it is too large
+ 3 - 2
core/math/big/example.odin


+ 143 - 1
core/math/big/helpers.odin

@@ -45,7 +45,7 @@ int_set_from_integer :: proc(dest: ^Int, src: $T, minimize := false, allocator :
 	return #force_inline internal_int_set_from_integer(dest, src, minimize);
 }
 
-set :: proc { int_set_from_integer, int_copy };
+set :: proc { int_set_from_integer, int_copy, int_atoi, };
 
 /*
 	Copy one `Int` to another.
@@ -464,6 +464,148 @@ clamp :: proc(a: ^Int, allocator := context.allocator) -> (err: Error) {
 	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;
+}
+
+/*
+	Size binary representation, Python `._to_bytes(num_bytes, "endianness", signed=Bool)` compatible.
+*/
+int_to_bytes_size_python :: proc(a: ^Int, signed := false, allocator := context.allocator) -> (size_in_bytes: int, err: Error) {
+	assert_if_nil(a);
+	size_in_bytes, err = int_to_bytes_size(a, signed, allocator);
+
+	/*
+		Python uses a complement representation of negative numbers and doesn't add a prefix byte.
+	*/
+	if signed {
+		size_in_bytes -= 1;
+	}
+	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; }
+	if size_in_bytes > len(buf) { return .Buffer_Overflow; }
+
+	size_in_bits := internal_count_bits(a);
+	i := 0;
+	if signed {
+		i += 1;
+		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 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 a.sign == .Zero_or_Positive {
+		return int_to_bytes_little(a, buf, signed, allocator);
+	}
+	if a.sign == .Negative && !signed { return .Invalid_Argument; }
+
+	l := len(buf);
+	if size_in_bytes, err = int_to_bytes_size_python(a, signed, allocator); err != nil { return err; }
+	if size_in_bytes > l              { return .Buffer_Overflow;  }
+
+	t := &Int{};
+	defer destroy(t);
+	if err = 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;
+	}
+	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 a.sign == .Zero_or_Positive {
+		return int_to_bytes_big(a, buf, signed, allocator);
+	}
+	if a.sign == .Negative && !signed { return .Invalid_Argument; }
+
+	l := len(buf);
+	if size_in_bytes, err = int_to_bytes_size_python(a, signed, allocator); err != nil { return err; }
+	if size_in_bytes > l              { return .Buffer_Overflow;  }
+
+	t := &Int{};
+	defer destroy(t);
+	if err = 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(a, offset, 8);
+		buf[i] = 255 - u8(bits & 255); i -= 1;
+	}
+	return;
+}
+
 /*
 	Initialize constants.
 */

+ 2 - 2
core/math/big/internal.odin

@@ -1719,9 +1719,9 @@ internal_int_neg :: proc(dest, src: ^Int, allocator := context.allocator) -> (er
 	/*
 		If `dest == src`, just fix `dest`'s sign.
 	*/
-	sign := Sign.Zero_or_Positive;
+	sign := Sign.Negative;
 	if #force_inline internal_is_zero(src) || #force_inline internal_is_negative(src) {
-		sign = .Negative;
+		sign = .Zero_or_Positive;
 	}
 	if dest == src {
 		dest.sign = sign;

+ 0 - 1
core/math/big/test.py

@@ -61,7 +61,6 @@ parser.add_argument(
 timed_or_fast.add_argument(
 	"-fast-tests",
 	help    = "Cut down on the number of iterations of each test",
-	default = True,
 	action  = "store_true",
 )
 

Some files were not shown because too many files changed in this diff