Browse Source

big: Add another way to estimate radix size.

Jeroen van Rijn 4 years ago
parent
commit
0a431eef19
3 changed files with 65 additions and 12 deletions
  1. 0 1
      core/math/big/helpers.odin
  2. 0 1
      core/math/big/log.odin
  3. 65 10
      core/math/big/radix.odin

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

@@ -37,7 +37,6 @@ int_set_from_integer :: proc(dest: ^Int, src: $T, minimize := false, allocator :
 	if err = clear_if_uninitialized(dest); err != .None {
 		return err;
 	}
-
 	dest.used = 0;
 	dest.sign = .Zero_or_Positive if src >= 0 else .Negative;
 	src = abs(src);

+ 0 - 1
core/math/big/log.odin

@@ -210,7 +210,6 @@ int_log_digit :: proc(a: DIGIT, base: DIGIT) -> (log: int, err: Error) {
    	}
 }
 
-
 /*
 	Internal implementation of log.	
 */

+ 65 - 10
core/math/big/radix.odin

@@ -232,6 +232,8 @@ itoa :: proc{itoa_string, itoa_raw};
 int_to_string  :: itoa;
 int_to_cstring :: itoa_cstring;
 
+
+
 /*
 	We size for `string` by default.
 */
@@ -251,17 +253,39 @@ radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false) -> (size: int, e
 		return 1, .None;
 	}
 
-	/*
-		Calculate `log` on a temporary "copy" with its sign set to positive.
-	*/
-	t := &Int{
-		used      = a.used,
-		sign      = .Zero_or_Positive,
-		digit     = a.digit,
-	};
+	if pot, _ := is_power_of_two(a); pot {
+		/*
+			Calculate `log` on a temporary "copy" with its sign set to positive.
+		*/
+		t := &Int{
+			used      = a.used,
+			sign      = .Zero_or_Positive,
+			digit     = a.digit,
+		};
 
-	if size, err = log(t, DIGIT(radix)); err != .None {
-		return 0, err;
+		if size, err = log(t, DIGIT(radix)); err != .None {
+			return 0, err;
+		}
+	} else {
+		la, k := &Int{}, &Int{};
+		defer destroy(la, k);
+
+		/* la = floor(log_2(a)) + 1 */
+		bit_count, _ := count_bits(a);
+		err = set(la, bit_count);
+
+		/* k = floor(2^29/log_2(radix)) + 1 */
+		lb := _log_bases;
+		err = set(k, lb[radix]);
+
+		/* n = floor((la *  k) / 2^29) + 1 */
+		if err = mul(k, la, k); err != .None { return 0, err; }
+		if err = shr(k, k, _RADIX_SIZE_SCALE); err != .None { return 0, err; }
+
+		/* The "+1" here is the "+1" in "floor((la *  k) / 2^29) + 1" */
+		/* n = n + 1 + EOS + sign */
+		size_, _ := get(k, u128);
+		size = int(size_);
 	}
 
 	/*
@@ -272,6 +296,37 @@ radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false) -> (size: int, e
 	return size, .None;
 }
 
+/*
+	Overestimate the size needed for the bigint to string conversion by a very small amount.
+	The error is about 10^-8; it will overestimate the result by at most 11 elements for
+	a number of the size 2^(2^31)-1 which is currently the largest possible in this library.
+	Some short tests gave no results larger than 5 (plus 2 for sign and EOS).
+ */
+
+/*
+	Table of {0, INT(log_2([1..64])*2^p)+1 } where p is the scale
+	factor defined in MP_RADIX_SIZE_SCALE and INT() extracts the integer part (truncating).
+	Good for 32 bit "int". Set MP_RADIX_SIZE_SCALE = 61 and recompute values
+	for 64 bit "int".
+ */
+
+_RADIX_SIZE_SCALE :: 29;
+_log_bases :: [65]u32{
+			0,         0, 0x20000001, 0x14309399, 0x10000001,
+	0xdc81a35, 0xc611924,  0xb660c9e,  0xaaaaaab,  0xa1849cd,
+	0x9a209a9, 0x94004e1,  0x8ed19c2,  0x8a5ca7d,  0x867a000,
+	0x830cee3, 0x8000001,  0x7d42d60,  0x7ac8b32,  0x7887847,
+	0x7677349, 0x749131f,  0x72d0163,  0x712f657,  0x6fab5db,
+	0x6e40d1b, 0x6ced0d0,  0x6badbde,  0x6a80e3b,  0x6964c19,
+	0x6857d31, 0x6758c38,  0x6666667,  0x657fb21,  0x64a3b9f,
+	0x63d1ab4, 0x6308c92,  0x624869e,  0x618ff47,  0x60dedea,
+	0x6034ab0, 0x5f90e7b,  0x5ef32cb,  0x5e5b1b2,  0x5dc85c3,
+	0x5d3aa02, 0x5cb19d9,  0x5c2d10f,  0x5bacbbf,  0x5b3064f,
+	0x5ab7d68, 0x5a42df0,  0x59d1506,  0x5962ffe,  0x58f7c57,
+	0x588f7bc, 0x582a000,  0x57c7319,  0x5766f1d,  0x5709243,
+	0x56adad9, 0x565474d,  0x55fd61f,  0x55a85e8,  0x5555556,
+};
+
 /*
 	Characters used in radix conversions.
 */