Explorar o código

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
Abuneri hai 1 ano
pai
achega
b5bed37074
Modificáronse 2 ficheiros con 25 adicións e 8 borrados
  1. 13 6
      AK/IntegralMath.h
  2. 12 2
      Tests/AK/TestIntegerMath.cpp

+ 13 - 6
AK/IntegralMath.h

@@ -57,15 +57,22 @@ constexpr I pow(I base, I exponent)
 template<auto base, Unsigned U = decltype(base)>
 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<U>(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<U>(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;
 }
 
 }

+ 12 - 2
Tests/AK/TestIntegerMath.cpp

@@ -11,6 +11,7 @@
 
 TEST_CASE(pow)
 {
+    EXPECT_EQ(AK::pow<u64>(0, 0), 1ull);
     EXPECT_EQ(AK::pow<u64>(10, 0), 1ull);
     EXPECT_EQ(AK::pow<u64>(10, 1), 10ull);
     EXPECT_EQ(AK::pow<u64>(10, 2), 100ull);
@@ -22,12 +23,21 @@ TEST_CASE(pow)
 
 TEST_CASE(is_power_of)
 {
-    constexpr auto check_prime = []<u64 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 prime>(u64 limit, u64 init = 0) {
+        for (u64 power = init; power < limit; ++power)
             EXPECT(AK::is_power_of<prime>(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);