Parcourir la source

bigint: itoa works for numbers <= 120 bits.

Jeroen van Rijn il y a 4 ans
Parent
commit
341e8a3c99
3 fichiers modifiés avec 61 ajouts et 20 suppressions
  1. 3 3
      core/math/bigint/example.odin
  2. 1 1
      core/math/bigint/helpers.odin
  3. 57 16
      core/math/bigint/radix.odin

+ 3 - 3
core/math/bigint/example.odin

@@ -46,13 +46,13 @@ demo :: proc() {
 	as, bs, cs: string;
 	err:  Error;
 
-	a, err = init(4);
+	a, err = init(-512);
 	defer destroy(a);
 	as, err = itoa(a);
 	fmt.printf("a: %v, err: %v\n\n", as, err);
 	delete(as);
 
-	b, err = init(4);
+	b, err = init(42);
 	defer destroy(b);
 	bs, err = itoa(b);
 	fmt.printf("b: %s, err: %v\n\n", bs, err);
@@ -68,7 +68,7 @@ demo :: proc() {
 	err = sub(c, a, b);
 
 	fmt.printf("Error: %v\n", err);
-	as, err = itoa(a);
+	as, err = itoa(a, 8);
 	bs, err = itoa(b);
 	cs, err = itoa(c);
 	fmt.printf("a: %v, bits: %v\n", as, count_bits(a));

+ 1 - 1
core/math/bigint/helpers.odin

@@ -88,6 +88,7 @@ set_integer :: proc(a: ^Int, n: $T, minimize := false, loc := #caller_location)
 	a.used = 0;
 	a.sign = .Zero_or_Positive if n >= 0 else .Negative;
 	n = abs(n);
+
 	for n != 0 {
 		a.digit[a.used] = DIGIT(n) & _MASK;
 		a.used += 1;
@@ -96,7 +97,6 @@ set_integer :: proc(a: ^Int, n: $T, minimize := false, loc := #caller_location)
 	if minimize {
 		shrink(a);
 	}
-
 	_zero_unused(a);
 }
 

+ 57 - 16
core/math/bigint/radix.odin

@@ -114,6 +114,9 @@ itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false) -> (
 		Radix defaults to 10.
 	*/
 	radix := radix if radix > 0 else 10;
+	if radix < 2 || radix > 64 {
+		return 0, .Invalid_Input;
+	}
 
 	/*
 		Early exit if we were given an empty buffer.
@@ -123,7 +126,7 @@ itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false) -> (
 		return 0, .Buffer_Overflow;
 	}
 	/*
-		Early exit if `Int` == 0 or the entire `Int` fits in a single radix digit.
+		Fast path for when `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;
@@ -148,15 +151,51 @@ itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false) -> (
 		return written, .OK;
 	}
 
+	/*
+		Fast path for when `Int` fits within a `_WORD`.
+	*/
+	if a.used == 1 || a.used == 2 {
+		val := _WORD(a.digit[1]) << _DIGIT_BITS + _WORD(a.digit[0]);
+		for val > 0 {
+			if available == 0 {
+				return written, .Buffer_Overflow;
+			}
+
+			q := val / _WORD(radix);
+			buffer[written] = RADIX_TABLE[val - (q * _WORD(radix))];
+			available -= 1;
+			written   += 1;
+
+			val = q;
+		}
+		if is_neg(a) {
+			if available == 0 {
+				return written, .Buffer_Overflow;
+			}
+			buffer[written] = '-';
+			available -= 1;
+			written   += 1;
+		}
+		/*
+			Reverse output.
+		*/
+		slice.reverse(buffer[:written]);
+		if zero_terminate {
+			if available == 0 {
+				return written, .Buffer_Overflow;
+			}
+			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 len(buffer), .OK;
 	}
 
 	return -1, .Unimplemented;
@@ -170,8 +209,6 @@ int_to_cstring :: itoa_cstring;
 	We size for `string`, not `cstring`.
 */
 radix_size :: proc(a: ^Int, radix: i8) -> (size: int, err: Error) {
-	t := a;
-
 	if radix < 2 || radix > 64 {
 		return -1, .Invalid_Input;
 	}
@@ -180,22 +217,26 @@ radix_size :: proc(a: ^Int, radix: i8) -> (size: int, err: Error) {
  		return 1, .OK;
  	}
 
- 	t.sign = .Zero_or_Positive;
- 	log: int;
-
- 	log, err = log_n(t, DIGIT(radix));
+ 	/*
+		Calculate `log` on a temporary "copy" with its sign set to positive.
+ 	*/
+	t := &Int{
+		used      = a.used,
+		allocated = a.allocated,
+		sign      = .Zero_or_Positive,
+		digit     = a.digit,
+	};
+
+ 	size, err = log_n(t, DIGIT(radix));
  	if err != .OK {
- 		return log, err;
+ 		return;
  	}
 
  	/*
 		log truncates to zero, so we need to add one more, and one for `-` if negative.
  	*/
- 	if is_neg(a) {
- 		return log + 2, .OK;
- 	} else {
- 		return log + 1, .OK;
- 	}
+ 	size += 2 if is_neg(a) else 1;
+ 	return size, .OK;
 }
 
 /*