diff --git a/CHANGELOG.md b/CHANGELOG.md index 264f81c7..8569cc3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - `sqrt` for `UD30x9` and `SD29x9` with round-down semantics. (#286) - `log2`, `ln`, `log10` for `UD30x9` (rounds down) and `SD29x9` (rounds toward zero). (#320) +### `openzeppelin_math` + +#### Changed + +- `u128::is_power_of_ten` and `u256::is_power_of_ten` now compute the result via `log10_floor` and `pow` instead of a hardcoded lookup table. (#323) + ## 1.1.0 (21-04-2026) ### `openzeppelin_fp_math` diff --git a/math/core/sources/internal/macros.move b/math/core/sources/internal/macros.move index c7e9b37b..3424330d 100644 --- a/math/core/sources/internal/macros.move +++ b/math/core/sources/internal/macros.move @@ -404,6 +404,26 @@ public(package) macro fun log10<$Int>($value: $Int, $rounding_mode: RoundingMode } } +/// Test whether `$n` is an exact non-negative integer power of ten (`10^k` for some +/// `k >= 0`). +/// +/// #### Generics +/// - `$Int`: Any unsigned integer type (`u8`, `u16`, `u32`, `u64`, `u128`, or `u256`). +/// +/// #### Parameters +/// - `$n`: The unsigned integer to test. +/// +/// #### Returns +/// - `true` if `$n` equals `10^k` for some `k >= 0`, otherwise `false`. +public(package) macro fun is_power_of_ten<$Int>($n: $Int): bool { + let n = $n as u256; + if (n == 0) { + return false + }; + let exp = log10_floor(n); + n == 10u256.pow(exp) +} + /// Compute floor(log10(value)) using binary search over powers of 10. /// /// This helper uses precomputed constants (`TEN_POW_2`, `TEN_POW_4`, etc.) to efficiently @@ -1015,41 +1035,3 @@ public(package) fun mul_mod_impl(a: u256, b: u256, modulus: u256): u256 { ((a * b) % modulus) } } - -/// Perform binary search on a sorted vector to find if `$needle` exists in the vector. -/// -/// The helper works across all unsigned widths and performs a standard iterative binary -/// search on a sorted vector. It compares the search value against the middle element -/// at each iteration until either the value is found or the search space is exhausted. -/// -/// #### Generics -/// - `$Int`: Any unsigned integer type (`u8`, `u16`, `u32`, `u64`, `u128`, or `u256`). -/// -/// #### Parameters -/// - `$haystack`: A sorted vector of unsigned integers to search through. -/// - `$needle`: The value to search for in the vector. -/// -/// #### Returns -/// `true` if `$needle` exists in `$haystack`, `false` otherwise. -public(package) macro fun binary_search<$Int>($haystack: vector<$Int>, $needle: $Int): bool { - let haystack = $haystack; - let needle = $needle; - - let mut left = 0; - let mut right = haystack.length(); - - while (left < right) { - let mid = left + (right - left) / 2; - let mid_val = *haystack.borrow(mid); - - if (mid_val == needle) { - return true - } else if (mid_val < needle) { - left = mid + 1; - } else { - right = mid; - } - }; - - false -} diff --git a/math/core/sources/u128.move b/math/core/sources/u128.move index f5519a50..45ac06e0 100644 --- a/math/core/sources/u128.move +++ b/math/core/sources/u128.move @@ -217,7 +217,6 @@ public fun mul_mod(a: u128, b: u128, modulus: u128): u128 { /// Returns `true` if `n` is a power of ten. /// -/// Uses a lookup table with binary search for efficiency. /// For `u128`, valid powers of ten range from 10^0 to 10^38. /// /// #### Parameters @@ -226,48 +225,5 @@ public fun mul_mod(a: u128, b: u128, modulus: u128): u128 { /// #### Returns /// - `true` if `n` is a power of ten within the `u128` range, otherwise `false`. public fun is_power_of_ten(n: u128): bool { - // Powers of 10 from 10^0 to 10^38 for u128 - let powers = vector[ - 1u128, - 10u128, - 100u128, - 1000u128, - 10000u128, - 100000u128, - 1000000u128, - 10000000u128, - 100000000u128, - 1000000000u128, - 10000000000u128, - 100000000000u128, - 1000000000000u128, - 10000000000000u128, - 100000000000000u128, - 1000000000000000u128, - 10000000000000000u128, - 100000000000000000u128, - 1000000000000000000u128, - 10000000000000000000u128, - 100000000000000000000u128, - 1000000000000000000000u128, - 10000000000000000000000u128, - 100000000000000000000000u128, - 1000000000000000000000000u128, - 10000000000000000000000000u128, - 100000000000000000000000000u128, - 1000000000000000000000000000u128, - 10000000000000000000000000000u128, - 100000000000000000000000000000u128, - 1000000000000000000000000000000u128, - 10000000000000000000000000000000u128, - 100000000000000000000000000000000u128, - 1000000000000000000000000000000000u128, - 10000000000000000000000000000000000u128, - 100000000000000000000000000000000000u128, - 1000000000000000000000000000000000000u128, - 10000000000000000000000000000000000000u128, - 100000000000000000000000000000000000000u128, - ]; - - macros::binary_search!(powers, n) + macros::is_power_of_ten!(n) } diff --git a/math/core/sources/u256.move b/math/core/sources/u256.move index 8fba9414..321b515d 100644 --- a/math/core/sources/u256.move +++ b/math/core/sources/u256.move @@ -226,7 +226,6 @@ public fun mul_mod(a: u256, b: u256, modulus: u256): u256 { /// Returns `true` if `n` is a power of ten. /// -/// Uses a lookup table with binary search for efficiency. /// For `u256`, valid powers of ten range from 10^0 to 10^77. /// /// #### Parameters @@ -235,87 +234,5 @@ public fun mul_mod(a: u256, b: u256, modulus: u256): u256 { /// #### Returns /// - `true` if `n` is a power of ten within the `u256` range, otherwise `false`. public fun is_power_of_ten(n: u256): bool { - // Powers of 10 from 10^0 to 10^77 for u256 - let powers = vector[ - 1u256, - 10u256, - 100u256, - 1000u256, - 10000u256, - 100000u256, - 1000000u256, - 10000000u256, - 100000000u256, - 1000000000u256, - 10000000000u256, - 100000000000u256, - 1000000000000u256, - 10000000000000u256, - 100000000000000u256, - 1000000000000000u256, - 10000000000000000u256, - 100000000000000000u256, - 1000000000000000000u256, - 10000000000000000000u256, - 100000000000000000000u256, - 1000000000000000000000u256, - 10000000000000000000000u256, - 100000000000000000000000u256, - 1000000000000000000000000u256, - 10000000000000000000000000u256, - 100000000000000000000000000u256, - 1000000000000000000000000000u256, - 10000000000000000000000000000u256, - 100000000000000000000000000000u256, - 1000000000000000000000000000000u256, - 10000000000000000000000000000000u256, - 100000000000000000000000000000000u256, - 1000000000000000000000000000000000u256, - 10000000000000000000000000000000000u256, - 100000000000000000000000000000000000u256, - 1000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000000000000000u256, - 1000000000000000000000000000000000000000000000000000000000000000000000000000u256, - 10000000000000000000000000000000000000000000000000000000000000000000000000000u256, - 100000000000000000000000000000000000000000000000000000000000000000000000000000u256, - ]; - - macros::binary_search!(powers, n) + macros::is_power_of_ten!(n) } diff --git a/math/core/tests/macros/binary_search.move b/math/core/tests/macros/binary_search.move deleted file mode 100644 index 741114aa..00000000 --- a/math/core/tests/macros/binary_search.move +++ /dev/null @@ -1,239 +0,0 @@ -#[test_only] -module openzeppelin_math::binary_search; - -use openzeppelin_math::macros; -use std::unit_test::assert_eq; - -#[test] -fun binary_search_finds_element_at_beginning() { - let haystack = vector[1u64, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!(macros::binary_search!(haystack, 1u64), true); -} - -#[test] -fun binary_search_finds_element_at_end() { - let haystack = vector[1u64, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!(macros::binary_search!(haystack, 10u64), true); -} - -#[test] -fun binary_search_finds_element_in_middle() { - let haystack = vector[1u64, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!(macros::binary_search!(haystack, 5u64), true); - assert_eq!(macros::binary_search!(haystack, 6u64), true); -} - -#[test] -fun binary_search_finds_all_elements() { - let haystack = vector[10u32, 20, 30, 40, 50]; - assert_eq!(macros::binary_search!(haystack, 10u32), true); - assert_eq!(macros::binary_search!(haystack, 20u32), true); - assert_eq!(macros::binary_search!(haystack, 30u32), true); - assert_eq!(macros::binary_search!(haystack, 40u32), true); - assert_eq!(macros::binary_search!(haystack, 50u32), true); -} - -#[test] -fun binary_search_returns_false_for_missing_element() { - let haystack = vector[1u64, 3, 5, 7, 9]; - assert_eq!(macros::binary_search!(haystack, 2u64), false); - assert_eq!(macros::binary_search!(haystack, 4u64), false); - assert_eq!(macros::binary_search!(haystack, 6u64), false); - assert_eq!(macros::binary_search!(haystack, 8u64), false); -} - -#[test] -fun binary_search_returns_false_for_value_below_range() { - let haystack = vector[10u64, 20, 30, 40, 50]; - assert_eq!(macros::binary_search!(haystack, 0u64), false); - assert_eq!(macros::binary_search!(haystack, 5u64), false); - assert_eq!(macros::binary_search!(haystack, 9u64), false); -} - -#[test] -fun binary_search_returns_false_for_value_above_range() { - let haystack = vector[10u64, 20, 30, 40, 50]; - assert_eq!(macros::binary_search!(haystack, 51u64), false); - assert_eq!(macros::binary_search!(haystack, 60u64), false); - assert_eq!(macros::binary_search!(haystack, 100u64), false); -} - -#[test] -fun binary_search_handles_empty_vector() { - let haystack = vector[]; - assert_eq!(macros::binary_search!(haystack, 0u64), false); - assert_eq!(macros::binary_search!(haystack, 1u64), false); -} - -#[test] -fun binary_search_handles_single_element_found() { - let haystack = vector[42u64]; - assert_eq!(macros::binary_search!(haystack, 42u64), true); -} - -#[test] -fun binary_search_handles_single_element_not_found() { - let haystack = vector[42u64]; - assert_eq!(macros::binary_search!(haystack, 41u64), false); - assert_eq!(macros::binary_search!(haystack, 43u64), false); -} - -#[test] -fun binary_search_handles_two_elements() { - let haystack = vector[10u64, 20]; - assert_eq!(macros::binary_search!(haystack, 10u64), true); - assert_eq!(macros::binary_search!(haystack, 20u64), true); - assert_eq!(macros::binary_search!(haystack, 5u64), false); - assert_eq!(macros::binary_search!(haystack, 15u64), false); - assert_eq!(macros::binary_search!(haystack, 25u64), false); -} - -#[test] -fun binary_search_handles_duplicates() { - let haystack = vector[1u64, 2, 2, 2, 3]; - assert_eq!(macros::binary_search!(haystack, 2u64), true); - assert_eq!(macros::binary_search!(haystack, 1u64), true); - assert_eq!(macros::binary_search!(haystack, 3u64), true); -} - -#[test] -fun binary_search_works_with_u8() { - let haystack = vector[1u8, 10, 20, 30, 40, 50, 100, 200, 255]; - assert_eq!(macros::binary_search!(haystack, 1u8), true); - assert_eq!(macros::binary_search!(haystack, 30u8), true); - assert_eq!(macros::binary_search!(haystack, 255u8), true); - assert_eq!(macros::binary_search!(haystack, 25u8), false); -} - -#[test] -fun binary_search_works_with_u16() { - let haystack = vector[100u16, 1000, 10000, 50000, 65535]; - assert_eq!(macros::binary_search!(haystack, 100u16), true); - assert_eq!(macros::binary_search!(haystack, 10000u16), true); - assert_eq!(macros::binary_search!(haystack, 65535u16), true); - assert_eq!(macros::binary_search!(haystack, 500u16), false); -} - -#[test] -fun binary_search_works_with_u32() { - let haystack = vector[1u32, 1000, 1000000, 1000000000]; - assert_eq!(macros::binary_search!(haystack, 1u32), true); - assert_eq!(macros::binary_search!(haystack, 1000000u32), true); - assert_eq!(macros::binary_search!(haystack, 999999u32), false); -} - -#[test] -fun binary_search_works_with_u128() { - let haystack = vector[ - 1u128, - 1000, - 1000000, - 1000000000, - 1000000000000, - 340282366920938463463374607431768211455, // max u128 - ]; - assert_eq!(macros::binary_search!(haystack, 1u128), true); - assert_eq!(macros::binary_search!(haystack, 1000000000000u128), true); - assert_eq!(macros::binary_search!(haystack, 340282366920938463463374607431768211455u128), true); - assert_eq!(macros::binary_search!(haystack, 999u128), false); -} - -#[test] -fun binary_search_works_with_u256() { - let haystack = vector[ - 1u256, - 1000, - 1000000, - 1000000000, - 1000000000000000000, - std::u256::max_value!(), - ]; - assert_eq!(macros::binary_search!(haystack, 1u256), true); - assert_eq!(macros::binary_search!(haystack, 1000000000u256), true); - assert_eq!(macros::binary_search!(haystack, std::u256::max_value!()), true); - assert_eq!(macros::binary_search!(haystack, 999u256), false); -} - -#[test] -fun binary_search_handles_large_sorted_vector() { - // Test with a larger vector to ensure binary search efficiency - let haystack = vector[ - 1u64, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - ]; - assert_eq!(macros::binary_search!(haystack, 1u64), true); - assert_eq!(macros::binary_search!(haystack, 15u64), true); - assert_eq!(macros::binary_search!(haystack, 30u64), true); - assert_eq!(macros::binary_search!(haystack, 16u64), true); - assert_eq!(macros::binary_search!(haystack, 0u64), false); - assert_eq!(macros::binary_search!(haystack, 31u64), false); -} - -#[test] -fun binary_search_handles_powers_of_ten() { - // Test with actual powers of 10 like in is_power_of_ten - let powers = vector[ - 1u64, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000, - ]; - - // All powers should be found - assert_eq!(macros::binary_search!(powers, 1u64), true); - assert_eq!(macros::binary_search!(powers, 10u64), true); - assert_eq!(macros::binary_search!(powers, 1000u64), true); - assert_eq!(macros::binary_search!(powers, 1000000u64), true); - assert_eq!(macros::binary_search!(powers, 10000000000000000000u64), true); - - // Non-powers should not be found - assert_eq!(macros::binary_search!(powers, 2u64), false); - assert_eq!(macros::binary_search!(powers, 11u64), false); - assert_eq!(macros::binary_search!(powers, 999u64), false); - assert_eq!(macros::binary_search!(powers, 1001u64), false); - assert_eq!(macros::binary_search!(powers, 9999u64), false); -} diff --git a/math/core/tests/macros/is_power_of_ten.move b/math/core/tests/macros/is_power_of_ten.move new file mode 100644 index 00000000..be536046 --- /dev/null +++ b/math/core/tests/macros/is_power_of_ten.move @@ -0,0 +1,123 @@ +#[test_only] +module openzeppelin_math::is_power_of_ten; + +use openzeppelin_math::macros; +use std::unit_test::assert_eq; + +// Exhaustive macro-level coverage of `is_power_of_ten!` across every supported +// width. u128 and u256 are the production-routed widths; u8–u64 do not currently +// route through the macro but are tested here so a future routing change cannot +// silently regress. +#[test] +fun is_power_of_ten_macro_works_on_u8() { + assert_eq!(macros::is_power_of_ten!(0u8), false); + assert_eq!(macros::is_power_of_ten!(1u8), true); + assert_eq!(macros::is_power_of_ten!(10u8), true); + assert_eq!(macros::is_power_of_ten!(100u8), true); + assert_eq!(macros::is_power_of_ten!(11u8), false); + assert_eq!(macros::is_power_of_ten!(std::u8::max_value!()), false); +} + +#[test] +fun is_power_of_ten_macro_works_on_u16() { + assert_eq!(macros::is_power_of_ten!(0u16), false); + assert_eq!(macros::is_power_of_ten!(1u16), true); + assert_eq!(macros::is_power_of_ten!(10000u16), true); + assert_eq!(macros::is_power_of_ten!(9999u16), false); + assert_eq!(macros::is_power_of_ten!(std::u16::max_value!()), false); +} + +#[test] +fun is_power_of_ten_macro_works_on_u32() { + assert_eq!(macros::is_power_of_ten!(0u32), false); + assert_eq!(macros::is_power_of_ten!(1u32), true); + assert_eq!(macros::is_power_of_ten!(1000000000u32), true); + assert_eq!(macros::is_power_of_ten!(999999999u32), false); + assert_eq!(macros::is_power_of_ten!(std::u32::max_value!()), false); +} + +#[test] +fun is_power_of_ten_macro_works_on_u64() { + assert_eq!(macros::is_power_of_ten!(0u64), false); + assert_eq!(macros::is_power_of_ten!(1u64), true); + assert_eq!(macros::is_power_of_ten!(10000000000000000000u64), true); // 10^19 + assert_eq!(macros::is_power_of_ten!(9999999999999999999u64), false); + assert_eq!(macros::is_power_of_ten!(std::u64::max_value!()), false); +} + +#[test] +fun is_power_of_ten_macro_works_on_u128() { + // Zero is not a power of ten. + assert_eq!(macros::is_power_of_ten!(0u128), false); + + // Sampled powers across the u128 range (10^0 .. 10^38). + assert_eq!(macros::is_power_of_ten!(1u128), true); + assert_eq!(macros::is_power_of_ten!(10u128), true); + assert_eq!(macros::is_power_of_ten!(100u128), true); + assert_eq!(macros::is_power_of_ten!(10000u128), true); // 10^4 + assert_eq!(macros::is_power_of_ten!(100000000u128), true); // 10^8 + assert_eq!(macros::is_power_of_ten!(10000000000000000u128), true); // 10^16 + assert_eq!(macros::is_power_of_ten!(10000000000000000000u128), true); // 10^19 + assert_eq!(macros::is_power_of_ten!(100000000000000000000000000000000u128), true); // 10^32 + assert_eq!(macros::is_power_of_ten!(100000000000000000000000000000000000000u128), true); // 10^38 (max power of ten in u128) + + // Off-by-one non-powers around several powers. + assert_eq!(macros::is_power_of_ten!(9u128), false); + assert_eq!(macros::is_power_of_ten!(11u128), false); + assert_eq!(macros::is_power_of_ten!(99u128), false); + assert_eq!(macros::is_power_of_ten!(101u128), false); + assert_eq!(macros::is_power_of_ten!(99999999999999999999999999999999999999u128), false); // 10^38 - 1 + + // Multiples of ten that are not powers of ten. + assert_eq!(macros::is_power_of_ten!(20u128), false); + assert_eq!(macros::is_power_of_ten!(500u128), false); + + // u128::MAX is not a power of ten. + assert_eq!(macros::is_power_of_ten!(std::u128::max_value!()), false); +} + +#[test] +fun is_power_of_ten_macro_works_on_u256() { + // Zero is not a power of ten. + assert_eq!(macros::is_power_of_ten!(0u256), false); + + // Sampled powers across the u256 range (10^0 .. 10^77). + assert_eq!(macros::is_power_of_ten!(1u256), true); + assert_eq!(macros::is_power_of_ten!(10u256), true); + assert_eq!(macros::is_power_of_ten!(100u256), true); + assert_eq!(macros::is_power_of_ten!(10000u256), true); // 10^4 + assert_eq!(macros::is_power_of_ten!(100000000u256), true); // 10^8 + assert_eq!(macros::is_power_of_ten!(10000000000000000u256), true); // 10^16 + assert_eq!(macros::is_power_of_ten!(100000000000000000000000000000000u256), true); // 10^32 + assert_eq!( + macros::is_power_of_ten!( + 10000000000000000000000000000000000000000000000000000000000000000u256, + ), + true, + ); // 10^64 + assert_eq!( + macros::is_power_of_ten!( + 100000000000000000000000000000000000000000000000000000000000000000000000000000u256, + ), + true, + ); // 10^77 (max power of ten in u256) + + // Off-by-one non-powers around several powers. + assert_eq!(macros::is_power_of_ten!(9u256), false); + assert_eq!(macros::is_power_of_ten!(11u256), false); + assert_eq!(macros::is_power_of_ten!(99u256), false); + assert_eq!(macros::is_power_of_ten!(101u256), false); + assert_eq!( + macros::is_power_of_ten!( + 99999999999999999999999999999999999999999999999999999999999999999999999999999u256, + ), + false, + ); // 10^77 - 1 + + // Multiples of ten that are not powers of ten. + assert_eq!(macros::is_power_of_ten!(20u256), false); + assert_eq!(macros::is_power_of_ten!(500u256), false); + + // u256::MAX is not a power of ten. + assert_eq!(macros::is_power_of_ten!(std::u256::max_value!()), false); +} diff --git a/math/core/tests/u128_tests.move b/math/core/tests/u128_tests.move index 0c690094..0a0a8209 100644 --- a/math/core/tests/u128_tests.move +++ b/math/core/tests/u128_tests.move @@ -827,15 +827,14 @@ fun is_power_of_ten_edge_cases() { } #[test] -fun is_power_of_ten_binary_search_paths() { - // Test values to exercise different binary search paths - // These test values at different positions in the lookup table +fun is_power_of_ten_diverse_inputs() { + // Sampled powers of ten across the u128 range. assert_eq!(u128::is_power_of_ten(100000), true); // 10^5 - lower middle assert_eq!(u128::is_power_of_ten(10000000000000), true); // 10^13 - middle assert_eq!(u128::is_power_of_ten(1000000000000000000000000), true); // 10^24 - upper middle assert_eq!(u128::is_power_of_ten(10000000000000000000000000000000), true); // 10^31 - upper range - // Test non-powers at various positions to exercise binary search failure paths + // Non-powers sampled at positions between consecutive powers of ten. assert_eq!(u128::is_power_of_ten(3), false); // Less than first non-1 power assert_eq!(u128::is_power_of_ten(15), false); // Between 10 and 100 assert_eq!(u128::is_power_of_ten(150), false); // Between 100 and 1000 @@ -844,3 +843,8 @@ fun is_power_of_ten_binary_search_paths() { assert_eq!(u128::is_power_of_ten(50000000000000000000), false); // Between 10^19 and 10^20 assert_eq!(u128::is_power_of_ten(500000000000000000000000000000), false); // Between 10^29 and 10^30 } + +#[test] +fun is_power_of_ten_u128_max() { + assert_eq!(u128::is_power_of_ten(std::u128::max_value!()), false); +} diff --git a/math/core/tests/u256_tests.move b/math/core/tests/u256_tests.move index d1be8874..302278f6 100644 --- a/math/core/tests/u256_tests.move +++ b/math/core/tests/u256_tests.move @@ -918,16 +918,15 @@ fun is_power_of_ten_edge_cases() { } #[test] -fun is_power_of_ten_binary_search_paths() { - // Test values to exercise different binary search paths - // These test values at different positions in the lookup table +fun is_power_of_ten_diverse_inputs() { + // Sampled powers of ten across the u256 range. assert_eq!(u256::is_power_of_ten(100000), true); // 10^5 - middle range assert_eq!(u256::is_power_of_ten(10000000000000), true); // 10^13 - middle range assert_eq!(u256::is_power_of_ten(100000000000000000000000000000000), true); // 10^32 - upper middle assert_eq!(u256::is_power_of_ten(1000000000000000000000000000000000000000), true); // 10^39 - upper range assert_eq!(u256::is_power_of_ten(10000000000000000000000000000000000000000000000000000), true); // 10^52 - high range - // Test non-powers at various positions to exercise binary search failure paths + // Non-powers sampled at positions between consecutive powers of ten. assert_eq!(u256::is_power_of_ten(3), false); // Less than first non-1 power assert_eq!(u256::is_power_of_ten(15), false); // Between 10 and 100 assert_eq!(u256::is_power_of_ten(150), false); // Between 100 and 1000 @@ -936,3 +935,61 @@ fun is_power_of_ten_binary_search_paths() { assert_eq!(u256::is_power_of_ten(50000000000000000000), false); // Between 10^19 and 10^20 assert_eq!(u256::is_power_of_ten(500000000000000000000000000000), false); // Between 10^29 and 10^30 } + +#[test] +fun is_power_of_ten_at_log10_floor_cascade_boundary() { + // 10^64 boundary (TEN_POW_64 cascade branch). + assert_eq!( + u256::is_power_of_ten( + 10000000000000000000000000000000000000000000000000000000000000000, + ), + true, + ); // 10^64 + assert_eq!( + u256::is_power_of_ten( + 9999999999999999999999999999999999999999999999999999999999999999, + ), + false, + ); // 10^64 - 1 + assert_eq!( + u256::is_power_of_ten( + 10000000000000000000000000000000000000000000000000000000000000001, + ), + false, + ); // 10^64 + 1 + + // 10^32 boundary (TEN_POW_32 cascade branch). + assert_eq!(u256::is_power_of_ten(100000000000000000000000000000000), true); // 10^32 + assert_eq!(u256::is_power_of_ten(99999999999999999999999999999999), false); // 10^32 - 1 + assert_eq!(u256::is_power_of_ten(100000000000000000000000000000001), false); // 10^32 + 1 + + // 10^16 boundary (TEN_POW_16 cascade branch). + assert_eq!(u256::is_power_of_ten(10000000000000000), true); // 10^16 + assert_eq!(u256::is_power_of_ten(9999999999999999), false); // 10^16 - 1 + assert_eq!(u256::is_power_of_ten(10000000000000001), false); // 10^16 + 1 + + // 10^8 boundary (TEN_POW_8 cascade branch). + assert_eq!(u256::is_power_of_ten(100000000), true); // 10^8 + assert_eq!(u256::is_power_of_ten(99999999), false); // 10^8 - 1 + assert_eq!(u256::is_power_of_ten(100000001), false); // 10^8 + 1 + + // 10^4 boundary (TEN_POW_4 cascade branch). + assert_eq!(u256::is_power_of_ten(10000), true); // 10^4 + assert_eq!(u256::is_power_of_ten(9999), false); // 10^4 - 1 + assert_eq!(u256::is_power_of_ten(10001), false); // 10^4 + 1 + + // 10^2 boundary (TEN_POW_2 cascade branch). + assert_eq!(u256::is_power_of_ten(100), true); // 10^2 + assert_eq!(u256::is_power_of_ten(99), false); // 10^2 - 1 + assert_eq!(u256::is_power_of_ten(101), false); // 10^2 + 1 + + // 10 boundary (`value >= 10` cascade branch). + assert_eq!(u256::is_power_of_ten(10), true); // 10 + assert_eq!(u256::is_power_of_ten(9), false); // 10 - 1 + assert_eq!(u256::is_power_of_ten(11), false); // 10 + 1 +} + +#[test] +fun is_power_of_ten_u256_max() { + assert_eq!(u256::is_power_of_ten(std::u256::max_value!()), false); +}