| // Copyright 2020 Google LLC |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| |
| #include <cfloat> // FLT_MAX |
| #include <cmath> // std::abs |
| |
| // For faster tests. Not using AES, hence NEON_WITHOUT_AES is sufficient. |
| // SVE is mostly superseded by SVE2. |
| #ifndef HWY_DISABLED_TARGETS |
| #define HWY_DISABLED_TARGETS (HWY_NEON | HWY_SVE) |
| #endif // HWY_DISABLED_TARGETS |
| |
| #include "third_party/highway/hwy/base.h" |
| |
| // clang-format off |
| #undef HWY_TARGET_INCLUDE |
| #define HWY_TARGET_INCLUDE "hwy/contrib/math/math_test.cc" |
| #include "third_party/highway/hwy/foreach_target.h" // IWYU pragma: keep |
| #include "third_party/highway/hwy/highway.h" |
| #include "third_party/highway/hwy/contrib/math/fast_math-inl.h" |
| #include "third_party/highway/hwy/contrib/math/math-inl.h" |
| #include "third_party/highway/hwy/tests/test_util-inl.h" |
| // clang-format on |
| |
| HWY_BEFORE_NAMESPACE(); |
| namespace hwy { |
| namespace HWY_NAMESPACE { |
| namespace { |
| |
| // We have had test failures caused by excess precision due to keeping |
| // intermediate results in 80-bit x87 registers. One such failure mode is that |
| // Log1p computes a 1.0 which is not exactly equal to 1.0f, causing is_pole to |
| // incorrectly evaluate to false. |
| #undef HWY_MATH_TEST_EXCESS_PRECISION |
| #if HWY_ARCH_X86_32 && HWY_COMPILER_GCC_ACTUAL && \ |
| (HWY_TARGET == HWY_SCALAR || HWY_TARGET == HWY_EMU128) |
| |
| // GCC 13+: because CMAKE_CXX_EXTENSIONS is OFF, we build with -std= and hence |
| // also -fexcess-precision=standard, so there is no problem. See #1708 and |
| // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323. |
| #if HWY_COMPILER_GCC_ACTUAL >= 1300 |
| #define HWY_MATH_TEST_EXCESS_PRECISION 0 |
| |
| #else // HWY_COMPILER_GCC_ACTUAL < 1300 |
| |
| // The build system must enable SSE2, e.g. via HWY_CMAKE_SSE2 - see |
| // https://stackoverflow.com/questions/20869904/c-handling-of-excess-precision . |
| #if defined(__SSE2__) // correct flag given, no problem |
| #define HWY_MATH_TEST_EXCESS_PRECISION 0 |
| #else |
| #define HWY_MATH_TEST_EXCESS_PRECISION 1 |
| #pragma message( \ |
| "Skipping scalar math_test on 32-bit x86 GCC <13 without HWY_CMAKE_SSE2") |
| #endif // defined(__SSE2__) |
| |
| #endif // HWY_COMPILER_GCC_ACTUAL |
| #else // not (x86-32, GCC, scalar target): running math_test normally |
| #define HWY_MATH_TEST_EXCESS_PRECISION 0 |
| #endif // HWY_ARCH_X86_32 etc |
| |
| template <class T, class D> |
| HWY_NOINLINE void TestMath(const char* name, T (*fx1)(T), |
| Vec<D> (*fxN)(D, VecArg<Vec<D>>), D d, T min, T max, |
| uint64_t max_error_ulp) { |
| if (HWY_MATH_TEST_EXCESS_PRECISION) { |
| static bool once = true; |
| if (once) { |
| once = false; |
| HWY_WARN("Skipping math_test due to GCC issue with excess precision.\n"); |
| } |
| return; |
| } |
| |
| using UintT = MakeUnsigned<T>; |
| |
| const UintT min_bits = BitCastScalar<UintT>(min); |
| const UintT max_bits = BitCastScalar<UintT>(max); |
| |
| // If min is negative and max is positive, the range needs to be broken into |
| // two pieces, [+0, max] and [-0, min], otherwise [min, max]. |
| int range_count = 1; |
| UintT ranges[2][2] = {{min_bits, max_bits}, {0, 0}}; |
| if ((min < T{0}) && (max > T{0})) { |
| ranges[0][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(+0.0)); |
| ranges[0][1] = max_bits; |
| ranges[1][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(-0.0)); |
| ranges[1][1] = min_bits; |
| range_count = 2; |
| } else { |
| // If not splitting, ensure we iterate from smaller uint to larger uint. |
| // For negative numbers, min (e.g. -1000) has larger uint representation |
| // than max (e.g. -1). |
| if (ranges[0][0] > ranges[0][1]) { |
| auto tmp = ranges[0][0]; |
| ranges[0][0] = ranges[0][1]; |
| ranges[0][1] = tmp; |
| } |
| } |
| |
| uint64_t max_ulp = 0; |
| // Emulation is slower, so cannot afford as many. |
| constexpr UintT kSamplesPerRange = |
| static_cast<UintT>(AdjustedReps(static_cast<size_t>(1000))); |
| for (int range_index = 0; range_index < range_count; ++range_index) { |
| const UintT start = ranges[range_index][0]; |
| const UintT stop = ranges[range_index][1]; |
| const UintT step = HWY_MAX(1, ((stop - start) / kSamplesPerRange)); |
| for (UintT value_bits = start; value_bits <= stop; value_bits += step) { |
| // For reasons unknown, the HWY_MAX is necessary on RVV, otherwise |
| // value_bits can be less than start, and thus possibly NaN. |
| const T value = |
| BitCastScalar<T>(HWY_MIN(HWY_MAX(start, value_bits), stop)); |
| const T actual = GetLane(fxN(d, Set(d, value))); |
| const T expected = fx1(value); |
| |
| // Skip small inputs and outputs on armv7, it flushes subnormals to zero. |
| #if HWY_TARGET <= HWY_NEON_WITHOUT_AES && HWY_ARCH_ARM_V7 |
| if ((std::abs(value) < 1e-37f) || (std::abs(expected) < 1e-37f)) { |
| continue; |
| } |
| #endif |
| |
| const auto ulp = hwy::detail::ComputeUlpDelta(actual, expected); |
| max_ulp = HWY_MAX(max_ulp, ulp); |
| if (ulp > max_error_ulp) { |
| fprintf(stderr, "%s: %s(%f) expected %E actual %E ulp %g max ulp %u\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), name, value, |
| static_cast<double>(expected), static_cast<double>(actual), |
| static_cast<double>(ulp), static_cast<uint32_t>(max_error_ulp)); |
| } |
| } |
| } |
| fprintf(stderr, "%s: %s max_ulp %g\n", hwy::TypeName(T(), Lanes(d)).c_str(), |
| name, static_cast<double>(max_ulp)); |
| HWY_ASSERT(max_ulp <= max_error_ulp); |
| } |
| |
| #define DEFINE_MATH_TEST_FUNC(NAME) \ |
| HWY_NOINLINE void TestAll##NAME() { \ |
| ForFloat3264Types(ForPartialVectors<Test##NAME>()); \ |
| } |
| |
| #undef DEFINE_MATH_TEST |
| #define DEFINE_MATH_TEST(NAME, F32x1, F32xN, F32_MIN, F32_MAX, F32_ERROR, \ |
| F64x1, F64xN, F64_MIN, F64_MAX, F64_ERROR) \ |
| struct Test##NAME { \ |
| template <class T, class D, HWY_IF_T_SIZE(T, 4)> \ |
| HWY_NOINLINE void operator()(T, D d) { \ |
| TestMath<T, D>(HWY_STR(NAME), F32x1, F32xN, d, F32_MIN, F32_MAX, \ |
| F32_ERROR); \ |
| } \ |
| template <class T, class D, HWY_IF_T_SIZE(T, 8)> \ |
| HWY_NOINLINE void operator()(T, D d) { \ |
| TestMath<T, D>(HWY_STR(NAME), F64x1, F64xN, d, static_cast<T>(F64_MIN), \ |
| static_cast<T>(F64_MAX), F64_ERROR); \ |
| } \ |
| }; \ |
| DEFINE_MATH_TEST_FUNC(NAME) |
| |
| // clang-format off |
| DEFINE_MATH_TEST(Exp, |
| std::exp, CallExp, -FLT_MAX, +104.0f, 1, |
| std::exp, CallExp, -DBL_MAX, +104.0, 1) |
| DEFINE_MATH_TEST(Exp2, |
| std::exp2, CallExp2, -FLT_MAX, +128.0f, 2, |
| std::exp2, CallExp2, -DBL_MAX, +128.0, 2) |
| DEFINE_MATH_TEST(Expm1, |
| std::expm1, CallExpm1, -FLT_MAX, +104.0f, 4, |
| std::expm1, CallExpm1, -DBL_MAX, +104.0, 4) |
| DEFINE_MATH_TEST(Log, |
| std::log, CallLog, +FLT_MIN, +FLT_MAX, 1, |
| std::log, CallLog, +DBL_MIN, +DBL_MAX, 1) |
| DEFINE_MATH_TEST(Log10, |
| std::log10, CallLog10, +FLT_MIN, +FLT_MAX, 2, |
| std::log10, CallLog10, +DBL_MIN, +DBL_MAX, 2) |
| DEFINE_MATH_TEST(Log1p, |
| std::log1p, CallLog1p, +0.0f, +FLT_MAX, 3, // NEON is 3 instead of 2 |
| std::log1p, CallLog1p, +0.0, +DBL_MAX, 2) |
| DEFINE_MATH_TEST(Log2, |
| std::log2, CallLog2, +FLT_MIN, +FLT_MAX, 2, |
| std::log2, CallLog2, +DBL_MIN, +DBL_MAX, 2) |
| DEFINE_MATH_TEST(Cbrt, |
| std::cbrt, CallCbrt, -FLT_MAX, +FLT_MAX, 6, |
| std::cbrt, CallCbrt, -DBL_MAX, +DBL_MAX, 6) |
| |
| // clang-format on |
| |
| template <class T, class D> |
| HWY_NOINLINE void TestMathRelative(const char* name, T (*fx1)(T), |
| Vec<D> (*fxN)(D, VecArg<Vec<D>>), D d, T min, |
| T max, double max_relative_error, |
| uint64_t samples = 4000) { |
| if (HWY_MATH_TEST_EXCESS_PRECISION) { |
| static bool once = true; |
| if (once) { |
| once = false; |
| HWY_WARN("Skipping math_test due to GCC issue with excess precision.\n"); |
| } |
| return; |
| } |
| |
| using UintT = MakeUnsigned<T>; |
| |
| const UintT min_bits = BitCastScalar<UintT>(min); |
| const UintT max_bits = BitCastScalar<UintT>(max); |
| |
| // If min is negative and max is positive, the range needs to be broken into |
| // two pieces, [+0, max] and [-0, min], otherwise [min, max]. |
| int range_count = 1; |
| UintT ranges[2][2] = {{min_bits, max_bits}, {0, 0}}; |
| if ((min < 0.0) && (max > 0.0)) { |
| ranges[0][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(+0.0)); |
| ranges[0][1] = max_bits; |
| ranges[1][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(-0.0)); |
| ranges[1][1] = min_bits; |
| range_count = 2; |
| } else { |
| // If not splitting, ensure we iterate from smaller uint to larger uint. |
| // For negative numbers, min (e.g. -1000) has larger uint representation |
| // than max (e.g. -1). |
| if (ranges[0][0] > ranges[0][1]) { |
| auto tmp = ranges[0][0]; |
| ranges[0][0] = ranges[0][1]; |
| ranges[0][1] = tmp; |
| } |
| } |
| |
| double max_actual_rel_error = 0.0; |
| double max_error_value = 0.0; |
| double sum_rel_error = 0.0; |
| uint64_t count = 0; |
| // Emulation is slower, so cannot afford as many. |
| const UintT kSamplesPerRange = |
| static_cast<UintT>(AdjustedReps(static_cast<size_t>(samples))); |
| for (int range_index = 0; range_index < range_count; ++range_index) { |
| const UintT start = ranges[range_index][0]; |
| const UintT stop = ranges[range_index][1]; |
| const UintT step = HWY_MAX(1, ((stop - start) / kSamplesPerRange)); |
| for (UintT value_bits = start; value_bits <= stop; value_bits += step) { |
| // For reasons unknown, the HWY_MAX is necessary on RVV, otherwise |
| // value_bits can be less than start, and thus possibly NaN. |
| const T value = |
| BitCastScalar<T>(HWY_MIN(HWY_MAX(start, value_bits), stop)); |
| const T actual = GetLane(fxN(d, Set(d, value))); |
| const T expected = fx1(value); |
| |
| // Skip small inputs and outputs on armv7, it flushes subnormals to zero. |
| #if HWY_TARGET <= HWY_NEON_WITHOUT_AES && HWY_ARCH_ARM_V7 |
| if ((std::abs(value) < 1e-37f) || (std::abs(expected) < 1e-37f)) { |
| continue; |
| } |
| #endif |
| |
| if (std::abs(expected) > 0.0) { |
| double rel = std::abs(static_cast<double>(actual) - |
| static_cast<double>(expected)) / |
| std::abs(static_cast<double>(expected)); |
| if (ScalarIsNaN(rel) || rel > max_actual_rel_error) { |
| max_actual_rel_error = rel; |
| max_error_value = static_cast<double>(value); |
| } |
| sum_rel_error += rel; |
| count++; |
| if (rel > max_relative_error) { |
| static int print_count = 0; |
| if (print_count < 10) { |
| fprintf(stderr, |
| "%s: %s(%f) expected %E actual %E rel %E max rel %E\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), name, |
| static_cast<double>(value), static_cast<double>(expected), |
| static_cast<double>(actual), rel, max_relative_error); |
| print_count++; |
| } |
| } |
| } |
| } |
| } |
| fprintf(stderr, "%s: %s max_rel_error %E at %E\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), name, max_actual_rel_error, |
| max_error_value); |
| if (count > 0) { |
| fprintf(stderr, "%s: %s avg_rel_error %E\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), name, |
| sum_rel_error / static_cast<double>(count)); |
| } |
| HWY_ASSERT(max_actual_rel_error <= max_relative_error); |
| } |
| |
| struct TestFastLog { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| const double max_relative_error = 1.15E-5; |
| const uint64_t samples = 1000000; |
| if (sizeof(T) == 4) { |
| TestMathRelative<T, D>("FastLog", std::log, CallFastLog, d, |
| static_cast<T>(FLT_MIN), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLogPositiveNormal", std::log, |
| CallFastLogPositiveNormal, d, |
| static_cast<T>(1.18e-38f), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| |
| } else { |
| TestMathRelative<T, D>("FastLog", std::log, CallFastLog, d, |
| static_cast<T>(DBL_MIN), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLogPositiveNormal", std::log, |
| CallFastLogPositiveNormal, d, |
| static_cast<T>(2.23e-308), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| } |
| } |
| }; |
| |
| struct TestFastExp { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| if (sizeof(T) == 4) { |
| // Float Normal Range: [-87.0, +88.0] |
| // exp(-87) ~= 1.6e-38 (just above min normal 1.17e-38) |
| TestMathRelative<T, D>("FastExpNormal", std::exp, CallFastExp, d, |
| static_cast<T>(-87.0), static_cast<T>(88.0), |
| 0.000008, 10'000'000); |
| |
| // Float Subnormal Range: [-104.0, -87.0] |
| // exp(-104) is very small. Quantization error is expected. |
| TestMathRelative<T, D>("FastExpSubnormal", std::exp, CallFastExp, d, |
| static_cast<T>(-104.0), static_cast<T>(-87.0), |
| 0.03); |
| } else { |
| // Double Normal Range: [-708.0, +706.0] |
| // exp(-708) ~= 2.2e-308 (min normal 2.22e-308) |
| TestMathRelative<T, D>("FastExpNormal", std::exp, CallFastExp, d, |
| static_cast<T>(-708.0), static_cast<T>(706.0), |
| 0.000008, 10'000'000); |
| |
| // Double Subnormal Range: [-744.0, -708.0] |
| // exp(-744) is very small. Quantization error is expected. |
| TestMathRelative<T, D>("FastExpSubnormal", std::exp, CallFastExp, d, |
| static_cast<T>(-744.0), static_cast<T>(-708.0), |
| 1.4E-4); |
| } |
| } |
| }; |
| |
| struct TestFastExp2 { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| if (sizeof(T) == 4) { |
| // Float Normal Range: [-126.0, +127.0] |
| // exp2(-126) is min normal |
| TestMathRelative<T, D>("FastExp2Normal", std::exp2, CallFastExp2, d, |
| static_cast<T>(-126.0), static_cast<T>(127.0), |
| 0.000008, 10'000'000); |
| |
| // Float Subnormal Range: [-150.0, -126.0] |
| TestMathRelative<T, D>("FastExp2Subnormal", std::exp2, CallFastExp2, d, |
| static_cast<T>(-150.0), static_cast<T>(-126.0), |
| 0.0009); |
| } else { |
| // Double Normal Range: [-1022.0, +1023.0] |
| TestMathRelative<T, D>("FastExp2Normal", std::exp2, CallFastExp2, d, |
| static_cast<T>(-1022.0), static_cast<T>(1023.0), |
| 0.000008, 10'000'000); |
| |
| // Double Subnormal Range: [-1075.0, -1022.0] |
| TestMathRelative<T, D>("FastExp2Subnormal", std::exp2, CallFastExp2, d, |
| static_cast<T>(-1075.0), static_cast<T>(-1022.0), |
| 0.0004); |
| } |
| } |
| }; |
| |
| struct TestFastExpMinusOrZero { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| if (sizeof(T) == 4) { |
| // Float Normal Range: [-87.0, 0.0] |
| TestMathRelative<T, D>("FastExpMinusOrZeroNormal", std::exp, |
| CallFastExpMinusOrZero, d, static_cast<T>(-87.0), |
| static_cast<T>(-0.0), 0.000008, 10'000'000); |
| } else { |
| // Double Normal Range: [-708.0, 0.0] |
| TestMathRelative<T, D>("FastExpMinusOrZeroNormal", std::exp, |
| CallFastExpMinusOrZero, d, static_cast<T>(-708.0), |
| static_cast<T>(-0.0), 0.000008, 10'000'000); |
| } |
| } |
| }; |
| |
| struct TestFastLog2 { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| const double max_relative_error = 1.15E-5; |
| const uint64_t samples = 1000000; |
| if (sizeof(T) == 4) { |
| TestMathRelative<T, D>("FastLog2", std::log2, CallFastLog2, d, |
| static_cast<T>(FLT_MIN), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog2PositiveNormal", std::log2, |
| CallFastLog2PositiveNormal, d, |
| static_cast<T>(1.18e-38f), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| } else { |
| TestMathRelative<T, D>("FastLog2", std::log2, CallFastLog2, d, |
| static_cast<T>(DBL_MIN), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog2PositiveNormal", std::log2, |
| CallFastLog2PositiveNormal, d, |
| static_cast<T>(2.23e-308), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| } |
| } |
| }; |
| |
| struct TestFastLog10 { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| const double max_relative_error = 1.15E-5; |
| const uint64_t samples = 1000000; |
| if (sizeof(T) == 4) { |
| TestMathRelative<T, D>("FastLog10", std::log10, CallFastLog10, d, |
| static_cast<T>(FLT_MIN), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog10PositiveNormal", std::log10, |
| CallFastLog10PositiveNormal, d, |
| static_cast<T>(1.18e-38f), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| } else { |
| TestMathRelative<T, D>("FastLog10", std::log10, CallFastLog10, d, |
| static_cast<T>(DBL_MIN), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog10PositiveNormal", std::log10, |
| CallFastLog10PositiveNormal, d, |
| static_cast<T>(2.23e-308), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| } |
| } |
| }; |
| |
| struct TestFastLog1p { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| const double max_relative_error = 1.15E-5; |
| const uint64_t samples = 1000000; |
| if (sizeof(T) == 4) { |
| TestMathRelative<T, D>("FastLog1p", std::log1p, CallFastLog1p, d, |
| static_cast<T>(-0.9f), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog1pPositiveNormal", std::log1p, |
| CallFastLog1pPositiveNormal, d, |
| static_cast<T>(0.0f), static_cast<T>(FLT_MAX), |
| max_relative_error, samples); |
| } else { |
| TestMathRelative<T, D>("FastLog1p", std::log1p, CallFastLog1p, d, |
| static_cast<T>(-0.9), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| TestMathRelative<T, D>("FastLog1pPositiveNormal", std::log1p, |
| CallFastLog1pPositiveNormal, d, |
| static_cast<T>(0.0), static_cast<T>(DBL_MAX), |
| max_relative_error, samples); |
| } |
| } |
| }; |
| |
| HWY_NOINLINE void TestAllFastExp() { |
| ForFloat3264Types(ForPartialVectors<TestFastExp>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastExp2() { |
| ForFloat3264Types(ForPartialVectors<TestFastExp2>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastExpMinusOrZero() { |
| ForFloat3264Types(ForPartialVectors<TestFastExpMinusOrZero>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastLog() { |
| ForFloat3264Types(ForPartialVectors<TestFastLog>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastLog2() { |
| ForFloat3264Types(ForPartialVectors<TestFastLog2>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastLog10() { |
| ForFloat3264Types(ForPartialVectors<TestFastLog10>()); |
| } |
| |
| HWY_NOINLINE void TestAllFastLog1p() { |
| ForFloat3264Types(ForPartialVectors<TestFastLog1p>()); |
| } |
| |
| struct TestFastPow { |
| template <class T, class D> |
| HWY_NOINLINE void operator()(T, D d) { |
| if (HWY_MATH_TEST_EXCESS_PRECISION) { |
| return; |
| } |
| |
| const T bases[] = { |
| static_cast<T>(0.1), static_cast<T>(0.5), static_cast<T>(0.99), |
| static_cast<T>(1.0), static_cast<T>(1.0001), static_cast<T>(1.5), |
| static_cast<T>(2.0), static_cast<T>(2.71828), static_cast<T>(10.0), |
| static_cast<T>(100.0), static_cast<T>(1.0e-10), static_cast<T>(1.0e10), |
| }; |
| |
| double max_actual_rel_error = 0.0; |
| double max_error_base = 0.0; |
| double max_error_exp = 0.0; |
| |
| for (T base : bases) { |
| T logb = std::log(base); |
| T limit = (sizeof(T) == 8) ? static_cast<T>(25.0) : static_cast<T>(25.0); |
| T min_exp_val = -limit / logb; |
| T max_exp_val = limit / logb; |
| |
| if (min_exp_val > max_exp_val) { |
| T tmp = min_exp_val; |
| min_exp_val = max_exp_val; |
| max_exp_val = tmp; |
| } |
| |
| using UintT = MakeUnsigned<T>; |
| const UintT min_bits = BitCastScalar<UintT>(min_exp_val); |
| const UintT max_bits = BitCastScalar<UintT>(max_exp_val); |
| |
| int range_count = 1; |
| UintT ranges[2][2] = {{min_bits, max_bits}, {0, 0}}; |
| if ((min_exp_val < 0.0) && (max_exp_val > 0.0)) { |
| ranges[0][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(+0.0)); |
| ranges[0][1] = max_bits; |
| ranges[1][0] = BitCastScalar<UintT>(ConvertScalarTo<T>(-0.0)); |
| ranges[1][1] = min_bits; |
| range_count = 2; |
| } else { |
| if (ranges[0][0] > ranges[0][1]) { |
| auto tmp = ranges[0][0]; |
| ranges[0][0] = ranges[0][1]; |
| ranges[0][1] = tmp; |
| } |
| } |
| |
| const UintT kSamplesPerRange = |
| static_cast<UintT>(AdjustedReps(static_cast<size_t>(10000))); |
| for (int range_index = 0; range_index < range_count; ++range_index) { |
| const UintT start = ranges[range_index][0]; |
| const UintT stop = ranges[range_index][1]; |
| const UintT step = HWY_MAX(1, ((stop - start) / kSamplesPerRange)); |
| for (UintT value_bits = start; value_bits <= stop; value_bits += step) { |
| const T exp_val = |
| BitCastScalar<T>(HWY_MIN(HWY_MAX(start, value_bits), stop)); |
| const T actual = |
| GetLane(CallFastPow(d, Set(d, base), Set(d, exp_val))); |
| const T expected = std::pow(base, exp_val); |
| |
| #if HWY_TARGET <= HWY_NEON_WITHOUT_AES && HWY_ARCH_ARM_V7 |
| if ((std::abs(exp_val) < 1e-37f) || (std::abs(expected) < 1e-37f)) { |
| continue; |
| } |
| #endif |
| |
| if (std::abs(expected) > 0.0) { |
| double rel = std::abs(static_cast<double>(actual) - |
| static_cast<double>(expected)) / |
| std::abs(static_cast<double>(expected)); |
| |
| if (ScalarIsNaN(rel) || rel > max_actual_rel_error) { |
| max_actual_rel_error = rel; |
| max_error_base = static_cast<double>(base); |
| max_error_exp = static_cast<double>(exp_val); |
| } |
| if (rel > 0.0003) { |
| static int print_count = 0; |
| if (print_count < 10) { |
| fprintf(stderr, |
| "%s: FastPow(%f, %f) expected %E actual %E rel %E max " |
| "rel %E\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), |
| static_cast<double>(base), static_cast<double>(exp_val), |
| static_cast<double>(expected), |
| static_cast<double>(actual), rel, 0.0003); |
| print_count++; |
| } |
| } |
| } |
| } |
| } |
| } |
| fprintf(stderr, "%s: FastPow max_rel_error %E at base=%E exp=%E\n", |
| hwy::TypeName(T(), Lanes(d)).c_str(), max_actual_rel_error, |
| max_error_base, max_error_exp); |
| HWY_ASSERT(max_actual_rel_error <= 0.0003); |
| } |
| }; |
| |
| HWY_NOINLINE void TestAllFastPow() { |
| ForFloat3264Types(ForPartialVectors<TestFastPow>()); |
| } |
| |
| } // namespace |
| // NOLINTNEXTLINE(google-readability-namespace-comments) |
| } // namespace HWY_NAMESPACE |
| } // namespace hwy |
| HWY_AFTER_NAMESPACE(); |
| |
| #if HWY_ONCE |
| namespace hwy { |
| namespace { |
| HWY_BEFORE_TEST(HwyMathTest); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllExp); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllExp2); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllExpm1); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllLog); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllLog10); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllLog1p); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllLog2); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllCbrt); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastLog); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastExp); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastExp2); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastExpMinusOrZero); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastLog2); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastLog10); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastLog1p); |
| HWY_EXPORT_AND_TEST_P(HwyMathTest, TestAllFastPow); |
| HWY_AFTER_TEST(); |
| } // namespace |
| } // namespace hwy |
| HWY_TEST_MAIN(); |
| #endif // HWY_ONCE |