From b5bed37074b3bd642e7f2ddbbdaa34f1033bd1b4 Mon Sep 17 00:00:00 2001 From: Abuneri Date: Sat, 4 May 2024 19:48:07 -0700 Subject: [PATCH] AK: Replace FP math in `is_power_of` with a purely integral algorithm The previous naive approach was causing test failures because of rounding issues in some exotic environments. In particular, MSVC via MSBuild --- AK/IntegralMath.h | 19 +++++++++++++------ Tests/AK/TestIntegerMath.cpp | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/AK/IntegralMath.h b/AK/IntegralMath.h index 03be42110a9..e7ea6f6f766 100644 --- a/AK/IntegralMath.h +++ b/AK/IntegralMath.h @@ -57,15 +57,22 @@ constexpr I pow(I base, I exponent) template constexpr bool is_power_of(U x) { - if constexpr (base == 2) + if constexpr (base == 1) + return x == 1; + else if constexpr (base == 2) return is_power_of_two(x); - // FIXME: I am naive! A log2-based approach (pow(base, (log2(x) / log2(base))) == x) does not work due to rounding errors. - for (U exponent = 0; exponent <= log2(x) / log2(base) + 1; ++exponent) { - if (pow(base, exponent) == x) - return true; + if (base == 0 && x == 0) + return true; + if (base == 0 || x == 0) + return false; + + while (x != 1) { + if (x % base != 0) + return false; + x /= base; } - return false; + return true; } } diff --git a/Tests/AK/TestIntegerMath.cpp b/Tests/AK/TestIntegerMath.cpp index c5d9b658c54..3e1d742ce5e 100644 --- a/Tests/AK/TestIntegerMath.cpp +++ b/Tests/AK/TestIntegerMath.cpp @@ -11,6 +11,7 @@ TEST_CASE(pow) { + EXPECT_EQ(AK::pow(0, 0), 1ull); EXPECT_EQ(AK::pow(10, 0), 1ull); EXPECT_EQ(AK::pow(10, 1), 10ull); EXPECT_EQ(AK::pow(10, 2), 100ull); @@ -22,12 +23,21 @@ TEST_CASE(pow) TEST_CASE(is_power_of) { - constexpr auto check_prime = [](u64 limit) { - for (u64 power = 0; power < limit; ++power) + EXPECT(!AK::is_power_of<0>(10ull)); + // We don't have enough context to know if the input was from 0^0 + EXPECT(!AK::is_power_of<0>(1ull)); + + EXPECT(!AK::is_power_of<1>(10ull)); + EXPECT(!AK::is_power_of<1>(0ull)); + + constexpr auto check_prime = [](u64 limit, u64 init = 0) { + for (u64 power = init; power < limit; ++power) EXPECT(AK::is_power_of(AK::pow(prime, power))); }; // Limits calculated as floor( log_{prime}(2^64) ) to prevent overflows. + check_prime.operator()<0>(42, 1); + check_prime.operator()<1>(36); check_prime.operator()<2>(64); check_prime.operator()<3>(40); check_prime.operator()<5>(27);