2
0
Эх сурвалжийг харах

bigint: log_n for bases that fit within one DIGIT or are a power of two.

Jeroen van Rijn 4 жил өмнө
parent
commit
767948ab46

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

@@ -76,7 +76,7 @@ demo :: proc() {
 	fmt.printf("c: %v, bits: %v\n", cs, count_bits(c));
 	fmt.printf("c: %v, bits: %v\n", cs, count_bits(c));
 	delete(as); delete(bs); delete(cs);
 	delete(as); delete(bs); delete(cs);
 
 
-	fmt.println("log2:", log_n(a, 8));
+	fmt.println("radix_size:", radix_size(a, 10));
 }
 }
 
 
 main :: proc() {
 main :: proc() {

+ 87 - 8
core/math/bigint/log.odin

@@ -9,28 +9,37 @@ package bigint
 	The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
 	The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
 */
 */
 
 
-log_n :: proc(a: ^Int, base: int) -> (log: int, err: Error) {
+import "core:fmt"
+
+log_n_int :: proc(a: ^Int, base: int) -> (log: int, err: Error) {
 	assert_initialized(a);
 	assert_initialized(a);
 	if is_neg(a) || is_zero(a) || base < 2 || DIGIT(base) > _DIGIT_MAX {
 	if is_neg(a) || is_zero(a) || base < 2 || DIGIT(base) > _DIGIT_MAX {
 		return -1, .Invalid_Input;
 		return -1, .Invalid_Input;
 	}
 	}
 
 
+	/*
+		Fast path for bases that are a power of two.
+	*/
 	if is_power_of_two(base) {
 	if is_power_of_two(base) {
 		return _log_power_of_two(a, base), .OK;
 		return _log_power_of_two(a, base), .OK;
 	}
 	}
 
 
-   // if (MP_HAS(S_MP_LOG_D) && (a->used == 1)) {
-   //    *c = s_mp_log_d((mp_digit)base, a->dp[0]);
-   //    return MP_OKAY;
-   // }
+	/*
+		Fast path for `Int`s that fit within a single `DIGIT`.
+	*/
+	if a.used == 1 {
+		return log_n_digit(a.digit[0], DIGIT(base)), .OK;
+	}
 
 
-   // if (MP_HAS(S_MP_LOG)) {
-   //    return s_mp_log(a, (mp_digit)base, c);
-   // }
+    // if (MP_HAS(S_MP_LOG)) {
+    //    return s_mp_log(a, (mp_digit)base, c);
+    // }
 
 
 	return -1, .Unimplemented;
 	return -1, .Unimplemented;
 }
 }
 
 
+log_n :: proc{log_n_int, log_n_digit};
+
 /*
 /*
 	Returns the log2 of an `Int`, provided `base` is a power of two.
 	Returns the log2 of an `Int`, provided `base` is a power of two.
 	Don't call it if it isn't.
 	Don't call it if it isn't.
@@ -44,3 +53,73 @@ _log_power_of_two :: proc(a: ^Int, base: int) -> (log: int) {
 	}
 	}
 	return (count_bits(a) - 1) / y;
 	return (count_bits(a) - 1) / y;
 }
 }
+
+/*
+
+*/
+small_pow :: proc(base: _WORD, exponent: _WORD) -> (result: _WORD) {
+	exponent := exponent; base := base;
+   	result = _WORD(1);
+
+   	for exponent != 0 {
+   		if exponent & 1 == 1 {
+   			result *= base;
+   		}
+   		exponent >>= 1;
+   		base *= base;
+   	}
+   	return result;
+}
+
+log_n_digit :: proc(a: DIGIT, base: DIGIT) -> (log: int) {
+	/*
+		If the number is smaller than the base, it fits within a fraction.
+		Therefore, we return 0.
+	*/
+	if a < base {
+		return 0;
+	}
+
+	/*
+		If a number equals the base, the log is 1.
+	*/
+	if a == base {
+		return 1;
+	}
+
+	N := _WORD(a);
+	bracket_low  := _WORD(1);
+	bracket_high := _WORD(base);
+	high := 1;
+	low  := 0;
+
+	for bracket_high < N {
+		low = high;
+		bracket_low = bracket_high;
+		high <<= 1;
+		bracket_high *= bracket_high;
+	}
+
+	for high - low > 1 {
+		mid := (low + high) >> 1;
+		bracket_mid := bracket_low * small_pow(_WORD(base), _WORD(mid - low));
+
+		if N < bracket_mid {
+			high = mid;
+			bracket_high = bracket_mid;
+		}
+		if N > bracket_mid {
+			low = mid;
+			bracket_low = bracket_mid;
+		}
+		if N == bracket_mid {
+			return mid;
+		}
+   	}
+
+   	if bracket_high == N {
+   		return high;
+   	} else {
+   		return low;
+   	}
+}

+ 29 - 26
core/math/bigint/radix.odin

@@ -26,8 +26,7 @@ itoa :: proc(a: ^Int, radix: int, allocator := context.allocator) -> (res: strin
 	/*
 	/*
 		Fast path for radixes that are a power of two.
 		Fast path for radixes that are a power of two.
 	*/
 	*/
-	if radix & 1 == 0 {
-
+	if is_power_of_two(radix) {
 
 
 	}
 	}
 
 
@@ -51,30 +50,34 @@ itoa :: proc(a: ^Int, radix: int, allocator := context.allocator) -> (res: strin
 
 
 int_to_string :: itoa;
 int_to_string :: itoa;
 
 
+/*
+	We size for `string`, not `cstring`.
+*/
+radix_size :: proc(a: ^Int, radix: int) -> (size: int, err: Error) {
+	t := a;
 
 
-radix_size :: proc(a: ^Int, base: int) -> (size: int, err: Error) {
-   // mp_err err;
-   // mp_int a_;
-   // int b;
-
-   // /* make sure the radix is in range */
-   // if ((radix < 2) || (radix > 64)) {
-   //    return MP_VAL;
-   // }
-
-   // if (mp_iszero(a)) {
-   //    *size = 2;
-   //    return MP_OKAY;
-   // }
-
-   // a_ = *a;
-   // a_.sign = MP_ZPOS;
-   // if ((err = mp_log_n(&a_, radix, &b)) != MP_OKAY) {
-   //    return err;
-   // }
-
-   // /* mp_ilogb truncates to zero, hence we need one extra put on top and one for `\0`. */
-   // *size = (size_t)b + 2U + (mp_isneg(a) ? 1U : 0U);
+	if radix < 2 || radix > 64 {
+		return -1, .Invalid_Input;
+	}
 
 
-   return size, .OK;
+ 	if is_zero(a) {
+ 		return 1, .OK;
+ 	}
+
+ 	t.sign = .Zero_or_Positive;
+ 	log: int;
+
+ 	log, err = log_n(t, radix);
+ 	if err != .OK {
+ 		return log, err;
+ 	}
+
+ 	/*
+		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;
+ 	}
 }
 }