From 2c131850aae2a26f92f40b6a5ec3cfafa4818dcf Mon Sep 17 00:00:00 2001 From: ianpike Date: Sat, 13 Jun 2026 21:31:46 -0400 Subject: [PATCH 01/52] Fix signed overflow in abs_diff --- include/ccmath/ext/abs_diff.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/ccmath/ext/abs_diff.hpp b/include/ccmath/ext/abs_diff.hpp index e8312d9d..4e635d3c 100644 --- a/include/ccmath/ext/abs_diff.hpp +++ b/include/ccmath/ext/abs_diff.hpp @@ -15,13 +15,16 @@ namespace ccm::ext { /** - * @brief Compute the absolute difference between two integral values without unsigned underflow. - * @tparam T Type of the input and output. + * @brief Compute the absolute difference between two integral values. + * @tparam T Integral type of both inputs. * @param lhs The first value. * @param rhs The second value. - * @return The absolute difference between lhs and rhs. + * @return The absolute difference between lhs and rhs as the matching unsigned type. */ template && !std::is_same_v, bool>, bool> = true> - [[nodiscard]] constexpr T abs_diff(T lhs, T rhs) noexcept - { return lhs > rhs ? static_cast(lhs - rhs) : static_cast(rhs - lhs); } + [[nodiscard]] constexpr std::make_unsigned_t abs_diff(T lhs, T rhs) noexcept + { + using U = std::make_unsigned_t; + return lhs > rhs ? static_cast(static_cast(lhs) - static_cast(rhs)) : static_cast(static_cast(rhs) - static_cast(lhs)); + } } // namespace ccm::ext \ No newline at end of file From a7554d5e9de03ce60a2effa0781438d7abc41742 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 14 Jun 2026 02:47:42 -0400 Subject: [PATCH 02/52] Enforce clang-tidy in CI and resolve internal findings --- .clang-tidy | 8 ++ .github/workflows/lint.yml | 51 +++++++++++++ include/ccmath/ext/.clang-tidy | 2 + include/ccmath/ext/abs_diff.hpp | 2 +- include/ccmath/ext/approximately.hpp | 2 +- include/ccmath/ext/ceil_div.hpp | 4 +- include/ccmath/ext/chgsign.hpp | 2 +- include/ccmath/ext/delta_angle.hpp | 4 +- include/ccmath/ext/inverse_lerp.hpp | 4 +- include/ccmath/ext/is_power_of_two.hpp | 12 +-- include/ccmath/ext/lerp_angle.hpp | 4 +- include/ccmath/ext/move_towards.hpp | 2 +- include/ccmath/ext/move_towards_angle.hpp | 4 +- include/ccmath/ext/normalize.hpp | 4 +- include/ccmath/ext/remap.hpp | 4 +- include/ccmath/ext/repeat.hpp | 4 +- include/ccmath/ext/saturate.hpp | 2 +- include/ccmath/ext/sign.hpp | 2 +- include/ccmath/ext/step.hpp | 2 +- include/ccmath/ext/unlerp.hpp | 4 +- .../internal/config/long_double_format.hpp | 3 +- .../internal/config/platform/android.hpp | 2 +- .../ccmath/internal/config/powl_policy.hpp | 2 +- .../ccmath/internal/config/type_support.hpp | 2 +- .../ccmath/internal/math/common/basic/fma.hpp | 2 +- .../ccmath/internal/math/common/basic/nan.hpp | 2 +- .../internal/math/common/fmanip/modf.hpp | 2 +- .../ccmath/internal/math/common/misc/lerp.hpp | 4 +- .../math/generic/builtins/basic/fma.hpp | 2 +- .../math/generic/func/expo/exp2_gen.hpp | 2 +- .../math/generic/func/expo/exp_gen.hpp | 2 +- .../math/generic/func/fmanip/frexp_gen.hpp | 2 +- .../math/generic/func/fmanip/ilogb_gen.hpp | 2 +- .../generic/func/fmanip/impl/ilogb_impl.hpp | 6 +- .../generic/func/fmanip/impl/logb_impl.hpp | 2 +- .../math/generic/func/fmanip/logb_gen.hpp | 2 +- .../math/generic/func/fmanip/modf_gen.hpp | 2 +- .../generic/func/fmanip/nextafter_gen.hpp | 4 +- .../math/generic/func/misc/lerp_gen.hpp | 4 +- .../math/generic/func/power/cbrt_gen.hpp | 2 +- .../math/generic/func/power/hypot_gen.hpp | 2 +- .../generic/func/power/impl/cbrt_impl.hpp | 4 +- .../generic/func/power/impl/hypot_impl.hpp | 2 +- .../generic/func/power/pow_impl/pow_impl.hpp | 27 +++---- .../func/power/pow_impl/pow_simd_impl.hpp | 8 +- .../generic/func/power/pow_impl/powf_impl.hpp | 14 ++-- .../func/power/pow_impl/powf_simd_impl.hpp | 10 +-- .../func/power/pow_impl/powl_ld80_kernel.hpp | 2 +- .../func/power/pow_impl/powl_ld80_tables.hpp | 2 +- .../math/generic/func/power/powl_gen.hpp | 26 +++---- .../math/generic/func/power/sqrt_gen.hpp | 8 +- .../math/runtime/func/nearest/ceil_rt.hpp | 2 +- .../math/runtime/func/nearest/floor_rt.hpp | 2 +- .../math/runtime/func/nearest/round_rt.hpp | 2 +- .../math/runtime/func/nearest/trunc_rt.hpp | 2 +- .../math/runtime/func/power/pow_rt.hpp | 6 +- .../math/runtime/func/power/sqrt_rt.hpp | 4 +- .../math/runtime/func/rt_dispatch.hpp | 2 +- .../ccmath/internal/math/runtime/pp/flags.hpp | 4 +- .../internal/math/runtime/pp/msvc_intrin.hpp | 2 +- .../internal/math/runtime/pp/reduce.hpp | 4 +- .../internal/math/runtime/pp/reference.hpp | 2 +- .../ccmath/internal/math/runtime/pp/simd.hpp | 38 +++++----- .../internal/math/runtime/pp/simd_cat.hpp | 2 +- .../internal/math/runtime/pp/simd_config.hpp | 9 ++- .../internal/math/runtime/pp/simd_mask.hpp | 4 +- .../internal/math/runtime/pp/simd_math.hpp | 14 ++-- .../internal/math/runtime/pp/utility.hpp | 22 +++--- .../ccmath/internal/math/runtime/pp/where.hpp | 2 + .../internal/math/runtime/simd/common.hpp | 2 +- .../math/runtime/simd/instructions/scalar.hpp | 4 +- .../internal/math/runtime/simd/simd.hpp | 2 +- .../math/runtime/simd/simd_vectorize.hpp | 2 +- .../math/runtime/simd/vector_size.hpp | 11 +-- include/ccmath/internal/predef/assume.hpp | 2 +- .../predef/attributes/always_inline.hpp | 6 +- .../predef/attributes/gsl_suppress.hpp | 2 +- .../internal/predef/attributes/optnone.hpp | 2 +- .../clang_compiler_suppression.hpp | 8 +- .../ccmath/internal/predef/has_attribute.hpp | 4 +- .../ccmath/internal/predef/has_builtin.hpp | 2 +- .../internal/predef/has_const_builtin.hpp | 2 +- .../predef/versioning/gcc_version.hpp | 6 +- .../predef/versioning/version_encode.hpp | 2 +- include/ccmath/internal/support/bits.hpp | 6 +- include/ccmath/internal/support/ctz.hpp | 2 +- .../internal/support/fenv/fenv_support.hpp | 2 +- .../support/floating_point_traits.hpp | 4 +- .../internal/support/fp/bit_mask_traits.hpp | 2 +- .../support/fp/directional_rounding_utils.hpp | 12 +-- include/ccmath/internal/support/fp/fma.hpp | 22 +++--- .../ccmath/internal/support/fp/fp_bits.hpp | 2 +- .../support/helpers/internal_ldexp.hpp | 6 +- .../internal/support/integer_literals.hpp | 12 ++- .../support/is_constant_evaluated.hpp | 2 +- .../ccmath/internal/support/math_support.hpp | 2 +- .../ccmath/internal/support/type_traits.hpp | 2 +- include/ccmath/internal/types/big_int.hpp | 6 +- .../ccmath/internal/types/double_double.hpp | 10 +-- .../ccmath/internal/types/dyadic_float.hpp | 2 +- .../internal/types/normalized_float.hpp | 2 +- include/ccmath/math/.clang-tidy | 2 + include/ccmath/math/compare/isnan.hpp | 4 +- include/ccmath/math/compare/signbit.hpp | 6 +- tools/apply_cast_style.sh | 25 +++++++ tools/clang_tidy_version | 1 + tools/ensure_cast_style.sh | 66 ++++++++++++++++ tools/ensure_cast_style_repo.sh | 75 +++++++++++++++++++ tools/install_clang_tidy_linux.sh | 29 +++++++ tools/run_clang_tidy.sh | 45 +++++++++++ 110 files changed, 562 insertions(+), 246 deletions(-) create mode 100644 include/ccmath/ext/.clang-tidy create mode 100644 include/ccmath/math/.clang-tidy create mode 100755 tools/apply_cast_style.sh create mode 100644 tools/clang_tidy_version create mode 100755 tools/ensure_cast_style.sh create mode 100755 tools/ensure_cast_style_repo.sh create mode 100755 tools/install_clang_tidy_linux.sh create mode 100755 tools/run_clang_tidy.sh diff --git a/.clang-tidy b/.clang-tidy index 83214b57..ec405b73 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,6 +8,8 @@ modernize-*, -modernize-use-trailing-return-type, -modernize-use-constraints, -modernize-avoid-c-arrays, +-modernize-use-nodiscard, +-modernize-use-auto, performance-*, -performance-avoid-endl, @@ -15,12 +17,18 @@ performance-*, readability-*, -readability-identifier-length, -readability-magic-numbers, +-readability-function-cognitive-complexity, cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-special-member-func, +-cppcoreguidelines-avoid-c-arrays, +-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, +-cppcoreguidelines-pro-bounds-constant-array-index, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, cert-dcl21-cpp, cert-dcl58-cpp, diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6d0ff98a..042c2476 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,6 +14,12 @@ on: - 'tools/apply_format.sh' - 'tools/clang_format_version' - 'tools/ensure_format.sh' + - 'tools/clang_tidy_version' + - 'tools/ensure_cast_style.sh' + - 'tools/ensure_cast_style_repo.sh' + - 'tools/install_clang_tidy_linux.sh' + - 'tools/run_clang_tidy.sh' + - '.clang-tidy' pull_request: branches: - '**' @@ -75,3 +81,48 @@ jobs: - name: Verify clang-format after auto-fix if: steps.check_format.outcome == 'failure' run: bash tools/ensure_format.sh + + cast_style_check: + if: github.event_name == 'pull_request' + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Install clang-tidy + run: bash tools/install_clang_tidy_linux.sh + + - name: Check cast style (library headers) + run: bash tools/ensure_cast_style.sh + + cast_style_repo_check: + if: github.event_name == 'pull_request' + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Install dependencies + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + + - name: Install clang-tidy + run: bash tools/install_clang_tidy_linux.sh + + - name: Configure for compile database + run: > + cmake -S . -B build-tidy -G Ninja + -DCCMATH_BUILD_TESTS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Check cast style (entire repo) + run: CCM_CAST_COMPILE_DB=build-tidy/compile_commands.json bash tools/ensure_cast_style_repo.sh + + - name: Run clang-tidy (advisory) + continue-on-error: true + run: bash tools/run_clang_tidy.sh build-tidy/compile_commands.json diff --git a/include/ccmath/ext/.clang-tidy b/include/ccmath/ext/.clang-tidy new file mode 100644 index 00000000..46c666ca --- /dev/null +++ b/include/ccmath/ext/.clang-tidy @@ -0,0 +1,2 @@ +--- +Checks: '-*' diff --git a/include/ccmath/ext/abs_diff.hpp b/include/ccmath/ext/abs_diff.hpp index 4e635d3c..c7f9699c 100644 --- a/include/ccmath/ext/abs_diff.hpp +++ b/include/ccmath/ext/abs_diff.hpp @@ -22,7 +22,7 @@ namespace ccm::ext * @return The absolute difference between lhs and rhs as the matching unsigned type. */ template && !std::is_same_v, bool>, bool> = true> - [[nodiscard]] constexpr std::make_unsigned_t abs_diff(T lhs, T rhs) noexcept + constexpr std::make_unsigned_t abs_diff(T lhs, T rhs) noexcept { using U = std::make_unsigned_t; return lhs > rhs ? static_cast(static_cast(lhs) - static_cast(rhs)) : static_cast(static_cast(rhs) - static_cast(lhs)); diff --git a/include/ccmath/ext/approximately.hpp b/include/ccmath/ext/approximately.hpp index 7b52e3f9..1f697b1e 100644 --- a/include/ccmath/ext/approximately.hpp +++ b/include/ccmath/ext/approximately.hpp @@ -25,6 +25,6 @@ namespace ccm::ext * @return True if the absolute difference between lhs and rhs is less than or equal to epsilon. */ template , bool> = true> - [[nodiscard]] constexpr bool approximately(T lhs, T rhs, T epsilon) noexcept + constexpr bool approximately(T lhs, T rhs, T epsilon) noexcept { return ccm::abs(lhs - rhs) <= epsilon; } } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/ceil_div.hpp b/include/ccmath/ext/ceil_div.hpp index 3bb5c20c..4ddd722b 100644 --- a/include/ccmath/ext/ceil_div.hpp +++ b/include/ccmath/ext/ceil_div.hpp @@ -25,7 +25,7 @@ namespace ccm::ext * @return The ceiling of value divided by divisor. */ template && !std::is_same_v, bool>, bool> = true> - [[nodiscard]] constexpr T ceil_div(T value, T divisor) noexcept + constexpr T ceil_div(T value, T divisor) noexcept { return static_cast((value + divisor - T(1)) / divisor); } namespace safe @@ -43,7 +43,7 @@ namespace ccm::ext * @return The ceiling of value divided by divisor. */ template && !std::is_same_v, bool>, bool> = true> - [[nodiscard]] constexpr T ceil_div(T value, T divisor) noexcept + constexpr T ceil_div(T value, T divisor) noexcept { if (divisor == T(0)) { return T(0); } diff --git a/include/ccmath/ext/chgsign.hpp b/include/ccmath/ext/chgsign.hpp index 72fcc657..1e1c9e68 100644 --- a/include/ccmath/ext/chgsign.hpp +++ b/include/ccmath/ext/chgsign.hpp @@ -27,7 +27,7 @@ namespace ccm::ext * @return x with the sign of y. */ template , bool> = true> - [[nodiscard]] constexpr T chgsign(T x, T y) noexcept + constexpr T chgsign(T x, T y) noexcept { const T magnitude = ::ccm::abs(x); const auto y_bits = ::ccm::support::bit_cast<::ccm::support::float_bits_t>(y); diff --git a/include/ccmath/ext/delta_angle.hpp b/include/ccmath/ext/delta_angle.hpp index 90d42c20..d896e106 100644 --- a/include/ccmath/ext/delta_angle.hpp +++ b/include/ccmath/ext/delta_angle.hpp @@ -24,7 +24,7 @@ namespace ccm::ext * @return The shortest signed angular difference in the range [-180, 180]. */ template , bool> = true> - [[nodiscard]] constexpr T delta_angle(T current, T target) noexcept + constexpr T delta_angle(T current, T target) noexcept { T delta = ext::repeat(target - current, T(360)); @@ -43,7 +43,7 @@ namespace ccm::ext * @return The shortest signed angular difference in the range [-180, 180]. */ template , bool> = true> - [[nodiscard]] constexpr T delta_angle(T current, T target) noexcept + constexpr T delta_angle(T current, T target) noexcept { T delta = ext::safe::repeat(target - current, T(360)); diff --git a/include/ccmath/ext/inverse_lerp.hpp b/include/ccmath/ext/inverse_lerp.hpp index 49222274..357b47ac 100644 --- a/include/ccmath/ext/inverse_lerp.hpp +++ b/include/ccmath/ext/inverse_lerp.hpp @@ -28,7 +28,7 @@ namespace ccm::ext * @return The interpolation parameter of value in the range [start, end]. */ template , bool> = true> - [[nodiscard]] constexpr T inverse_lerp(T start, T end, T value) noexcept + constexpr T inverse_lerp(T start, T end, T value) noexcept { return ext::unlerp(start, end, value); } namespace safe @@ -48,7 +48,7 @@ namespace ccm::ext * @return The interpolation parameter of value in the range [start, end]. */ template , bool> = true> - [[nodiscard]] constexpr T inverse_lerp(T start, T end, T value) noexcept + constexpr T inverse_lerp(T start, T end, T value) noexcept { return ext::safe::unlerp(start, end, value); } } // namespace safe } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/is_power_of_two.hpp b/include/ccmath/ext/is_power_of_two.hpp index 9678569c..6eac2efc 100644 --- a/include/ccmath/ext/is_power_of_two.hpp +++ b/include/ccmath/ext/is_power_of_two.hpp @@ -24,7 +24,7 @@ namespace ccm::ext * @return True if the input is a power of two, false otherwise. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool is_power_of_two(T n) noexcept + constexpr bool is_power_of_two(T n) noexcept { return support::has_single_bit(n); } /** @@ -38,7 +38,7 @@ namespace ccm::ext * overflow on the minimum representable value. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool is_power_of_two(T n) noexcept + constexpr bool is_power_of_two(T n) noexcept { return n && !(n & (n - T(1))); } /** @@ -49,7 +49,7 @@ namespace ccm::ext * @return True if the input is a power of two, false otherwise. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool ispow2(T n) noexcept + constexpr bool ispow2(T n) noexcept { return is_power_of_two(n); } namespace safe @@ -62,7 +62,7 @@ namespace ccm::ext * @return True if the input is a power of two, false otherwise. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool is_power_of_two(T n) noexcept + constexpr bool is_power_of_two(T n) noexcept { return support::has_single_bit(n); } /** @@ -73,7 +73,7 @@ namespace ccm::ext * @return True if the input is a positive power of two, false otherwise. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool is_power_of_two(T n) noexcept + constexpr bool is_power_of_two(T n) noexcept { using unsigned_type = std::make_unsigned_t; @@ -88,7 +88,7 @@ namespace ccm::ext * @return True if the input is a power of two, false otherwise. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr bool ispow2(T n) noexcept + constexpr bool ispow2(T n) noexcept { return safe::is_power_of_two(n); } } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/lerp_angle.hpp b/include/ccmath/ext/lerp_angle.hpp index a8f1c0e9..e118910d 100644 --- a/include/ccmath/ext/lerp_angle.hpp +++ b/include/ccmath/ext/lerp_angle.hpp @@ -28,7 +28,7 @@ namespace ccm::ext * @return The interpolated angle, in degrees. */ template , bool> = true> - [[nodiscard]] constexpr T lerp_angle(T start, T end, T t) noexcept + constexpr T lerp_angle(T start, T end, T t) noexcept { return start + (ext::delta_angle(start, end) * t); } namespace safe @@ -45,7 +45,7 @@ namespace ccm::ext * @return The interpolated angle, in degrees. */ template , bool> = true> - [[nodiscard]] constexpr T lerp_angle(T start, T end, T t) noexcept + constexpr T lerp_angle(T start, T end, T t) noexcept { return start + (ext::safe::delta_angle(start, end) * t); } } // namespace safe } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/move_towards.hpp b/include/ccmath/ext/move_towards.hpp index 656eba7e..456fd8f4 100644 --- a/include/ccmath/ext/move_towards.hpp +++ b/include/ccmath/ext/move_towards.hpp @@ -25,7 +25,7 @@ namespace ccm::ext * @return The moved value. */ template , bool> = true> - [[nodiscard]] constexpr T move_towards(T current, T target, T max_delta) noexcept + constexpr T move_towards(T current, T target, T max_delta) noexcept { const T delta = target - current; diff --git a/include/ccmath/ext/move_towards_angle.hpp b/include/ccmath/ext/move_towards_angle.hpp index d69b8834..47c025ff 100644 --- a/include/ccmath/ext/move_towards_angle.hpp +++ b/include/ccmath/ext/move_towards_angle.hpp @@ -26,7 +26,7 @@ namespace ccm::ext * @return The moved angle, in degrees. */ template , bool> = true> - [[nodiscard]] constexpr T move_towards_angle(T current, T target, T max_delta) noexcept + constexpr T move_towards_angle(T current, T target, T max_delta) noexcept { const T delta = ext::delta_angle(current, target); return ext::move_towards(current, current + delta, max_delta); @@ -43,7 +43,7 @@ namespace ccm::ext * @return The moved angle, in degrees. */ template , bool> = true> - [[nodiscard]] constexpr T move_towards_angle(T current, T target, T max_delta) noexcept + constexpr T move_towards_angle(T current, T target, T max_delta) noexcept { const T delta = ext::safe::delta_angle(current, target); return ext::move_towards(current, current + delta, max_delta); diff --git a/include/ccmath/ext/normalize.hpp b/include/ccmath/ext/normalize.hpp index c9b4ac42..d18e035b 100644 --- a/include/ccmath/ext/normalize.hpp +++ b/include/ccmath/ext/normalize.hpp @@ -25,7 +25,7 @@ namespace ccm::ext * @return The normalized value. */ template , bool> = true> - [[nodiscard]] constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept + constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept { return ext::clamp((value - min) / (max - min), T(0), T(1)); } namespace safe @@ -45,7 +45,7 @@ namespace ccm::ext * @return The normalized value. */ template , bool> = true> - [[nodiscard]] constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept + constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept { const T length = max - min; diff --git a/include/ccmath/ext/remap.hpp b/include/ccmath/ext/remap.hpp index b14bf245..bf3e0dd7 100644 --- a/include/ccmath/ext/remap.hpp +++ b/include/ccmath/ext/remap.hpp @@ -35,7 +35,7 @@ namespace ccm::ext * @return The remapped value. */ template , bool> = true> - [[nodiscard]] constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept + constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept { return dst_start + ((x - src_start) * (dst_end - dst_start)) / (src_end - src_start); } namespace safe @@ -62,7 +62,7 @@ namespace ccm::ext * @return The remapped value. */ template , bool> = true> - [[nodiscard]] constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept + constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept { const T length = src_end - src_start; diff --git a/include/ccmath/ext/repeat.hpp b/include/ccmath/ext/repeat.hpp index 7d698257..2e7ac30b 100644 --- a/include/ccmath/ext/repeat.hpp +++ b/include/ccmath/ext/repeat.hpp @@ -24,7 +24,7 @@ namespace ccm::ext * @return The repeated value in the range [0, length). */ template , bool> = true> - [[nodiscard]] constexpr T repeat(T value, T length) noexcept + constexpr T repeat(T value, T length) noexcept { return value - (ccm::floor(value / length) * length); } namespace safe @@ -41,7 +41,7 @@ namespace ccm::ext * @return The repeated value in the range [0, length). */ template , bool> = true> - [[nodiscard]] constexpr T repeat(T value, T length) noexcept + constexpr T repeat(T value, T length) noexcept { if (length == T(0)) { return T(0); } diff --git a/include/ccmath/ext/saturate.hpp b/include/ccmath/ext/saturate.hpp index 56b83b96..75f725e5 100644 --- a/include/ccmath/ext/saturate.hpp +++ b/include/ccmath/ext/saturate.hpp @@ -23,6 +23,6 @@ namespace ccm::ext * @return The value clamped to the range [0, 1]. */ template , bool> = true> - [[nodiscard]] constexpr T saturate(T value) noexcept + constexpr T saturate(T value) noexcept { return ext::clamp(value, T(0), T(1)); } } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/sign.hpp b/include/ccmath/ext/sign.hpp index 04157f10..cdd7171a 100644 --- a/include/ccmath/ext/sign.hpp +++ b/include/ccmath/ext/sign.hpp @@ -21,6 +21,6 @@ namespace ccm::ext * @return -1 if value is negative, 0 if value is zero, otherwise 1. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr T sign(T value) noexcept + constexpr T sign(T value) noexcept { return static_cast((T(0) < value) - (value < T(0))); } } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/step.hpp b/include/ccmath/ext/step.hpp index 1bba5fae..c2b3fb2c 100644 --- a/include/ccmath/ext/step.hpp +++ b/include/ccmath/ext/step.hpp @@ -22,6 +22,6 @@ namespace ccm::ext * @return 0 if value is less than edge, otherwise 1. */ template && !std::is_same_v, bool> = true> - [[nodiscard]] constexpr T step(T edge, T value) noexcept + constexpr T step(T edge, T value) noexcept { return value < edge ? T(0) : T(1); } } // namespace ccm::ext \ No newline at end of file diff --git a/include/ccmath/ext/unlerp.hpp b/include/ccmath/ext/unlerp.hpp index 16c2f490..5802f69a 100644 --- a/include/ccmath/ext/unlerp.hpp +++ b/include/ccmath/ext/unlerp.hpp @@ -27,7 +27,7 @@ namespace ccm::ext * @return The interpolation parameter of value in the range [start, end]. */ template , bool> = true> - [[nodiscard]] constexpr T unlerp(T start, T end, T value) noexcept + constexpr T unlerp(T start, T end, T value) noexcept { return (value - start) / (end - start); } namespace safe @@ -48,7 +48,7 @@ namespace ccm::ext * @return The interpolation parameter of value in the range [start, end]. */ template , bool> = true> - [[nodiscard]] constexpr T unlerp(T start, T end, T value) noexcept + constexpr T unlerp(T start, T end, T value) noexcept { const T length = end - start; diff --git a/include/ccmath/internal/config/long_double_format.hpp b/include/ccmath/internal/config/long_double_format.hpp index 240c13c6..a2a07816 100644 --- a/include/ccmath/internal/config/long_double_format.hpp +++ b/include/ccmath/internal/config/long_double_format.hpp @@ -10,11 +10,12 @@ #pragma once +#include #include namespace ccm::config { - enum class LongDoubleFormat + enum class LongDoubleFormat : std::uint8_t { Double, X87Extended, diff --git a/include/ccmath/internal/config/platform/android.hpp b/include/ccmath/internal/config/platform/android.hpp index 5b33f3fd..ab0e94de 100644 --- a/include/ccmath/internal/config/platform/android.hpp +++ b/include/ccmath/internal/config/platform/android.hpp @@ -10,7 +10,7 @@ #pragma once -#if defined(__ANDROID__) +#ifdef __ANDROID__ // Check that what we plan to define is not already defined #ifdef CCM_TARGET_PLATFORM_ANDROID diff --git a/include/ccmath/internal/config/powl_policy.hpp b/include/ccmath/internal/config/powl_policy.hpp index b1187e0d..c4c983f3 100644 --- a/include/ccmath/internal/config/powl_policy.hpp +++ b/include/ccmath/internal/config/powl_policy.hpp @@ -18,7 +18,7 @@ namespace ccm::config // Set CCMATH_DISABLE_REDUCED_PRECISION_POWL=ON to return quiet NaN on incomplete tiers. constexpr bool reduced_precision_powl_fallback_enabled() noexcept { -#if defined(CCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL) +#ifdef CCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL return false; #else return true; diff --git a/include/ccmath/internal/config/type_support.hpp b/include/ccmath/internal/config/type_support.hpp index e123efaa..1d0a1432 100644 --- a/include/ccmath/internal/config/type_support.hpp +++ b/include/ccmath/internal/config/type_support.hpp @@ -13,7 +13,7 @@ #include // LDBL_MANT_DIG #include // UINT64_MAX, __SIZEOF_INT128__ -#if defined(UINT64_MAX) +#ifdef UINT64_MAX #define CCM_TYPES_HAS_INT64 #endif // UINT64_MAX diff --git a/include/ccmath/internal/math/common/basic/fma.hpp b/include/ccmath/internal/math/common/basic/fma.hpp index d5a97f64..1c9fc4e3 100644 --- a/include/ccmath/internal/math/common/basic/fma.hpp +++ b/include/ccmath/internal/math/common/basic/fma.hpp @@ -27,7 +27,7 @@ namespace ccm * calculated as a single ternary floating-point operation). */ template , bool> = true> - constexpr T fma(T x, T y, T z) noexcept + constexpr T fma(T x, T y, T z) noexcept // NOLINT(bugprone-exception-escape) { return support::fp::dispatch_fma(x, y, z); } template , bool> = true> diff --git a/include/ccmath/internal/math/common/basic/nan.hpp b/include/ccmath/internal/math/common/basic/nan.hpp index 70489fc6..2a679872 100644 --- a/include/ccmath/internal/math/common/basic/nan.hpp +++ b/include/ccmath/internal/math/common/basic/nan.hpp @@ -39,7 +39,7 @@ namespace ccm */ constexpr long double nanl(const char * arg) noexcept { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 return static_cast(ccm::nan(arg)); #else return internal::impl::nan_ldouble_impl(arg); diff --git a/include/ccmath/internal/math/common/fmanip/modf.hpp b/include/ccmath/internal/math/common/fmanip/modf.hpp index 7f2d4f98..1892b922 100644 --- a/include/ccmath/internal/math/common/fmanip/modf.hpp +++ b/include/ccmath/internal/math/common/fmanip/modf.hpp @@ -21,7 +21,7 @@ namespace ccm constexpr T modf(T x, T * iptr) noexcept { if constexpr (ccm::builtin::has_constexpr_modf) { return ccm::builtin::modf_ct(x, iptr); } - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } + else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } else { diff --git a/include/ccmath/internal/math/common/misc/lerp.hpp b/include/ccmath/internal/math/common/misc/lerp.hpp index 5e3759a6..27d50315 100644 --- a/include/ccmath/internal/math/common/misc/lerp.hpp +++ b/include/ccmath/internal/math/common/misc/lerp.hpp @@ -36,11 +36,11 @@ namespace ccm "Do not use ccm::lerp_old it is only being kept as a fallback until ccm::lerp has been validated as conforming to std::lerp")]] constexpr T old_lerp(T a, T b, T t) noexcept { - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return t * b + (1 - t) * a; } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return (t * b) + ((1 - t) * a); } if (t == 1) { return b; } - const T x = a + t * (b - a); + const T x = a + (t * (b - a)); if ((t > 1) == (b > a)) { return b < x ? x : b; } return x < b ? x : b; diff --git a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp index b9cdfb4c..0999be1d 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp @@ -122,7 +122,7 @@ namespace ccm::builtin template inline constexpr bool runtime_builtin_fma_long_double_supported = -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 true #else !std::is_same_v diff --git a/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp b/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp index 578abdb2..8481f0a3 100644 --- a/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen { // TODO(IanP): Wire internal poly_approx_* kernels here instead of delegating to exp2_*_impl. template , bool> = true> - constexpr T exp2_gen(T num) noexcept + constexpr T exp2_gen(T num) noexcept // NOLINT(bugprone-exception-escape) { if constexpr (std::is_same_v) { return ccm::internal::exp2_float(num); } else if constexpr (std::is_same_v) { return ccm::internal::exp2_double(num); } diff --git a/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp b/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp index 9f6ba566..66a51e97 100644 --- a/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp @@ -18,7 +18,7 @@ namespace ccm::gen { template , bool> = true> - constexpr T exp_gen(T num) noexcept + constexpr T exp_gen(T num) noexcept // NOLINT(bugprone-exception-escape) { if constexpr (std::is_same_v) { return ccm::internal::impl::exp_float_impl(num); } else if constexpr (std::is_same_v) { return ccm::internal::impl::exp_double_impl(num); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp index 4b3f9710..7ae6c57c 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr T frexp_gen(T x, int & exp) { - if constexpr (std::is_same_v) { return ccm::internal::impl::frexp_impl(x, exp); } + if constexpr (std::is_same_v) { return ccm::internal::impl::frexp_impl(x, exp); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return ccm::internal::impl::frexp_impl(x, exp); } else { diff --git a/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp index 8dcb4d1a..e99cf744 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr int ilogb_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::ilogb_impl(num); } + if constexpr (std::is_same_v) { return ccm::internal::impl::ilogb_impl(num); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return ccm::internal::impl::ilogb_impl(num); } else { diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp index f70d092f..ffd30100 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp @@ -22,7 +22,7 @@ namespace ccm::internal::impl { - inline constexpr int fp_ilogb0() noexcept + constexpr int fp_ilogb0() noexcept { #ifdef FP_ILOGB0 return FP_ILOGB0; @@ -31,7 +31,7 @@ namespace ccm::internal::impl #endif } - inline constexpr int fp_ilogbnan() noexcept + constexpr int fp_ilogbnan() noexcept { #ifdef FP_ILOGBNAN return FP_ILOGBNAN; @@ -45,7 +45,7 @@ namespace ccm::internal::impl { using fp_bits_t = ccm::support::fp::FPBits; - fp_bits_t bits(x); + fp_bits_t const bits(x); if (CCM_UNLIKELY(bits.is_nan())) { diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp index 39bb5fe5..d9bde56d 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp @@ -24,7 +24,7 @@ namespace ccm::internal::impl { using fp_bits_t = ccm::support::fp::FPBits; - fp_bits_t bits(x); + fp_bits_t const bits(x); if (CCM_UNLIKELY(bits.is_nan())) { return x; } diff --git a/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp index 5ec15413..28376791 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr T logb_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::logb_impl(num); } + if constexpr (std::is_same_v) { return ccm::internal::impl::logb_impl(num); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return ccm::internal::impl::logb_impl(num); } else { diff --git a/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp index 16bb4020..2c507702 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr T modf_gen(T x, T * iptr) noexcept { - if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } + if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } else { diff --git a/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp index de439ba7..9aeb4bca 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp @@ -26,7 +26,7 @@ namespace ccm::gen ccm::support::fp::FPBits from_bits(from); if (from_bits.is_nan()) { return from; } - ccm::support::fp::FPBits to_bits(to); + ccm::support::fp::FPBits const to_bits(to); if (to_bits.is_nan()) { return static_cast(to); } // This evaluation will only work so long as 'Arithmetic2' is a greater than or equal to the precision of 'Arithmetic1' @@ -54,7 +54,7 @@ namespace ccm::gen return from_bits.get_val(); } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 // Specialization for 80-bit long double // NOLINTNEXTLINE(readability-function-cognitive-complexity) - This function is complex by nature due to the nature of handling 80-bit long double constexpr long double nextafter(long double from, long double to) diff --git a/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp b/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp index 04874e9d..f2ada2bc 100644 --- a/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp @@ -38,11 +38,11 @@ namespace ccm "Do not use ccm::lerp_old it is only being kept as a fallback until ccm::lerp has been validated as conforming to std::lerp")]] constexpr T old_lerp(T a, T b, T t) noexcept { - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return t * b + (1 - t) * a; } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return (t * b) + ((1 - t) * a); } if (t == 1) { return b; } - const T x = a + t * (b - a); + const T x = a + (t * (b - a)); if ((t > 1) == (b > a)) { return b < x ? x : b; } return x < b ? x : b; diff --git a/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp b/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp index fed35514..6c42b89b 100644 --- a/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr T cbrt_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::cbrt_impl(num); } + if constexpr (std::is_same_v) { return ccm::internal::impl::cbrt_impl(num); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return ccm::internal::impl::cbrt_impl(num); } else { diff --git a/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp b/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp index 725672c6..5d946dbc 100644 --- a/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp @@ -19,7 +19,7 @@ namespace ccm::gen template , bool> = true> constexpr T hypot_gen(T x, T y) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::hypot_impl(x, y); } + if constexpr (std::is_same_v) { return ccm::internal::impl::hypot_impl(x, y); } // NOLINT(bugprone-branch-clone) else if constexpr (std::is_same_v) { return ccm::internal::impl::hypot_impl(x, y); } else { diff --git a/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp b/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp index 2e6f05ff..f6558fd5 100644 --- a/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp @@ -28,7 +28,7 @@ namespace ccm::internal::impl { using fp_bits_t = ccm::support::fp::FPBits; - fp_bits_t bits(x); + fp_bits_t const bits(x); if (CCM_UNLIKELY(bits.is_nan())) { return x; } @@ -41,7 +41,7 @@ namespace ccm::internal::impl T y = ccm::exp(ccm::log(ax) / static_cast(3)); const T y_sq = y * y; - y = (static_cast(2) * y + ax / y_sq) / static_cast(3); + y = ((static_cast(2) * y) + (ax / y_sq)) / static_cast(3); return negative ? -y : y; } diff --git a/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp b/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp index e2cab091..babcd9a3 100644 --- a/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp @@ -44,6 +44,6 @@ namespace ccm::internal::impl } const T ratio = y / x; - return x * ccm::sqrt(static_cast(1) + ratio * ratio); + return x * ccm::sqrt(static_cast(1) + (ratio * ratio)); } } // namespace ccm::internal::impl diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp index 785851e0..2660042a 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp @@ -39,7 +39,7 @@ namespace ccm::gen::impl { inline double fma_dx(double x, double y, double z) noexcept { -#if defined(CCMATH_TARGET_CPU_HAS_FMA) +#ifdef CCMATH_TARGET_CPU_HAS_FMA return support::multiply_add(x, y, z); #elif CCM_HAS_BUILTIN(__builtin_fma) return __builtin_fma(x, y, z); @@ -382,10 +382,10 @@ namespace ccm::gen::impl // Walk to the floor: the largest double r with r^2*x <= 1, i.e. r <= 1/sqrt(x). The // faithful seed is at most a few ulps away, so this is a short loop. - int s = rsqrt_residual_sign(r, 0.0, x); + int const s = rsqrt_residual_sign(r, 0.0, x); if (s > 0) { - do { r = next_down(r); } while (rsqrt_residual_sign(r, 0.0, x) > 0); + do { r = next_down(r); } while (rsqrt_residual_sign(r, 0.0, x) > 0); // NOLINT(cppcoreguidelines-avoid-do-while) } else if (s < 0) { @@ -414,7 +414,8 @@ namespace ccm::gen::impl // normal final value collapses with a single binary64 addition, correctly rounded in // every mode; a subnormal value (only reachable through the 2^-512 underflow scale) is // instead rounded straight onto the subnormal grid to avoid a double rounding. - constexpr double pow_double_double(unsigned idx_x, double dx, double e_x, double y6, std::uint64_t sign, double scale) noexcept + constexpr double + pow_double_double(unsigned idx_x, double dx, double e_x, double y6, std::uint64_t sign, double scale) noexcept // NOLINT(bugprone-exception-escape) { using FPBits_t = support::fp::FPBits; @@ -563,7 +564,7 @@ namespace ccm::gen::impl return result; } - constexpr double pow_kernel(double base, double exp, std::uint64_t sign) noexcept + constexpr double pow_kernel(double base, double exp, std::uint64_t sign) noexcept // NOLINT(bugprone-exception-escape) { using FPBits_t = support::fp::FPBits; @@ -587,8 +588,8 @@ namespace ccm::gen::impl if (support::is_constant_evaluated()) { - const typename FPBits_t::storage_type masked_mantissa = static_cast( - FPBits_t(m_x).uintval() & static_cast(0x3fff'e000'0000'0000ULL)); + const typename FPBits_t::storage_type masked_mantissa = + static_cast(FPBits_t(m_x).uintval() & static_cast(0x3fff'e000'0000'0000ULL)); const double c = FPBits_t(masked_mantissa).get_val(); dx = support::multiply_add( support::constants::RD.at(static_cast(idx_x)), m_x - c, support::constants::CD.at(static_cast(idx_x))); @@ -603,8 +604,8 @@ namespace ccm::gen::impl } else { - const typename FPBits_t::storage_type masked_mantissa = static_cast( - FPBits_t(m_x).uintval() & static_cast(0x3fff'e000'0000'0000ULL)); + const typename FPBits_t::storage_type masked_mantissa = + static_cast(FPBits_t(m_x).uintval() & static_cast(0x3fff'e000'0000'0000ULL)); const double c = FPBits_t(masked_mantissa).get_val(); dx = support::multiply_add( support::constants::RD.at(static_cast(idx_x)), m_x - c, support::constants::CD.at(static_cast(idx_x))); @@ -722,7 +723,7 @@ namespace ccm::gen::impl double final = result * scale; if (sign == 0) { - FPBits_t final_bits(final); + FPBits_t const final_bits(final); if (final_bits.sign().is_neg()) { if (final_bits.is_inf()) @@ -755,13 +756,13 @@ namespace ccm::gen::impl using FPBits_t = support::fp::FPBits; using Sign = types::Sign; - FPBits_t base_bits(base); - FPBits_t exp_bits(exp); + FPBits_t const base_bits(base); + FPBits_t const exp_bits(exp); // Screen the operands by bit inspection so no floating point compare touches a // signaling NaN. UCRT's pow returns quietly for sNaN operands while glibc and // Apple libm signal invalid, so the raise is explicit and platform scoped. -#if !defined(_WIN32) +#ifndef _WIN32 if (base_bits.is_signaling_nan() || exp_bits.is_signaling_nan()) { support::fenv::raise_except_if_required(FE_INVALID); } #endif diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp index 61327ef1..c7f1b718 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp @@ -76,10 +76,10 @@ namespace ccm::gen::impl const VDD as = v_split(a); const VDD bs = v_split(b); const DVec hi = a * b; - const DVec t1 = as.hi * bs.hi - hi; - const DVec t2 = as.hi * bs.lo + t1; - const DVec t3 = as.lo * bs.hi + t2; - return { hi, as.lo * bs.lo + t3 }; + const DVec t1 = (as.hi * bs.hi) - hi; + const DVec t2 = (as.hi * bs.lo) + t1; + const DVec t3 = (as.lo * bs.hi) + t2; + return { hi, (as.lo * bs.lo) + t3 }; } // types::quick_mult(double, DoubleDouble). diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp index dcdfdc49..7062a782 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp @@ -117,7 +117,7 @@ namespace ccm::gen::impl const DoubleDouble prod = quick_mult(y6, log2_x); // 2^6 * (y * log2(x) - (hi + mid)) = 2^6 * lo - DoubleDouble lo6 = larger_exponent(prod.hi, lo6_hi) ? add(prod, lo6_hi_dd) : add(lo6_hi_dd, prod); + DoubleDouble const lo6 = larger_exponent(prod.hi, lo6_hi) ? add(prod, lo6_hi_dd) : add(lo6_hi_dd, prod); constexpr std::array EXP2_COEFFS = { DoubleDouble{ 0x1p0, 0 }, DoubleDouble{ 0x1.62e42fefa39efp-7, 0x1.abc9e3b398024p-62 }, @@ -131,7 +131,7 @@ namespace ccm::gen::impl DoubleDouble{ 0x1.b5251ff97bee1p-78, -0x1.8483eabd9642dp-132 }, }; - DoubleDouble pp = ::ccm::support::polyeval(lo6, + DoubleDouble const pp = ::ccm::support::polyeval(lo6, EXP2_COEFFS[0], EXP2_COEFFS[1], EXP2_COEFFS[2], @@ -178,7 +178,7 @@ namespace ccm::gen::impl { if (y == 0.0F) { return 1.0F; } - switch (y_abs) + switch (y_abs) // NOLINT(hicpp-multiway-paths-covered) { case 0x7f800000: { // y is +/-infinity @@ -326,7 +326,7 @@ namespace ccm::gen::impl // Check for exceptional cases if (auto exceptional_case = handle_exceptional_cases(x, y, xbits, ybits, x_u, ex, sign); CCM_UNLIKELY(exceptional_case.has_value())) { - return *exceptional_case; + return *exceptional_case; // NOLINT(bugprone-unchecked-optional-access) guarded by has_value() above } if (x < 0.0F && !is_integer(y)) @@ -427,9 +427,9 @@ namespace ccm::gen::impl // For those exponents that are out of range, the final conversion will round // them correctly to inf/max float or 0/min float accordingly. auto hm_i = static_cast(hm); - hm_i = (hm_i > (1 << 15)) ? (1 << 15) : (hm_i < (-(1 << 15)) ? -(1 << 15) : hm_i); + hm_i = (hm_i > (1 << 15)) ? (1 << 15) : (hm_i < (-(1 << 15)) ? -(1 << 15) : hm_i); // NOLINT(readability-avoid-nested-conditional-operator) - int idx_y = hm_i & 0x3f; + int const idx_y = static_cast(hm_i & 0x3f); // 2^hi // The shift goes through uint64 because left-shifting a negative value is undefined @@ -500,7 +500,7 @@ namespace ccm::gen::impl } } // namespace internal::impl - constexpr float powf_impl(float base, float exp) noexcept + constexpr float powf_impl(float base, float exp) noexcept // NOLINT(bugprone-exception-escape) { return internal::impl::powf_impl(base, exp); // return internal::impl::powf_impl(base, exp); diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp index 028eb89d..a0b7c8ce 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp @@ -92,8 +92,8 @@ namespace ccm::gen::impl // Replace exceptional lanes with a benign value so the unconditional vector main path cannot // raise spurious floating point exceptions on them. The originals are kept for the scalar // fallback, which is what actually computes those lanes. - const FVec xs = pp::simd_select(special, FVec(1.0f), x); - const FVec ys = pp::simd_select(special, FVec(1.0f), y); + const FVec xs = pp::simd_select(special, FVec(1.0F), x); + const FVec ys = pp::simd_select(special, FVec(1.0F), y); const U32 xu = pp::simd_bit_cast(xs); @@ -111,7 +111,7 @@ namespace ccm::gen::impl const DVec lr_mid([&](auto i) { return internal::impl::LOG2_R_TD[static_cast(idx_x_u[i])].mid; }); // dx = m_x * R[idx] - 1, computed in float then widened (exact). - const DVec dx = DVec(pp::fma(m_x, r_tab, FVec(-1.0f))); + const DVec dx = DVec(pp::fma(m_x, r_tab, FVec(-1.0F))); const DVec dx2 = dx * dx; const DVec c0 = pp::fma(dx, DVec(simd_detail::kLog2Coeffs[1]), DVec(simd_detail::kLog2Coeffs[0])); const DVec c1 = pp::fma(dx, DVec(simd_detail::kLog2Coeffs[3]), DVec(simd_detail::kLog2Coeffs[2])); @@ -119,7 +119,7 @@ namespace ccm::gen::impl const DVec p = pp::fma(dx2, pp::fma(dx2, c2, c1), c0); // polyeval(dx2, c0, c1, c2) const DVec s = pp::fma(dx, p, log2r + e_x); - const DVec y6 = DVec(ys * FVec(0x1.0p6f)); + const DVec y6 = DVec(ys * FVec(0x1.0p6F)); const DVec hm = simd_detail::v_nearest_integer(s * y6); const DVec lo6_hi = pp::fma(y6, e_x + lr_hi, -hm); const DVec lo6 = pp::fma(y6, pp::fma(dx, p, lr_mid), lo6_hi); @@ -175,7 +175,7 @@ namespace ccm::gen::impl ccm::support::fenv::set_errno_if_required(ERANGE); ccm::support::fenv::raise_except_if_required(FE_OVERFLOW); } - if (pp::any_of(fast_lane & (r_upper == FVec(0.0f)))) + if (pp::any_of(fast_lane & (r_upper == FVec(0.0F)))) { ccm::support::fenv::set_errno_if_required(ERANGE); ccm::support::fenv::raise_except_if_required(FE_UNDERFLOW); diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp index 0ee6cce9..158087a4 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp @@ -10,7 +10,7 @@ #pragma once -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 #include "ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_tables.hpp" #include "ccmath/internal/predef/has_builtin.hpp" diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_tables.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_tables.hpp index af0f01e9..f44abc89 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_tables.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_tables.hpp @@ -17,7 +17,7 @@ #pragma once -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 #include diff --git a/include/ccmath/internal/math/generic/func/power/powl_gen.hpp b/include/ccmath/internal/math/generic/func/power/powl_gen.hpp index 45d6e3ef..ed22fb86 100644 --- a/include/ccmath/internal/math/generic/func/power/powl_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/powl_gen.hpp @@ -13,7 +13,7 @@ #include "ccmath/internal/config/powl_policy.hpp" #include "ccmath/internal/config/type_support.hpp" #include "ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp" -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 #include "ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp" #endif #include "ccmath/internal/support/bits.hpp" @@ -62,7 +62,7 @@ namespace ccm::gen return static_cast(unit) << static_cast(scale); } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 constexpr typename PowlFPBits_t::storage_type float80_unit_mantissa(const PowlFPBits_t & bits) noexcept { typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); @@ -99,7 +99,7 @@ namespace ccm::gen constexpr bool is_integer(const PowlFPBits_t & bits) noexcept { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 return is_integer_float80_bits(bits); #else if (bits.is_nan() || bits.is_inf()) { return false; } @@ -116,7 +116,7 @@ namespace ccm::gen constexpr bool is_odd_integer(const PowlFPBits_t & bits) noexcept { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 std::int64_t magnitude = 0; if (try_extract_int64(bits, magnitude)) { return (magnitude & 1) != 0; } return is_odd_integer_float80_bits(bits); @@ -144,15 +144,15 @@ namespace ccm::gen if (!is_integer(bits)) { return false; } const int exponent = bits.get_explicit_exponent(); -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); if (bits.get_implicit_bit()) { mantissa |= PowlFPBits_t::EXPLICIT_BIT_MASK; } const int trailing_zeros = storage_countr_zero(mantissa); const int scale = exponent + trailing_zeros - static_cast(PowlFPBits_t::fraction_length); #else - typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); - const int trailing_zeros = storage_countr_zero(mantissa); - const int scale = exponent + trailing_zeros - static_cast(PowlFPBits_t::fraction_length); + PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); + const int trailing_zeros = storage_countr_zero(mantissa); + const int scale = exponent + trailing_zeros - static_cast(PowlFPBits_t::fraction_length); #endif if (scale < 0) { return false; } @@ -189,7 +189,7 @@ namespace ccm::gen constexpr long double powl_reduced_precision_double_fallback(long double base, long double exp) noexcept { return static_cast(::ccm::gen::impl::pow_impl(static_cast(base), static_cast(exp))); } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 // Double-double (compensated) arithmetic for integer powers. Naive exponentiation by // squaring loses the bits past the 64-bit significand on every multiply, which for large // |exp| (e.g. 10^1000) drifts well past the 4 ULP budget. Accumulating the squaring in a @@ -247,11 +247,11 @@ namespace ccm::gen } // namespace powl_dd #endif - constexpr long double powl_bounded_integer(long double base, std::int64_t exp) noexcept + constexpr long double powl_bounded_integer(long double base, std::int64_t exp) noexcept // NOLINT(misc-no-recursion) { if (exp == 0) { return 1.0L; } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 const std::uint64_t magnitude = exp < 0 ? static_cast(-(exp + 1)) + 1U : static_cast(exp); const powl_dd::Pair p = powl_dd::ipow(base, magnitude); if (exp < 0) { return powl_dd::reciprocal(p); } @@ -272,7 +272,7 @@ namespace ccm::gen #endif } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 inline constexpr long double kPowlHugeExponentThreshold = 2.0L * static_cast(-LDBL_MIN_EXP + LDBL_MANT_DIG + 1) / static_cast(LDBL_EPSILON); @@ -393,7 +393,7 @@ namespace ccm::gen constexpr long double powl_impl(long double base, long double exp) noexcept { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 // long double is a double alias. Same kernel as pow, not extended precision. return static_cast(::ccm::gen::impl::pow_impl(static_cast(base), static_cast(exp))); #elif defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) diff --git a/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp b/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp index b4a19c57..c8422b55 100644 --- a/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp @@ -28,7 +28,7 @@ namespace ccm::gen static constexpr bool value = false; }; -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 template <> struct Is80BitLongDouble { @@ -42,12 +42,12 @@ namespace ccm::gen template constexpr void normalize(int& exponent, typename support::fp::FPBits::storage_type& mantissa) { - const int shift = support::countl_zero(mantissa) - (8 * static_cast(sizeof(mantissa)) - 1 - support::fp::FPBits::fraction_length); + const int shift = support::countl_zero(mantissa) - ((8 * static_cast(sizeof(mantissa))) - 1 - support::fp::FPBits::fraction_length); exponent -= shift; mantissa <<= shift; } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 template <> constexpr void normalize(int& exponent, std::uint64_t& mantissa) { normalize(exponent, mantissa); } @@ -70,7 +70,7 @@ namespace ccm::gen // ReSharper disable once CppFunctionIsNotImplemented static constexpr long double sqrt_calc_80bits(long double x); -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 static constexpr long double sqrt_calc_80bits(long double x) { using Bits = support::fp::FPBits; diff --git a/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp index f76e1c9e..e197b625 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp @@ -22,7 +22,7 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T ceil_rt(T num) noexcept { -#if !defined(CCMATH_COMPILER_GCC) +#ifndef CCMATH_COMPILER_GCC if constexpr (ccm::builtin::has_runtime_ceil) { return ccm::builtin::ceil_rt(num); } else #endif diff --git a/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp index 5f47ab80..079e7e4f 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp @@ -22,7 +22,7 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T floor_rt(T num) noexcept { -#if !defined(CCMATH_COMPILER_GCC) +#ifndef CCMATH_COMPILER_GCC if constexpr (ccm::builtin::has_runtime_floor) { return ccm::builtin::floor_rt(num); } else #endif diff --git a/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp index e9ee4493..3830ab10 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp @@ -24,7 +24,7 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T round_rt(T num) noexcept { -#if !defined(CCMATH_COMPILER_GCC) +#ifndef CCMATH_COMPILER_GCC if constexpr (ccm::builtin::has_runtime_round) { return ccm::builtin::round_rt(num); } else #endif diff --git a/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp index 926c1f43..4f4869c0 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp @@ -22,7 +22,7 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T trunc_rt(T num) noexcept { -#if !defined(CCMATH_COMPILER_GCC) +#ifndef CCMATH_COMPILER_GCC if constexpr (ccm::builtin::has_runtime_trunc) { return ccm::builtin::trunc_rt(num); } else #endif diff --git a/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp b/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp index cb878cc8..3215b924 100644 --- a/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp @@ -24,7 +24,7 @@ namespace ccm::rt::simd_impl { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 template , bool> = true> #else template && !std::is_same_v, bool> = true> @@ -59,7 +59,7 @@ namespace ccm::rt template , bool> = true> T pow_rt(T base, T exp) { -#if !defined(CCM_CONFIG_TEST_DISABLE_RUNTIME_BUILTIN_POW) +#ifndef CCM_CONFIG_TEST_DISABLE_RUNTIME_BUILTIN_POW if constexpr (ccm::builtin::has_runtime_pow) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -68,7 +68,7 @@ namespace ccm::rt return ccm::builtin::pow_rt(base, exp); } #endif -#if defined(CCMATH_HAS_SIMD) +#ifdef CCMATH_HAS_SIMD // In the unlikely event, the rounding mode is not the default, use the runtime implementation instead. if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return gen::pow_gen(base, exp); } #if !defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) // If long double is different from double, use the generic implementation instead. diff --git a/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp b/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp index 055e4744..0cac56c4 100644 --- a/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp @@ -24,7 +24,7 @@ namespace ccm::rt::simd_impl { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 template , bool> = true> #else template && !std::is_same_v, bool> = true> @@ -52,7 +52,7 @@ namespace ccm::rt T sqrt_rt(T num) { if constexpr (ccm::builtin::has_runtime_sqrt) { return ccm::builtin::sqrt_rt(num); } -#if defined(CCMATH_HAS_SIMD) +#ifdef CCMATH_HAS_SIMD // In the unlikely event, the rounding mode is not the default, use the runtime implementation instead. if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return gen::sqrt_gen(num); } #if !defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) // If long double is different from double, use the generic implementation instead. diff --git a/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp b/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp index 1e9c2506..99987d5c 100644 --- a/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp +++ b/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp @@ -24,7 +24,7 @@ namespace ccm::rt::detail [[nodiscard]] inline bool simd_runtime_ok() noexcept { return ccm::support::fenv::get_rounding_mode() == FE_TONEAREST; } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 template inline constexpr bool simd_eligible_v = std::is_same_v || std::is_same_v || std::is_same_v; #else diff --git a/include/ccmath/internal/math/runtime/pp/flags.hpp b/include/ccmath/internal/math/runtime/pp/flags.hpp index 3efe8b10..19f60fb9 100644 --- a/include/ccmath/internal/math/runtime/pp/flags.hpp +++ b/include/ccmath/internal/math/runtime/pp/flags.hpp @@ -59,14 +59,16 @@ namespace ccm::pp struct flags_align_request : std::integral_constant { }; + // NOLINTBEGIN(readability-avoid-nested-conditional-operator) template struct flags_align_request> : std::integral_constant::value) != 0) ? (std::size_t{ 0 } + ... + overalign_value::value) - : ((std::is_same::value || ...) ? std::size_t{ 1 } : std::size_t{ 0 })> + : ((std::is_same_v || ...) ? std::size_t{ 1 } : std::size_t{ 0 })> { }; + // NOLINTEND(readability-avoid-nested-conditional-operator) // Communicate a known alignment to the optimizer. The pointer value is // unchanged; the compiler may then use aligned moves / assume alignment. diff --git a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp index a166d81a..467f7c20 100644 --- a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp +++ b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp @@ -12,7 +12,7 @@ #include "ccmath/internal/math/runtime/pp/declaration.hpp" #include "ccmath/internal/math/runtime/pp/utility.hpp" -#include "ccmath/internal/math/runtime/pp/vec_ext.hpp" +#include "ccmath/internal/math/runtime/pp/vec_ext.hpp" // NOLINT(misc-header-include-cycle) #include "ccmath/internal/predef/attributes/always_inline.hpp" // SSE2 intrinsic backend for compilers without the GNU/Clang vector extensions diff --git a/include/ccmath/internal/math/runtime/pp/reduce.hpp b/include/ccmath/internal/math/runtime/pp/reduce.hpp index 49bb8095..fb5d51a7 100644 --- a/include/ccmath/internal/math/runtime/pp/reduce.hpp +++ b/include/ccmath/internal/math/runtime/pp/reduce.hpp @@ -31,7 +31,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T reduce_min_identity() { - if constexpr (std::is_floating_point::value) { return std::numeric_limits::infinity(); } + if constexpr (std::is_floating_point_v) { return std::numeric_limits::infinity(); } else { return std::numeric_limits::max(); @@ -40,7 +40,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T reduce_max_identity() { - if constexpr (std::is_floating_point::value) { return -std::numeric_limits::infinity(); } + if constexpr (std::is_floating_point_v) { return -std::numeric_limits::infinity(); } else { return std::numeric_limits::lowest(); diff --git a/include/ccmath/internal/math/runtime/pp/reference.hpp b/include/ccmath/internal/math/runtime/pp/reference.hpp index 4236fdf7..c217c7fd 100644 --- a/include/ccmath/internal/math/runtime/pp/reference.hpp +++ b/include/ccmath/internal/math/runtime/pp/reference.hpp @@ -21,7 +21,7 @@ namespace ccm::pp::detail { template - class SimdReference + class SimdReference // NOLINT(cppcoreguidelines-special-member-functions) { using Traits = SimdTraits; typename Traits::SimdMember *data_; diff --git a/include/ccmath/internal/math/runtime/pp/simd.hpp b/include/ccmath/internal/math/runtime/pp/simd.hpp index 889f7814..d622ac97 100644 --- a/include/ccmath/internal/math/runtime/pp/simd.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd.hpp @@ -48,10 +48,10 @@ namespace ccm::pp // Generator: gen(integral_constant) supplies lane i. template ::value && - std::is_convertible()(std::integral_constant{})), T>::value, + std::enable_if_t && + std::is_convertible_v()(std::integral_constant{})), T>, int> = 0> - CCM_ALWAYS_INLINE explicit basic_simd(G &&gen) + CCM_ALWAYS_INLINE explicit basic_simd(G &&gen) // NOLINT(cppcoreguidelines-missing-std-forward) { detail::unroll_ic([&](auto i) { Traits::set(data_, i, static_cast(gen(i))); }); } @@ -59,9 +59,7 @@ namespace ccm::pp // Converting constructor: same lane count, different element type, per-lane // static_cast. Explicit (we do not model the standard's value-preserving // implicit-conversion rules). - template ::size() == size()) && !std::is_same, basic_simd>::value, int> = 0> + template ::size() == size()) && !std::is_same_v, basic_simd>, int> = 0> CCM_ALWAYS_INLINE explicit basic_simd(basic_simd const &o) { for (detail::SimdSizeType i = 0; i < size(); ++i) { (*this)[i] = static_cast(o[i]); } @@ -69,7 +67,7 @@ namespace ccm::pp // Load from contiguous memory. template , std::enable_if_t, int> = 0> - CCM_ALWAYS_INLINE basic_simd(T const *ptr, Flags flags = {}) + CCM_ALWAYS_INLINE basic_simd(T const *ptr, Flags flags = {}) // NOLINT(google-explicit-constructor) { copy_from(ptr, flags); } template > @@ -129,59 +127,59 @@ namespace ccm::pp return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator%(basic_simd const &a, basic_simd const &b) { return from_member(Traits::mod(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator&(basic_simd const &a, basic_simd const &b) { return from_member(Traits::band(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator|(basic_simd const &a, basic_simd const &b) { return from_member(Traits::bor(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator^(basic_simd const &a, basic_simd const &b) { return from_member(Traits::bxor(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator<<(basic_simd const &a, basic_simd const &b) { return from_member(Traits::shl(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE friend basic_simd operator>>(basic_simd const &a, basic_simd const &b) { return from_member(Traits::shr(a.data_, b.data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd operator~() const { return from_member(Traits::bnot(data_)); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator%=(basic_simd const &o) { data_ = Traits::mod(data_, o.data_); return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator&=(basic_simd const &o) { data_ = Traits::band(data_, o.data_); return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator|=(basic_simd const &o) { data_ = Traits::bor(data_, o.data_); return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator^=(basic_simd const &o) { data_ = Traits::bxor(data_, o.data_); return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator<<=(basic_simd const &o) { data_ = Traits::shl(data_, o.data_); return *this; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd &operator>>=(basic_simd const &o) { data_ = Traits::shr(data_, o.data_); diff --git a/include/ccmath/internal/math/runtime/pp/simd_cat.hpp b/include/ccmath/internal/math/runtime/pp/simd_cat.hpp index d9270047..16d92e2e 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_cat.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_cat.hpp @@ -42,7 +42,7 @@ namespace ccm::pp return r; } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE std::array(basic_simd::size() / V::size())> simd_split(basic_simd const &v) { constexpr detail::SimdSizeType parts = basic_simd::size() / V::size(); diff --git a/include/ccmath/internal/math/runtime/pp/simd_config.hpp b/include/ccmath/internal/math/runtime/pp/simd_config.hpp index 7ae74bad..6c0a48e3 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_config.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_config.hpp @@ -240,7 +240,7 @@ // Intel Short Vector Math Library (SVML) // As far as I am aware, there is no reliable way to detect SVML support at compile-time. -#if defined(CCM_CONFIG_RT_SIMD_HAS_SVML) +#ifdef CCM_CONFIG_RT_SIMD_HAS_SVML #ifndef CCMATH_HAS_SIMD #define CCMATH_HAS_SIMD 1 #endif @@ -252,7 +252,7 @@ #endif // ARM macros -#if defined __ARM_NEON +#ifdef __ARM_NEON #define CCMATH_SIMD_HAVE_NEON 1 #else #define CCMATH_SIMD_HAVE_NEON 0 @@ -274,7 +274,7 @@ #endif // CCM_SIMD_INTRINSIC -#if defined(CCM_CONFIG_NO_SIMD_INLINE) +#ifdef CCM_CONFIG_NO_SIMD_INLINE #define CCM_SIMD_INTRINSIC #elif defined(CCM_CONFIG_NO_FORCED_SIMD_INLINE) #define CCM_SIMD_INTRINSIC inline @@ -312,7 +312,8 @@ namespace ccm::pp::config::detail { template - CCM_SIMD_ENFORCED_ALWAYS_INLINE constexpr void simd_int_pack(std::integer_sequence, F && code) + CCM_SIMD_ENFORCED_ALWAYS_INLINE constexpr void simd_int_pack(std::integer_sequence /*unused*/, + F && code) // NOLINT(cppcoreguidelines-missing-std-forward) { code(std::integer_sequence{}); } } // namespace ccm::pp::config::detail diff --git a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp index 362307f7..73a31f51 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp @@ -58,7 +58,7 @@ namespace ccm::pp unsigned long long bits = 0; for (detail::SimdSizeType i = 0; i < size(); ++i) { - if (Traits::mget(data_, i)) { bits |= (1ull << i); } + if (Traits::mget(data_, i)) { bits |= (1ULL << i); } } return bits; } @@ -67,7 +67,7 @@ namespace ccm::pp CCM_ALWAYS_INLINE static basic_simd_mask from_bits(unsigned long long bits) { basic_simd_mask r; - for (detail::SimdSizeType i = 0; i < size(); ++i) { Traits::mset(r.data_, i, ((bits >> i) & 1ull) != 0); } + for (detail::SimdSizeType i = 0; i < size(); ++i) { Traits::mset(r.data_, i, ((bits >> i) & 1ULL) != 0); } return r; } diff --git a/include/ccmath/internal/math/runtime/pp/simd_math.hpp b/include/ccmath/internal/math/runtime/pp/simd_math.hpp index 1402e7d2..b3a6d0d4 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_math.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_math.hpp @@ -47,7 +47,7 @@ namespace ccm::pp } // namespace detail #define CCM_PP_MATH1(NAME, OP) \ - template ::value, int> = 0> \ + template , int> = 0> \ CCM_ALWAYS_INLINE basic_simd NAME(basic_simd const & v) \ { return basic_simd::from_member(SimdTraits::OP(v.get())); } CCM_PP_MATH1(sqrt, op_sqrt) @@ -58,7 +58,7 @@ namespace ccm::pp CCM_PP_MATH1(fabs, op_fabs) #undef CCM_PP_MATH1 - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd fma(basic_simd const & a, basic_simd const & b, basic_simd const & c) { return basic_simd::from_member(SimdTraits::op_fma(a.get(), b.get(), c.get())); } @@ -70,27 +70,27 @@ namespace ccm::pp { return basic_simd::from_member(SimdTraits::op_max(a.get(), b.get())); } // abs: floating point reuses the fabs primitive; integral negates negatives. - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd abs(basic_simd const & v) { return basic_simd::from_member(SimdTraits::op_fabs(v.get())); } - template ::value && std::is_signed::value, int> = 0> + template && std::is_signed_v, int> = 0> CCM_ALWAYS_INLINE basic_simd abs(basic_simd const & v) { return detail::map1(v, [](T x) { return x < T(0) ? static_cast(-x) : x; }); } // Per-lane transcendental baseline. - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd exp(basic_simd const & v) { return detail::map1(v, [](T x) { return detail::s_exp(x); }); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd log(basic_simd const & v) { return detail::map1(v, [](T x) { return detail::s_log(x); }); } - template ::value, int> = 0> + template , int> = 0> CCM_ALWAYS_INLINE basic_simd pow(basic_simd const & a, basic_simd const & b) { return detail::map2(a, b, [](T x, T y) { return detail::s_pow(x, y); }); diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index 1b37e386..7ac5d65a 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -33,7 +33,7 @@ namespace ccm::pp // Compile-time unrolled apply over the lane indices [0, N). The callback // receives a plain index so it can be used to subscript a builtin vector. template - CCM_ALWAYS_INLINE constexpr void unroll_impl(F && f, std::index_sequence /*unused*/) + CCM_ALWAYS_INLINE constexpr void unroll_impl(F && f, std::index_sequence /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) { (f(static_cast(Is)), ...); } template @@ -43,7 +43,7 @@ namespace ccm::pp // As unroll, but passes a std::integral_constant so the callback can use // the index in a constant-expression context (used by the generator ctor). template - CCM_ALWAYS_INLINE constexpr void unroll_ic_impl(F && f, std::index_sequence /*unused*/) + CCM_ALWAYS_INLINE constexpr void unroll_ic_impl(F && f, std::index_sequence /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) { (f(std::integral_constant(Is)>{}), ...); } template @@ -60,7 +60,7 @@ namespace ccm::pp template \ CCM_ALWAYS_INLINE T NAME(T x) \ { \ - if constexpr (std::is_same::value) { return BF(x); } \ + if constexpr (std::is_same_v) { return BF(x); } \ else \ { \ return BD(x); \ @@ -79,7 +79,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { - if constexpr (std::is_same::value) { return __builtin_fmaf(a, b, c); } + if constexpr (std::is_same_v) { return __builtin_fmaf(a, b, c); } else { return __builtin_fma(a, b, c); @@ -88,7 +88,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_pow(T a, T b) { - if constexpr (std::is_same::value) { return __builtin_powf(a, b); } + if constexpr (std::is_same_v) { return __builtin_powf(a, b); } else { return __builtin_pow(a, b); @@ -99,7 +99,7 @@ namespace ccm::pp template \ CCM_ALWAYS_INLINE T NAME(T x) \ { \ - if constexpr (std::is_same::value) { return ::CF(x); } \ + if constexpr (std::is_same_v) { return ::CF(x); } \ else \ { \ return ::CD(x); \ @@ -118,7 +118,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { - if constexpr (std::is_same::value) { return ::fmaf(a, b, c); } + if constexpr (std::is_same_v) { return ::fmaf(a, b, c); } else { return ::fma(a, b, c); @@ -127,7 +127,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_pow(T a, T b) { - if constexpr (std::is_same::value) { return ::powf(a, b); } + if constexpr (std::is_same_v) { return ::powf(a, b); } else { return ::pow(a, b); @@ -161,7 +161,7 @@ namespace ccm::pp template \ CCM_ALWAYS_INLINE T NAME(T x) \ { \ - if constexpr (std::is_same::value) { return CF(x); } \ + if constexpr (std::is_same_v) { return CF(x); } \ else \ { \ return CD(x); \ @@ -180,7 +180,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { - if constexpr (std::is_same::value) { return fmaf(a, b, c); } + if constexpr (std::is_same_v) { return fmaf(a, b, c); } else { return fma(a, b, c); @@ -189,7 +189,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE T s_pow(T a, T b) { - if constexpr (std::is_same::value) { return powf(a, b); } + if constexpr (std::is_same_v) { return powf(a, b); } else { return pow(a, b); diff --git a/include/ccmath/internal/math/runtime/pp/where.hpp b/include/ccmath/internal/math/runtime/pp/where.hpp index 01660b9a..4f35a21d 100644 --- a/include/ccmath/internal/math/runtime/pp/where.hpp +++ b/include/ccmath/internal/math/runtime/pp/where.hpp @@ -39,8 +39,10 @@ namespace ccm::pp CCM_ALWAYS_INLINE where_expression(MaskType const& mask, SimdType& data) : mask_(mask), data_(&data) {} + // NOLINTBEGIN(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) CCM_ALWAYS_INLINE void operator=(SimdType const& rhs) { *data_ = simd_select(mask_, rhs, *data_); } CCM_ALWAYS_INLINE void operator=(value_type rhs) { *data_ = simd_select(mask_, SimdType(rhs), *data_); } + // NOLINTEND(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) CCM_ALWAYS_INLINE void operator+=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ + rhs, *data_); } CCM_ALWAYS_INLINE void operator-=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ - rhs, *data_); } diff --git a/include/ccmath/internal/math/runtime/simd/common.hpp b/include/ccmath/internal/math/runtime/simd/common.hpp index bf627b28..fba9ba5f 100644 --- a/include/ccmath/internal/math/runtime/simd/common.hpp +++ b/include/ccmath/internal/math/runtime/simd/common.hpp @@ -28,7 +28,7 @@ namespace ccm::intrin template CCM_GPU_HOST_DEVICE constexpr T const& choose(bool a, T const& b, T const& c) - { return a ? b : c; } + { return a ? b : c; } // NOLINT(bugprone-return-const-ref-from-parameter) callers pass lvalues template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd& operator+=(simd& a, simd const& b) diff --git a/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp b/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp index 3ad6f402..4ca531a2 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp @@ -90,8 +90,8 @@ namespace ccm::intrin CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE static constexpr int size() { return 1; } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T value) : m_value(value) {} // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(storage_type const &value) - { copy_from(value.data(), element_aligned_tag()); } // NOLINT(google-explicit-constructor) + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(storage_type const &value) // NOLINT(google-explicit-constructor) + { copy_from(value.data(), element_aligned_tag()); } // NOLINT(google-explicit-constructor) CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd &operator=(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); diff --git a/include/ccmath/internal/math/runtime/simd/simd.hpp b/include/ccmath/internal/math/runtime/simd/simd.hpp index 735f3f5b..54ab3e01 100644 --- a/include/ccmath/internal/math/runtime/simd/simd.hpp +++ b/include/ccmath/internal/math/runtime/simd/simd.hpp @@ -38,7 +38,7 @@ namespace ccm::intrin { namespace abi { -#if defined(CCMATH_HAS_SIMD_AVX2) +#ifdef CCMATH_HAS_SIMD_AVX2 using native = avx2; #elif defined(CCMATH_HAS_SIMD_SSE4) using native = sse4; diff --git a/include/ccmath/internal/math/runtime/simd/simd_vectorize.hpp b/include/ccmath/internal/math/runtime/simd/simd_vectorize.hpp index 68837354..78515f58 100644 --- a/include/ccmath/internal/math/runtime/simd/simd_vectorize.hpp +++ b/include/ccmath/internal/math/runtime/simd/simd_vectorize.hpp @@ -11,7 +11,7 @@ #pragma once #ifndef CCM_SIMD_VECTORIZE - #if defined(_OPENMP) + #ifdef _OPENMP #define CCM_SIMD_VECTORIZE _Pragma("omp simd") // msvc #elif defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/math/runtime/simd/vector_size.hpp b/include/ccmath/internal/math/runtime/simd/vector_size.hpp index 2e8c2a94..78838819 100644 --- a/include/ccmath/internal/math/runtime/simd/vector_size.hpp +++ b/include/ccmath/internal/math/runtime/simd/vector_size.hpp @@ -46,7 +46,7 @@ namespace ccm::intrin static constexpr int size() { return N / sizeof(T); } CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(static_cast(value)) {} CCM_ALWAYS_INLINE explicit simd_mask(native_type value) : m_value(value) {} - CCM_ALWAYS_INLINE int operator[](int i) { return reinterpret_cast(&m_value)[i]; } + CCM_ALWAYS_INLINE int operator[](int i) { return reinterpret_cast(&m_value)[i]; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) [[nodiscard]] CCM_ALWAYS_INLINE native_type const &get() const { return m_value; } CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(m_value || other.m_value); } CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(m_value && other.m_value); } @@ -70,7 +70,8 @@ namespace ccm::intrin static constexpr int size() { return N / sizeof(long long); } CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(static_cast(value)) {} CCM_ALWAYS_INLINE explicit simd_mask(native_type value) : m_value(value) {} - CCM_ALWAYS_INLINE long long operator[](int i) { return reinterpret_cast(&m_value)[i]; } + CCM_ALWAYS_INLINE long long operator[](int i) + { return reinterpret_cast(&m_value)[i]; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) [[nodiscard]] CCM_ALWAYS_INLINE native_type const &get() const { return m_value; } CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(m_value || other.m_value); } CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(m_value && other.m_value); } @@ -113,7 +114,7 @@ namespace ccm::intrin static constexpr int size() { return N / sizeof(T); } CCM_ALWAYS_INLINE explicit simd(T value) { - for (int i = 0; i < size(); i++) { reinterpret_cast(&m_value)[i] = value; } + for (int i = 0; i < size(); i++) { reinterpret_cast(&m_value)[i] = value; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } explicit CCM_ALWAYS_INLINE simd(const native_type &value) : m_value(value) {} CCM_ALWAYS_INLINE explicit simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } @@ -135,14 +136,14 @@ namespace ccm::intrin { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) { - reinterpret_cast(&m_value)[i] = ptr[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + reinterpret_cast(&m_value)[i] = ptr[i]; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } } CCM_ALWAYS_INLINE void copy_to(T *ptr, element_aligned_tag /*unused*/) const { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) { - ptr[i] = reinterpret_cast(&m_value)[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[i] = reinterpret_cast(&m_value)[i]; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } } CCM_ALWAYS_INLINE constexpr T operator[](int i) const { return m_value[i]; } diff --git a/include/ccmath/internal/predef/assume.hpp b/include/ccmath/internal/predef/assume.hpp index 39c212f5..6db2aec8 100644 --- a/include/ccmath/internal/predef/assume.hpp +++ b/include/ccmath/internal/predef/assume.hpp @@ -22,7 +22,7 @@ // Be aware that CLion Nova's C++ Engine does not recognize __builtin_assume as a valid symbol. // It is incorrect and this is a known bug that is fixed in CLion 2024.2. // The code will work fine, so ignore the warning it may emit. - #if defined(__clang__) + #ifdef __clang__ #define CCM_ASSUME(...) \ do { __builtin_assume(__VA_ARGS__); } while (0) #elif defined(_MSC_VER) diff --git a/include/ccmath/internal/predef/attributes/always_inline.hpp b/include/ccmath/internal/predef/attributes/always_inline.hpp index b1b67139..726f1700 100644 --- a/include/ccmath/internal/predef/attributes/always_inline.hpp +++ b/include/ccmath/internal/predef/attributes/always_inline.hpp @@ -14,7 +14,7 @@ // Should only be used for runtime func and not used with compile time func. // If you want inlining for a compile time function, use the inline keyword. #ifndef CCM_ALWAYS_INLINE - #if defined(CCM_CONFIG_NO_FORCED_INLINE) + #ifdef CCM_CONFIG_NO_FORCED_INLINE #define CCM_ALWAYS_INLINE inline #elif defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)) #define CCM_ALWAYS_INLINE __attribute__((always_inline)) inline @@ -26,7 +26,7 @@ #endif // Keep this separate for CUDA and HIP to be explicit. -#if defined(CCM_CONFIG_NO_FORCED_INLINE) +#ifdef CCM_CONFIG_NO_FORCED_INLINE #define CCM_CUDA_ALWAYS_INLINE inline #elif defined(__CUDACC__) #define CCM_CUDA_ALWAYS_INLINE __forceinline__ @@ -34,7 +34,7 @@ #define CCM_CUDA_ALWAYS_INLINE inline #endif -#if defined(CCM_CONFIG_NO_FORCED_INLINE) +#ifdef CCM_CONFIG_NO_FORCED_INLINE #define CCM_HIP_ALWAYS_INLINE inline #elif defined(__HIPCC__) #define CCM_HIP_ALWAYS_INLINE __forceinline__ diff --git a/include/ccmath/internal/predef/attributes/gsl_suppress.hpp b/include/ccmath/internal/predef/attributes/gsl_suppress.hpp index 5be823ea..ff02957b 100644 --- a/include/ccmath/internal/predef/attributes/gsl_suppress.hpp +++ b/include/ccmath/internal/predef/attributes/gsl_suppress.hpp @@ -10,7 +10,7 @@ #pragma once -#if defined(__clang__) +#ifdef __clang__ #define CCM_SUPPRESS(x) [[gsl::suppress("x")]] #else #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) diff --git a/include/ccmath/internal/predef/attributes/optnone.hpp b/include/ccmath/internal/predef/attributes/optnone.hpp index 6c0b4d73..a54aa2c5 100644 --- a/include/ccmath/internal/predef/attributes/optnone.hpp +++ b/include/ccmath/internal/predef/attributes/optnone.hpp @@ -17,7 +17,7 @@ #undef CCM_ATTR_OPTNONE #endif // CCM_ATTR_OPTNONE -#if defined(__has_attribute) +#ifdef __has_attribute #if __has_attribute(optnone) #define CCM_ATTR_OPTNONE __attribute__((optnone)) #else diff --git a/include/ccmath/internal/predef/compiler_suppression/clang_compiler_suppression.hpp b/include/ccmath/internal/predef/compiler_suppression/clang_compiler_suppression.hpp index 86beadbd..058aef22 100644 --- a/include/ccmath/internal/predef/compiler_suppression/clang_compiler_suppression.hpp +++ b/include/ccmath/internal/predef/compiler_suppression/clang_compiler_suppression.hpp @@ -27,7 +27,7 @@ /// CCM_RESTORE_CLANG_WARNING() /// #ifndef CCM_DISABLE_CLANG_WARNING - #if defined(__clang__) + #ifdef __clang__ // Disable clang format so it does not format "-Wunknown-warning-option" to "- Wunknown - Wwarning - Woption" // clang-format off #define CCM_CLANG_WHELP0(x) #x // Helper macros - do not use directly @@ -60,7 +60,7 @@ /// CCM_RESTORE_CLANG_WARNING() /// #ifndef CCM_RESTORE_CLANG_WARNING - #if defined(__clang__) + #ifdef __clang__ // Restore a warning for Clang in a stack-based manner. // Must be called after CCM_DISABLE_CLANG_WARNING(w). #define CCM_RESTORE_CLANG_WARNING() _Pragma("clang diagnostic pop") @@ -85,7 +85,7 @@ /// CCM_DISABLE_CLANG_WARNING_AS_ERROR() /// #ifndef CCM_ENABLE_CLANG_WARNING_AS_ERROR - #if defined(__clang__) + #ifdef __clang__ // Helper macros #define CCM_CLANG_WERROR_HELP0(x) #x // Helper macros - do not use directly #define CCM_CLANG_WERROR_HELP1(x) CCM_CLANG_WERROR_HELP0(clang diagnostic error x) // Helper macros - do not use directly @@ -114,7 +114,7 @@ /// CCM_DISABLE_CLANG_WARNING_AS_ERROR() /// #ifndef CCM_DISABLE_CLANG_WARNING_AS_ERROR - #if defined(__clang__) + #ifdef __clang__ // This will disable a warning as an error for clang in a stack-based manner. // Must be called after CCM_ENABLE_CLANG_WARNING_AS_ERROR(w). #define CCM_DISABLE_CLANG_WARNING_AS_ERROR() _Pragma("clang diagnostic pop") diff --git a/include/ccmath/internal/predef/has_attribute.hpp b/include/ccmath/internal/predef/has_attribute.hpp index c372d355..5d167296 100644 --- a/include/ccmath/internal/predef/has_attribute.hpp +++ b/include/ccmath/internal/predef/has_attribute.hpp @@ -48,11 +48,11 @@ /// \return True if the compiler has the attribute, false otherwise. /// #ifndef CCM_HAS_DECLSPEC_ATTR - #if defined(_MSC_VER) + #ifdef _MSC_VER // MSVC will always return true for this macro. #define CCM_HAS_DECLSPEC_ATTR(x) 1 #elif defined(__clang__) - #if defined(__has_declspec_attribute) + #ifdef __has_declspec_attribute // This will check if the compiler has access to a specific declspec attribute. Only works for Clang based compilers. #define CCM_HAS_DECLSPEC_ATTR(x) __has_declspec_attribute(x) #else diff --git a/include/ccmath/internal/predef/has_builtin.hpp b/include/ccmath/internal/predef/has_builtin.hpp index e62e0695..8612e871 100644 --- a/include/ccmath/internal/predef/has_builtin.hpp +++ b/include/ccmath/internal/predef/has_builtin.hpp @@ -14,7 +14,7 @@ #undef CCM_HAS_BUILTIN #endif // CCM_HAS_BUILTIN -#if defined(__has_builtin) +#ifdef __has_builtin #define CCM_HAS_BUILTIN(builtin) __has_builtin(builtin) #else #define CCM_HAS_BUILTIN(builtin) (0) diff --git a/include/ccmath/internal/predef/has_const_builtin.hpp b/include/ccmath/internal/predef/has_const_builtin.hpp index 50beb2bc..3a36f11a 100644 --- a/include/ccmath/internal/predef/has_const_builtin.hpp +++ b/include/ccmath/internal/predef/has_const_builtin.hpp @@ -12,7 +12,7 @@ // This is a clang extension that allows for checking if a builtin is available at compile-time. #ifndef CCM_HAS_CONST_BUILTIN - #if defined(__has_constexpr_builtin) + #ifdef __has_constexpr_builtin #define CCM_HAS_CONST_BUILTIN(BUILTIN) __has_constexpr_builtin(BUILTIN) #else #define CCM_HAS_CONST_BUILTIN(BUILTIN) 0 diff --git a/include/ccmath/internal/predef/versioning/gcc_version.hpp b/include/ccmath/internal/predef/versioning/gcc_version.hpp index 0a818e81..78dc6da7 100644 --- a/include/ccmath/internal/predef/versioning/gcc_version.hpp +++ b/include/ccmath/internal/predef/versioning/gcc_version.hpp @@ -12,7 +12,7 @@ #include "ccmath/internal/predef/versioning/version_encode.hpp" -#if defined(CCM_GCC_VERSION) +#ifdef CCM_GCC_VERSION #undef CCM_GCC_VERSION #endif #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) @@ -21,10 +21,10 @@ #define CCM_GCC_VERSION CCM_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) #endif -#if defined(CCM_GCC_VERSION_CHECK) +#ifdef CCM_GCC_VERSION_CHECK #undef CCM_GCC_VERSION_CHECK #endif -#if defined(CCM_GCC_VERSION) +#ifdef CCM_GCC_VERSION #define CCM_GCC_VERSION_CHECK(major, minor, patch) (CCM_GCC_VERSION >= CCM_VERSION_ENCODE(major, minor, patch)) #else #define CCM_GCC_VERSION_CHECK(major, minor, patch) (0) diff --git a/include/ccmath/internal/predef/versioning/version_encode.hpp b/include/ccmath/internal/predef/versioning/version_encode.hpp index 78d7cd49..65948638 100644 --- a/include/ccmath/internal/predef/versioning/version_encode.hpp +++ b/include/ccmath/internal/predef/versioning/version_encode.hpp @@ -10,7 +10,7 @@ #pragma once -#if defined(CCM_VERSION_ENCODE) +#ifdef CCM_VERSION_ENCODE #undef CCM_VERSION_ENCODE #endif #define CCM_VERSION_ENCODE(major, minor, revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) diff --git a/include/ccmath/internal/support/bits.hpp b/include/ccmath/internal/support/bits.hpp index 118969c3..fa268eaf 100644 --- a/include/ccmath/internal/support/bits.hpp +++ b/include/ccmath/internal/support/bits.hpp @@ -44,8 +44,8 @@ namespace ccm::support { // Rebuilding a long double from its integer storage is safe in a constant expression: every // bit of the result is sourced from the determinate integer, so the intrinsic handles it. - if constexpr (std::is_same_v) { return __builtin_bit_cast(To, from); } -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) + if constexpr (std::is_same_v) { return __builtin_bit_cast(To, from); } // NOLINT(bugprone-branch-clone) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 else { // The other direction cannot go through a single __builtin_bit_cast. An x87 80-bit long @@ -79,7 +79,7 @@ namespace ccm::support To> bit_cast(const From & from) { -#if defined(__clang__) +#ifdef __clang__ if constexpr (sizeof(long double) != sizeof(double)) { if constexpr ((std::is_same_v || std::is_same_v) && sizeof(To) == sizeof(From)) diff --git a/include/ccmath/internal/support/ctz.hpp b/include/ccmath/internal/support/ctz.hpp index a4b21431..20001a96 100644 --- a/include/ccmath/internal/support/ctz.hpp +++ b/include/ccmath/internal/support/ctz.hpp @@ -48,7 +48,7 @@ namespace ccm::support constexpr int ctz(T value) noexcept { #if CCM_HAS_BUILTIN(__builtin_ctzg) - return __builtin_ctzg(value); + return __builtin_ctzg(value); // NOLINT(cppcoreguidelines-pro-type-vararg) #else return internal::generic_ctz(value); #endif diff --git a/include/ccmath/internal/support/fenv/fenv_support.hpp b/include/ccmath/internal/support/fenv/fenv_support.hpp index 6f77cecb..490906f4 100644 --- a/include/ccmath/internal/support/fenv/fenv_support.hpp +++ b/include/ccmath/internal/support/fenv/fenv_support.hpp @@ -47,7 +47,7 @@ namespace ccm::support::fenv::detail *loc = err; #endif #if defined(__GNUC__) || defined(__clang__) - __asm__ __volatile__("" : : "r"(loc) : "memory"); + __asm__ __volatile__("" : : "r"(loc) : "memory"); // NOLINT(hicpp-no-assembler) #endif #endif } diff --git a/include/ccmath/internal/support/floating_point_traits.hpp b/include/ccmath/internal/support/floating_point_traits.hpp index a4b80bb8..0942abb9 100644 --- a/include/ccmath/internal/support/floating_point_traits.hpp +++ b/include/ccmath/internal/support/floating_point_traits.hpp @@ -56,7 +56,7 @@ namespace ccm::support template <> struct floating_point_traits { -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT128) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT128 using upgraded_floating_type = long double; #elif defined(CCM_TYPES_HAS_FLOAT128) using upgraded_floating_type = ccm::types::float128; @@ -92,7 +92,7 @@ namespace ccm::support static constexpr double max_safe_integer = 0x1p+53; // 9007199254740992.0 (2^53) }; -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT128) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT128 template <> struct floating_point_traits { diff --git a/include/ccmath/internal/support/fp/bit_mask_traits.hpp b/include/ccmath/internal/support/fp/bit_mask_traits.hpp index 9e5831c4..744d33cd 100644 --- a/include/ccmath/internal/support/fp/bit_mask_traits.hpp +++ b/include/ccmath/internal/support/fp/bit_mask_traits.hpp @@ -85,7 +85,7 @@ namespace ccm::support // TODO: Implement long double bit masks for both extended and quad precision. -#if defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT128) +#ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT128 // Values from this table: // https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format/ diff --git a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp index 8f867ed2..740dc0b4 100644 --- a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp +++ b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp @@ -44,12 +44,12 @@ namespace ccm::support::fp using FPBits_t = ccm::support::fp::FPBits; using Storage_t = typename FPBits_t::storage_type; - FPBits_t bits(val); + FPBits_t const bits(val); if (bits.is_inf_or_nan() || bits.is_zero()) { return val; } - bool is_neg = bits.is_neg(); - int exponent = bits.get_exponent(); + bool const is_neg = bits.is_neg(); + int const exponent = bits.get_exponent(); // If our exponent is greater than the most negative possible mantissa, then x is in fact already an integral. if (exponent >= static_cast(FPBits_t::fraction_length)) { return val; } @@ -90,12 +90,12 @@ namespace ccm::support::fp if (truncated_value == val) { return val; } // Bits below the rounding point and the half-way bit for tie handling. - Storage_t trimmed_value = bits.get_mantissa() & ((Storage_t(1) << trimming_length) - 1); - Storage_t half_value = Storage_t(1) << (trimming_length - 1); + Storage_t const trimmed_value = bits.get_mantissa() & ((Storage_t(1) << trimming_length) - 1); + Storage_t const half_value = Storage_t(1) << (trimming_length - 1); // If the event that the exponent is 0, the trimmed_length variable will be equal to the width of the mantissa and truncated_value_is_odd // will not be the correct value. In such instances, we handle this special case below inside the switch statement. - Storage_t truncated_value_is_odd = new_bits.get_mantissa() & (Storage_t(1) << trimming_length); + Storage_t const truncated_value_is_odd = new_bits.get_mantissa() & (Storage_t(1) << trimming_length); switch (provided_round_mode) { diff --git a/include/ccmath/internal/support/fp/fma.hpp b/include/ccmath/internal/support/fp/fma.hpp index 429a3d83..ebef73db 100644 --- a/include/ccmath/internal/support/fp/fma.hpp +++ b/include/ccmath/internal/support/fp/fma.hpp @@ -164,7 +164,7 @@ namespace ccm::support::fp // When the addend is a quiet NaN, glibc std::fma does not raise FE_INVALID for an // inf*0 product. Apple libm does. Match the platform libm observable behavior. -#if defined(__APPLE__) +#ifdef __APPLE__ if (CCM_UNLIKELY((x_bits.is_zero() && y_bits.is_inf()) || (x_bits.is_inf() && y_bits.is_zero()))) #else if (CCM_UNLIKELY(!z_bits.is_quiet_nan() && ((x_bits.is_zero() && y_bits.is_inf()) || (x_bits.is_inf() && y_bits.is_zero())))) @@ -363,9 +363,9 @@ namespace ccm::support::fp using AccInt = typename Traits::acc_int; using Sign = types::Sign; - FPBits x_bits(x); - FPBits y_bits(y); - FPBits z_bits(z); + FPBits const x_bits(x); + FPBits const y_bits(y); + FPBits const z_bits(z); T special_result{}; if (special_case_fma(x_bits, y_bits, z_bits, special_result)) { return special_result; } @@ -510,7 +510,7 @@ namespace ccm::support::fp return FPBits::quiet_nan().get_val(); } - if (CCM_UNLIKELY(x == T(0) || y == T(0) || z == T(0))) { return x * y + z; } + if (CCM_UNLIKELY(x == T(0) || y == T(0) || z == T(0))) { return (x * y) + z; } int x_exp = 0; int y_exp = 0; @@ -549,7 +549,7 @@ namespace ccm::support::fp if (CCM_UNLIKELY(x_exp == FPBits::max_biased_exponent || y_exp == FPBits::max_biased_exponent || z_exp == FPBits::max_biased_exponent)) { if (CCM_UNLIKELY(x_exp != FPBits::max_biased_exponent && y_exp != FPBits::max_biased_exponent && z_bits.is_inf())) { return z; } - return x * y + z; + return (x * y) + z; } const StorageType x_mant = x_bits.get_explicit_mantissa(); @@ -557,13 +557,13 @@ namespace ccm::support::fp TmpResultType z_mant = TmpResultType(z_bits.get_explicit_mantissa()); TmpResultType prod_mant = TmpResultType(x_mant) * TmpResultType(y_mant); - int prod_lsb_exp = x_exp + y_exp - (FPBits::exponent_bias + 2 * static_cast(FPBits::fraction_length)); + int prod_lsb_exp = x_exp + y_exp - (FPBits::exponent_bias + (2 * static_cast(FPBits::fraction_length))); constexpr int RESULT_MIN_LEN = static_cast(PROD_LEN) - static_cast(FPBits::fraction_length); z_mant <<= static_cast(RESULT_MIN_LEN); - int z_lsb_exp = z_exp - (static_cast(FPBits::fraction_length) + RESULT_MIN_LEN); - bool sticky_bits = false; - bool z_shifted = false; + int const z_lsb_exp = z_exp - (static_cast(FPBits::fraction_length) + RESULT_MIN_LEN); + bool sticky_bits = false; + bool z_shifted = false; if (prod_lsb_exp < z_lsb_exp) { @@ -640,7 +640,7 @@ namespace ccm::support::fp template > struct dispatch_fma_constexpr_builtin { - static constexpr T tonearest(T, T, T) { return T{}; } + static constexpr T tonearest(T /*unused*/, T /*unused*/, T /*unused*/) { return T{}; } }; template diff --git a/include/ccmath/internal/support/fp/fp_bits.hpp b/include/ccmath/internal/support/fp/fp_bits.hpp index eb9278de..e705d479 100644 --- a/include/ccmath/internal/support/fp/fp_bits.hpp +++ b/include/ccmath/internal/support/fp/fp_bits.hpp @@ -818,7 +818,7 @@ namespace ccm::support::fp constexpr FPBits() = default; template - inline constexpr explicit FPBits(XType x) + constexpr explicit FPBits(XType x) { using UnQual = typename std::remove_cv_t; if constexpr (std::is_same_v) { BASE::bits = support::bit_cast(x); } diff --git a/include/ccmath/internal/support/helpers/internal_ldexp.hpp b/include/ccmath/internal/support/helpers/internal_ldexp.hpp index 018eeb55..d79657fa 100644 --- a/include/ccmath/internal/support/helpers/internal_ldexp.hpp +++ b/include/ccmath/internal/support/helpers/internal_ldexp.hpp @@ -28,7 +28,7 @@ namespace ccm::support::helpers template constexpr std::enable_if_t && std::is_integral_v, T> internal_ldexp(T x, U exp) { - fp::FPBits bits(x); + fp::FPBits const bits(x); if (CCM_UNLIKELY((exp == 0) || bits.is_zero() || bits.is_inf_or_nan())) { return x; } constexpr int exponent_limit = fp::FPBits::max_biased_exponent + fp::FPBits::fraction_length + 1; @@ -38,7 +38,7 @@ namespace ccm::support::helpers if (CCM_UNLIKELY(exp > exponent_limit)) { int const rounding_mode = fenv::get_rounding_mode(); - types::Sign sign = bits.sign(); + types::Sign const sign = bits.sign(); if ((sign == types::Sign::POS && rounding_mode == FE_DOWNWARD) || (sign == types::Sign::NEG && rounding_mode == FE_UPWARD) || (rounding_mode == FE_TOWARDZERO)) @@ -55,7 +55,7 @@ namespace ccm::support::helpers if (CCM_UNLIKELY(exp < -exponent_limit)) { int const rounding_mode = fenv::get_rounding_mode(); - types::Sign sign = bits.sign(); + types::Sign const sign = bits.sign(); if ((sign == types::Sign::POS && rounding_mode == FE_UPWARD) || (sign == types::Sign::NEG && rounding_mode == FE_DOWNWARD)) { diff --git a/include/ccmath/internal/support/integer_literals.hpp b/include/ccmath/internal/support/integer_literals.hpp index 441ec8c9..eb95e4de 100644 --- a/include/ccmath/internal/support/integer_literals.hpp +++ b/include/ccmath/internal/support/integer_literals.hpp @@ -179,10 +179,16 @@ namespace ccm::support { using P = Parser; - if (view.size() >= 2 && view[0] == '0' && view[1] == 'b') { return P::template parse<2>(view.substr(2).data()); } - if (view.size() >= 2 && view[0] == '0' && view[1] == 'x') { return P::template parse<16>(view.substr(2).data()); } + if (view.size() >= 2 && view[0] == '0' && view[1] == 'b') + { + return P::template parse<2>(view.substr(2).data()); // NOLINT(bugprone-suspicious-stringview-data-usage) + } + if (view.size() >= 2 && view[0] == '0' && view[1] == 'x') + { + return P::template parse<16>(view.substr(2).data()); // NOLINT(bugprone-suspicious-stringview-data-usage) + } - return P::template parse<10>(view.data()); + return P::template parse<10>(view.data()); // NOLINT(bugprone-suspicious-stringview-data-usage) } template diff --git a/include/ccmath/internal/support/is_constant_evaluated.hpp b/include/ccmath/internal/support/is_constant_evaluated.hpp index ac0b1b3f..39d24086 100644 --- a/include/ccmath/internal/support/is_constant_evaluated.hpp +++ b/include/ccmath/internal/support/is_constant_evaluated.hpp @@ -30,7 +30,7 @@ namespace ccm::support { constexpr bool is_constant_evaluated() noexcept { -#if defined(CCMATH_HAS_BUILTIN_IS_CONSTANT_EVALUATED) +#ifdef CCMATH_HAS_BUILTIN_IS_CONSTANT_EVALUATED return __builtin_is_constant_evaluated(); #else return false; diff --git a/include/ccmath/internal/support/math_support.hpp b/include/ccmath/internal/support/math_support.hpp index a018976a..2caf7147 100644 --- a/include/ccmath/internal/support/math_support.hpp +++ b/include/ccmath/internal/support/math_support.hpp @@ -121,7 +121,7 @@ namespace ccm::support // The carry out is stored in carry_out when provided, dropped otherwise. // We keep the pass by pointer interface for consistency with the intrinsic. template - [[nodiscard]] inline constexpr std::enable_if_t, T> sub_with_borrow(T a, T b, T carry_in, T & carry_out) + [[nodiscard]] constexpr std::enable_if_t, T> sub_with_borrow(T a, T b, T carry_in, T & carry_out) { if (!is_constant_evaluated()) { diff --git a/include/ccmath/internal/support/type_traits.hpp b/include/ccmath/internal/support/type_traits.hpp index b564b81c..3354788e 100644 --- a/include/ccmath/internal/support/type_traits.hpp +++ b/include/ccmath/internal/support/type_traits.hpp @@ -132,7 +132,7 @@ namespace ccm::support::traits template struct ccm_is_unsigned : std::bool_constant<(ccm_is_arithmetic_v && (T(-1) > T(0)))> { - constexpr operator bool() const { return ccm_is_unsigned::value; } + constexpr operator bool() const { return ccm_is_unsigned::value; } // NOLINT(google-explicit-constructor) constexpr bool operator()() const { return ccm_is_unsigned::value; } }; template diff --git a/include/ccmath/internal/types/big_int.hpp b/include/ccmath/internal/types/big_int.hpp index f42f5a07..c475c1b9 100644 --- a/include/ccmath/internal/types/big_int.hpp +++ b/include/ccmath/internal/types/big_int.hpp @@ -434,7 +434,7 @@ namespace ccm::types for (std::size_t i = 0; i < N; ++i) { carry += mul_add_with_carry(acc, lhs[i], rhs[N - 1 - i]); } // Accumulate and propagate carry for the remaining elements. - for (std::size_t i = N; i < 2 * N - 1; ++i) + for (std::size_t i = N; i < (2 * N) - 1; ++i) { acc.advance(carry); carry = 0; @@ -1091,7 +1091,7 @@ namespace ccm::types // This uses the lower half (WORD_SIZE / 2) bits of the current WORD_SIZE-bit chunk. rem <<= HALF_WORD_SIZE; rem += val[pos] & HALF_MASK; - quotient.val[q_pos - 1] = (q_tmp << HALF_WORD_SIZE) + rem / x_word; + quotient.val[q_pos - 1] = (q_tmp << HALF_WORD_SIZE) + (rem / x_word); rem %= x_word; } @@ -1475,7 +1475,7 @@ namespace std static constexpr ccm::types::BigInt max() { return ccm::types::BigInt::max(); } static constexpr ccm::types::BigInt min() { return ccm::types::BigInt::min(); } - static constexpr int digits = static_cast(Bits - Signed); + static constexpr int digits = static_cast(Bits - static_cast(Signed)); }; } // namespace std diff --git a/include/ccmath/internal/types/double_double.hpp b/include/ccmath/internal/types/double_double.hpp index 1bf64950..efc67319 100644 --- a/include/ccmath/internal/types/double_double.hpp +++ b/include/ccmath/internal/types/double_double.hpp @@ -25,7 +25,7 @@ namespace ccm // True fused multiply-add for the error-free transforms below. This path stays on the quiet // software fallback when a builtin is unavailable so the residual remains exact without // raising spurious public FE_INEXACT / range exceptions. - constexpr double exact_fma(double x, double y, double z) noexcept + constexpr double exact_fma(double x, double y, double z) noexcept // NOLINT(bugprone-exception-escape) { if (support::is_constant_evaluated()) { return support::fp::generic_fma(x, y, z); } #if CCM_HAS_BUILTIN(__builtin_fma) @@ -83,10 +83,10 @@ namespace ccm const DoubleDouble as = split(a); const DoubleDouble bs = split(b); r.hi = a * b; - const double t1 = as.hi * bs.hi - r.hi; - const double t2 = as.hi * bs.lo + t1; - const double t3 = as.lo * bs.hi + t2; - r.lo = as.lo * bs.lo + t3; + const double t1 = (as.hi * bs.hi) - r.hi; + const double t2 = (as.hi * bs.lo) + t1; + const double t3 = (as.lo * bs.hi) + t2; + r.lo = (as.lo * bs.lo) + t3; return r; } diff --git a/include/ccmath/internal/types/dyadic_float.hpp b/include/ccmath/internal/types/dyadic_float.hpp index 7ceb24af..3b7d0a3a 100644 --- a/include/ccmath/internal/types/dyadic_float.hpp +++ b/include/ccmath/internal/types/dyadic_float.hpp @@ -114,7 +114,7 @@ namespace ccm::types constexpr DyadicFloat() = default; template , int> = 0> - constexpr DyadicFloat(T x) + constexpr DyadicFloat(T x) // NOLINT(google-explicit-constructor) { static_assert(support::fp::FPBits::fraction_length < Bits); support::fp::FPBits x_bits(x); diff --git a/include/ccmath/internal/types/normalized_float.hpp b/include/ccmath/internal/types/normalized_float.hpp index 1eefa109..61df02b8 100644 --- a/include/ccmath/internal/types/normalized_float.hpp +++ b/include/ccmath/internal/types/normalized_float.hpp @@ -159,7 +159,7 @@ namespace ccm::types [[nodiscard]] constexpr T convert_ieee() const { - int biased_exponent = exponent + FPBits::exponent_bias; + int const biased_exponent = exponent + FPBits::exponent_bias; constexpr int max_exponent_value = (1 << FPBits::exponent_length) - 2; if (biased_exponent > max_exponent_value) { return FPBits::inf(sign).get_val(); } diff --git a/include/ccmath/math/.clang-tidy b/include/ccmath/math/.clang-tidy new file mode 100644 index 00000000..46c666ca --- /dev/null +++ b/include/ccmath/math/.clang-tidy @@ -0,0 +1,2 @@ +--- +Checks: '-*' diff --git a/include/ccmath/math/compare/isnan.hpp b/include/ccmath/math/compare/isnan.hpp index 7533279a..885c86ee 100644 --- a/include/ccmath/math/compare/isnan.hpp +++ b/include/ccmath/math/compare/isnan.hpp @@ -24,7 +24,7 @@ namespace ccm * @return True if the number is NaN, false otherwise. */ template , bool> = true> - [[nodiscard]] constexpr bool isnan(T num) noexcept + constexpr bool isnan(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_isnan) { return ccm::builtin::isnan_ct(num); } else @@ -42,7 +42,7 @@ namespace ccm * @return False, as integers can never be NaN. */ template , bool> = true> - [[nodiscard]] constexpr bool isnan(Integer /* num */) + constexpr bool isnan(Integer /* num */) { return false; // Integers can never be NaN. } diff --git a/include/ccmath/math/compare/signbit.hpp b/include/ccmath/math/compare/signbit.hpp index 6267a9d9..db85c2e5 100644 --- a/include/ccmath/math/compare/signbit.hpp +++ b/include/ccmath/math/compare/signbit.hpp @@ -31,7 +31,7 @@ namespace ccm * to compile if you are not using the cmake build for ccmath or do not have access to __builtin_bit_cast. */ template , bool> = true> - [[nodiscard]] constexpr bool signbit(T num) noexcept + constexpr bool signbit(T num) noexcept { if constexpr (builtin::has_constexpr_signbit) { return builtin::signbit_ct(num); } else @@ -56,7 +56,7 @@ namespace ccm * @note This function is constexpr and will return the same values as std::signbit along with being static_assert-able. */ template && std::is_signed_v, bool> = true> - [[nodiscard]] constexpr bool signbit(Integer num) noexcept + constexpr bool signbit(Integer num) noexcept { // There is no concept of -0 for integers. So we can just check if the number is less than 0. return num < 0; @@ -70,7 +70,7 @@ namespace ccm * @note This function is constexpr and will return the same values as std::signbit along with being static_assert-able. */ template && !std::is_signed_v, bool> = true> - [[nodiscard]] constexpr bool signbit(Integer /* num */) noexcept + constexpr bool signbit(Integer /* num */) noexcept { // If the number is unsigned, then it can't be negative. return false; diff --git a/tools/apply_cast_style.sh b/tools/apply_cast_style.sh new file mode 100755 index 00000000..30f2ccb8 --- /dev/null +++ b/tools/apply_cast_style.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Auto-fix C-style and concrete casts under include/ with clang-tidy. +# Casts written as T(value) with a template parameter type are left alone. + +set -euo pipefail + +CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" +checks='-*,google-readability-casting,cppcoreguidelines-pro-type-cstyle-cast' +compile_args=(-std=c++17 -Iinclude -xc++ -Wno-pragma-once-outside-header) + +if ! find include -name '*.hpp' -print -quit | grep -q .; then + echo "No header files found under include/." + exit 0 +fi + +while IFS= read -r -d '' file; do + # Skip headers that do not parse on their own. + if "${CLANG_TIDY}" "${file}" --quiet --checks="${checks}" -- "${compile_args[@]}" 2>&1 \ + | grep -q 'clang-diagnostic-error'; then + continue + fi + "${CLANG_TIDY}" "${file}" --quiet --fix --checks="${checks}" -- "${compile_args[@]}" >/dev/null 2>&1 || true +done < <(find include -name '*.hpp' -print0) + +echo "Cast-style auto-fixes applied (concrete casts only)." diff --git a/tools/clang_tidy_version b/tools/clang_tidy_version new file mode 100644 index 00000000..9138b4a4 --- /dev/null +++ b/tools/clang_tidy_version @@ -0,0 +1 @@ +22.1.6 diff --git a/tools/ensure_cast_style.sh b/tools/ensure_cast_style.sh new file mode 100755 index 00000000..63bfb7ee --- /dev/null +++ b/tools/ensure_cast_style.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Copyright (c) Ian Pike +# Copyright (c) CCMath contributors +# +# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. +# See LICENSE for more information. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Check that the internal headers use no C-style casts. +# The public front-facing API under ext/ and math/ is exempt from clang-tidy. +# clang-tidy finds them. Add // NOLINT(google-readability-casting) if one is unavoidable. +# Headers that do not parse on their own are skipped. The .cpp files are checked by +# ensure_cast_style_repo.sh. + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +expected_version="$(tr -d '[:space:]' < "${repo_root}/tools/clang_tidy_version")" +CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" + +cstyle_checks='-*,google-readability-casting,cppcoreguidelines-pro-type-cstyle-cast' +compile_args=(-std=c++17 -Iinclude -xc++ -Wno-pragma-once-outside-header) + +if ! "${CLANG_TIDY}" --version | grep -Fq "${expected_version}"; then + echo "${CLANG_TIDY} version mismatch: expected ${expected_version}" >&2 + "${CLANG_TIDY}" --version >&2 + exit 1 +fi + +if ! find include/ccmath/internal -name '*.hpp' -print -quit | grep -q .; then + echo "No header files found under include/ccmath/internal." + exit 0 +fi + +violations=0 +skipped=0 + +while IFS= read -r -d '' file; do + tidy_out="$("${CLANG_TIDY}" "${file}" --quiet --checks="${cstyle_checks}" -- "${compile_args[@]}" 2>&1 || true)" + + # Skip headers that do not parse on their own. + if printf '%s' "${tidy_out}" | grep -q 'clang-diagnostic-error'; then + echo "Skipped (does not parse standalone): ${file}" + skipped=$((skipped + 1)) + continue + fi + + if printf '%s' "${tidy_out}" | grep -q 'C-style casts are discouraged'; then + echo "C-style cast (forbidden): ${file}" + printf '%s\n' "${tidy_out}" | grep -E 'warning: C-style casts' || true + violations=$((violations + 1)) + fi +done < <(find include/ccmath/internal -name '*.hpp' -print0) + +[[ "${skipped}" -gt 0 ]] && echo && echo "Skipped ${skipped} header(s) that do not parse standalone (see above)." + +if [[ "${violations}" -gt 0 ]]; then + echo + echo "C-style casts found in ${violations} file(s)." + echo "Use static_cast/bit_cast, or // NOLINT(google-readability-casting) if absolutely required." + exit 1 +fi + +echo "Cast style is up to date." diff --git a/tools/ensure_cast_style_repo.sh b/tools/ensure_cast_style_repo.sh new file mode 100755 index 00000000..56adc58a --- /dev/null +++ b/tools/ensure_cast_style_repo.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# +# Copyright (c) Ian Pike +# Copyright (c) CCMath contributors +# +# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. +# See LICENSE for more information. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Check the whole repo for C-style casts using a compile database. +# This covers the .cpp files that only build with their real flags. +# Make the database with cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON and pass it as +# CCM_CAST_COMPILE_DB or the first argument. +# Add // NOLINT(google-readability-casting) if a cast is unavoidable. + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +expected_version="$(tr -d '[:space:]' < "${repo_root}/tools/clang_tidy_version")" +CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" +db="${CCM_CAST_COMPILE_DB:-build/compile_commands.json}" + +cstyle_checks='-*,google-readability-casting,cppcoreguidelines-pro-type-cstyle-cast' +header_filter="${repo_root}/(include|tests|fuzzing|benchmark|benchmarks|tools)/" +# The public front-facing API under ext/ and math/ is exempt from clang-tidy. +exclude_public="${repo_root}/include/ccmath/(ext|math)/" + +if [[ ! -f "${db}" ]]; then + echo "No compile database at '${db}'." >&2 + echo "Configure a build with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON and set CCM_CAST_COMPILE_DB." >&2 + exit 1 +fi + +if ! "${CLANG_TIDY}" --version | grep -Fq "${expected_version}"; then + echo "${CLANG_TIDY} version mismatch: expected ${expected_version}" >&2 + "${CLANG_TIDY}" --version >&2 + exit 1 +fi + +db_dir="$(cd "$(dirname "${db}")" && pwd)" + +violations=0 + +while IFS= read -r -d '' tu; do + out="$("${CLANG_TIDY}" -p "${db_dir}" "${tu}" --quiet \ + --checks="${cstyle_checks}" --header-filter="${header_filter}" \ + --exclude-header-filter="${exclude_public}" 2>&1 || true)" + if printf '%s' "${out}" | grep -q 'C-style casts are discouraged'; then + echo "C-style cast (forbidden): ${tu}" + printf '%s\n' "${out}" | grep -E 'warning: C-style casts' || true + violations=$((violations + 1)) + fi +done < <(python3 - "${db}" "${repo_root}" <<'PY' +import json, os, sys +db_path, root = sys.argv[1], os.path.realpath(sys.argv[2]) +with open(db_path) as f: + entries = json.load(f) +seen = set() +for e in entries: + p = os.path.realpath(os.path.join(e.get("directory", ""), e["file"])) + if p.startswith(root + os.sep) and p not in seen: + seen.add(p) + sys.stdout.write(p + "\0") +PY +) + +if [[ "${violations}" -gt 0 ]]; then + echo + echo "C-style casts found in ${violations} translation unit(s)." + echo "Use static_cast/bit_cast, or // NOLINT(google-readability-casting) if absolutely required." + exit 1 +fi + +echo "No C-style casts in the compiled translation units." diff --git a/tools/install_clang_tidy_linux.sh b/tools/install_clang_tidy_linux.sh new file mode 100755 index 00000000..ea41ecb2 --- /dev/null +++ b/tools/install_clang_tidy_linux.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Install the pinned clang-tidy for Linux CI. +# It ships in the same LLVM release as clang-format, so this reuses that cache. + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +version="$(tr -d '[:space:]' < "${repo_root}/tools/clang_tidy_version")" +cache_dir="${HOME}/.cache/ccmath/llvm-${version}" +archive="LLVM-${version}-Linux-X64.tar.xz" +archive_path="${cache_dir}/${archive}" +extract_dir="${cache_dir}/LLVM-${version}-Linux-X64" +url="https://github.com/llvm/llvm-project/releases/download/llvmorg-${version}/${archive}" + +mkdir -p "${cache_dir}" + +if [[ ! -x "${extract_dir}/bin/clang-tidy" ]]; then + bash "${repo_root}/tools/download_with_retry.sh" "${url}" "${archive_path}" + rm -rf "${extract_dir}" + tar -xf "${archive_path}" -C "${cache_dir}" +fi + +sudo ln -sf "${extract_dir}/bin/clang-tidy" /usr/local/bin/clang-tidy +clang-tidy --version + +if ! clang-tidy --version | grep -Fq "${version}"; then + echo "error: clang-tidy version mismatch, expected ${version}" >&2 + exit 1 +fi diff --git a/tools/run_clang_tidy.sh b/tools/run_clang_tidy.sh new file mode 100755 index 00000000..4cc5f594 --- /dev/null +++ b/tools/run_clang_tidy.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright (c) Ian Pike +# Copyright (c) CCMath contributors +# +# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. +# See LICENSE for more information. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Run clang-tidy over a compile database using the project .clang-tidy config. +# Advisory only. Prints warnings and always exits 0. + +set -euo pipefail + +db="${1:-${CCM_COMPILE_DB:-build/compile_commands.json}}" +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" +header_filter="${repo_root}/(include|tests|fuzzing|benchmark|benchmarks|tools)/" +# The public front-facing API under ext/ and math/ is exempt from clang-tidy. +exclude_public="${repo_root}/include/ccmath/(ext|math)/" + +if [[ ! -f "${db}" ]]; then + echo "No compile database at '${db}'." + exit 0 +fi + +db_dir="$(cd "$(dirname "${db}")" && pwd)" + +while IFS= read -r -d '' tu; do + "${CLANG_TIDY}" -p "${db_dir}" "${tu}" --quiet --header-filter="${header_filter}" \ + --exclude-header-filter="${exclude_public}" 2>&1 || true +done < <(python3 - "${db}" "${repo_root}" <<'PY' +import json, os, sys +db_path, root = sys.argv[1], os.path.realpath(sys.argv[2]) +seen = set() +for e in json.load(open(db_path)): + p = os.path.realpath(os.path.join(e.get("directory", ""), e["file"])) + if p.startswith(root + os.sep) and p not in seen: + seen.add(p) + sys.stdout.write(p + "\0") +PY +) + +echo "clang-tidy advisory run complete." From 109a1141b049ceb475053b1292fbf6c4755f3cc3 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 14 Jun 2026 03:22:49 -0400 Subject: [PATCH 03/52] Fix overflow in ceil_div and support negative operands --- include/ccmath/ext/ceil_div.hpp | 30 +++++++++++++++------- tests/src/ext/ceil_div_test.cpp | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/include/ccmath/ext/ceil_div.hpp b/include/ccmath/ext/ceil_div.hpp index 4ddd722b..83783c36 100644 --- a/include/ccmath/ext/ceil_div.hpp +++ b/include/ccmath/ext/ceil_div.hpp @@ -15,29 +15,41 @@ namespace ccm::ext { /** - * @brief Divide two integral values and round the result toward positive infinity. + * @brief Divide two integral values and round the quotient toward positive infinity. * - * This function is intended for non-negative integral values. + * Works for signed and unsigned types and rounds correctly for negative operands. + * The behavior is undefined if divisor is zero or if the exact quotient is not + * representable in T, for example the most negative value divided by minus one. * - * @tparam T Type of the input and output. + * @tparam T Integral type of the inputs and output. * @param value The dividend. * @param divisor The divisor. * @return The ceiling of value divided by divisor. */ template && !std::is_same_v, bool>, bool> = true> constexpr T ceil_div(T value, T divisor) noexcept - { return static_cast((value + divisor - T(1)) / divisor); } + { + // Divide first, then add the correction, so there is no pre-division addition that can overflow. + if constexpr (std::is_signed_v) + { + const bool quotient_positive = (value < 0) == (divisor < 0); + return static_cast(value / divisor + (value % divisor != 0 && quotient_positive)); + } + else + { + return static_cast(value / divisor + (value % divisor != 0)); + } + } namespace safe { /** - * @brief Safely divide two integral values and round the result toward positive infinity. - * - * If divisor is zero, this function returns 0. + * @brief Safely divide two integral values and round the quotient toward positive infinity. * - * This function is intended for non-negative integral values. + * If divisor is zero, this function returns 0. Otherwise it matches ext::ceil_div, + * which works for signed and unsigned types and rounds correctly for negative operands. * - * @tparam T Type of the input and output. + * @tparam T Integral type of the inputs and output. * @param value The dividend. * @param divisor The divisor. * @return The ceiling of value divided by divisor. diff --git a/tests/src/ext/ceil_div_test.cpp b/tests/src/ext/ceil_div_test.cpp index e09c21af..6a953cf6 100644 --- a/tests/src/ext/ceil_div_test.cpp +++ b/tests/src/ext/ceil_div_test.cpp @@ -12,6 +12,8 @@ #include +#include + TEST(CcmathExtCeilDivTest, Smoke) { static_assert(ccm::ext::ceil_div(7, 3) == 3); @@ -22,3 +24,46 @@ TEST(CcmathExtCeilDivTest, Smoke) EXPECT_EQ(ccm::ext::ceil_div(10, 4), 3); EXPECT_EQ(ccm::ext::safe::ceil_div(10, 0), 0); } + +TEST(CcmathExtCeilDivTest, ExactDivisionDoesNotRoundUp) +{ + static_assert(ccm::ext::ceil_div(6, 2) == 3); + static_assert(ccm::ext::ceil_div(0, 5) == 0); + EXPECT_EQ(ccm::ext::ceil_div(100, 10), 10); +} + +TEST(CcmathExtCeilDivTest, NegativeOperands) +{ + // Rounds toward positive infinity for every sign combination. + static_assert(ccm::ext::ceil_div(-7, 2) == -3); // ceil(-3.5) + static_assert(ccm::ext::ceil_div(7, -2) == -3); // ceil(-3.5) + static_assert(ccm::ext::ceil_div(-7, -2) == 4); // ceil(3.5) + static_assert(ccm::ext::ceil_div(-6, 2) == -3); // exact, no rounding + static_assert(ccm::ext::ceil_div(-1, 4) == 0); // ceil(-0.25) + + EXPECT_EQ(ccm::ext::ceil_div(-10, 4), -2); // ceil(-2.5) + EXPECT_EQ(ccm::ext::ceil_div(10, -4), -2); + EXPECT_EQ(ccm::ext::safe::ceil_div(-7, 2), -3); +} + +TEST(CcmathExtCeilDivTest, NoOverflowAtExtremes) +{ + // The old value + divisor - 1 form overflowed near the type maximum. + constexpr int imax = std::numeric_limits::max(); + static_assert(ccm::ext::ceil_div(imax, 1) == imax); + static_assert(ccm::ext::ceil_div(imax, 2) == imax / 2 + 1); + EXPECT_EQ(ccm::ext::ceil_div(imax, 1), imax); + + constexpr int imin = std::numeric_limits::min(); + static_assert(ccm::ext::ceil_div(imin, 1) == imin); +} + +TEST(CcmathExtCeilDivTest, Unsigned) +{ + static_assert(ccm::ext::ceil_div(5u, 2u) == 3u); + static_assert(ccm::ext::ceil_div(8u, 4u) == 2u); + + constexpr unsigned umax = std::numeric_limits::max(); + static_assert(ccm::ext::ceil_div(umax, 1u) == umax); // no overflow + EXPECT_EQ(ccm::ext::ceil_div(255u, 16u), 16u); +} From 21abafa5c28f1844f309e3d5053da23a63ca62ad Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 14 Jun 2026 03:45:13 -0400 Subject: [PATCH 04/52] Remove ext::mix in favor of ccm::lerp --- include/ccmath/ext/CMakeLists.txt | 1 - include/ccmath/ext/mix.hpp | 45 -------------------------- tests/src/ext/constexpr_smoke_test.cpp | 2 -- tests/src/ext/mix_test.cpp | 23 ------------- 4 files changed, 71 deletions(-) delete mode 100644 include/ccmath/ext/mix.hpp delete mode 100644 tests/src/ext/mix_test.cpp diff --git a/include/ccmath/ext/CMakeLists.txt b/include/ccmath/ext/CMakeLists.txt index aada5250..ba4aa4dc 100644 --- a/include/ccmath/ext/CMakeLists.txt +++ b/include/ccmath/ext/CMakeLists.txt @@ -13,7 +13,6 @@ ccm_add_headers( is_power_of_two.hpp lerp_angle.hpp lerp_smooth.hpp - mix.hpp move_towards.hpp move_towards_angle.hpp normalize.hpp diff --git a/include/ccmath/ext/mix.hpp b/include/ccmath/ext/mix.hpp deleted file mode 100644 index 580ab2ef..00000000 --- a/include/ccmath/ext/mix.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * This library is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#pragma once - -#include - -namespace ccm::ext -{ - /** - * @brief Performs a linear interpolation between x and y using a to weight between them. - * @tparam T Type of the input and output. - * @param x Start of the range in which to interpolate. - * @param y End of the range in which to interpolate. - * @param a The value to use to interpolate between x and y. - * @return The interpolated value. - */ - template - constexpr T mix(T x, T y, T a) noexcept - { - // ReSharper disable once CppRedundantParentheses - return (x * (1 - a)) + (y * a); - } - - /** - * @brief Performs a linear interpolation between x and y using a to weight between them. - * @tparam TStart Type of the start of the range. - * @tparam TEnd Type of the end of the range. - * @tparam TAplha Type of the value to use to interpolate between x and y. - * @param x Start of the range in which to interpolate. - * @param y End of the range in which to interpolate. - * @param a The value to use to interpolate between x and y. - * @return The interpolated value. - */ - template - constexpr std::common_type_t mix(TStart x, TEnd y, TAplha a) noexcept - { return (x * (1 - a)) + (y * a); } -} // namespace ccm::ext diff --git a/tests/src/ext/constexpr_smoke_test.cpp b/tests/src/ext/constexpr_smoke_test.cpp index 7bd17d48..9812df2c 100644 --- a/tests/src/ext/constexpr_smoke_test.cpp +++ b/tests/src/ext/constexpr_smoke_test.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -61,7 +60,6 @@ TEST(CcmathExtConstexprSmokeTest, AllFunctions) static_assert(ccm::ext::ispow2(16)); static_assert(ccm::ext::lerp_angle(0.0, 90.0, 0.5) == 45.0); static_assert(ccm::ext::lerp_smooth(0.0, 10.0, 0.0, 1.0) == 0.0); - static_assert(ccm::ext::mix(0.0, 10.0, 0.5) == 5.0); static_assert(ccm::ext::move_towards(0.0, 10.0, 5.0) == 5.0); static_assert(ccm::ext::move_towards_angle(0.0, 90.0, 45.0) == 45.0); static_assert(ccm::ext::normalize(5.0, 0.0, 10.0) == 0.5); diff --git a/tests/src/ext/mix_test.cpp b/tests/src/ext/mix_test.cpp deleted file mode 100644 index d97d8673..00000000 --- a/tests/src/ext/mix_test.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#include - -#include - -TEST(CcmathExtMixTest, Smoke) -{ - static_assert(ccm::ext::mix(0.0, 10.0, 0.25) == 2.5); - static_assert(ccm::ext::mix(0.0, 10.0, 0.0) == 0.0); - static_assert(ccm::ext::mix(0.0, 10.0, 1.0) == 10.0); - - EXPECT_DOUBLE_EQ(ccm::ext::mix(1.0, 3.0, 0.5), 2.0); - EXPECT_DOUBLE_EQ(ccm::ext::mix(0, 100, 0.25), 25.0); -} From 2c78a70b83f15dcaa431443645736139ba4929b4 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 14 Jun 2026 03:58:59 -0400 Subject: [PATCH 05/52] Remove ext::chgsign in favor of ccm::copysign --- include/ccmath/ext/CMakeLists.txt | 1 - include/ccmath/ext/chgsign.hpp | 39 -------------------------- tests/src/ext/chgsign_test.cpp | 26 ----------------- tests/src/ext/constexpr_smoke_test.cpp | 2 -- 4 files changed, 68 deletions(-) delete mode 100644 include/ccmath/ext/chgsign.hpp delete mode 100644 tests/src/ext/chgsign_test.cpp diff --git a/include/ccmath/ext/CMakeLists.txt b/include/ccmath/ext/CMakeLists.txt index ba4aa4dc..d1be0f7d 100644 --- a/include/ccmath/ext/CMakeLists.txt +++ b/include/ccmath/ext/CMakeLists.txt @@ -3,7 +3,6 @@ ccm_add_headers( align.hpp approximately.hpp ceil_div.hpp - chgsign.hpp clamp.hpp cubic.hpp degrees.hpp diff --git a/include/ccmath/ext/chgsign.hpp b/include/ccmath/ext/chgsign.hpp deleted file mode 100644 index 1e1c9e68..00000000 --- a/include/ccmath/ext/chgsign.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#pragma once - -#include "ccmath/internal/support/bits.hpp" -#include "ccmath/internal/support/floating_point_traits.hpp" -#include "ccmath/math/basic/fabs.hpp" - -#include - -namespace ccm::ext -{ - /** - * @brief Returns x with the sign of y. - * - * @tparam T Floating-point type of the input and output. - * @param x Value whose magnitude is used. - * @param y Value whose sign is used. - * @return x with the sign of y. - */ - template , bool> = true> - constexpr T chgsign(T x, T y) noexcept - { - const T magnitude = ::ccm::abs(x); - const auto y_bits = ::ccm::support::bit_cast<::ccm::support::float_bits_t>(y); - - if ((y_bits & ::ccm::support::sign_mask_v) != 0) { return -magnitude; } - - return magnitude; - } -} // namespace ccm::ext \ No newline at end of file diff --git a/tests/src/ext/chgsign_test.cpp b/tests/src/ext/chgsign_test.cpp deleted file mode 100644 index e0ee00a0..00000000 --- a/tests/src/ext/chgsign_test.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#include - -#include - -TEST(CcmathExtChgsignTest, Smoke) -{ - static_assert(ccm::ext::chgsign(5.0, -1.0) == -5.0); - static_assert(ccm::ext::chgsign(-3.0, 2.0) == 3.0); - static_assert(ccm::ext::chgsign(4.0, -0.0) == -4.0); - - EXPECT_DOUBLE_EQ(ccm::ext::chgsign(5.0, -1.0), -5.0); - EXPECT_DOUBLE_EQ(ccm::ext::chgsign(-3.0, 2.0), 3.0); - EXPECT_DOUBLE_EQ(ccm::ext::chgsign(4.0, -2.0), -4.0); - EXPECT_DOUBLE_EQ(ccm::ext::chgsign(-4.0, 2.0), 4.0); - EXPECT_DOUBLE_EQ(ccm::ext::chgsign(4.0, -0.0), -4.0); -} diff --git a/tests/src/ext/constexpr_smoke_test.cpp b/tests/src/ext/constexpr_smoke_test.cpp index 9812df2c..103e026a 100644 --- a/tests/src/ext/constexpr_smoke_test.cpp +++ b/tests/src/ext/constexpr_smoke_test.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -49,7 +48,6 @@ TEST(CcmathExtConstexprSmokeTest, AllFunctions) static_assert((ccm::ext::align(9) == 16)); static_assert(ccm::ext::approximately(1.0, 1.0, 0.001)); static_assert(ccm::ext::ceil_div(7, 3) == 3); - static_assert(ccm::ext::chgsign(4.0, -1.0) == -4.0); static_assert(ccm::ext::clamp(5.0, 0.0, 10.0) == 5.0); static_assert(ccm::ext::cubic(0.0, 1.0, 2.0, 3.0, 0.5) == 1.5); static_assert(ccm::ext::degrees(ccm::numbers::pi_v) == 180.0); From 54bbcc318306ef391e570fa450cdae8e48fda637 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 14 Jun 2026 04:07:52 -0400 Subject: [PATCH 06/52] Reject zero alignment in ccm::pp::assume_aligned --- include/ccmath/internal/math/runtime/pp/assume_aligned.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp index 278f79c7..793a3573 100644 --- a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp +++ b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp @@ -14,8 +14,7 @@ namespace ccm::pp template CCM_ALWAYS_INLINE void * assume_aligned(void * ptr) { - // TODO: Decide whether the power-of-two alignment static_assert is needed. - static_assert((Alignment & (Alignment - 1)) == 0, "Alignment must be a power of 2"); + static_assert(Alignment != 0 && (Alignment & (Alignment - 1)) == 0, "Alignment must be a nonzero power of 2"); #if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)) return __builtin_assume_aligned(ptr, Alignment); #elif defined(_MSC_VER) From dcbf3e4f637b327d1090d8701323327a3e90d9aa Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 16:38:52 -0400 Subject: [PATCH 07/52] Restrict abs builtin dispatch to floating-point types --- .../internal/math/common/basic/fabs.hpp | 9 ++++++- .../math/generic/builtins/basic/fabs.hpp | 25 ++++++------------- tests/src/math/basic/abs_test.cpp | 4 +++ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/ccmath/internal/math/common/basic/fabs.hpp b/include/ccmath/internal/math/common/basic/fabs.hpp index ccd665f9..ec4ae40c 100644 --- a/include/ccmath/internal/math/common/basic/fabs.hpp +++ b/include/ccmath/internal/math/common/basic/fabs.hpp @@ -10,6 +10,7 @@ #pragma once +#include "ccmath/internal/math/generic/builtins/basic/fabs.hpp" #include "ccmath/internal/math/generic/func/basic/abs_gen.hpp" namespace ccm::func @@ -20,6 +21,12 @@ namespace ccm::func */ template constexpr auto fabs(T num) -> std::enable_if_t && (std::is_floating_point_v || std::is_signed_v || std::is_unsigned_v), T> - { return gen::abs(num); } + { + if constexpr (ccm::builtin::has_constexpr_abs) { return ccm::builtin::abs_ct(num); } + else + { + return gen::abs(num); + } + } } // namespace ccm::func diff --git a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp index e55d5251..4cf9bdad 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp @@ -10,6 +10,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "ccmath/internal/math/generic/builtins/builtin_helpers.hpp" #include "ccmath/internal/support/always_false.hpp" #include @@ -71,27 +73,26 @@ namespace ccm::builtin template inline constexpr bool has_constexpr_abs = #ifdef CCMATH_HAS_CONSTEXPR_BUILTIN_ABS - std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || ( - std::is_integral_v && std::is_signed_v) || (std::is_integral_v && std::is_unsigned_v); + is_valid_builtin_type; #else false; #endif // clang-format on - // TODO: determine actual compiler/version support for runtime __builtin_abs. + // TODO: determine actual compiler/version support for runtime __builtin_fabs. template inline constexpr bool has_runtime_abs = #ifdef CCMATH_HAS_BUILTIN_ABS - has_constexpr_abs; + is_valid_builtin_type; #else false; #endif /** * @internal - * Wrapper for constexpr __builtin_abs functions. + * Wrapper for constexpr __builtin_fabs functions. * This should be used internally and always be wrapped in an if constexpr statement. - * It exists only to allow for usage of __builtin_abs functions without triggering a compiler error + * It exists only to allow for usage of __builtin_fabs functions without triggering a compiler error * when the compiler does not support them. */ template @@ -100,12 +101,6 @@ namespace ccm::builtin if constexpr (std::is_same_v) { return __builtin_fabsf(x); } else if constexpr (std::is_same_v) { return __builtin_fabs(x); } else if constexpr (std::is_same_v) { return __builtin_fabsl(x); } - else if constexpr (std::is_same_v) { return __builtin_llabs(x); } - else if constexpr (std::is_integral_v && std::is_signed_v) { return __builtin_abs(x); } - else if constexpr (std::is_integral_v && std::is_unsigned_v) - { - return x; // Absolute value of unsigned is the value itself - } else { // This should never be reached @@ -120,12 +115,6 @@ namespace ccm::builtin if constexpr (std::is_same_v) { return __builtin_fabsf(x); } else if constexpr (std::is_same_v) { return __builtin_fabs(x); } else if constexpr (std::is_same_v) { return __builtin_fabsl(x); } - else if constexpr (std::is_same_v) { return __builtin_llabs(x); } - else if constexpr (std::is_integral_v && std::is_signed_v) { return __builtin_abs(x); } - else if constexpr (std::is_integral_v && std::is_unsigned_v) - { - return x; // Absolute value of unsigned is the value itself - } else { // This should never be reached diff --git a/tests/src/math/basic/abs_test.cpp b/tests/src/math/basic/abs_test.cpp index 04259ff6..a19c46c8 100644 --- a/tests/src/math/basic/abs_test.cpp +++ b/tests/src/math/basic/abs_test.cpp @@ -249,6 +249,10 @@ TEST(CcmathBasicTests, CcmAbsCanBeEvaluatedAtCompileTime) { constexpr auto abs{ ccm::abs(1) }; static_assert(abs == 1, "abs has failed testing that it is static_assert-able!"); + // Integer abs avoids compiler builtins, so it must stay constexpr on every compiler even + // where __builtin_abs and __builtin_llabs are not usable in constant evaluation. + static_assert(ccm::abs(-1) == 1); + static_assert(ccm::abs(-1LL) == 1LL); static_assert(ccm::fabs(-2.0) == 2.0); static_assert(ccm::fabsf(-2.0F) == 2.0F); EXPECT_EQ(ccm::fabsl(-2.0L), 2.0L); From c94a5f2f9ab16accbca8ef0ce267a88c4eb20971 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 18:37:02 -0400 Subject: [PATCH 08/52] Fix narrowing in integer abs overloads --- include/ccmath/math/basic/fabs.hpp | 28 ++++++++++++---------------- tests/src/math/basic/abs_test.cpp | 8 ++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/ccmath/math/basic/fabs.hpp b/include/ccmath/math/basic/fabs.hpp index 2e504013..c39eb7e8 100644 --- a/include/ccmath/math/basic/fabs.hpp +++ b/include/ccmath/math/basic/fabs.hpp @@ -42,30 +42,26 @@ namespace ccm template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> { - // If num is less than zero, return -num, otherwise return num. - return num < 0 ? -num : num; + // If num is less than zero, return -num, otherwise return num. The explicit cast keeps the + // result in T, where -num integer-promotes to int and would otherwise narrow on return for + // types narrower than int such as signed char and short. + return static_cast(num < 0 ? -num : num); } /** - * @brief Computes the absolute value of a number. - * @tparam T Unsigned numeric type. - * @param num Floating-point or integer value. - * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. + * @brief Computes the absolute value of an unsigned value. + * @tparam T Unsigned integer type. + * @param num Unsigned value. + * @return num unchanged. An unsigned value is already non-negative, so its absolute value is itself. * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto abs(T num) -> std::enable_if_t, T> { - // If abs is called with an argument of type X for which is_unsigned_v is true, and - // if X cannot be converted to int by integral promotion, the program is ill-formed. - // See: http://eel.is/c++draft/c.math.abs#3 - // See: ISO/IEC 9899:2018, 7.12.7.2, 7.22.6.1 - if constexpr (std::is_convertible_v) { return ccm::abs(static_cast(num)); } - else - { - static_assert(sizeof(T) == 0, "Taking the absolute value of an unsigned type that cannot be converted to int by integral promotion is ill-formed."); - return static_cast(0); - } + // An unsigned value is already non-negative, so the absolute value is the value itself. + // Returning num directly is lossless and type-preserving, where routing through int would + // narrow wide unsigned values and trip -Wsign-conversion. + return num; } /** diff --git a/tests/src/math/basic/abs_test.cpp b/tests/src/math/basic/abs_test.cpp index a19c46c8..3f4e0024 100644 --- a/tests/src/math/basic/abs_test.cpp +++ b/tests/src/math/basic/abs_test.cpp @@ -253,6 +253,14 @@ TEST(CcmathBasicTests, CcmAbsCanBeEvaluatedAtCompileTime) // where __builtin_abs and __builtin_llabs are not usable in constant evaluation. static_assert(ccm::abs(-1) == 1); static_assert(ccm::abs(-1LL) == 1LL); + // Unsigned abs is the identity and must be lossless for wide types, where the previous int + // round-trip narrowed and tripped -Wsign-conversion. + static_assert(ccm::abs(5U) == 5U); + static_assert(ccm::abs(0U) == 0U); + static_assert(ccm::abs(std::numeric_limits::max()) == std::numeric_limits::max()); + // Narrow signed types must not narrow on the int-promoted negation under -Wconversion. + static_assert(ccm::abs(static_cast(-5)) == 5); + static_assert(ccm::abs(static_cast(-5)) == 5); static_assert(ccm::fabs(-2.0) == 2.0); static_assert(ccm::fabsf(-2.0F) == 2.0F); EXPECT_EQ(ccm::fabsl(-2.0L), 2.0L); From 219721bcb1ff34cb6c7438010975269049b1afd9 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 18:56:42 -0400 Subject: [PATCH 09/52] Cast simd pack width to size_t for array storage --- include/ccmath/internal/math/runtime/simd/pack.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ccmath/internal/math/runtime/simd/pack.hpp b/include/ccmath/internal/math/runtime/simd/pack.hpp index 74b32d57..c49c7e72 100644 --- a/include/ccmath/internal/math/runtime/simd/pack.hpp +++ b/include/ccmath/internal/math/runtime/simd/pack.hpp @@ -61,7 +61,7 @@ namespace ccm::intrin } private: - std::array m_value; + std::array(N)> m_value; }; template @@ -102,7 +102,7 @@ namespace ccm::intrin } private: - std::array m_value; + std::array(N)> m_value; }; template @@ -214,7 +214,7 @@ namespace ccm::intrin } private: - std::array m_value; + std::array(N)> m_value; }; template From 37f1464e0cbbd9d39a0c9f55a2c4a9e49723d1d3 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 19:21:08 -0400 Subject: [PATCH 10/52] Enforce trailing newline at end of file --- .clang-format | 1 + include/ccmath/ext/abs_diff.hpp | 2 +- include/ccmath/ext/approximately.hpp | 2 +- include/ccmath/ext/ceil_div.hpp | 2 +- include/ccmath/ext/inverse_lerp.hpp | 2 +- include/ccmath/ext/lerp_angle.hpp | 2 +- include/ccmath/ext/move_towards.hpp | 2 +- include/ccmath/ext/move_towards_angle.hpp | 2 +- include/ccmath/ext/normalize.hpp | 2 +- include/ccmath/ext/remap.hpp | 2 +- include/ccmath/ext/repeat.hpp | 2 +- include/ccmath/ext/saturate.hpp | 2 +- include/ccmath/ext/sign.hpp | 2 +- include/ccmath/ext/step.hpp | 2 +- include/ccmath/ext/unlerp.hpp | 2 +- .../internal/math/generic/func/power/pow_impl/pow_data.hpp | 2 +- .../internal/math/generic/func/power/pow_impl/powf_data.hpp | 2 +- include/ccmath/internal/math/runtime/pp/assume_aligned.hpp | 2 +- include/ccmath/internal/math/runtime/pp/may_alias.hpp | 2 +- include/ccmath/internal/support/common_math_constants.hpp | 2 +- include/ccmath/internal/support/fp/nearest_integer.hpp | 2 +- include/ccmath/internal/support/fp/ops/add.hpp | 2 +- include/ccmath/internal/support/fp/ops/add_or_sub.hpp | 2 +- include/ccmath/internal/support/fp/ops/mul.hpp | 2 +- include/ccmath/internal/support/fp/ops/sub.hpp | 2 +- tests/src/math/fmanip/ldexp_test.cpp | 2 +- 26 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.clang-format b/.clang-format index 9cbc44b6..c28a1864 100644 --- a/.clang-format +++ b/.clang-format @@ -62,6 +62,7 @@ IncludeCategories: Priority: 50 IndentPPDirectives: BeforeHash IndentWidth: 4 +InsertNewlineAtEOF: true Language: Cpp NamespaceIndentation: All PointerAlignment: Middle diff --git a/include/ccmath/ext/abs_diff.hpp b/include/ccmath/ext/abs_diff.hpp index c7f9699c..abfd66c2 100644 --- a/include/ccmath/ext/abs_diff.hpp +++ b/include/ccmath/ext/abs_diff.hpp @@ -27,4 +27,4 @@ namespace ccm::ext using U = std::make_unsigned_t; return lhs > rhs ? static_cast(static_cast(lhs) - static_cast(rhs)) : static_cast(static_cast(rhs) - static_cast(lhs)); } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/approximately.hpp b/include/ccmath/ext/approximately.hpp index 1f697b1e..e4a7f679 100644 --- a/include/ccmath/ext/approximately.hpp +++ b/include/ccmath/ext/approximately.hpp @@ -27,4 +27,4 @@ namespace ccm::ext template , bool> = true> constexpr bool approximately(T lhs, T rhs, T epsilon) noexcept { return ccm::abs(lhs - rhs) <= epsilon; } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/ceil_div.hpp b/include/ccmath/ext/ceil_div.hpp index 83783c36..bafebad8 100644 --- a/include/ccmath/ext/ceil_div.hpp +++ b/include/ccmath/ext/ceil_div.hpp @@ -62,4 +62,4 @@ namespace ccm::ext return ext::ceil_div(value, divisor); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/inverse_lerp.hpp b/include/ccmath/ext/inverse_lerp.hpp index 357b47ac..de515258 100644 --- a/include/ccmath/ext/inverse_lerp.hpp +++ b/include/ccmath/ext/inverse_lerp.hpp @@ -51,4 +51,4 @@ namespace ccm::ext constexpr T inverse_lerp(T start, T end, T value) noexcept { return ext::safe::unlerp(start, end, value); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/lerp_angle.hpp b/include/ccmath/ext/lerp_angle.hpp index e118910d..e5875295 100644 --- a/include/ccmath/ext/lerp_angle.hpp +++ b/include/ccmath/ext/lerp_angle.hpp @@ -48,4 +48,4 @@ namespace ccm::ext constexpr T lerp_angle(T start, T end, T t) noexcept { return start + (ext::safe::delta_angle(start, end) * t); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/move_towards.hpp b/include/ccmath/ext/move_towards.hpp index 456fd8f4..564a0443 100644 --- a/include/ccmath/ext/move_towards.hpp +++ b/include/ccmath/ext/move_towards.hpp @@ -33,4 +33,4 @@ namespace ccm::ext return current + ((delta > T(0)) ? max_delta : -max_delta); } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/move_towards_angle.hpp b/include/ccmath/ext/move_towards_angle.hpp index 47c025ff..f0596ec2 100644 --- a/include/ccmath/ext/move_towards_angle.hpp +++ b/include/ccmath/ext/move_towards_angle.hpp @@ -49,4 +49,4 @@ namespace ccm::ext return ext::move_towards(current, current + delta, max_delta); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/normalize.hpp b/include/ccmath/ext/normalize.hpp index d18e035b..ad320eeb 100644 --- a/include/ccmath/ext/normalize.hpp +++ b/include/ccmath/ext/normalize.hpp @@ -54,4 +54,4 @@ namespace ccm::ext return ext::clamp((value - min) / length, T(0), T(1)); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/remap.hpp b/include/ccmath/ext/remap.hpp index bf3e0dd7..ccd336cf 100644 --- a/include/ccmath/ext/remap.hpp +++ b/include/ccmath/ext/remap.hpp @@ -71,4 +71,4 @@ namespace ccm::ext return dst_start + ((x - src_start) * (dst_end - dst_start)) / length; } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/repeat.hpp b/include/ccmath/ext/repeat.hpp index 2e7ac30b..5d9e5648 100644 --- a/include/ccmath/ext/repeat.hpp +++ b/include/ccmath/ext/repeat.hpp @@ -48,4 +48,4 @@ namespace ccm::ext return ext::repeat(value, length); } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/saturate.hpp b/include/ccmath/ext/saturate.hpp index 75f725e5..d8e95630 100644 --- a/include/ccmath/ext/saturate.hpp +++ b/include/ccmath/ext/saturate.hpp @@ -25,4 +25,4 @@ namespace ccm::ext template , bool> = true> constexpr T saturate(T value) noexcept { return ext::clamp(value, T(0), T(1)); } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/sign.hpp b/include/ccmath/ext/sign.hpp index cdd7171a..10984724 100644 --- a/include/ccmath/ext/sign.hpp +++ b/include/ccmath/ext/sign.hpp @@ -23,4 +23,4 @@ namespace ccm::ext template && !std::is_same_v, bool> = true> constexpr T sign(T value) noexcept { return static_cast((T(0) < value) - (value < T(0))); } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/step.hpp b/include/ccmath/ext/step.hpp index c2b3fb2c..35e32f6a 100644 --- a/include/ccmath/ext/step.hpp +++ b/include/ccmath/ext/step.hpp @@ -24,4 +24,4 @@ namespace ccm::ext template && !std::is_same_v, bool> = true> constexpr T step(T edge, T value) noexcept { return value < edge ? T(0) : T(1); } -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/ext/unlerp.hpp b/include/ccmath/ext/unlerp.hpp index 5802f69a..27618419 100644 --- a/include/ccmath/ext/unlerp.hpp +++ b/include/ccmath/ext/unlerp.hpp @@ -57,4 +57,4 @@ namespace ccm::ext return (value - start) / length; } } // namespace safe -} // namespace ccm::ext \ No newline at end of file +} // namespace ccm::ext diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp index b9c1f59c..5dcf2d85 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp @@ -4,4 +4,4 @@ namespace ccm::gen::impl::internal::impl { -} // namespace ccm::gen::impl::internal::impl \ No newline at end of file +} // namespace ccm::gen::impl::internal::impl diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_data.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_data.hpp index 0b1a3a6e..088d9892 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_data.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_data.hpp @@ -371,4 +371,4 @@ namespace ccm::gen::impl::internal::impl DoubleDouble{ -0x1.dc930484501f8p-63, 0x1.6fdf461d2e4f8p-7 }, }; -} // namespace ccm::gen::impl::internal::impl \ No newline at end of file +} // namespace ccm::gen::impl::internal::impl diff --git a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp index 793a3573..36ae2953 100644 --- a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp +++ b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp @@ -24,4 +24,4 @@ namespace ccm::pp return ptr; #endif } -} // namespace ccm::pp \ No newline at end of file +} // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/may_alias.hpp b/include/ccmath/internal/math/runtime/pp/may_alias.hpp index 204e877d..1ef93bea 100644 --- a/include/ccmath/internal/math/runtime/pp/may_alias.hpp +++ b/include/ccmath/internal/math/runtime/pp/may_alias.hpp @@ -16,4 +16,4 @@ #else #define CCM_ATTR_MAY_ALIAS #endif -#endif \ No newline at end of file +#endif diff --git a/include/ccmath/internal/support/common_math_constants.hpp b/include/ccmath/internal/support/common_math_constants.hpp index 277473ef..cd921a2b 100644 --- a/include/ccmath/internal/support/common_math_constants.hpp +++ b/include/ccmath/internal/support/common_math_constants.hpp @@ -620,4 +620,4 @@ namespace ccm::support::constants types::TripleDouble{ -0x1.0d40cee4b81afp-112, 0x1.4a47a505b3a47p-54, 0x1.02b338c811703p0 }, types::TripleDouble{ 0x1.6ae7d36d7c1f7p-109, 0x1.e47120223467fp-54, 0x1.02be6e199c811p0 }, }; -} // namespace ccm::support::constants \ No newline at end of file +} // namespace ccm::support::constants diff --git a/include/ccmath/internal/support/fp/nearest_integer.hpp b/include/ccmath/internal/support/fp/nearest_integer.hpp index 0abbcce2..3718a657 100644 --- a/include/ccmath/internal/support/fp/nearest_integer.hpp +++ b/include/ccmath/internal/support/fp/nearest_integer.hpp @@ -67,4 +67,4 @@ namespace ccm::support::fp } return x; } -} // namespace ccm::support::fp \ No newline at end of file +} // namespace ccm::support::fp diff --git a/include/ccmath/internal/support/fp/ops/add.hpp b/include/ccmath/internal/support/fp/ops/add.hpp index 51e012ff..5852ad86 100644 --- a/include/ccmath/internal/support/fp/ops/add.hpp +++ b/include/ccmath/internal/support/fp/ops/add.hpp @@ -11,4 +11,4 @@ namespace ccm::support::fp::op constexpr std::enable_if_t && std::is_floating_point_v && sizeof(OutType) <= sizeof(InType), OutType> add(InType x, InType y) { return add_or_sub(x, y); } -} // namespace ccm::support::fp::op \ No newline at end of file +} // namespace ccm::support::fp::op diff --git a/include/ccmath/internal/support/fp/ops/add_or_sub.hpp b/include/ccmath/internal/support/fp/ops/add_or_sub.hpp index e11549aa..47350e7b 100644 --- a/include/ccmath/internal/support/fp/ops/add_or_sub.hpp +++ b/include/ccmath/internal/support/fp/ops/add_or_sub.hpp @@ -183,4 +183,4 @@ namespace ccm::support::fp::op return result.template as(); } -} // namespace ccm::support::fp::op \ No newline at end of file +} // namespace ccm::support::fp::op diff --git a/include/ccmath/internal/support/fp/ops/mul.hpp b/include/ccmath/internal/support/fp/ops/mul.hpp index aaffd9e5..10697f7f 100644 --- a/include/ccmath/internal/support/fp/ops/mul.hpp +++ b/include/ccmath/internal/support/fp/ops/mul.hpp @@ -101,4 +101,4 @@ namespace ccm::support::fp::op types::DyadicFloat(InFPBits::significand_length))> result = types::quick_mul(xd, yd); return result.template as(); } -} // namespace ccm::support::fp::op \ No newline at end of file +} // namespace ccm::support::fp::op diff --git a/include/ccmath/internal/support/fp/ops/sub.hpp b/include/ccmath/internal/support/fp/ops/sub.hpp index ab3dc555..71c25d7e 100644 --- a/include/ccmath/internal/support/fp/ops/sub.hpp +++ b/include/ccmath/internal/support/fp/ops/sub.hpp @@ -11,4 +11,4 @@ namespace ccm::support::fp::op constexpr std::enable_if_t && std::is_floating_point_v && sizeof(OutType) <= sizeof(InType), OutType> sub(InType x, InType y) { return add_or_sub(x, y); } -} // namespace ccm::support::fp::op \ No newline at end of file +} // namespace ccm::support::fp::op diff --git a/tests/src/math/fmanip/ldexp_test.cpp b/tests/src/math/fmanip/ldexp_test.cpp index 52671a82..879f4a84 100644 --- a/tests/src/math/fmanip/ldexp_test.cpp +++ b/tests/src/math/fmanip/ldexp_test.cpp @@ -83,4 +83,4 @@ TYPED_TEST(CcmathFmanipTests, LdexpSpecialValues) ccm::test::ExpectSameAsStd(ccm_nan, std_nan); } -// NOLINTEND \ No newline at end of file +// NOLINTEND From 30ba83763c3857ea6c516bc8857985fb38cb2ded Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 19:36:19 -0400 Subject: [PATCH 11/52] Extend clang-format to all sources and reformat --- .github/workflows/lint.yml | 8 +- benchmarks/shared/paths.hpp | 20 +-- benchmarks/shared/register.hpp | 153 ++++++++++-------- benchmarks/src/math/power/sqrt.bench.cpp | 11 +- examples/common/io.hpp | 4 +- examples/src/constexpr_abs.cpp | 2 +- examples/src/decibel_gain.cpp | 2 +- examples/src/fp_classes.cpp | 8 +- examples/src/hypot_distance.cpp | 4 +- examples/src/rounding_edge_cases.cpp | 5 +- examples/src/signed_magnitude.cpp | 10 +- examples/src/unit_circle.cpp | 8 +- fuzzing/shared/fuzz_calls.hpp | 2 +- fuzzing/shared/harness.hpp | 3 +- fuzzing/shared/input.hpp | 14 +- fuzzing/src/math/power_fuzz.cpp | 5 +- .../conformance/rounding_conformance_test.cpp | 6 +- .../shared/oracle/pow_adversarial_search.cpp | 2 +- .../oracle/runners/coremath_pow_runner.hpp | 47 +++--- .../shared/oracle/runners/mpfr_pow_runner.hpp | 58 +++---- .../math/basic/fma_correct_rounding_test.cpp | 32 +++- tests/src/math/expo/log10_test.cpp | 4 +- tests/src/math/expo/log_test.cpp | 5 +- .../src/math/internal/types/big_int_test.cpp | 6 +- .../src/math/power/pow_deterministic_test.cpp | 8 +- tests/src/math/power/pow_simd_test.cpp | 36 +++-- tests/src/math/power/powf_simd_test.cpp | 14 +- .../math/trig/trig_kernel_regression_test.cpp | 22 ++- tools/apply_format.sh | 18 ++- tools/ensure_format.sh | 22 ++- 30 files changed, 283 insertions(+), 256 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 042c2476..5b3b4fcb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,9 @@ on: - '.clang-format' - '.github/actions/install-clang-format/**' - '.github/workflows/lint.yml' - - 'include/**/*.hpp' + - '**/*.hpp' + - '**/*.cpp' + - '**/*.h' - 'tools/apply_format.sh' - 'tools/clang_format_version' - 'tools/ensure_format.sh' @@ -67,14 +69,14 @@ jobs: - name: Commit clang-format fixes if: steps.check_format.outcome == 'failure' run: | - if git diff --quiet -- include; then + if git diff --quiet -- '*.hpp' '*.cpp' '*.h'; then echo "No formatting changes to commit." exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add include + git add '*.hpp' '*.cpp' '*.h' git commit -m "ci: apply clang-format" git push origin HEAD:${{ github.ref_name }} diff --git a/benchmarks/shared/paths.hpp b/benchmarks/shared/paths.hpp index d213659d..4fab3397 100644 --- a/benchmarks/shared/paths.hpp +++ b/benchmarks/shared/paths.hpp @@ -4,32 +4,32 @@ // Per-target: -DCCM_BM_CONFIG_NO_CT or -DCCM_BM_CONFIG_NO_RT via CMake. #if !defined(CCM_BM_CONFIG_NO_CT) -#define CCMATH_BENCH_HAS_CT 1 + #define CCMATH_BENCH_HAS_CT 1 #else -#define CCMATH_BENCH_HAS_CT 0 + #define CCMATH_BENCH_HAS_CT 0 #endif #if !defined(CCM_BM_CONFIG_NO_RT) -#define CCMATH_BENCH_HAS_RT 1 + #define CCMATH_BENCH_HAS_RT 1 #else -#define CCMATH_BENCH_HAS_RT 0 + #define CCMATH_BENCH_HAS_RT 0 #endif // GCC libstdc++ constexpr std::sqrt makes RT-vs-std comparison meaningless on GCC 6.1+. #if CCMATH_BENCH_HAS_RT && (defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__)) -#define CCMATH_BENCH_HAS_RT_SUITE 0 + #define CCMATH_BENCH_HAS_RT_SUITE 0 #else -#define CCMATH_BENCH_HAS_RT_SUITE CCMATH_BENCH_HAS_RT + #define CCMATH_BENCH_HAS_RT_SUITE CCMATH_BENCH_HAS_RT #endif #if CCMATH_BENCH_HAS_CT -#define CCMATH_BENCH_IF_CT(...) __VA_ARGS__ + #define CCMATH_BENCH_IF_CT(...) __VA_ARGS__ #else -#define CCMATH_BENCH_IF_CT(...) + #define CCMATH_BENCH_IF_CT(...) #endif #if CCMATH_BENCH_HAS_RT_SUITE -#define CCMATH_BENCH_IF_RT(...) __VA_ARGS__ + #define CCMATH_BENCH_IF_RT(...) __VA_ARGS__ #else -#define CCMATH_BENCH_IF_RT(...) + #define CCMATH_BENCH_IF_RT(...) #endif diff --git a/benchmarks/shared/register.hpp b/benchmarks/shared/register.hpp index 279a6127..bdaff79b 100644 --- a/benchmarks/shared/register.hpp +++ b/benchmarks/shared/register.hpp @@ -9,66 +9,74 @@ #define CCMATH_BENCH_APPLY_RANGE ->RangeMultiplier(2)->Range(ccm::bench::kRangeMin, ccm::bench::kRangeMax)->Complexity() -#define CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ +#define CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::unary_scalar(state, [&](std::int64_t x) { StdBody; }); } \ + { \ + ccm::bench::unary_scalar(state, [&](std::int64_t x) { StdBody; }); \ + } \ static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::unary_scalar(state, [&](std::int64_t x) { CcmBody; }); } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + { \ + ccm::bench::unary_scalar(state, [&](std::int64_t x) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ { \ auto inputs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::unary_vector(state, [&](int x) { StdBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ { \ auto inputs = ccm::bench::random_integers(state.range(0)); \ - ccm::bench::unary_vector(state, [&](int x) { CcmBody; }, inputs); \ + ccm::bench::unary_vector(state, [&](int x) { CcmBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ { \ auto inputs = ccm::bench::random_doubles(state.range(0)); \ - ccm::bench::unary_vector(state, [&](double x) { StdBody; }, inputs); \ + ccm::bench::unary_vector(state, [&](double x) { StdBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ { \ auto inputs = ccm::bench::random_doubles(state.range(0)); \ - ccm::bench::unary_vector(state, [&](double x) { CcmBody; }, inputs); \ + ccm::bench::unary_vector(state, [&](double x) { CcmBody; }, inputs); \ } -#define CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, Suffix) \ +#define CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, Suffix) \ BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::binary_scalar(state, [&](double x, double y) { StdBody; }); } \ + { \ + ccm::bench::binary_scalar(state, [&](double x, double y) { StdBody; }); \ + } \ static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::binary_scalar(state, [&](double x, double y) { CcmBody; }); } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_integers(state.range(0)); \ - auto ys = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::binary_scalar(state, [&](double x, double y) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ ccm::bench::binary_vector(state, [&](int x, int y) { StdBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_integers(state.range(0)); \ - auto ys = ccm::bench::random_integers(state.range(0)); \ - ccm::bench::binary_vector(state, [&](int x, int y) { CcmBody; }, xs, ys); \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::binary_vector(state, [&](int x, int y) { CcmBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::binary_vector(state, [&](double x, double y) { StdBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::binary_vector(state, [&](double x, double y) { CcmBody; }, xs, ys); \ } @@ -76,40 +84,44 @@ #define CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, Suffix) \ BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { StdBody; }); } \ + { \ + ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { StdBody; }); \ + } \ static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ - { ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { CcmBody; }); } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_integers(state.range(0)); \ - auto ys = ccm::bench::random_integers(state.range(0)); \ - auto zs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + auto zs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::ternary_vector(state, [&](int x, int y, int z) { StdBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_integers(state.range(0)); \ - auto ys = ccm::bench::random_integers(state.range(0)); \ - auto zs = ccm::bench::random_integers(state.range(0)); \ - ccm::bench::ternary_vector(state, [&](int x, int y, int z) { CcmBody; }, xs, ys, zs); \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + auto zs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::ternary_vector(state, [&](int x, int y, int z) { CcmBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ auto zs = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::ternary_vector(state, [&](double x, double y, double z) { StdBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ { \ - auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ auto zs = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::ternary_vector(state, [&](double x, double y, double z) { CcmBody; }, xs, ys, zs); \ @@ -118,16 +130,20 @@ #define CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, Suffix) \ BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, Suffix, Profile, Value, StdBody, CcmBody) \ static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State& state) \ - { ccm::bench::unary_fixed(state, Value, [&](double x) { StdBody; }); } \ + { \ + ccm::bench::unary_fixed(state, Value, [&](double x) { StdBody; }); \ + } \ static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State& state) \ - { ccm::bench::unary_fixed(state, Value, [&](double x) { CcmBody; }); } + { \ + ccm::bench::unary_fixed(state, Value, [&](double x) { CcmBody; }); \ + } #define CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, Suffix, Profile) \ BENCHMARK(BM_##Module##_##Name##_std##Suffix##_##Profile); \ @@ -135,12 +151,16 @@ #define CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, Suffix, Profile, X, Y, StdBody, CcmBody) \ static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State& state) \ - { ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { StdBody; }); } \ + { \ + ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { StdBody; }); \ + } \ static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State& state) \ - { ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { CcmBody; }); } + { \ + ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { CcmBody; }); \ + } #define CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, Suffix, Profile) \ - BENCHMARK(BM_##Module##_##Name##_std##Suffix##_##Profile); \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_##Profile); \ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_##Profile); #define CCMATH_BENCH_UNARY_COMPARE(Module, Name, StdBody, CcmBody) \ @@ -150,27 +170,27 @@ #define CCMATH_BENCH_UNARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, )) \ - CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, _rt)) #define CCMATH_BENCH_BINARY_COMPARE(Module, Name, StdBody, CcmBody) \ - CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, )) #define CCMATH_BENCH_BINARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, )) \ - CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, _rt)) -#define CCMATH_BENCH_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) \ +#define CCMATH_BENCH_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, )) #define CCMATH_BENCH_TERNARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, )) \ - CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, _rt)) #define CCMATH_BENCH_UNARY_PROFILE(Module, Name, Profile, Value, StdBody, CcmBody) \ @@ -180,30 +200,27 @@ #define CCMATH_BENCH_UNARY_PROFILE_PATHS(Module, Name, Profile, Value, StdBody, CcmCtBody, CcmRtBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, , Profile, Value, StdBody, CcmCtBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, , Profile)) \ - CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, _rt, Profile, Value, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, _rt, Profile, Value, StdBody, CcmRtBody)) \ CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, _rt, Profile)) #define CCMATH_BENCH_BINARY_PROFILE(Module, Name, Profile, X, Y, StdBody, CcmBody) \ - CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, , Profile, X, Y, StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, , Profile, X, Y, StdBody, CcmBody)) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, , Profile)) #define CCMATH_BENCH_BINARY_PROFILE_PATHS(Module, Name, Profile, X, Y, StdBody, CcmCtBody, CcmRtBody) \ CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, , Profile, X, Y, StdBody, CcmCtBody)) \ - CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, , Profile)) \ - CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, _rt, Profile, X, Y, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, , Profile)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, _rt, Profile, X, Y, StdBody, CcmRtBody)) \ CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, _rt, Profile)) -#define CCMATH_BENCH_DECLARE_UNARY_COMPARE(Module, Name, StdBody, CcmBody) \ - CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmBody) +#define CCMATH_BENCH_DECLARE_UNARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmBody) #define CCMATH_BENCH_REGISTER_UNARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, ) -#define CCMATH_BENCH_DECLARE_BINARY_COMPARE(Module, Name, StdBody, CcmBody) \ - CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody) +#define CCMATH_BENCH_DECLARE_BINARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody) #define CCMATH_BENCH_REGISTER_BINARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, ) -#define CCMATH_BENCH_DECLARE_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) \ - CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmBody) +#define CCMATH_BENCH_DECLARE_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmBody) #define CCMATH_BENCH_REGISTER_TERNARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, ) diff --git a/benchmarks/src/math/power/sqrt.bench.cpp b/benchmarks/src/math/power/sqrt.bench.cpp index 1aacbf1c..45d9d101 100644 --- a/benchmarks/src/math/power/sqrt.bench.cpp +++ b/benchmarks/src/math/power/sqrt.bench.cpp @@ -10,14 +10,9 @@ namespace { return ccm::sqrt(v); } } // namespace -CCMATH_BENCH_UNARY_COMPARE_PATHS(power, sqrt, - return std::sqrt(static_cast(x)), - return ccm::sqrt(static_cast(x)), - return sqrt_rt(static_cast(x))) +CCMATH_BENCH_UNARY_COMPARE_PATHS( + power, sqrt, return std::sqrt(static_cast(x)), return ccm::sqrt(static_cast(x)), return sqrt_rt(static_cast(x))) -CCMATH_BENCH_UNARY_PROFILE_PATHS(power, sqrt, positive_finite_general, 4.0, - return std::sqrt(x), - return ccm::sqrt(x), - return sqrt_rt(x)) +CCMATH_BENCH_UNARY_PROFILE_PATHS(power, sqrt, positive_finite_general, 4.0, return std::sqrt(x), return ccm::sqrt(x), return sqrt_rt(x)) BENCHMARK_MAIN(); diff --git a/examples/common/io.hpp b/examples/common/io.hpp index 747c0045..8ee813d2 100644 --- a/examples/common/io.hpp +++ b/examples/common/io.hpp @@ -8,9 +8,7 @@ namespace ccm::examples { inline void banner(std::string_view title) - { - std::cout << "\n=== " << title << " ===\n"; - } + { std::cout << "\n=== " << title << " ===\n"; } inline void section(std::string_view name) { std::cout << '\n' << name << '\n'; } diff --git a/examples/src/constexpr_abs.cpp b/examples/src/constexpr_abs.cpp index c43b350b..ac7921d4 100644 --- a/examples/src/constexpr_abs.cpp +++ b/examples/src/constexpr_abs.cpp @@ -22,7 +22,7 @@ int main() ccm::examples::banner("constexpr abs"); constexpr auto compiled = ccm::abs(kInput); - const auto runtime = ccm::abs(kInput); + const auto runtime = ccm::abs(kInput); ccm::examples::print_row("input", kInput); ccm::examples::print_row("constexpr result", compiled); diff --git a/examples/src/decibel_gain.cpp b/examples/src/decibel_gain.cpp index d0ba9833..628b23e8 100644 --- a/examples/src/decibel_gain.cpp +++ b/examples/src/decibel_gain.cpp @@ -22,7 +22,7 @@ int main() { ccm::examples::banner("decibel conversion"); - double const ratios[] = {1.0, 2.0, 10.0, 100.0}; + double const ratios[] = { 1.0, 2.0, 10.0, 100.0 }; ccm::examples::table_header("power ratio", "dB (10 log10)"); diff --git a/examples/src/fp_classes.cpp b/examples/src/fp_classes.cpp index 19c8aaaf..7fefddde 100644 --- a/examples/src/fp_classes.cpp +++ b/examples/src/fp_classes.cpp @@ -7,10 +7,9 @@ #include "io.hpp" -#include - #include +#include #include #include @@ -46,10 +45,7 @@ int main() ccm::examples::table_header("value", "class", "signbit"); - for (double v : kValues) - { - std::cout << std::left << std::setw(14) << v << std::setw(18) << classify(v) << std::setw(18) << ccm::signbit(v) << '\n'; - } + for (double v : kValues) { std::cout << std::left << std::setw(14) << v << std::setw(18) << classify(v) << std::setw(18) << ccm::signbit(v) << '\n'; } ccm::examples::section("ordering predicates"); ccm::examples::print_row("isfinite(1)", ccm::isfinite(1.0)); diff --git a/examples/src/hypot_distance.cpp b/examples/src/hypot_distance.cpp index 7118f900..1a68f43f 100644 --- a/examples/src/hypot_distance.cpp +++ b/examples/src/hypot_distance.cpp @@ -18,8 +18,8 @@ namespace double z; }; - Vec3 const kSensor{12.0, -4.5, 1.25}; - Vec3 const kTarget{27.0, 18.0, 6.0}; + Vec3 const kSensor{ 12.0, -4.5, 1.25 }; + Vec3 const kTarget{ 27.0, 18.0, 6.0 }; double planar_distance(Vec3 const& a, Vec3 const& b) { return ccm::hypot(b.x - a.x, b.y - a.y); } diff --git a/examples/src/rounding_edge_cases.cpp b/examples/src/rounding_edge_cases.cpp index 2908f3f8..be6a630f 100644 --- a/examples/src/rounding_edge_cases.cpp +++ b/examples/src/rounding_edge_cases.cpp @@ -16,7 +16,7 @@ namespace double x; }; - constexpr Sample kSamples[] = {{2.5}, {3.5}, {-2.5}, {1e8 + 0.5}}; + constexpr Sample kSamples[] = { { 2.5 }, { 3.5 }, { -2.5 }, { 1e8 + 0.5 } }; } // namespace int main() @@ -27,8 +27,7 @@ int main() for (auto const& s : kSamples) { - std::cout << std::left << std::setw(14) << s.x << std::setw(18) << ccm::nearbyint(s.x) << std::setw(18) << ccm::round(s.x) - << '\n'; + std::cout << std::left << std::setw(14) << s.x << std::setw(18) << ccm::nearbyint(s.x) << std::setw(18) << ccm::round(s.x) << '\n'; } ccm::examples::section("floor / ceil / trunc at pi"); diff --git a/examples/src/signed_magnitude.cpp b/examples/src/signed_magnitude.cpp index ca06c457..843116e3 100644 --- a/examples/src/signed_magnitude.cpp +++ b/examples/src/signed_magnitude.cpp @@ -18,9 +18,9 @@ namespace }; constexpr Pair kCases[] = { - {3.0, -1.0}, - {-4.0, -4.0}, - {0.0, -0.0}, + { 3.0, -1.0 }, + { -4.0, -4.0 }, + { 0.0, -0.0 }, }; } // namespace @@ -33,8 +33,8 @@ int main() for (auto const& c : kCases) { double const positive_diff = ccm::fdim(c.a, c.b); - double const magnitude = ccm::fabs(c.a); - double const sign = ccm::copysign(1.0, c.a); + double const magnitude = ccm::fabs(c.a); + double const sign = ccm::copysign(1.0, c.a); std::cout << std::left << std::setw(14) << c.a << std::setw(18) << c.b << std::setw(18) << positive_diff << '\n'; ccm::examples::print_row(" fabs(a)", magnitude); diff --git a/examples/src/unit_circle.cpp b/examples/src/unit_circle.cpp index 75e417d7..55f31922 100644 --- a/examples/src/unit_circle.cpp +++ b/examples/src/unit_circle.cpp @@ -20,11 +20,7 @@ namespace }; constexpr AngleCase kAngles[] = { - {0.0, 0.0}, - {30.0, kPi / 6.0}, - {45.0, kPi / 4.0}, - {60.0, kPi / 3.0}, - {90.0, kPi / 2.0}, + { 0.0, 0.0 }, { 30.0, kPi / 6.0 }, { 45.0, kPi / 4.0 }, { 60.0, kPi / 3.0 }, { 90.0, kPi / 2.0 }, }; } // namespace @@ -42,7 +38,7 @@ int main() } ccm::examples::section("identity check"); - double const x = kPi / 5.0; + double const x = kPi / 5.0; double const lhs = ccm::sin(x) * ccm::sin(x) + ccm::cos(x) * ccm::cos(x); ccm::examples::print_row("sin^2(x) + cos^2(x)", lhs); diff --git a/fuzzing/shared/fuzz_calls.hpp b/fuzzing/shared/fuzz_calls.hpp index fe729e09..4362d611 100644 --- a/fuzzing/shared/fuzz_calls.hpp +++ b/fuzzing/shared/fuzz_calls.hpp @@ -87,7 +87,7 @@ namespace ccm::fuzz::calls template \ T name(T x) \ { return ccm::name(x); } - #define CCMATH_FUZZ_UNARY_CALL_POSITIVE(name) CCMATH_FUZZ_UNARY_CALL(name) + #define CCMATH_FUZZ_UNARY_CALL_POSITIVE(name) CCMATH_FUZZ_UNARY_CALL(name) #define CCMATH_FUZZ_UNARY_CALL_ABOVE_NEG_ONE(name) CCMATH_FUZZ_UNARY_CALL(name) #define CCMATH_FUZZ_BINARY_CALL(name) \ template \ diff --git a/fuzzing/shared/harness.hpp b/fuzzing/shared/harness.hpp index 6e997397..fbd83180 100644 --- a/fuzzing/shared/harness.hpp +++ b/fuzzing/shared/harness.hpp @@ -10,10 +10,9 @@ #pragma once -#include "input.hpp" - #include "ccmath/ccmath.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" +#include "input.hpp" #include #include diff --git a/fuzzing/shared/input.hpp b/fuzzing/shared/input.hpp index 2d629165..a938f494 100644 --- a/fuzzing/shared/input.hpp +++ b/fuzzing/shared/input.hpp @@ -98,14 +98,14 @@ namespace ccm::fuzz case 11: return limits::infinity(); case 12: return limits::quiet_NaN(); case 13: return limits::signaling_NaN(); - case 14: return T(1) / limits::epsilon(); // integer parity threshold 2^p - case 15: return T(2) / limits::epsilon(); // 2^(p+1), everything above is even - case 16: return T(2.5); // half integer - case 17: return T(1023.5); // pow half-integer bound region - case 18: return T(1024); // pow integer ipow bound - case 19: return T(2048); // pow two_exp bound + case 14: return T(1) / limits::epsilon(); // integer parity threshold 2^p + case 15: return T(2) / limits::epsilon(); // 2^(p+1), everything above is even + case 16: return T(2.5); // half integer + case 17: return T(1023.5); // pow half-integer bound region + case 18: return T(1024); // pow integer ipow bound + case 19: return T(2048); // pow two_exp bound case 20: return static_cast(0x1.74910d52d3052p+62); // pow huge-exponent clamp threshold - case 21: return static_cast(0x1.62e42fefa39efp+9); // near the exp overflow boundary + case 21: return static_cast(0x1.62e42fefa39efp+9); // near the exp overflow boundary case 22: return T(0x1.0p-100); case 23: return T(0x1.0p100); default: return T(0); diff --git a/fuzzing/src/math/power_fuzz.cpp b/fuzzing/src/math/power_fuzz.cpp index c80585bd..4bbc927e 100644 --- a/fuzzing/src/math/power_fuzz.cpp +++ b/fuzzing/src/math/power_fuzz.cpp @@ -38,7 +38,10 @@ namespace #else const bool skip_hypot = false; #endif - if (!skip_hypot) { ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::fuzz::calls::hypot, [](T a, T b) { return std::hypot(a, b); }); } + if (!skip_hypot) + { + ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::fuzz::calls::hypot, [](T a, T b) { return std::hypot(a, b); }); + } ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::fuzz::calls::pow, [](T a, T b) { return std::pow(a, b); }); } diff --git a/tests/conformance/rounding_conformance_test.cpp b/tests/conformance/rounding_conformance_test.cpp index 92eba1e6..b23b1e19 100644 --- a/tests/conformance/rounding_conformance_test.cpp +++ b/tests/conformance/rounding_conformance_test.cpp @@ -35,7 +35,7 @@ namespace // the host libm is most likely to disagree with the correctly rounded result under a directed // mode. Valid inputs for log, log2 and log10. constexpr std::array kLogDirectedProbes = { - 0x1.5fdffffffff32p-1, 0x1.921fb54442d18p+0, 0x1.5bf0a8b145769p+1, 0x1.62e42fefa39efp+2, 0x1.b7e151628aed3p+4, + 0x1.5fdffffffff32p-1, 0x1.921fb54442d18p+0, 0x1.5bf0a8b145769p+1, 0x1.62e42fefa39efp+2, 0x1.b7e151628aed3p+4, 0x1.0000000000001p+0, 0x1.fffffffffffffp+9, 0x1.199999999999ap-7, 0x1.5fdffffffff32p+40, }; @@ -53,7 +53,7 @@ namespace // result is a full-precision normal value rather than a saturated edge. constexpr std::array kExpDirectedProbes = { -0x1.62e42fefa39efp+5, -0x1.5fdffffffff32p+4, -0x1.921fb54442d18p+0, -0x1.199999999999ap-7, 0x1.199999999999ap-7, - 0x1.5bf0a8b145769p+1, 0x1.921fb54442d18p+0, 0x1.5fdffffffff32p+4, 0x1.b7e151628aed3p+2, 0x1.62e42fefa39efp+5, + 0x1.5bf0a8b145769p+1, 0x1.921fb54442d18p+0, 0x1.5fdffffffff32p+4, 0x1.b7e151628aed3p+2, 0x1.62e42fefa39efp+5, }; // The expo runtime headers route to the generic kernel outside FE_TONEAREST, because the libm @@ -63,7 +63,7 @@ namespace // is not correctly rounded. Both sides run under the forced mode through a runtime value so the // generic kernel reads the dynamic mode instead of folding at compile time. template - void ExpectRuntimeMatchesGenericInDirectedModes(const std::array & inputs, CcmFn ccm_fn, GenFn gen_fn) + void ExpectRuntimeMatchesGenericInDirectedModes(const std::array& inputs, CcmFn ccm_fn, GenFn gen_fn) { ccm::test::ForEachRoundingModeOrSkip( [&](int mode) diff --git a/tests/shared/oracle/pow_adversarial_search.cpp b/tests/shared/oracle/pow_adversarial_search.cpp index 4e56e5c4..4c375525 100644 --- a/tests/shared/oracle/pow_adversarial_search.cpp +++ b/tests/shared/oracle/pow_adversarial_search.cpp @@ -48,7 +48,7 @@ int main(int argc, char** argv) const std::string mode = ccm::test::oracle::option_value(argc, argv, "--mode=").value_or("random-bit-pattern"); const std::string output_path = ccm::test::oracle::option_value(argc, argv, "--json-output=").value_or("pow-search-results.json"); const std::uint64_t seed = ccm::test::oracle::parse_option_or( - ccm::test::oracle::option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xBAD5EEDULL); + ccm::test::oracle::option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xBAD5EEDULL); const std::size_t count = ccm::test::oracle::parse_option_or( ccm::test::oracle::option_value(argc, argv, "--count="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 4096); const std::uint64_t max_ulp = ccm::test::oracle::parse_option_or( diff --git a/tests/shared/oracle/runners/coremath_pow_runner.hpp b/tests/shared/oracle/runners/coremath_pow_runner.hpp index 1c7efc51..537a1c60 100644 --- a/tests/shared/oracle/runners/coremath_pow_runner.hpp +++ b/tests/shared/oracle/runners/coremath_pow_runner.hpp @@ -21,32 +21,29 @@ namespace ccm::test::oracle::pow_coremath template <> struct traits { - static constexpr std::string_view function_name = "ccm::pow"; + static constexpr std::string_view function_name = "ccm::pow"; static constexpr std::string_view summary_prefix = "coremath-pow-double-"; - static constexpr std::uint64_t default_seed = 0xC0DEC0FFEEULL; - static constexpr std::string_view corpus_label = "finite-binary64-corpus"; - - static std::vector> build_cases(campaign_mode mode, - std::set const&, - std::uint64_t seed, - std::vector&, - std::vector&) + static constexpr std::uint64_t default_seed = 0xC0DEC0FFEEULL; + static constexpr std::string_view corpus_label = "finite-binary64-corpus"; + + static std::vector> + build_cases(campaign_mode mode, std::set const&, std::uint64_t seed, std::vector&, std::vector&) { return coremath_cases::build_double_cases(mode, seed); } }; template <> struct traits { - static constexpr std::string_view function_name = "ccm::powf"; + static constexpr std::string_view function_name = "ccm::powf"; static constexpr std::string_view summary_prefix = "coremath-powf-"; - static constexpr std::uint64_t default_seed = 0xFACEB00CU; - static constexpr std::string_view corpus_label = "finite-binary32-corpus"; + static constexpr std::uint64_t default_seed = 0xFACEB00CU; + static constexpr std::string_view corpus_label = "finite-binary32-corpus"; static std::vector> build_cases(campaign_mode mode, - std::set const& domain_filter, - std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) + std::set const& domain_filter, + std::uint64_t seed, + std::vector& domains_covered, + std::vector& domains_skipped) { return coremath_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); } }; @@ -83,10 +80,7 @@ namespace ccm::test::oracle::pow_coremath } }, [&](run_summary const& path_summary, std::uint64_t elapsed_ms) - { - return make_coremath_campaign_report( - pow_path::path_name(path), mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); - }, + { return make_coremath_campaign_report(pow_path::path_name(path), mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); }, [&](auto const& report, std::string const& summary_path) { std::cout << "path=" << report.path << " configuration=" << report.configuration_name << " cases=" << report.case_count @@ -98,22 +92,19 @@ namespace ccm::test::oracle::pow_coremath template inline int run_campaign(int argc, char** argv) { - auto const mode = parse_mode(option_value(argc, argv, "--mode=")); - auto const output_path = resolve_event_log_path(argc, argv); - auto const domain_raw = option_value(argc, argv, "--domain="); + auto const mode = parse_mode(option_value(argc, argv, "--mode=")); + auto const output_path = resolve_event_log_path(argc, argv); + auto const domain_raw = option_value(argc, argv, "--domain="); auto const domain_filter = powf_domains::parse_domain_filter(domain_raw); std::uint64_t const seed = parse_option_or( option_value(argc, argv, "--seed="), [](std::string const& value) { return std::stoull(value); }, traits::default_seed); - auto paths = parse_paths(argc, argv); + auto paths = parse_paths(argc, argv); auto const rounding_modes = parse_rounding_modes(argc, argv); std::vector domains_covered; std::vector domains_skipped; - if constexpr (std::is_same_v) - { - domains_covered = { std::string(traits::corpus_label) }; - } + if constexpr (std::is_same_v) { domains_covered = { std::string(traits::corpus_label) }; } auto const cases = traits::build_cases(mode, domain_filter, seed, domains_covered, domains_skipped); std::vector> events; diff --git a/tests/shared/oracle/runners/mpfr_pow_runner.hpp b/tests/shared/oracle/runners/mpfr_pow_runner.hpp index 5102cf3a..ef711033 100644 --- a/tests/shared/oracle/runners/mpfr_pow_runner.hpp +++ b/tests/shared/oracle/runners/mpfr_pow_runner.hpp @@ -21,34 +21,31 @@ namespace ccm::test::oracle::pow_mpfr template <> struct traits { - static constexpr std::string_view function_name = "ccm::pow"; + static constexpr std::string_view function_name = "ccm::pow"; static constexpr std::string_view summary_prefix = "mpfr-pow-double-"; - static constexpr std::uint64_t default_seed = 0xC0DEC0FFEEULL; - static constexpr mpfr_prec_t default_precision = 256; - static constexpr std::string_view corpus_label = "structured-binary64-corpus"; - - static std::vector> build_cases(campaign_mode mode, - std::set const&, - std::uint64_t seed, - std::vector&, - std::vector&) + static constexpr std::uint64_t default_seed = 0xC0DEC0FFEEULL; + static constexpr mpfr_prec_t default_precision = 256; + static constexpr std::string_view corpus_label = "structured-binary64-corpus"; + + static std::vector> + build_cases(campaign_mode mode, std::set const&, std::uint64_t seed, std::vector&, std::vector&) { return pow_cases::build_double_cases(mode, seed); } }; template <> struct traits { - static constexpr std::string_view function_name = "ccm::powf"; + static constexpr std::string_view function_name = "ccm::powf"; static constexpr std::string_view summary_prefix = "mpfr-powf-"; - static constexpr std::uint64_t default_seed = 0xFACEB00CU; - static constexpr mpfr_prec_t default_precision = 192; - static constexpr std::string_view corpus_label = "structured-binary32-corpus"; + static constexpr std::uint64_t default_seed = 0xFACEB00CU; + static constexpr mpfr_prec_t default_precision = 192; + static constexpr std::string_view corpus_label = "structured-binary32-corpus"; static std::vector> build_cases(campaign_mode mode, - std::set const& domain_filter, - std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) + std::set const& domain_filter, + std::uint64_t seed, + std::vector& domains_covered, + std::vector& domains_skipped) { return pow_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); } }; @@ -78,10 +75,7 @@ namespace ccm::test::oracle::pow_mpfr if (!scope) { continue; } for (auto const& test_case : cases) { - if (path == validation_path::generic_modeled_domain && !is_modeled_generic_pow_case(test_case.base, test_case.exponent)) - { - continue; - } + if (path == validation_path::generic_modeled_domain && !is_modeled_generic_pow_case(test_case.base, test_case.exponent)) { continue; } (void)evaluate_case( test_case, traits::function_name, @@ -99,10 +93,7 @@ namespace ccm::test::oracle::pow_mpfr } }, [&](run_summary const& path_summary, std::uint64_t elapsed_ms) - { - return make_campaign_report( - pow_path::path_name(path), path, mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); - }, + { return make_campaign_report(pow_path::path_name(path), path, mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); }, [&](auto const& report, std::string const& summary_path) { std::cout << "path=" << report.path << " configuration=" << report.configuration_name << " rounding_modes=" << rounding_modes.size() @@ -116,18 +107,16 @@ namespace ccm::test::oracle::pow_mpfr template inline int run_campaign(int argc, char** argv) { - auto const mode = parse_mode(option_value(argc, argv, "--mode=")); - auto const output_path = resolve_event_log_path(argc, argv); - auto const domain_raw = option_value(argc, argv, "--domain="); + auto const mode = parse_mode(option_value(argc, argv, "--mode=")); + auto const output_path = resolve_event_log_path(argc, argv); + auto const domain_raw = option_value(argc, argv, "--domain="); auto const domain_filter = powf_domains::parse_domain_filter(domain_raw); std::uint64_t const seed = parse_option_or( option_value(argc, argv, "--seed="), [](std::string const& value) { return std::stoull(value); }, traits::default_seed); std::uint64_t const max_ulp = parse_option_or( option_value(argc, argv, "--max-ulp="), [](std::string const& value) { return static_cast(std::stoull(value)); }, 4); std::uint64_t const target_ulp = parse_option_or( - option_value(argc, argv, "--target-ulp="), - [](std::string const& value) { return static_cast(std::stoull(value)); }, - 0); + option_value(argc, argv, "--target-ulp="), [](std::string const& value) { return static_cast(std::stoull(value)); }, 0); mpfr_prec_t const precision = parse_option_or( option_value(argc, argv, "--oracle-precision="), [](std::string const& value) { return static_cast(std::stoul(value)); }, @@ -145,10 +134,7 @@ namespace ccm::test::oracle::pow_mpfr std::vector domains_covered; std::vector domains_skipped; - if constexpr (std::is_same_v) - { - domains_covered = { std::string(traits::corpus_label) }; - } + if constexpr (std::is_same_v) { domains_covered = { std::string(traits::corpus_label) }; } auto const cases = traits::build_cases(mode, domain_filter, seed, domains_covered, domains_skipped); std::vector> events; diff --git a/tests/src/math/basic/fma_correct_rounding_test.cpp b/tests/src/math/basic/fma_correct_rounding_test.cpp index db60315a..06830c82 100644 --- a/tests/src/math/basic/fma_correct_rounding_test.cpp +++ b/tests/src/math/basic/fma_correct_rounding_test.cpp @@ -77,21 +77,37 @@ namespace }; constexpr DoubleCase kDoubleCases[] = { - { 0x3ff0000002000000ULL, 0x3ff0000002000000ULL, 0x3950000000000000ULL, + { 0x3ff0000002000000ULL, + 0x3ff0000002000000ULL, + 0x3950000000000000ULL, { 0x3ff0000004000000ULL, 0x3ff0000004000000ULL, 0x3ff0000004000001ULL, 0x3ff0000004000000ULL } }, - { 0x3ff0000002000000ULL, 0x3ff0000002000000ULL, 0xb950000000000000ULL, + { 0x3ff0000002000000ULL, + 0x3ff0000002000000ULL, + 0xb950000000000000ULL, { 0x3ff0000004000000ULL, 0x3ff0000004000000ULL, 0x3ff0000004000001ULL, 0x3ff0000004000000ULL } }, - { 0xc04f419b20000000ULL, 0x42605061a0000000ULL, 0xbacb322040000000ULL, + { 0xc04f419b20000000ULL, + 0x42605061a0000000ULL, + 0xbacb322040000000ULL, { 0xc2bfdea1ddec8140ULL, 0xc2bfdea1ddec8140ULL, 0xc2bfdea1ddec8140ULL, 0xc2bfdea1ddec8141ULL } }, - { 0x4008000000000000ULL, 0x4014000000000000ULL, 0x401c000000000000ULL, + { 0x4008000000000000ULL, + 0x4014000000000000ULL, + 0x401c000000000000ULL, { 0x4036000000000000ULL, 0x4036000000000000ULL, 0x4036000000000000ULL, 0x4036000000000000ULL } }, - { 0x7e444ecd0d33972bULL, 0x46293e5939a08ceaULL, 0x3ff0000000000000ULL, + { 0x7e444ecd0d33972bULL, + 0x46293e5939a08ceaULL, + 0x3ff0000000000000ULL, { 0x7ff0000000000000ULL, 0x7fefffffffffffffULL, 0x7ff0000000000000ULL, 0x7fefffffffffffffULL } }, - { 0xfe444ecd0d33972bULL, 0x46293e5939a08ceaULL, 0x3ff0000000000000ULL, + { 0xfe444ecd0d33972bULL, + 0x46293e5939a08ceaULL, + 0x3ff0000000000000ULL, { 0xfff0000000000000ULL, 0xffefffffffffffffULL, 0xffefffffffffffffULL, 0xfff0000000000000ULL } }, - { 0x01a56e1fc2f8f359ULL, 0x39b4484bfeebc2a0ULL, 0x0000000000000000ULL, + { 0x01a56e1fc2f8f359ULL, + 0x39b4484bfeebc2a0ULL, + 0x0000000000000000ULL, { 0x0000000000000000ULL, 0x0000000000000000ULL, 0x0000000000000001ULL, 0x0000000000000000ULL } }, - { 0x3fffffffffffffffULL, 0x3ff0000000000001ULL, 0x3c30000000000000ULL, + { 0x3fffffffffffffffULL, + 0x3ff0000000000001ULL, + 0x3c30000000000000ULL, { 0x4000000000000001ULL, 0x4000000000000000ULL, 0x4000000000000001ULL, 0x4000000000000000ULL } }, }; diff --git a/tests/src/math/expo/log10_test.cpp b/tests/src/math/expo/log10_test.cpp index 09b7c74e..2d8339ac 100644 --- a/tests/src/math/expo/log10_test.cpp +++ b/tests/src/math/expo/log10_test.cpp @@ -37,8 +37,8 @@ TEST(CcmathExponentialTests, Log10DoubleLastTableBucket) static_assert(ccm::log10(0x1.5fdffffffff32p+0) == 0x1.1aed92aadd55p-3, "log10 must use the final log table entry in the last bucket"); constexpr double inputs[] = { - 0x1.5fdffffffff32p-1011, 0x1.5fdffffffff32p-1, 0x1.5fdffffffff32p+0, 0x1.5fdffffffff32p+1, - 0x1.5ep+0, 0x1.5ffffffffffffp+0, 0x1.5ep-512, 0x1.5ffffffffffffp+512, + 0x1.5fdffffffff32p-1011, 0x1.5fdffffffff32p-1, 0x1.5fdffffffff32p+0, 0x1.5fdffffffff32p+1, 0x1.5ep+0, + 0x1.5ffffffffffffp+0, 0x1.5ep-512, 0x1.5ffffffffffffp+512, }; for (double input : inputs) { diff --git a/tests/src/math/expo/log_test.cpp b/tests/src/math/expo/log_test.cpp index 1f50f4f7..6f579172 100644 --- a/tests/src/math/expo/log_test.cpp +++ b/tests/src/math/expo/log_test.cpp @@ -46,9 +46,8 @@ TEST(CcmathExponentialTests, LogDoubleLastTableBucket) // Same patterns plus the bucket boundaries through the runtime generic kernel. constexpr double inputs[] = { - 0x1.5fdffffffff32p-1011, 0x1.5fdffffffff32p-1, 0x1.5fdffffffff32p+0, 0x1.5fdffffffff32p+1, - 0x1.5ep+0, 0x1.5ffffffffffffp+0, 0x1.5ep-512, 0x1.5ffffffffffffp+512, - 0x1.5dfffffffffffp+0, 0x1.6p+0, + 0x1.5fdffffffff32p-1011, 0x1.5fdffffffff32p-1, 0x1.5fdffffffff32p+0, 0x1.5fdffffffff32p+1, 0x1.5ep+0, + 0x1.5ffffffffffffp+0, 0x1.5ep-512, 0x1.5ffffffffffffp+512, 0x1.5dfffffffffffp+0, 0x1.6p+0, }; for (double input : inputs) { diff --git a/tests/src/math/internal/types/big_int_test.cpp b/tests/src/math/internal/types/big_int_test.cpp index 300f2432..74f8cea9 100644 --- a/tests/src/math/internal/types/big_int_test.cpp +++ b/tests/src/math/internal/types/big_int_test.cpp @@ -162,9 +162,9 @@ namespace { const std::size_t out_index = i + j; const std::uint64_t accum = static_cast(out.limbs[out_index]) + - static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; - out.limbs[out_index] = static_cast(accum); - carry = accum >> limb_bits; + static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; + out.limbs[out_index] = static_cast(accum); + carry = accum >> limb_bits; } for (std::size_t out_index = i + RefUint::limb_count; carry != 0 && out_index < RefUint::limb_count; ++out_index) { diff --git a/tests/src/math/power/pow_deterministic_test.cpp b/tests/src/math/power/pow_deterministic_test.cpp index 6054434b..bff90281 100644 --- a/tests/src/math/power/pow_deterministic_test.cpp +++ b/tests/src/math/power/pow_deterministic_test.cpp @@ -72,10 +72,10 @@ TEST(PowDeterministic, LongDoubleDelegatesToDouble) { for (double exp : kExponents) { - const auto b = static_cast(base); - const auto e = static_cast(exp); - const long double got = ccm::pow(b, e); - const long double want = static_cast(ccm::pow(static_cast(b), static_cast(e))); + const auto b = static_cast(base); + const auto e = static_cast(exp); + const long double got = ccm::pow(b, e); + const long double want = static_cast(ccm::pow(static_cast(b), static_cast(e))); EXPECT_TRUE(same_bits(got, want)) << "base=" << base << " exp=" << exp; } } diff --git a/tests/src/math/power/pow_simd_test.cpp b/tests/src/math/power/pow_simd_test.cpp index 17ecfbfb..2bedc78c 100644 --- a/tests/src/math/power/pow_simd_test.cpp +++ b/tests/src/math/power/pow_simd_test.cpp @@ -66,11 +66,12 @@ namespace const double maxd = std::numeric_limits::max(); // Exceptional grid: zeros, ones, infinities, NaN, denormals, extremes, negative bases. - const double special_bases[] = { 0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 10.0, 0.5, - inf, -inf, qnan, denorm, -denorm, minn, maxd, -maxd, - 1.0000000000000002, 0.9999999999999999, 1.5, -1.5 }; - const double special_exps[] = { 0.0, -0.0, 1.0, -1.0, 2.0, 3.0, -3.0, 0.5, -0.5, 2.5, - 1023.0, -1074.0, 1025.0, inf, -inf, qnan, 1e300, -1e300, 0.3333333333333333, 5.5e18 }; + const double special_bases[] = { + 0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 10.0, 0.5, inf, -inf, qnan, denorm, -denorm, minn, maxd, -maxd, 1.0000000000000002, 0.9999999999999999, 1.5, -1.5 + }; + const double special_exps[] = { + 0.0, -0.0, 1.0, -1.0, 2.0, 3.0, -3.0, 0.5, -0.5, 2.5, 1023.0, -1074.0, 1025.0, inf, -inf, qnan, 1e300, -1e300, 0.3333333333333333, 5.5e18 + }; for (double b : special_bases) { for (double e : special_exps) { add(b, e); } @@ -143,7 +144,7 @@ namespace } template - void expect_matches_scalar(const std::vector & xs, const std::vector & ys, const char * tag) + void expect_matches_scalar(const std::vector& xs, const std::vector& ys, const char* tag) { constexpr int width = static_cast(V::size()); ASSERT_EQ(xs.size(), ys.size()); @@ -168,17 +169,15 @@ namespace { const double got = rv[i]; const double ref = ccm::gen::impl::pow_impl(xb[i], yb[i]); - EXPECT_TRUE(bit_equal(got, ref)) << tag << " pow(" << xb[i] << ", " << yb[i] << ") got=0x" << std::hex << double_bits(got) - << " ref=0x" << double_bits(ref); + EXPECT_TRUE(bit_equal(got, ref)) << tag << " pow(" << xb[i] << ", " << yb[i] << ") got=0x" << std::hex << double_bits(got) << " ref=0x" + << double_bits(ref); } } } template double scalar_lane(double x, double y) - { - return ccm::gen::impl::pow_simd(V(x), V(y))[0]; - } + { return ccm::gen::impl::pow_simd(V(x), V(y))[0]; } struct FenvObservation { @@ -190,13 +189,16 @@ namespace }; template - FenvObservation observe_fenv(F && run) + FenvObservation observe_fenv(F&& run) { std::feclearexcept(FE_ALL_EXCEPT); errno = 0; run(); - return { std::fetestexcept(FE_OVERFLOW) != 0, std::fetestexcept(FE_UNDERFLOW) != 0, std::fetestexcept(FE_INVALID) != 0, - std::fetestexcept(FE_DIVBYZERO) != 0, errno }; + return { std::fetestexcept(FE_OVERFLOW) != 0, + std::fetestexcept(FE_UNDERFLOW) != 0, + std::fetestexcept(FE_INVALID) != 0, + std::fetestexcept(FE_DIVBYZERO) != 0, + errno }; } } // namespace @@ -290,10 +292,10 @@ TEST(CcmathPowSimd, FenvAndErrnoParityWithScalarKernel) { 1.0000001, 1e15 }, // overflow from a near-one base }; - for (const auto & c : cases) + for (const auto& c : cases) { - volatile double simd_out = 0.0; - volatile double scal_out = 0.0; + volatile double simd_out = 0.0; + volatile double scal_out = 0.0; const FenvObservation simd_obs = observe_fenv([&] { simd_out = ccm::gen::impl::pow_simd(V(c[0]), V(c[1]))[0]; }); const FenvObservation scal_obs = observe_fenv([&] { scal_out = ccm::gen::impl::pow_impl(c[0], c[1]); }); diff --git a/tests/src/math/power/powf_simd_test.cpp b/tests/src/math/power/powf_simd_test.cpp index 8fc341c8..1d29ea47 100644 --- a/tests/src/math/power/powf_simd_test.cpp +++ b/tests/src/math/power/powf_simd_test.cpp @@ -56,8 +56,8 @@ namespace ys.push_back(y); }; - const float inf = std::numeric_limits::infinity(); - const float qnan = std::numeric_limits::quiet_NaN(); + const float inf = std::numeric_limits::infinity(); + const float qnan = std::numeric_limits::quiet_NaN(); const float denorm = std::numeric_limits::denorm_min(); // Exceptional grid: zeros, ones, powers of two, infinities, NaN, denormals, negative bases. @@ -107,7 +107,7 @@ namespace } template - void expect_matches_scalar(const std::vector & xs, const std::vector & ys, const char * tag) + void expect_matches_scalar(const std::vector& xs, const std::vector& ys, const char* tag) { constexpr int width = static_cast(V::size()); ASSERT_EQ(xs.size(), ys.size()); @@ -132,17 +132,15 @@ namespace { const float got = rv[i]; const float ref = ccm::gen::impl::powf_impl(xb[i], yb[i]); - EXPECT_TRUE(bit_equal(got, ref)) << tag << " pow(" << xb[i] << ", " << yb[i] << ") got=0x" << std::hex << float_bits(got) - << " ref=0x" << float_bits(ref); + EXPECT_TRUE(bit_equal(got, ref)) << tag << " pow(" << xb[i] << ", " << yb[i] << ") got=0x" << std::hex << float_bits(got) << " ref=0x" + << float_bits(ref); } } } template float scalar_lane(float x, float y) - { - return ccm::gen::impl::powf_simd(V(x), V(y))[0]; - } + { return ccm::gen::impl::powf_simd(V(x), V(y))[0]; } } // namespace TEST(CcmathPowfSimd, BitIdenticalToScalarKernelNativeWidth) diff --git a/tests/src/math/trig/trig_kernel_regression_test.cpp b/tests/src/math/trig/trig_kernel_regression_test.cpp index ee76a410..b3decb3f 100644 --- a/tests/src/math/trig/trig_kernel_regression_test.cpp +++ b/tests/src/math/trig/trig_kernel_regression_test.cpp @@ -61,8 +61,16 @@ namespace std::uint32_t u = f_bits(v); return (u & 0x80000000U) ? -static_cast(u & 0x7fffffffU) : static_cast(u | 0x80000000U); } - long long d_ulp(double a, double b) { long long d = d_mono(a) - d_mono(b); return d < 0 ? -d : d; } - long long f_ulp(float a, float b) { long long d = f_mono(a) - f_mono(b); return d < 0 ? -d : d; } + long long d_ulp(double a, double b) + { + long long d = d_mono(a) - d_mono(b); + return d < 0 ? -d : d; + } + long long f_ulp(float a, float b) + { + long long d = f_mono(a) - f_mono(b); + return d < 0 ? -d : d; + } } // namespace TEST(CcmathTrigKernelRegression, DoubleWithinContract) @@ -77,9 +85,9 @@ TEST(CcmathTrigKernelRegression, DoubleWithinContract) TEST(CcmathTrigKernelRegression, FloatWithinContract) { - EXPECT_LE(f_ulp(ccm::gen::sin_gen(f_from(0x4b800000U)), f_from(0xbf47917cU)), 4); // sin(2^24) - EXPECT_LE(f_ulp(ccm::gen::cos_gen(f_from(0x60ad78ecU)), f_from(0x3f411723U)), 4); // cos(1e20) - EXPECT_LE(f_ulp(ccm::gen::sin_gen(f_from(0x494cccc6U)), f_from(0xbf7cc946U)), 4); // sin(838860.4) + EXPECT_LE(f_ulp(ccm::gen::sin_gen(f_from(0x4b800000U)), f_from(0xbf47917cU)), 4); // sin(2^24) + EXPECT_LE(f_ulp(ccm::gen::cos_gen(f_from(0x60ad78ecU)), f_from(0x3f411723U)), 4); // cos(1e20) + EXPECT_LE(f_ulp(ccm::gen::sin_gen(f_from(0x494cccc6U)), f_from(0xbf7cc946U)), 4); // sin(838860.4) EXPECT_LE(f_ulp(ccm::internal::impl::atan_impl(f_from(0x3f210625U)), f_from(0x3f0fbc89U)), 4); // atan(0.629) } @@ -87,7 +95,7 @@ TEST(CcmathTrigKernelRegression, AsinTinyArgument) { // The tiny-|x| fast path used the wrong cubic sign (returned x - x^3/6 instead of x + x^3/6). EXPECT_LE(d_ulp(ccm::internal::impl::asin_impl(d_from(0x3f0f75104d551d69ULL)), d_from(0x3f0f75104da62dffULL)), 4); // asin(6e-5) - EXPECT_EQ(d_bits(ccm::internal::impl::asin_impl(d_from(0x3ddb7cdfd9d7bdbbULL))), 0x3ddb7cdfd9d7bdbbULL); // asin(1e-10) = x + EXPECT_EQ(d_bits(ccm::internal::impl::asin_impl(d_from(0x3ddb7cdfd9d7bdbbULL))), 0x3ddb7cdfd9d7bdbbULL); // asin(1e-10) = x // asin(x) must exceed x for small x > 0. EXPECT_GT(ccm::internal::impl::asin_impl(6e-5), 6e-5); } @@ -97,7 +105,7 @@ TEST(CcmathTrigKernelRegression, SignOfZero) EXPECT_EQ(d_bits(ccm::gen::sin_gen(-0.0)), 0x8000000000000000ULL); // sin(-0) = -0 EXPECT_EQ(d_bits(ccm::gen::sin_gen(0.0)), 0x0ULL); // sin(+0) = +0 EXPECT_EQ(ccm::gen::cos_gen(-0.0), 1.0); // cos(-0) = 1 - EXPECT_EQ(d_bits(ccm::gen::tan_gen(-0.0)), 0x8000000000000000ULL); // tan(-0) = -0 + EXPECT_EQ(d_bits(ccm::gen::tan_gen(-0.0)), 0x8000000000000000ULL); // tan(-0) = -0 EXPECT_EQ(f_bits(ccm::gen::sin_gen(-0.0F)), 0x80000000U); EXPECT_EQ(ccm::gen::cos_gen(-0.0F), 1.0F); } diff --git a/tools/apply_format.sh b/tools/apply_format.sh index 97c84c50..d2a079a0 100644 --- a/tools/apply_format.sh +++ b/tools/apply_format.sh @@ -1,15 +1,25 @@ #!/bin/bash -# Apply clang-format to all headers under include/. +# Apply clang-format to every tracked C++ source, excluding build glue, vendored code, and docs demos. set -euo pipefail +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CLANG_FORMAT="${CLANG_FORMAT:-clang-format}" STYLE_ARGS=(-style=file -fallback-style=none) -if ! find include -name '*.hpp' -print -quit | grep -q .; then - echo "No header files found under include/." +cd "${repo_root}" + +if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then + echo "Not inside a git work tree, cannot enumerate sources." >&2 + exit 1 +fi + +source_pathspec=('*.hpp' '*.cpp' '*.h' ':!:docs/**' ':!:thirdparty/**' ':!:cmake/**') + +if [ -z "$(git ls-files -- "${source_pathspec[@]}" | head -n 1)" ]; then + echo "No source files found." exit 0 fi -find include -name '*.hpp' -print0 | xargs -0 "${CLANG_FORMAT}" -i "${STYLE_ARGS[@]}" +git ls-files -z -- "${source_pathspec[@]}" | xargs -0 "${CLANG_FORMAT}" -i "${STYLE_ARGS[@]}" echo "Formatting applied." diff --git a/tools/ensure_format.sh b/tools/ensure_format.sh index 8c95aad5..764f12c2 100644 --- a/tools/ensure_format.sh +++ b/tools/ensure_format.sh @@ -22,17 +22,29 @@ if ! "${CLANG_FORMAT}" --version | grep -Fq "${expected_version}"; then exit 1 fi -if ! find include -name '*.hpp' -print -quit | grep -q .; then - echo "No header files found under include/." - exit 0 +cd "${repo_root}" + +if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then + echo "Not inside a git work tree, cannot enumerate sources." >&2 + exit 1 fi +# Every tracked C++ source, excluding build glue, vendored code, and docs demos. +source_pathspec=('*.hpp' '*.cpp' '*.h' ':!:docs/**' ':!:thirdparty/**' ':!:cmake/**') + +checked=0 while IFS= read -r -d '' file; do + checked=1 if ! "${CLANG_FORMAT}" --dry-run --Werror "${STYLE_ARGS[@]}" "${file}"; then echo "Formatting check failed: ${file}" - echo "Run tools/apply_format.sh locally or fix headers manually." + echo "Run tools/apply_format.sh locally or fix the file manually." exit 1 fi -done < <(find include -name '*.hpp' -print0) +done < <(git ls-files -z -- "${source_pathspec[@]}") + +if [ "${checked}" -eq 0 ]; then + echo "No source files found." + exit 0 +fi echo "Formatting is up to date." From f1cd16c8e6362bb75c27dd5d22e2b85cfc971320 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 19:36:39 -0400 Subject: [PATCH 12/52] Add path and review-state PR labeling workflows --- .github/labeler.yml | 119 ++++++++++++++++++ .github/workflows/labeler-branch.yml | 38 ++++++ .../workflows/labeler-changes-requested.yml | 65 ++++++++++ .github/workflows/labeler-conflict.yml | 27 ++++ .github/workflows/labeler-needsreview.yml | 44 +++++++ .github/workflows/labeler-pr.yml | 19 +++ .github/workflows/labeler-review.yml | 65 ++++++++++ .github/workflows/labeler-size.yml | 29 +++++ .github/workflows/labeler-stale.yml | 30 +++++ .github/workflows/labeler-untriaged.yml | 79 ++++++++++++ 10 files changed, 515 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler-branch.yml create mode 100644 .github/workflows/labeler-changes-requested.yml create mode 100644 .github/workflows/labeler-conflict.yml create mode 100644 .github/workflows/labeler-needsreview.yml create mode 100644 .github/workflows/labeler-pr.yml create mode 100644 .github/workflows/labeler-review.yml create mode 100644 .github/workflows/labeler-size.yml create mode 100644 .github/workflows/labeler-stale.yml create mode 100644 .github/workflows/labeler-untriaged.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..2b0c9500 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,119 @@ +# Path-based labels applied by .github/workflows/labeler-pr.yml (actions/labeler). +# Helper file for that workflow. Does nothing on its own. + +"A: Basic": + - changed-files: + - any-glob-to-any-file: + - "**/basic/**" + - "**/basic.hpp" + +"A: Compare": + - changed-files: + - any-glob-to-any-file: + - "**/compare/**" + - "**/compare.hpp" + +"A: Expo": + - changed-files: + - any-glob-to-any-file: + - "**/expo/**" + - "**/expo.hpp" + +"A: Fmanip": + - changed-files: + - any-glob-to-any-file: + - "**/fmanip/**" + - "**/fmanip.hpp" + +"A: Hyper": + - changed-files: + - any-glob-to-any-file: + - "**/hyper/**" + - "**/hyper.hpp" + +"A: Misc": + - changed-files: + - any-glob-to-any-file: + - "**/misc/**" + +"A: Nearest": + - changed-files: + - any-glob-to-any-file: + - "**/nearest/**" + - "**/nearest.hpp" + +"A: Power": + - changed-files: + - any-glob-to-any-file: + - "**/power/**" + - "**/power.hpp" + +"A: Special": + - changed-files: + - any-glob-to-any-file: + - "**/special/**" + - "**/special.hpp" + +"A: Trig": + - changed-files: + - any-glob-to-any-file: + - "**/trig/**" + - "**/trig.hpp" + +"A: SIMD": + - changed-files: + - any-glob-to-any-file: + - "**/runtime/simd/**" + - "**/runtime/pp/**" + - "**/intrin/**" + +"A: Runtime Kernel": + - changed-files: + - any-glob-to-any-file: + - "**/internal/math/runtime/**" + +"A: Build": + - changed-files: + - any-glob-to-any-file: + - "**/CMakeLists.txt" + - "**/*.cmake" + - "cmake/**" + - "CMakePresets.json" + - "meson.build" + - "meson_options.txt" + - "premake5.lua" + +"A: Tooling": + - changed-files: + - any-glob-to-any-file: + - "tools/**" + - "benchmarks/**" + - "fuzzing/**" + - "integration/**" + - "examples/**" + - "showcase/**" + +"A: Numeric Proof": + - changed-files: + - any-glob-to-any-file: + - "docs/proofs/**" + - "docs/approximating_functions/sollya/**" + - "docs/approximating_functions/gappa/**" + +"T: Tests": + - changed-files: + - any-glob-to-any-file: + - "tests/**" + +"T: Documentation": + - changed-files: + - any-glob-to-any-file: + - "**/*.md" + - "docs/**" + +"Changes: Public API": + - changed-files: + - any-glob-to-any-file: + - "include/ccmath/*.hpp" + - "include/ccmath/math/**" + - "include/ccmath/ext/**" diff --git a/.github/workflows/labeler-branch.yml b/.github/workflows/labeler-branch.yml new file mode 100644 index 00000000..9703c838 --- /dev/null +++ b/.github/workflows/labeler-branch.yml @@ -0,0 +1,38 @@ +name: "Labels: Branch target" + +on: + pull_request_target: + types: + - opened + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-branch-label: + runs-on: ubuntu-latest + + steps: + - name: Label by base branch + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const base = context.payload.pull_request.base.ref; + const label = base.startsWith('release/') + ? 'Branch: release' + : (base === 'main' ? 'Branch: main' : null); + + if (!label) { + core.info(`No branch label for base ${base}`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: [label] + }); diff --git a/.github/workflows/labeler-changes-requested.yml b/.github/workflows/labeler-changes-requested.yml new file mode 100644 index 00000000..21f33074 --- /dev/null +++ b/.github/workflows/labeler-changes-requested.yml @@ -0,0 +1,65 @@ +name: "Labels: Awaiting Changes" + +on: + pull_request_review: + types: + - submitted + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-awaiting-changes: + if: github.event.review.state == 'changes_requested' + runs-on: ubuntu-latest + + steps: + - name: Add Awaiting Changes for trusted reviewers + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const review = context.payload.review; + const pr = context.payload.pull_request; + if (!review?.user?.login || pr?.number == null) { + core.info('Missing review user or pull request, skipping.'); + return; + } + + const reviewUser = review.user.login; + const issue_number = pr.number; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: reviewUser + }); + + const allowed = new Set(['admin', 'maintain', 'write']); + + if (!allowed.has(permission.data.permission)) { + core.info(`${reviewUser} does not have sufficient repo permissions`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Awaiting Changes'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Approved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-conflict.yml b/.github/workflows/labeler-conflict.yml new file mode 100644 index 00000000..4abd62a9 --- /dev/null +++ b/.github/workflows/labeler-conflict.yml @@ -0,0 +1,27 @@ +name: Check Merge Conflicts + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + label: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Check for Merge Conflicts + uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 + with: + dirtyLabel: "S: Merge Conflict" + repoToken: ${{ secrets.GITHUB_TOKEN }} + commentOnDirty: This pull request has conflicts, please resolve those before we can evaluate the pull request. diff --git a/.github/workflows/labeler-needsreview.yml b/.github/workflows/labeler-needsreview.yml new file mode 100644 index 00000000..047f9b53 --- /dev/null +++ b/.github/workflows/labeler-needsreview.yml @@ -0,0 +1,44 @@ +name: "Labels: Needs Review" + +on: + pull_request_target: + types: + - opened + - review_requested + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + update-review-labels: + runs-on: ubuntu-latest + + steps: + - name: Add Needs Review and remove Awaiting Changes + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue_number = context.payload.pull_request.number; + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Needs Review'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Awaiting Changes' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-pr.yml b/.github/workflows/labeler-pr.yml new file mode 100644 index 00000000..e1d27f76 --- /dev/null +++ b/.github/workflows/labeler-pr.yml @@ -0,0 +1,19 @@ +name: "Labels: PR" + +on: + pull_request_target: + +permissions: + contents: read + pull-requests: write + +jobs: + labeler: + runs-on: ubuntu-latest + + steps: + - name: Apply path-based labels + uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler.yml diff --git a/.github/workflows/labeler-review.yml b/.github/workflows/labeler-review.yml new file mode 100644 index 00000000..aac085b0 --- /dev/null +++ b/.github/workflows/labeler-review.yml @@ -0,0 +1,65 @@ +name: "Labels: Approved" + +on: + pull_request_review: + types: + - submitted + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-approved-label: + if: github.event.review.state == 'approved' + runs-on: ubuntu-latest + + steps: + - name: Add Approved label for trusted reviewers + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const review = context.payload.review; + const pr = context.payload.pull_request; + if (!review?.user?.login || pr?.number == null) { + core.info('Missing review user or pull request, skipping.'); + return; + } + + const reviewUser = review.user.login; + const issue_number = pr.number; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: reviewUser + }); + + const allowed = new Set(['admin', 'maintain', 'write']); + + if (!allowed.has(permission.data.permission)) { + core.info(`${reviewUser} does not have sufficient repo permissions`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Approved'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Needs Review' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-size.yml b/.github/workflows/labeler-size.yml new file mode 100644 index 00000000..e7c83595 --- /dev/null +++ b/.github/workflows/labeler-size.yml @@ -0,0 +1,29 @@ +name: "Labels: Size" + +on: + pull_request_target: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + size-label: + runs-on: ubuntu-latest + + steps: + - name: Apply size label + uses: pascalgn/size-label-action@56b489b027932ec0cf60438a1a5f1a19c8fc71ff # v0.5.7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Values are suffixes only. The action applies labels as size/ (size/XS, size/S, and so on). + sizes: > + { + "0": "XS", + "10": "S", + "100": "M", + "1000": "L", + "5000": "XL" + } diff --git a/.github/workflows/labeler-stale.yml b/.github/workflows/labeler-stale.yml new file mode 100644 index 00000000..d01f1c58 --- /dev/null +++ b/.github/workflows/labeler-stale.yml @@ -0,0 +1,30 @@ +name: "Labels: Stale" + +on: + schedule: + - cron: "0 6 * * *" + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + stale: + runs-on: ubuntu-latest + + steps: + - name: Mark inactive issues and pull requests + uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: -1 + stale-issue-label: "S: Stale" + stale-pr-label: "S: Stale" + remove-stale-when-updated: true + exempt-issue-labels: "P1: Critical, D0: Irrational, S: Blocked" + exempt-pr-labels: "P1: Critical, S: Blocked, S: Approved" + stale-issue-message: "This issue has been inactive for 60 days and is now marked stale. Comment to keep it open." + stale-pr-message: "This pull request has been inactive for 60 days and is now marked stale. Push or comment to keep it open." diff --git a/.github/workflows/labeler-untriaged.yml b/.github/workflows/labeler-untriaged.yml new file mode 100644 index 00000000..840bdf50 --- /dev/null +++ b/.github/workflows/labeler-untriaged.yml @@ -0,0 +1,79 @@ +name: "Labels: Untriaged" + +on: + issues: + types: + - opened + - labeled + pull_request_target: + types: + - opened + - labeled + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-untriaged: + if: github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - name: Add Untriaged label when no labels exist + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labels = + context.payload.issue?.labels ?? + context.payload.pull_request?.labels ?? + []; + + if (labels.length !== 0) { + core.info('Issue or pull request already has labels'); + return; + } + + const issue_number = + context.payload.issue?.number ?? + context.payload.pull_request?.number; + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Untriaged'] + }); + + remove-untriaged-when-other-label-added: + if: "github.event.action == 'labeled' && github.event.label.name != 'S: Untriaged'" + runs-on: ubuntu-latest + + steps: + - name: "Remove S: Untriaged when any other label is added" + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const untriaged = 'S: Untriaged'; + const item = + context.payload.issue ?? context.payload.pull_request; + if (!item) { + core.setFailed('No issue or pull request in payload'); + return; + } + + const hasUntriaged = item.labels.some(l => l.name === untriaged); + if (!hasUntriaged) { + core.info('S: Untriaged not present, nothing to remove'); + return; + } + + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: item.number, + name: untriaged + }); From 18fa29f68a1e163299ac3db94f3410f72ec08483 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 19:37:16 -0400 Subject: [PATCH 13/52] Add issue and PR templates and main-branch guard --- .github/ISSUE_TEMPLATE/bug_report.yml | 207 +++++++++++++++++---- .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 51 +++++ .github/PULL_REQUEST_TEMPLATE.md | 31 ++- .github/workflows/close-main-pr.yml | 30 +++ 5 files changed, 287 insertions(+), 40 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/workflows/close-main-pr.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 116b710e..fb642b46 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,76 +1,205 @@ name: Bug Report -description: Create a report to help us improve +description: Report incorrect results, build failures, or other defects in ccmath title: '[BUG]: ' -labels: ['bug'] +labels: ['T: Bugfix', 'S: Untriaged'] body: - type: checkboxes attributes: - label: Due Diligence - description: Before you submit a bug report, please check the following + label: Due diligence options: - - label: I have searched for similar issues before starting this bug report + - label: I searched existing issues and did not find a duplicate required: true + - label: I reproduced this against the latest main (or a tagged release) + required: true + + - type: input + id: function + attributes: + label: Affected function(s) + description: 'The ccm:: function(s) involved.' + placeholder: ccm::pow, ccm::sin + validations: + required: true + + - type: dropdown + id: family + attributes: + label: Function family + options: + - basic + - compare + - expo + - fmanip + - hyper + - misc + - nearest + - power + - special + - trig + - not sure + validations: + required: true + + - type: dropdown + id: code-path + attributes: + label: Code path + description: Where the wrong result comes from. Select all that apply. + multiple: true + options: + - constexpr (compile-time) + - runtime (scalar kernel) + - SIMD (vectorized) + - deterministic mode (CCMATH_ENABLE_DETERMINISTIC) + - not sure + validations: + required: true + + - type: dropdown + id: scalar-type + attributes: + label: Scalar type + options: + - float + - double + - long double + - integral + - multiple / not applicable + validations: + required: true + - type: textarea id: description attributes: label: Describe the bug - description: A clear and concise description of what the bug is. - placeholder: e.g. ccm::sqrt(4) is incorrectly outputting 1.9999999... instead of 2.0 on clang-12 x86-64 + description: What is wrong, in one or two sentences. validations: required: true + + - type: textarea + id: input + attributes: + label: Input value(s) + description: Exact inputs. Prefer hex float so no precision is lost. + placeholder: 'x = 0x1.0000000000001p+0 (1.0000000000000002)' + validations: + required: true + + - type: input + id: expected + attributes: + label: Expected result (and source of truth) + placeholder: '0x1.5bf0a8b145769p+1, per MPFR / the C++ standard' + validations: + required: true + + - type: input + id: actual + attributes: + label: Actual result + placeholder: '0x1.5bf0a8b14576ap+1 (1 ULP high)' + validations: + required: true + + - type: input + id: error + attributes: + label: Error magnitude (ULP) if known + validations: + required: false + + - type: dropdown + id: rounding-mode + attributes: + label: Rounding mode + options: + - FE_TONEAREST (default) + - FE_DOWNWARD + - FE_UPWARD + - FE_TOWARDZERO + - unchanged / not sure + validations: + required: true + - type: textarea id: reproduction attributes: - label: Steps to reproduce - description: Steps used to reproduce the bug. + label: Minimal reproduction + description: A self-contained snippet. A Godbolt link is ideal. Print exact bits, not via raw std::cout. + render: cpp placeholder: | - 1. Set the compiler to clang-12 on x86-64 - 2. Write the following code: - ```cpp - #include // std::cout and std::endl + #include #include int main() { - std::cout << ccm::sqrt(4) << std::endl; + printf("%a\n", ccm::sqrt(4.0)); // expected 0x1p+1 } - ``` - 3. Execute the program - 4. See the output validations: required: true - - type: textarea - id: expected-behavior + + - type: markdown attributes: - label: Expected behavior - description: A clear and concise description of what you expected to happen. - placeholder: The output should be outputting 2.0 as this is the square root of 4, but it is outputting 1.9999999... instead. + value: '## Environment' + + - type: input + id: ccmath-version + attributes: + label: ccmath version or commit + placeholder: v0.2.0 or commit 65cb8ba9 validations: required: true - - type: markdown - attributes: - value: '# System information' + - type: input - id: compiler-version-and-type + id: compiler attributes: - label: Compiler version and type - description: The name of your compile and it's version - placeholder: GCC 13.2.1 (20230801) + label: Compiler and version + placeholder: Clang 18.1.0 validations: required: true + + - type: dropdown + id: cpp-standard + attributes: + label: C++ standard + options: + - 'C++17' + - 'C++20' + - 'C++23' + - 'C++26' + validations: + required: true + + - type: dropdown + id: arch + attributes: + label: Architecture + options: + - x86-64 + - x86 + - AArch64 / ARM + - RISC-V + - other + validations: + required: true + + - type: input + id: opt-level + attributes: + label: Optimization level and relevant flags + placeholder: '-O2 -ffp-contract=fast' + validations: + required: false + - type: input - id: operating-system + id: os attributes: - label: Operating System - description: - If applicable (i.e, an issue with how the code compiles for your OS) the OS version this bug is happening in - placeholder: Ubuntu Linux 22.04 + label: Operating system + placeholder: Ubuntu 24.04 validations: required: false + - type: textarea - id: screenshots + id: context attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. - placeholder: As you can see here, the output is not showing up - value: Not applicable + label: Additional context validations: required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d6963368 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Question or design discussion + url: https://discord.gg/p3mVxAbdmc + about: Usage questions, design discussion, and general help belong on Discord. + - name: Security vulnerability + url: https://github.com/Rinzii/ccmath/security/policy + about: Report security issues privately as described in SECURITY.md. Do not open a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..df8a983b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,51 @@ +name: Feature Request +description: Propose a new function, behavior, or capability for ccmath +title: '[FEATURE]: ' +labels: ['T: New Feature', 'S: Untriaged'] +body: + - type: checkboxes + attributes: + label: Due diligence + options: + - label: I searched existing issues and did not find a duplicate + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: What should ccmath do that it does not do today. + validations: + required: true + + - type: input + id: function + attributes: + label: Function or area + description: The function or family this concerns, if applicable. + placeholder: ccm::lgamma, or trig + validations: + required: false + + - type: textarea + id: standard + attributes: + label: Standard reference + description: The relevant C++ standard clause or cppreference page, if this maps to a standard function. + validations: + required: false + + - type: textarea + id: motivation + attributes: + label: Motivation + description: The problem this solves and why it belongs in ccmath. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a36d61a..84e11c2f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1 +1,30 @@ - + + +## Summary + + + +## How tested + + + +## Checklist + +- [ ] Targets `main` (or a `release/N.x` branch for a backport) +- [ ] Tests added or updated in this change +- [ ] No `` include added under `include/` +- [ ] Formatting and lint pass locally + +### For numeric or kernel changes + + + +- [ ] Standard edge cases handled (NaN, signed zero, infinities, domain and range) +- [ ] Accuracy within the 4 ULP policy, validated against an oracle (MPFR, Sollya, or Gappa) with the source of truth noted +- [ ] Any accuracy or rounding-mode compromise is documented at the function with a `TODO` and a tracking issue +- [ ] Boundary inputs tested, not a single point (overflow, underflow, subnormals) +- [ ] The test reaches the changed kernel via `constexpr`/`static_assert` or a direct `ccm::gen::*_gen` call, not only a runtime call that may resolve to a builtin +- [ ] Every instantiated type covered (`float` and `double` at a minimum) +- [ ] Approach agreed on the issue first, for work labeled `D0`/`D1` or a numeric-rigor `A:` area + + diff --git a/.github/workflows/close-main-pr.yml b/.github/workflows/close-main-pr.yml new file mode 100644 index 00000000..ec74bdba --- /dev/null +++ b/.github/workflows/close-main-pr.yml @@ -0,0 +1,30 @@ +# Close PRs that were opened from a main development branch instead of a topic branch. + +name: Close PRs from main branches + +on: + pull_request_target: + types: + - opened + - ready_for_review + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + run: + if: ${{ (github.head_ref == 'main' || github.head_ref == 'master' || github.head_ref == 'develop') && github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + + steps: + - name: Close PR opened from a main development branch + uses: superbrothers/close-pull-request@9c18513d320d7b2c7185fb93396d0c664d5d8448 # v3 + with: + comment: | + Thank you for your contribution. + + It looks like this pull request was opened from a main development branch. That is something you should generally avoid, so this pull request has been automatically closed. + + Please move your work to a separate branch, then reopen the pull request from that branch. From 769860a20fc4330506b0c174c43f9d42ff2ced0b Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 19:37:36 -0400 Subject: [PATCH 14/52] Document contribution workflow and performance goals --- CONTRIBUTING.md | 162 +++++++++++++++++++++++++++++++++++++++++++++++- README.md | 9 ++- 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cff1c126..fe4bac78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Apache-2.0 WITH LLVM-exception. ## Core tenets 1. Prefer constexpr evaluation where the standard allows it (`gen` paths, `static_assert` smoke). -2. Match `` observable behavior. Accuracy targets are documented in tests, typically within 4 ULP of the platform libm unless noted. +2. Match `` observable behavior. Correctly rounded in all four rounding modes is the goal, with documented compromises up to 4 ULP or fewer modes allowed to ship sooner. See [Accuracy and rounding goals](#accuracy-and-rounding-goals). 3. Keep runtime performance in the same ballpark as the platform libm on covered paths. 4. Follow the existing dispatch layout (`builtin` / `gen` / `rt`, or impl-direct where the family already does). 5. Prefer portable generic kernels over platform forks when both satisfy the standard. @@ -22,6 +22,36 @@ Apache-2.0 WITH LLVM-exception. 8. Doxygen on exported entry points (`@brief`, `@param`, `@return`). 9. Keep the public header layout aligned with [cppreference ``](https://en.cppreference.com/w/cpp/header/cmath). +## Accuracy and rounding goals + +The end goal for every function is a correctly rounded result, bit-for-bit the +infinitely precise value rounded to the destination type, in all four IEEE +rounding modes (`FE_TONEAREST`, `FE_DOWNWARD`, `FE_UPWARD`, `FE_TOWARDZERO`). That +is the bar the library is held to over time. + +We accept documented compromises when they get working, in-contract functionality +out sooner: + +- A function that is not yet correctly rounded may land with a stated, tested + error bound of no more than 4 ULP. The ULP suites under `tests/shared/utils` + check against the platform libm, and an MPFR oracle checks against the correctly + rounded value where one is targeted. +- A function may support fewer than the four rounding modes. Landing + `FE_TONEAREST` first, with the directed modes to follow, is fine. + +A compromise only counts if it is written down. State the current ULP bound and +the supported rounding modes at the function, say what is missing, and leave a +`TODO():` plus a tracking issue so the gap stays visible. An +undocumented shortfall is a bug. A documented one is a known limitation we can +close later. + +Accuracy and standards conformance are the top priority. Performance is the next +priority after that. Once a function is correct it should stay in the same +ballpark as the platform libm on covered paths, and the usual shape is a fast +path with an accurate fallback rather than a single slow path. When the two +genuinely conflict, correctness wins, but reach for a layered design before +trading away speed. + ## First PR ```bash @@ -46,6 +76,136 @@ Implementation work should stay under `include/ccmath/` using the patterns alrea present in the function family you touch. Do not add `` includes under `include/`. +## Issue and pull request workflow + +### Branches + +`main` is the development trunk and the target for almost all PRs. `release/N.x` +branches carry stabilization for a given minor series. Open fixes against `main` +first. A maintainer backports to a release branch when it applies. Never +force-push `main`. + +### Releases and tags + +Releases are annotated tags in the form `vMAJOR.MINOR.PATCH`, for example +`v0.2.0`. Release candidates use the semver pre-release form +`vMAJOR.MINOR.PATCH-rc.N`, for example `v0.3.0-rc.1`. While the project is below +`1.0`, a minor bump may carry breaking API changes. + +### Labels + +Issues and PRs use a prefixed label taxonomy. Maintainers apply most of these +during triage. The groups are: + +- `T:` type of work (`T: Bugfix`, `T: New Feature`, `T: Performance`, `T: Refactor`, `T: Cleanup`, `T: Documentation`, `T: Tests`, `T: Chore`) +- `A:` area, covering both function families (`A: Trig`, `A: Power`, `A: Expo`, and the rest) and numeric or implementation axes (`A: Correctly Rounded`, `A: Accuracy/ULP`, `A: Range Reduction`, `A: Rounding Modes`, `A: Edge Cases`, `A: Constexpr`, `A: Runtime Kernel`, `A: SIMD`, `A: Deterministic`, `A: Numeric Proof`, `A: Build`, `A: Tooling`) +- `D:` difficulty, from `D3: Low` for approachable work up to `D0: Irrational` for hard-to-round problems that may take unreasonable effort +- `P:` priority, from `P1: Critical` to `P4: Low` +- `S:` status through review (`S: Untriaged`, `S: Needs Discussion`, `S: Needs Review`, `S: Approved`, `S: Awaiting Changes`, `S: Blocked`, `S: Merge Conflict`, `S: Stale`, `S: DO NOT MERGE`, `S: Won't Fix`) +- `Bug:` reproduction state (`Bug: Needs Replicating`, `Bug: Replicated`) +- `Platform:`, `Arch:`, and `Compiler:` for environment-specific reports +- `Branch:` for the branch a PR targets (`Branch: main`, `Branch: release`) +- `Changes:` for the surface a PR touches (`Changes: Public API`, `Changes: Headers Only`) +- `size/` for PR size, from `size/XS` to `size/XL` + +A contributor looking for a starting point should filter on `D3: Low` and +`good first issue`. + +### Filing an issue + +Bug reports use the form under `.github/ISSUE_TEMPLATE`. For a numeric defect, +give the exact function, the affected code path (constexpr, runtime, SIMD, or +deterministic mode), the scalar type, and the exact input, expected, and actual +values. Hex float literals such as `0x1.0p+2` avoid precision loss. State the +active rounding mode and the source of truth for the expected result (MPFR, the +C++ standard, or a reference libm). New bug reports start as `T: Bugfix` and +`S: Untriaged`. + +### Agree the approach first + +Hard numeric work is design-reviewed on the issue before it is implemented. If an +issue sits at the hard end of the `D:` scale (`D0` or `D1`) or carries +`A: Correctly Rounded`, `A: Accuracy/ULP`, `A: Rounding Modes`, or +`A: Range Reduction`, comment on the issue with your intended approach and the +exact inputs you plan to validate against, and wait for a maintainer to ack it +before opening a non-draft PR. A draft PR holding only the acceptance tests and a +validation plan is a good way to start that conversation, and a maintainer may +move the issue to `S: Needs Discussion` while it is settled. The point is to +catch a wrong method in a short comment rather than in a finished kernel, so you +do not pour effort into an approach that has to be redone. + +### Opening a pull request + +PRs target `main` unless the fix is specific to a release branch. Keep the +change minimal and aligned with the function family you touch. Add tests in the +same change. Use a one-line imperative subject with no trailing period. + +Before you request review, read your own diff once outside the editor, make sure +the warning-as-error build and the test presets are green, and write a short +summary of what changed and why. Small focused PRs get reviewed faster and more +carefully than large ones, so split unrelated work and leave drive-by refactors +to their own change. Once review starts, answer every comment, even if only to +say it is done. When a reviewer misreads the code, prefer making the code or a +why comment clearer over explaining only in the thread, since the next reader +sees the code and not the discussion. + +### Testing math changes + +Tests land in the same change as the code. A few specifics matter for math +kernels and are easy to get wrong: + +- Exercise the code you changed. A public call such as `ccm::pow` can resolve to + a compiler builtin at runtime, so a plain runtime test may never reach the + ccmath kernel. Drive the kernel through a `constexpr` context (a + `static_assert`) or by calling the generic kernel directly (`ccm::gen::*_gen`). +- Validate numeric changes against an oracle. Use MPFR, Sollya, or Gappa and note + the source of truth rather than eyeballing a value or trusting a system libm at + a boundary. The MPFR oracle harnesses under `tests/shared/oracle` are the + pattern to follow. +- Test boundaries, not a single point. Cover overflow, underflow, subnormals, and + signed zero, along with the worst-case inputs for the function, not just one + representative value. +- Cover every type the function is instantiated for, `float` and `double` at a + minimum. +- For exception or errno behavior, use the fenv fixture at + `tests/shared/utils/fenv_fixture.hpp`. The ULP and conformance suites live under + `tests/shared/utils`. + +### Reviewing a pull request + +A review is a dialogue, not a verdict. Ask rather than demand, assume the author +acted in good faith, and talk about the code as ours rather than yours. Give real +credit when something is done well. + +Mark each comment as blocking or a nit so the author knows what gates the merge. +A wrong result, a standards violation, a red build, and a claim the change does +not actually deliver are blocking. Style, naming, and scoping are nits, and any +reviewer can raise those, while contract and architecture calls are left to a +maintainer. + +Back a numeric claim with evidence. A failing input and its correctly-rounded +value from MPFR, a worst-case grid result, or a link to the algorithm reference +carries more than an assertion. Read the tests first, since the most common gap +here is a test that never reaches the kernel or only checks a single point. Do +not gatekeep past the accuracy policy. In-contract and honest is the bar, not +sub-ULP perfection everywhere. If a thread keeps going back and forth, move it to +a synchronous chat and write up the outcome. + +### Automation + +Several labels are applied for you, so you do not need to set them by hand: + +- `A:` area labels from the files a PR changes, plus `Changes: Public API` +- `size/` from the number of lines changed +- `Branch: main` or `Branch: release` from the base branch +- `S: Untriaged` on new issues and PRs that open without labels, cleared once any other label is added +- `S: Merge Conflict` when a PR develops conflicts +- `S: Needs Review`, `S: Approved`, and `S: Awaiting Changes` as a maintainer review progresses +- `S: Stale` after 60 days of inactivity, removed on the next update + +PRs opened from a fork's `main`, `master`, or `develop` branch are closed +automatically with a note to move the work to a topic branch. + ## Build from the command line ### Prerequisites diff --git a/README.md b/README.md index 6309026c..b4bedb4b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ CCMath targets: - Runtime execution - SIMD acceleration - Numerical correctness +- Competitive performance - Cross-platform portability These goals overlap with the comparison columns plus numerical validation. Per-function progress is tracked in [STATUS.md](docs/STATUS.md). @@ -98,10 +99,14 @@ Selected functions pick up SIMD on supported targets: ### Numerical Correctness -We aim for correct rounding under all four IEEE rounding modes. Coverage is function-by-function: some paths are validated today under all modes, while other functions still target round-to-nearest ties-to-even first. +The goal for every function is a correctly rounded result under all four IEEE rounding modes. Coverage is function-by-function: some paths are validated today under all modes, while other functions still target round-to-nearest ties-to-even first, or land within a documented bound of no more than 4 ULP while the correctly rounded version is in progress. ULP harnesses, all-mode rounding probes, worst-case grids, and cross-compiler CI matrices live in-tree. The validation section below describes how they run in practice. +### Performance + +After numerical correctness, performance is the next priority. Once a function is correct it is expected to stay in the same ballpark as the platform libm on covered paths, with a fast path plus an accurate fallback rather than a single slow path. Benchmarks comparing `ccm::*` against the platform libm live in-tree. + --- ## Function Coverage @@ -213,7 +218,7 @@ CodeQL, OpenSSF Scorecard, warning-as-error builds, and strict compiler warning ### Style and Consistency -clang-format runs in CI on every platform matrix job and on pull requests that touch headers. clang-tidy is available locally via lint.sh and lint.bat. +clang-format runs in CI on every platform matrix job and on pull requests that touch C++ sources. clang-tidy is available locally via lint.sh and lint.bat. --- From dc914cf4d1b0d9719f171fa0c6f68feb9b71aacd Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 20:48:47 -0400 Subject: [PATCH 15/52] Consolidate cast checks into clang-tidy and skip vendored code --- .github/workflows/lint.yml | 24 +----- .../internal/support/math_support_test.cpp | 4 +- .../src/math/internal/types/big_int_test.cpp | 9 ++- tools/ensure_cast_style.sh | 66 ---------------- tools/ensure_cast_style_repo.sh | 75 ------------------- tools/run_clang_tidy.sh | 45 +++++++++-- 6 files changed, 50 insertions(+), 173 deletions(-) delete mode 100755 tools/ensure_cast_style.sh delete mode 100755 tools/ensure_cast_style_repo.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5b3b4fcb..ef24bd6e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,8 +17,6 @@ on: - 'tools/clang_format_version' - 'tools/ensure_format.sh' - 'tools/clang_tidy_version' - - 'tools/ensure_cast_style.sh' - - 'tools/ensure_cast_style_repo.sh' - 'tools/install_clang_tidy_linux.sh' - 'tools/run_clang_tidy.sh' - '.clang-tidy' @@ -84,21 +82,7 @@ jobs: if: steps.check_format.outcome == 'failure' run: bash tools/ensure_format.sh - cast_style_check: - if: github.event_name == 'pull_request' - permissions: - contents: read - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - name: Install clang-tidy - run: bash tools/install_clang_tidy_linux.sh - - - name: Check cast style (library headers) - run: bash tools/ensure_cast_style.sh - - cast_style_repo_check: + clang_tidy: if: github.event_name == 'pull_request' permissions: contents: read @@ -122,9 +106,5 @@ jobs: -DCCMATH_ENABLE_SEARCH_TOOLS=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - - name: Check cast style (entire repo) - run: CCM_CAST_COMPILE_DB=build-tidy/compile_commands.json bash tools/ensure_cast_style_repo.sh - - - name: Run clang-tidy (advisory) - continue-on-error: true + - name: Run clang-tidy run: bash tools/run_clang_tidy.sh build-tidy/compile_commands.json diff --git a/tests/src/math/internal/support/math_support_test.cpp b/tests/src/math/internal/support/math_support_test.cpp index 08a3d71a..2a638dbb 100644 --- a/tests/src/math/internal/support/math_support_test.cpp +++ b/tests/src/math/internal/support/math_support_test.cpp @@ -119,7 +119,7 @@ TEST(CcmathInternalSupportTests, AddWithCarryUint8MatchesReferenceExhaustively) { for (int rhs = 0; rhs <= 0xFF; ++rhs) { - for (std::uint8_t carry_in : { std::uint8_t(0), std::uint8_t(1) }) + for (std::uint8_t carry_in : { static_cast(0), static_cast(1) }) { std::uint8_t carry_out = 0; const std::uint8_t actual = @@ -139,7 +139,7 @@ TEST(CcmathInternalSupportTests, SubWithBorrowUint8MatchesReferenceExhaustively) { for (int rhs = 0; rhs <= 0xFF; ++rhs) { - for (std::uint8_t borrow_in : { std::uint8_t(0), std::uint8_t(1) }) + for (std::uint8_t borrow_in : { static_cast(0), static_cast(1) }) { std::uint8_t borrow_out = 0; const std::uint8_t actual = diff --git a/tests/src/math/internal/types/big_int_test.cpp b/tests/src/math/internal/types/big_int_test.cpp index 74f8cea9..8559f9c3 100644 --- a/tests/src/math/internal/types/big_int_test.cpp +++ b/tests/src/math/internal/types/big_int_test.cpp @@ -498,7 +498,14 @@ namespace out.push_back(carry_chain); RefUint sparse = RefUint::zero(); - for (std::size_t bit : { std::size_t(0), std::size_t(1), std::size_t(31), std::size_t(32), Bits / 2 - 1, Bits / 2, Bits - 2, Bits - 1 }) + for (std::size_t bit : { static_cast(0), + static_cast(1), + static_cast(31), + static_cast(32), + Bits / 2 - 1, + Bits / 2, + Bits - 2, + Bits - 1 }) { sparse.set_bit(bit); } diff --git a/tools/ensure_cast_style.sh b/tools/ensure_cast_style.sh deleted file mode 100755 index 63bfb7ee..00000000 --- a/tools/ensure_cast_style.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -# -# Copyright (c) Ian Pike -# Copyright (c) CCMath contributors -# -# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. -# See LICENSE for more information. -# -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# -# Check that the internal headers use no C-style casts. -# The public front-facing API under ext/ and math/ is exempt from clang-tidy. -# clang-tidy finds them. Add // NOLINT(google-readability-casting) if one is unavoidable. -# Headers that do not parse on their own are skipped. The .cpp files are checked by -# ensure_cast_style_repo.sh. - -set -euo pipefail - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -expected_version="$(tr -d '[:space:]' < "${repo_root}/tools/clang_tidy_version")" -CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" - -cstyle_checks='-*,google-readability-casting,cppcoreguidelines-pro-type-cstyle-cast' -compile_args=(-std=c++17 -Iinclude -xc++ -Wno-pragma-once-outside-header) - -if ! "${CLANG_TIDY}" --version | grep -Fq "${expected_version}"; then - echo "${CLANG_TIDY} version mismatch: expected ${expected_version}" >&2 - "${CLANG_TIDY}" --version >&2 - exit 1 -fi - -if ! find include/ccmath/internal -name '*.hpp' -print -quit | grep -q .; then - echo "No header files found under include/ccmath/internal." - exit 0 -fi - -violations=0 -skipped=0 - -while IFS= read -r -d '' file; do - tidy_out="$("${CLANG_TIDY}" "${file}" --quiet --checks="${cstyle_checks}" -- "${compile_args[@]}" 2>&1 || true)" - - # Skip headers that do not parse on their own. - if printf '%s' "${tidy_out}" | grep -q 'clang-diagnostic-error'; then - echo "Skipped (does not parse standalone): ${file}" - skipped=$((skipped + 1)) - continue - fi - - if printf '%s' "${tidy_out}" | grep -q 'C-style casts are discouraged'; then - echo "C-style cast (forbidden): ${file}" - printf '%s\n' "${tidy_out}" | grep -E 'warning: C-style casts' || true - violations=$((violations + 1)) - fi -done < <(find include/ccmath/internal -name '*.hpp' -print0) - -[[ "${skipped}" -gt 0 ]] && echo && echo "Skipped ${skipped} header(s) that do not parse standalone (see above)." - -if [[ "${violations}" -gt 0 ]]; then - echo - echo "C-style casts found in ${violations} file(s)." - echo "Use static_cast/bit_cast, or // NOLINT(google-readability-casting) if absolutely required." - exit 1 -fi - -echo "Cast style is up to date." diff --git a/tools/ensure_cast_style_repo.sh b/tools/ensure_cast_style_repo.sh deleted file mode 100755 index 56adc58a..00000000 --- a/tools/ensure_cast_style_repo.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash -# -# Copyright (c) Ian Pike -# Copyright (c) CCMath contributors -# -# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. -# See LICENSE for more information. -# -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# -# Check the whole repo for C-style casts using a compile database. -# This covers the .cpp files that only build with their real flags. -# Make the database with cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON and pass it as -# CCM_CAST_COMPILE_DB or the first argument. -# Add // NOLINT(google-readability-casting) if a cast is unavoidable. - -set -euo pipefail - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -expected_version="$(tr -d '[:space:]' < "${repo_root}/tools/clang_tidy_version")" -CLANG_TIDY="${CLANG_TIDY:-clang-tidy}" -db="${CCM_CAST_COMPILE_DB:-build/compile_commands.json}" - -cstyle_checks='-*,google-readability-casting,cppcoreguidelines-pro-type-cstyle-cast' -header_filter="${repo_root}/(include|tests|fuzzing|benchmark|benchmarks|tools)/" -# The public front-facing API under ext/ and math/ is exempt from clang-tidy. -exclude_public="${repo_root}/include/ccmath/(ext|math)/" - -if [[ ! -f "${db}" ]]; then - echo "No compile database at '${db}'." >&2 - echo "Configure a build with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON and set CCM_CAST_COMPILE_DB." >&2 - exit 1 -fi - -if ! "${CLANG_TIDY}" --version | grep -Fq "${expected_version}"; then - echo "${CLANG_TIDY} version mismatch: expected ${expected_version}" >&2 - "${CLANG_TIDY}" --version >&2 - exit 1 -fi - -db_dir="$(cd "$(dirname "${db}")" && pwd)" - -violations=0 - -while IFS= read -r -d '' tu; do - out="$("${CLANG_TIDY}" -p "${db_dir}" "${tu}" --quiet \ - --checks="${cstyle_checks}" --header-filter="${header_filter}" \ - --exclude-header-filter="${exclude_public}" 2>&1 || true)" - if printf '%s' "${out}" | grep -q 'C-style casts are discouraged'; then - echo "C-style cast (forbidden): ${tu}" - printf '%s\n' "${out}" | grep -E 'warning: C-style casts' || true - violations=$((violations + 1)) - fi -done < <(python3 - "${db}" "${repo_root}" <<'PY' -import json, os, sys -db_path, root = sys.argv[1], os.path.realpath(sys.argv[2]) -with open(db_path) as f: - entries = json.load(f) -seen = set() -for e in entries: - p = os.path.realpath(os.path.join(e.get("directory", ""), e["file"])) - if p.startswith(root + os.sep) and p not in seen: - seen.add(p) - sys.stdout.write(p + "\0") -PY -) - -if [[ "${violations}" -gt 0 ]]; then - echo - echo "C-style casts found in ${violations} translation unit(s)." - echo "Use static_cast/bit_cast, or // NOLINT(google-readability-casting) if absolutely required." - exit 1 -fi - -echo "No C-style casts in the compiled translation units." diff --git a/tools/run_clang_tidy.sh b/tools/run_clang_tidy.sh index 4cc5f594..8e545466 100755 --- a/tools/run_clang_tidy.sh +++ b/tools/run_clang_tidy.sh @@ -9,7 +9,9 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # # Run clang-tidy over a compile database using the project .clang-tidy config. -# Advisory only. Prints warnings and always exits 0. +# Most checks are advisory, but cast style is enforced: any C-style cast fails the run. +# Vendored and generated code (the build tree, _deps, thirdparty, integration) is not linted. +# Add // NOLINT(google-readability-casting) if a cast is genuinely required. set -euo pipefail @@ -27,19 +29,48 @@ fi db_dir="$(cd "$(dirname "${db}")" && pwd)" +cast_violations=0 + while IFS= read -r -d '' tu; do - "${CLANG_TIDY}" -p "${db_dir}" "${tu}" --quiet --header-filter="${header_filter}" \ - --exclude-header-filter="${exclude_public}" 2>&1 || true + out="$("${CLANG_TIDY}" -p "${db_dir}" "${tu}" --quiet --header-filter="${header_filter}" \ + --exclude-header-filter="${exclude_public}" 2>&1 || true)" + if [[ -n "${out}" ]]; then + printf '%s\n' "${out}" + fi + if printf '%s' "${out}" | grep -q 'C-style casts are discouraged'; then + cast_violations=$((cast_violations + 1)) + fi done < <(python3 - "${db}" "${repo_root}" <<'PY' import json, os, sys db_path, root = sys.argv[1], os.path.realpath(sys.argv[2]) +# The compile database lives in the build tree, which also holds _deps and generated code. +db_dir = os.path.dirname(os.path.realpath(db_path)) + + +def is_vendored_or_generated(p): + if p.startswith(db_dir + os.sep): + return True + for seg in ("_deps", "thirdparty", "integration"): + if (os.sep + seg + os.sep) in p: + return True + return False + + seen = set() for e in json.load(open(db_path)): p = os.path.realpath(os.path.join(e.get("directory", ""), e["file"])) - if p.startswith(root + os.sep) and p not in seen: - seen.add(p) - sys.stdout.write(p + "\0") + if not p.startswith(root + os.sep) or is_vendored_or_generated(p) or p in seen: + continue + seen.add(p) + sys.stdout.write(p + "\0") PY ) -echo "clang-tidy advisory run complete." +if [[ "${cast_violations}" -gt 0 ]]; then + echo + echo "C-style casts found in ${cast_violations} translation unit(s)." + echo "Use static_cast/bit_cast, or // NOLINT(google-readability-casting) if absolutely required." + exit 1 +fi + +echo "clang-tidy run complete. No C-style casts." From 9c82406893ec5851b1e85d53765656a72e5f6d72 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 23:46:36 -0400 Subject: [PATCH 16/52] Add feature approval, label sync, and issue-form triage automation --- .github/labels.yml | 237 ++++++++++++++++++++++ .github/workflows/feature-approval.yml | 103 ++++++++++ .github/workflows/labeler-conflict.yml | 4 + .github/workflows/labeler-issue-form.yml | 64 ++++++ .github/workflows/labeler-needsreview.yml | 1 + .github/workflows/labeler-pr.yml | 4 + .github/workflows/labeler-size.yml | 4 + .github/workflows/labeler-stale.yml | 3 + .github/workflows/labeler-untriaged.yml | 10 +- .github/workflows/labels-sync.yml | 34 ++++ .github/workflows/lint-actions.yml | 38 ++++ CONTRIBUTING.md | 12 +- 12 files changed, 508 insertions(+), 6 deletions(-) create mode 100644 .github/labels.yml create mode 100644 .github/workflows/feature-approval.yml create mode 100644 .github/workflows/labeler-issue-form.yml create mode 100644 .github/workflows/labels-sync.yml create mode 100644 .github/workflows/lint-actions.yml diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..72c25f15 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,237 @@ +# Source of truth for repository labels. Synced by .github/workflows/labels-sync.yml. +# Edit here and open a PR; do not edit labels in the GitHub UI. + +- name: "A: Accuracy/ULP" + color: "d6fcff" + description: "Out-of-contract ULP error." +- name: "A: Basic" + color: "d6fcff" + description: "Area: basic math functions." +- name: "A: Build" + color: "2c1183" + description: "Build, CI, and project generation (CMake/Meson/premake)." +- name: "A: Compare" + color: "d6fcff" + description: "Area: comparison functions." +- name: "A: Constexpr" + color: "d6fcff" + description: "Compile-time evaluation path." +- name: "A: Correctly Rounded" + color: "d6fcff" + description: "Correct-rounding work or CR regressions." +- name: "A: Deterministic" + color: "d6fcff" + description: "CCMATH_ENABLE_DETERMINISTIC behavior." +- name: "A: Edge Cases" + color: "d6fcff" + description: "Sign of zero, NaN/inf, standards conformance." +- name: "A: Expo" + color: "d6fcff" + description: "Area: exponential and logarithmic functions." +- name: "A: Fmanip" + color: "d6fcff" + description: "Area: floating-point manipulation functions." +- name: "A: Hyper" + color: "d6fcff" + description: "Area: hyperbolic functions." +- name: "A: Misc" + color: "d6fcff" + description: "Area: miscellaneous functions." +- name: "A: Nearest" + color: "d6fcff" + description: "Area: rounding and nearest-integer functions." +- name: "A: Numeric Proof" + color: "d6fcff" + description: "Sollya/Gappa/MPFR validation owed or in progress." +- name: "A: Overflow/Underflow" + color: "d6fcff" + description: "Range and clamp behavior at the extremes." +- name: "A: Power" + color: "d6fcff" + description: "Area: power and root functions." +- name: "A: Range Reduction" + color: "d6fcff" + description: "Argument reduction (Payne-Hanek, Cody-Waite)." +- name: "A: Rounding Modes" + color: "d6fcff" + description: "Directed rounding and fenv flag behavior." +- name: "A: Runtime Kernel" + color: "d6fcff" + description: "Generic runtime path." +- name: "A: SIMD" + color: "d6fcff" + description: "Vectorized path." +- name: "A: Special" + color: "d6fcff" + description: "Area: special functions." +- name: "A: Tooling" + color: "d6fcff" + description: "Benchmarks, asmlab, and developer tooling." +- name: "A: Trig" + color: "d6fcff" + description: "Area: trigonometric functions." +- name: "Arch: AArch64" + color: "c2e0c6" + description: "AArch64 and ARM specific behavior." +- name: "Arch: RISC-V" + color: "c2e0c6" + description: "RISC-V specific behavior." +- name: "Arch: x86" + color: "c2e0c6" + description: "x86 and x86-64 specific behavior." +- name: "Branch: main" + color: "1d76db" + description: "Pull request targets the main trunk." +- name: "Branch: release" + color: "5319e7" + description: "Pull request targets a release/N.x branch." +- name: "Bug: Needs Replicating" + color: "0e8a16" + description: "Still needs reproduction by at least one other person." +- name: "Bug: Replicated" + color: "0e8a16" + description: "Reproduced by at least one other person." +- name: "Changes: Headers Only" + color: "c5def5" + description: "Changes are limited to header files." +- name: "Changes: Public API" + color: "c5def5" + description: "Changes touch the public API surface." +- name: "Compiler: Clang" + color: "bfd4f2" + description: "Clang-specific behavior or support." +- name: "Compiler: GCC" + color: "bfd4f2" + description: "GCC-specific behavior or support." +- name: "Compiler: MSVC" + color: "bfd4f2" + description: "MSVC-specific behavior or support." +- name: "D0: Irrational" + color: "c603fc" + description: "A cursed correctly-rounded or hard-to-round problem that may take unreasonable effort." +- name: "D1: High" + color: "b60205" + description: "Requires deep floating-point and codebase knowledge." +- name: "D2: Medium" + color: "fbca04" + description: "Requires a fair amount of numeric and codebase knowledge." +- name: "D3: Low" + color: "84dc3e" + description: "Reasonably approachable for newer contributors." +- name: "Feature: Approved" + color: "0e8a16" + description: "Feature request approved by an admin. Work may proceed." +- name: "Feature: Unapproved" + color: "fbca04" + description: "Feature request awaiting admin approval before work begins." +- name: "P1: Critical" + color: "b60205" + description: "Needs immediate attention." +- name: "P2: High" + color: "d93f0b" + description: "Important and should be addressed soon." +- name: "P3: Standard" + color: "fef2c0" + description: "Normal priority." +- name: "P4: Low" + color: "ededed" + description: "Lower priority and not time-sensitive." +- name: "Platform: Linux" + color: "5319e7" + description: "Linux-specific behavior or support." +- name: "Platform: Other" + color: "5319e7" + description: "Other platform-specific behavior or support." +- name: "Platform: Windows" + color: "5319e7" + description: "Windows-specific behavior or support." +- name: "Platform: macOS" + color: "5319e7" + description: "macOS-specific behavior or support." +- name: "S: Approved" + color: "0e8a16" + description: "Has an approved review." +- name: "S: Awaiting Changes" + color: "b60205" + description: "Needs changes before review continues." +- name: "S: Blocked" + color: "fbca04" + description: "Blocked by another issue or pull request." +- name: "S: DO NOT MERGE" + color: "b60205" + description: "Must not be merged." +- name: "S: Help Wanted" + color: "0e8a16" + description: "Additional help is wanted." +- name: "S: Merge Conflict" + color: "d73a4a" + description: "Has merge conflicts." +- name: "S: Needs Discussion" + color: "fbca04" + description: "Needs more discussion before action is clear." +- name: "S: Needs Review" + color: "fbca04" + description: "Ready for review." +- name: "S: Stale" + color: "888888" + description: "Has gone inactive." +- name: "S: Untriaged" + color: "ededed" + description: "Not yet triaged." +- name: "S: Won't Fix" + color: "9a5399" + description: "This will not be fixed." +- name: "T: Bugfix" + color: "fff7b0" + description: "Bugs and bug fixes." +- name: "T: Chore" + color: "562d4b" + description: "Routine maintenance and upkeep." +- name: "T: Cleanup" + color: "fff7b0" + description: "Code cleanup." +- name: "T: Documentation" + color: "fff7b0" + description: "Documentation changes." +- name: "T: New Feature" + color: "fff7b0" + description: "New functionality." +- name: "T: Performance" + color: "fff7b0" + description: "Performance issues or optimizations." +- name: "T: Refactor" + color: "fff7b0" + description: "Refactoring existing code." +- name: "T: Tests" + color: "91e343" + description: "Test additions, fixes, or coverage work." +- name: "ai-assisted" + color: "ededed" + description: "Contribution used AI assistance." +- name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists." +- name: "good first issue" + color: "7057ff" + description: "Good for newcomers." +- name: "invalid" + color: "e4e669" + description: "This does not seem right." +- name: "question" + color: "d876e3" + description: "Further information is requested." +- name: "size/L" + color: "ededed" + description: "Denotes a PR that changes 1000-4999 lines." +- name: "size/M" + color: "ededed" + description: "Denotes a PR that changes 100-999 lines." +- name: "size/S" + color: "ededed" + description: "Denotes a PR that changes 10-99 lines." +- name: "size/XL" + color: "ededed" + description: "Denotes a PR that changes 5000+ lines." +- name: "size/XS" + color: "ededed" + description: "Denotes a PR that changes 0-9 lines." diff --git a/.github/workflows/feature-approval.yml b/.github/workflows/feature-approval.yml new file mode 100644 index 00000000..133acee9 --- /dev/null +++ b/.github/workflows/feature-approval.yml @@ -0,0 +1,103 @@ +name: "Labels: Feature approval" + +on: + issues: + types: + - opened + - labeled + +permissions: + contents: read + issues: write + +jobs: + mark-unapproved: + if: "github.event.action == 'opened' || github.event.label.name == 'T: New Feature'" + runs-on: ubuntu-latest + + steps: + - name: Mark new feature requests as unapproved + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue = context.payload.issue; + const labels = issue.labels.map(l => l.name); + + if (!labels.includes('T: New Feature')) { + core.info('Not a feature request, nothing to do'); + return; + } + if (labels.includes('Feature: Approved')) { + core.info('Already approved, not marking unapproved'); + return; + } + if (labels.includes('Feature: Unapproved')) { + core.info('Already marked unapproved'); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['Feature: Unapproved'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: 'Thanks for the request. It is labeled `Feature: Unapproved` and waiting on a maintainer to approve it before implementation work begins.' + }); + + approve: + if: "github.event.action == 'labeled' && github.event.label.name == 'Feature: Approved'" + runs-on: ubuntu-latest + + steps: + - name: Clear unapproved when an admin approves + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const actor = context.actor; + const issue = context.payload.issue; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: actor + }); + + const admins = new Set(['admin', 'maintain']); + + if (!admins.has(permission.data.permission)) { + core.info(`${actor} is not an admin, reverting the approval`); + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'Feature: Approved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + return; + } + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'Feature: Unapproved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-conflict.yml b/.github/workflows/labeler-conflict.yml index 4abd62a9..030407f3 100644 --- a/.github/workflows/labeler-conflict.yml +++ b/.github/workflows/labeler-conflict.yml @@ -13,6 +13,10 @@ permissions: pull-requests: write issues: write +concurrency: + group: labeler-conflict-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: label: if: github.event.pull_request.draft == false diff --git a/.github/workflows/labeler-issue-form.yml b/.github/workflows/labeler-issue-form.yml new file mode 100644 index 00000000..c71b7c9c --- /dev/null +++ b/.github/workflows/labeler-issue-form.yml @@ -0,0 +1,64 @@ +name: "Labels: Issue form" + +on: + issues: + types: + - opened + - edited + +permissions: + contents: read + issues: write + +jobs: + autolabel: + if: "contains(github.event.issue.labels.*.name, 'T: Bugfix')" + runs-on: ubuntu-latest + + steps: + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Parse the bug report form + id: parse + uses: stefanbuck/github-issue-parser@cb6e97157cbf851e3a393ff8d57c93a484cc323f # v3 + with: + template-path: .github/ISSUE_TEMPLATE/bug_report.yml + + - name: Map form answers to labels + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + env: + FORM_JSON: ${{ steps.parse.outputs.jsonString }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const data = JSON.parse(process.env.FORM_JSON); + + const familyMap = { + basic: 'A: Basic', compare: 'A: Compare', expo: 'A: Expo', + fmanip: 'A: Fmanip', hyper: 'A: Hyper', misc: 'A: Misc', + nearest: 'A: Nearest', power: 'A: Power', special: 'A: Special', + trig: 'A: Trig' + }; + const archMap = { + 'x86-64': 'Arch: x86', 'x86': 'Arch: x86', + 'AArch64 / ARM': 'Arch: AArch64', 'RISC-V': 'Arch: RISC-V' + }; + + const toAdd = new Set(); + const family = (data.family || '').trim().toLowerCase(); + if (familyMap[family]) toAdd.add(familyMap[family]); + const arch = (data.arch || '').trim(); + if (archMap[arch]) toAdd.add(archMap[arch]); + + if (toAdd.size === 0) { + core.info('No mappable form fields, nothing to add'); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [...toAdd] + }); diff --git a/.github/workflows/labeler-needsreview.yml b/.github/workflows/labeler-needsreview.yml index 047f9b53..654249c5 100644 --- a/.github/workflows/labeler-needsreview.yml +++ b/.github/workflows/labeler-needsreview.yml @@ -13,6 +13,7 @@ permissions: jobs: update-review-labels: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: diff --git a/.github/workflows/labeler-pr.yml b/.github/workflows/labeler-pr.yml index e1d27f76..6eaccc3d 100644 --- a/.github/workflows/labeler-pr.yml +++ b/.github/workflows/labeler-pr.yml @@ -7,6 +7,10 @@ permissions: contents: read pull-requests: write +concurrency: + group: labeler-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: labeler: runs-on: ubuntu-latest diff --git a/.github/workflows/labeler-size.yml b/.github/workflows/labeler-size.yml index e7c83595..319f2d71 100644 --- a/.github/workflows/labeler-size.yml +++ b/.github/workflows/labeler-size.yml @@ -8,6 +8,10 @@ permissions: pull-requests: write issues: write +concurrency: + group: labeler-size-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: size-label: runs-on: ubuntu-latest diff --git a/.github/workflows/labeler-stale.yml b/.github/workflows/labeler-stale.yml index d01f1c58..e29bc772 100644 --- a/.github/workflows/labeler-stale.yml +++ b/.github/workflows/labeler-stale.yml @@ -24,6 +24,9 @@ jobs: stale-issue-label: "S: Stale" stale-pr-label: "S: Stale" remove-stale-when-updated: true + exempt-draft-pr: true + exempt-all-assignees: true + exempt-all-milestones: true exempt-issue-labels: "P1: Critical, D0: Irrational, S: Blocked" exempt-pr-labels: "P1: Critical, S: Blocked, S: Approved" stale-issue-message: "This issue has been inactive for 60 days and is now marked stale. Comment to keep it open." diff --git a/.github/workflows/labeler-untriaged.yml b/.github/workflows/labeler-untriaged.yml index 840bdf50..44258cf3 100644 --- a/.github/workflows/labeler-untriaged.yml +++ b/.github/workflows/labeler-untriaged.yml @@ -5,14 +5,9 @@ on: types: - opened - labeled - pull_request_target: - types: - - opened - - labeled permissions: contents: read - pull-requests: write issues: write jobs: @@ -65,6 +60,11 @@ jobs: return; } + if (context.actor.endsWith('[bot]')) { + core.info(`Label added by ${context.actor}, leaving S: Untriaged for a human to clear`); + return; + } + const hasUntriaged = item.labels.some(l => l.name === untriaged); if (!hasUntriaged) { core.info('S: Untriaged not present, nothing to remove'); diff --git a/.github/workflows/labels-sync.yml b/.github/workflows/labels-sync.yml new file mode 100644 index 00000000..3a1ced7e --- /dev/null +++ b/.github/workflows/labels-sync.yml @@ -0,0 +1,34 @@ +name: "Labels: Sync" + +on: + push: + branches: + - main + paths: + - .github/labels.yml + - .github/workflows/labels-sync.yml + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Sync labels from .github/labels.yml + uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5 + with: + yaml-file: .github/labels.yml + skip-delete: false + dry-run: false diff --git a/.github/workflows/lint-actions.yml b/.github/workflows/lint-actions.yml new file mode 100644 index 00000000..6ec9334d --- /dev/null +++ b/.github/workflows/lint-actions.yml @@ -0,0 +1,38 @@ +name: "Lint: Actions" + +on: + push: + branches: + - main + paths: + - .github/workflows/** + - .github/actions/** + pull_request: + paths: + - .github/workflows/** + - .github/actions/** + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + actionlint: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Run actionlint + uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: github-check + fail_on_error: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe4bac78..8a7704a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,6 +102,7 @@ during triage. The groups are: - `D:` difficulty, from `D3: Low` for approachable work up to `D0: Irrational` for hard-to-round problems that may take unreasonable effort - `P:` priority, from `P1: Critical` to `P4: Low` - `S:` status through review (`S: Untriaged`, `S: Needs Discussion`, `S: Needs Review`, `S: Approved`, `S: Awaiting Changes`, `S: Blocked`, `S: Merge Conflict`, `S: Stale`, `S: DO NOT MERGE`, `S: Won't Fix`) +- `Feature:` approval state for feature requests (`Feature: Unapproved`, `Feature: Approved`) - `Bug:` reproduction state (`Bug: Needs Replicating`, `Bug: Replicated`) - `Platform:`, `Arch:`, and `Compiler:` for environment-specific reports - `Branch:` for the branch a PR targets (`Branch: main`, `Branch: release`) @@ -111,6 +112,10 @@ during triage. The groups are: A contributor looking for a starting point should filter on `D3: Low` and `good first issue`. +The label set is managed as code in `.github/labels.yml` and synced on merge to +`main`. Change that file in a pull request rather than editing labels in the +GitHub UI, otherwise the next sync reverts the change. + ### Filing an issue Bug reports use the form under `.github/ISSUE_TEMPLATE`. For a numeric defect, @@ -196,12 +201,17 @@ a synchronous chat and write up the outcome. Several labels are applied for you, so you do not need to set them by hand: - `A:` area labels from the files a PR changes, plus `Changes: Public API` +- `A:` and `Arch:` labels on a bug report, read from the function family and architecture answers in the issue form - `size/` from the number of lines changed - `Branch: main` or `Branch: release` from the base branch -- `S: Untriaged` on new issues and PRs that open without labels, cleared once any other label is added +- `S: Untriaged` on new issues and PRs that open without labels, cleared once a maintainer applies another label by hand (labels added by automation do not clear it) - `S: Merge Conflict` when a PR develops conflicts - `S: Needs Review`, `S: Approved`, and `S: Awaiting Changes` as a maintainer review progresses - `S: Stale` after 60 days of inactivity, removed on the next update +- `Feature: Unapproved` on any issue labeled `T: New Feature`, until an admin adds `Feature: Approved`, which clears it + +A feature request stays `Feature: Unapproved` until a maintainer with admin +rights approves it. Hold off on implementation work until a request is approved. PRs opened from a fork's `main`, `master`, or `develop` branch are closed automatically with a note to move the work to a topic branch. From 0305e32692780af397e2527fcd16330d695ccfc5 Mon Sep 17 00:00:00 2001 From: ianpike Date: Mon, 15 Jun 2026 23:52:12 -0400 Subject: [PATCH 17/52] Rewrite apt install fallback to satisfy shellcheck SC2015 --- .github/workflows/ci-linux.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index e0d5d444..ae4c3401 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -340,7 +340,10 @@ jobs: key: ${{ runner.os }}-coremath-prefix-v1-${{ hashFiles('cmake/coremath/**', 'tools/coremath/**') }} - name: Install dependencies - run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev sollya gappa git || sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev git + run: | + if ! sudo apt-get update -qq || ! sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev sollya gappa git; then + sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev git + fi - name: Build CORE-MATH oracle prefix run: | From 25de37b995d2b05463c8a83edc58fbdae4d133ac Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 05:58:04 -0400 Subject: [PATCH 18/52] Cleanup code --- include/ccmath/internal/support/fenv/fenv_support.hpp | 6 ++---- showcase/common/backends/gcem_backend.hpp | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/ccmath/internal/support/fenv/fenv_support.hpp b/include/ccmath/internal/support/fenv/fenv_support.hpp index 490906f4..cda22458 100644 --- a/include/ccmath/internal/support/fenv/fenv_support.hpp +++ b/include/ccmath/internal/support/fenv/fenv_support.hpp @@ -34,11 +34,9 @@ extern "C" int *__errno_location(void) __attribute__((__nothrow__, __const__)); namespace ccm::support::fenv::detail { - CCM_NEVER_INLINE inline void write_errno(int err) noexcept + CCM_NEVER_INLINE inline void write_errno([[maybe_unused]] int err) noexcept { -#if defined(__FAST_MATH__) || defined(CCM_CONFIG_DISABLE_ERRNO) - (void)err; -#else +#if !defined(__FAST_MATH__) && !defined(CCM_CONFIG_DISABLE_ERRNO) #if defined(_GNU_SOURCE) || defined(__GLIBC__) || defined(__ANDROID__) volatile int *const loc = __errno_location(); *loc = err; diff --git a/showcase/common/backends/gcem_backend.hpp b/showcase/common/backends/gcem_backend.hpp index c2256388..5ddd93a7 100644 --- a/showcase/common/backends/gcem_backend.hpp +++ b/showcase/common/backends/gcem_backend.hpp @@ -12,22 +12,20 @@ namespace ccm::showcase::backends static double eval_sqrt(double x) { - volatile double input = x; + [[maybe_unused]] volatile double input = x; #if defined(CCMATH_SHOWCASE_HAS_GCEM) return gcem::sqrt(input); #else - (void)input; return 0.0; #endif } static double eval_sin(double x) { - volatile double input = x; + [[maybe_unused]] volatile double input = x; #if defined(CCMATH_SHOWCASE_HAS_GCEM) return gcem::sin(input); #else - (void)input; return 0.0; #endif } From f111d0ec4b1843042c327c1dafb9531c79884009 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 07:50:44 -0400 Subject: [PATCH 19/52] General work --- cmake/config/ProjectCompileDefinitions.cmake | 2 +- cmake/config/UserOptions.cmake | 4 +- .../simd/SelectSimdInstructionSet.cmake | 8 +- cmake/modules/CcmLibFuzzer.cmake | 2 +- examples/item/README.md | 3 +- fuzzing/shared/input.hpp | 2 +- fuzzing/src/math/expo_fuzz.cpp | 2 +- .../internal/math/common/fmanip/ldexp.hpp | 143 +++++------------- .../math/generic/builtins/basic/fma.hpp | 2 +- .../generic/func/power/pow_impl/pow_impl.hpp | 20 +-- .../func/power/pow_impl/powf_simd_impl.hpp | 2 +- .../internal/math/runtime/pp/declaration.hpp | 2 +- .../ccmath/internal/math/runtime/pp/flags.hpp | 4 +- .../internal/math/runtime/pp/msvc_intrin.hpp | 8 +- .../ccmath/internal/math/runtime/pp/pp.hpp | 2 +- .../internal/math/runtime/pp/reference.hpp | 2 +- .../ccmath/internal/math/runtime/pp/simd.hpp | 4 +- .../internal/math/runtime/pp/simd_config.hpp | 2 +- .../internal/math/runtime/pp/simd_math.hpp | 2 +- .../internal/math/runtime/pp/utility.hpp | 2 +- .../internal/math/runtime/pp/vec_ext.hpp | 6 +- .../ccmath/internal/types/dyadic_float.hpp | 31 ++-- include/ccmath/internal/types/fp_types.hpp | 8 +- include/ccmath/math/expo/log10.hpp | 2 +- include/ccmath/math/expo/log1p.hpp | 2 +- include/ccmath/math/fmanip/ldexp.hpp | 143 +++++------------- .../ccmath/math/trig/impl/inv_trig_impl.hpp | 6 +- .../math/trig/impl/sincos_double_data.hpp | 8 +- .../math/trig/impl/sincos_payne_hanek.hpp | 10 +- showcase/README.md | 30 ++-- showcase/item/README.md | 41 ++--- tests/conformance/fenv_exception_test.cpp | 5 +- tests/conformance/fma_software_fenv_test.cpp | 5 +- tests/conformance/pow_errno_fenv_test.cpp | 7 +- tests/shared/oracle/big_int_gmp.cpp | 2 +- tests/shared/oracle/coremath_pow_common.hpp | 9 +- tests/shared/oracle/mpfr_oracle_harness.hpp | 2 +- .../shared/oracle/oracle_campaign_common.hpp | 5 +- tests/shared/utils/math_samples.hpp | 2 +- tests/src/math/power/pow_test.cpp | 5 +- .../math/power/power_ulp_accuracy_test.cpp | 4 +- .../math/trig/trig_kernel_regression_test.cpp | 4 +- tools/asmlab/CMakeLists.txt | 2 +- tools/asmlab/scripts/run_meta.py | 13 +- tools/asmlab/scripts/source_map.py | 5 +- tools/gen_secondary_builds.py | 2 +- 46 files changed, 208 insertions(+), 369 deletions(-) diff --git a/cmake/config/ProjectCompileDefinitions.cmake b/cmake/config/ProjectCompileDefinitions.cmake index 17872d03..284038ba 100644 --- a/cmake/config/ProjectCompileDefinitions.cmake +++ b/cmake/config/ProjectCompileDefinitions.cmake @@ -3,7 +3,7 @@ ccmath_manifest_apply_library_compile_definitions(ccmath) # ccmath is header-only, so the math compiles in the consumer's translation unit. # Deterministic mode must stop the compiler from contracting stray a*b+c into a -# hardware FMA (which would diverge between FMA and non-FMA targets); the flag has +# hardware FMA (which would diverge between FMA and non-FMA targets). The flag has # to be INTERFACE so it reaches consumers. MSVC does not contract under the default # /fp:precise, so no MSVC flag is needed. if (CCMATH_ENABLE_DETERMINISTIC) diff --git a/cmake/config/UserOptions.cmake b/cmake/config/UserOptions.cmake index df2534fb..f3f35a54 100644 --- a/cmake/config/UserOptions.cmake +++ b/cmake/config/UserOptions.cmake @@ -103,8 +103,8 @@ option(CCMATH_DISABLE_SVML_USAGE # Opt-in: choose which SIMD instruction set ccmath builds compile for. # DEFAULT - use whatever the toolchain / -march already selects (no extra flag). # LOWEST - the most portable baseline for the architecture (SSE2 on x86_64, NEON on AArch64). -# HIGHEST - the best instruction set in ccmath's supported ladder the compiler can emit (up to AVX2; AVX-512 excluded). -# Defaults to DEFAULT (does nothing); downstream consumers may enable or disable it. +# HIGHEST - the best instruction set in ccmath's supported ladder the compiler can emit (up to AVX2, AVX-512 excluded). +# Defaults to DEFAULT (does nothing). Downstream consumers may enable or disable it. set(CCMATH_SIMD_PREFER "DEFAULT" CACHE STRING "Preferred SIMD instruction set for ccmath builds: DEFAULT, LOWEST or HIGHEST") set_property(CACHE CCMATH_SIMD_PREFER PROPERTY STRINGS DEFAULT LOWEST HIGHEST) diff --git a/cmake/features/simd/SelectSimdInstructionSet.cmake b/cmake/features/simd/SelectSimdInstructionSet.cmake index d6ba1860..40e8c441 100644 --- a/cmake/features/simd/SelectSimdInstructionSet.cmake +++ b/cmake/features/simd/SelectSimdInstructionSet.cmake @@ -31,7 +31,7 @@ endif () include(CheckCXXSourceCompiles) # Probe whether the compiler accepts ${_flag} and can compile ${_src}. A flag of -# NONE means "no extra flag" (used for baseline rungs; an empty string cannot be +# NONE means "no extra flag" (used for baseline rungs, an empty string cannot be # carried as a list element). function(_ccmath_probe_isa _flag _src _resultvar) set(_saved "${CMAKE_REQUIRED_FLAGS}") @@ -66,7 +66,7 @@ int main() { float32x4_t v = vdupq_n_f32(1.0f); v = vaddq_f32(v, v); return (int ]=]) # Build the rung ladder (low -> high) for this architecture and compiler. Only -# the rung names and flags are kept as lists; the source snippets contain +# the rung names and flags are kept as lists. The source snippets contain # semicolons and must never be stored as list elements (they are looked up by # name as _src_ instead). set(_names "") @@ -74,7 +74,7 @@ set(_flags "") if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64|i[3-6]86|x86)$") if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - # MSVC: SSE2 is the x64 baseline (no flag); no SSE4 arch switch exists. + # MSVC: SSE2 is the x64 baseline (no flag), and no SSE4 arch switch exists. set(_names SSE2 AVX AVX2) set(_flags NONE "/arch:AVX" "/arch:AVX2") else () @@ -86,7 +86,7 @@ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64|i[3-6]86|x86)$") set(_flags "-march=x86-64" "-march=x86-64-v2" "-march=x86-64-v3") endif () elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") - # NEON is the mandatory AArch64 baseline; SVE is intentionally out of scope. + # NEON is the mandatory AArch64 baseline. SVE is intentionally out of scope. set(_names NEON) set(_flags NONE) else () diff --git a/cmake/modules/CcmLibFuzzer.cmake b/cmake/modules/CcmLibFuzzer.cmake index db70a590..6350ce1d 100644 --- a/cmake/modules/CcmLibFuzzer.cmake +++ b/cmake/modules/CcmLibFuzzer.cmake @@ -6,7 +6,7 @@ # CCMATH_FUZZ_LINK_FLAGS - link options for fuzz executables # # Optional environment: -# LIB_FUZZING_ENGINE - OSS-Fuzz / external engine path (link-only; compile uses fuzzer-no-link) +# LIB_FUZZING_ENGINE - OSS-Fuzz / external engine path (link-only, compile uses fuzzer-no-link) include(CheckCXXSourceCompiles) diff --git a/examples/item/README.md b/examples/item/README.md index 6a81f11c..5acff3f3 100644 --- a/examples/item/README.md +++ b/examples/item/README.md @@ -4,7 +4,8 @@ CLion picks up any `*.run.xml` file under the project tree. The example configs ## One-time setup -1. **CMake profile:** Settings → CMake → Profiles → add preset **CLion Clang Debug + examples** (`clion-clang-debug-examples`). Output directory: `out/clang-examples`. +1. **CMake profile:** Settings > CMake > Profiles > add preset **CLion Clang Debug + examples** ( + `clion-clang-debug-examples`). Output directory: `out/clang-examples`. 2. **Build:** Run `cmake --build --preset build-clion-clang-debug-examples` once, or let CLion build on first launch. 3. **Run:** Open the Run menu and choose any entry under **examples/**. diff --git a/fuzzing/shared/input.hpp b/fuzzing/shared/input.hpp index a938f494..4b10ad8d 100644 --- a/fuzzing/shared/input.hpp +++ b/fuzzing/shared/input.hpp @@ -113,7 +113,7 @@ namespace ccm::fuzz } // Optional per-operand selector bytes trail the raw operands. A set high bit swaps the - // operand for a table entry; bit 6 flips the sign. Inputs without trailing bytes (and + // operand for a table entry. Bit 6 flips the sign. Inputs without trailing bytes (and // every pre-existing seed) decode exactly as before. template void apply_selector(T & value, uint8_t const * data, size_t size, size_t selector_offset) diff --git a/fuzzing/src/math/expo_fuzz.cpp b/fuzzing/src/math/expo_fuzz.cpp index 5f414061..6e1a02b8 100644 --- a/fuzzing/src/math/expo_fuzz.cpp +++ b/fuzzing/src/math/expo_fuzz.cpp @@ -26,7 +26,7 @@ namespace if (!in.load_x(data, size)) { return; } // fuzz_calls routes to the generic kernels in the generic build and to the public - // dispatch otherwise; the exp and log kernels meet the contract for both float and + // dispatch otherwise. The exp and log kernels meet the contract for both float and // double, so the same call set covers both build flavors. ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::fuzz::calls::exp, [](T v) { return std::exp(v); }); ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::fuzz::calls::exp2, [](T v) { return std::exp2(v); }); diff --git a/include/ccmath/internal/math/common/fmanip/ldexp.hpp b/include/ccmath/internal/math/common/fmanip/ldexp.hpp index ca8f1a14..ad45c068 100644 --- a/include/ccmath/internal/math/common/fmanip/ldexp.hpp +++ b/include/ccmath/internal/math/common/fmanip/ldexp.hpp @@ -43,55 +43,19 @@ namespace ccm { /** - * @brief Multiplies a floating-point or integer value \p num by 2 raised to the power \p exp. - * - * Additional overloads accept an integer \p num and treat it as double. - * - * Parameters - * - \p num: Floating-point or integer value to be scaled. - * - \p exp: Integer exponent by which \p num is multiplied (power of two). - * - * Return value - * - If no errors occur, returns the product \f$\text{num} \times 2^\text{exp}\f$. - * - If a range error due to overflow occurs, ±HUGE_VAL, ±HUGE_VALF, or ±HUGE_VALL is returned. - * - If a range error due to underflow occurs, the correct result (after rounding) is returned, - * which may be 0 or a denormalized value. - * - * Error handling - * - Errors are reported according to the platform’s math error-handling rules and if errno has not been disabled. - * - If range errors occur, \c errno may be set to \c ERANGE and floating-point exceptions such - * as \c FE_OVERFLOW or \c FE_UNDERFLOW may be raised, depending on the implementation. - * - If \p num is ±0 or ±∞, the function returns it unmodified. - * - If \p exp is 0, \p num is returned unmodified. - * - If \p num is NaN, NaN is returned. - * - * Notes - * - On binary systems where FLT_RADIX is 2, this function can be viewed as a specialized way - * of scaling \p num by a power of two. - * - Using arithmetic operators with powers of two may be faster on some platforms, but - * this function ensures a clear, consistent interface for exponent shifting. - * - Overloads are available for all integer types, converting \p num to \c double first. - * - * Example - * \code{.cpp} - * #include - * #include - * - * int main() { - * double val = 1.75; - * int expVal = 3; - * double result = ccm::ldexp(val, expVal); - * std::printf("ldexp(%f, %d) = %f\n", val, expVal, result); - * - * int i = 5; - * double resultInt = ccm::ldexp(i, 2); - * std::printf("ldexp(%d, %d) = %f\n", i, 2, resultInt); - * return 0; - * } - * \endcode - * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. - * + * @brief Scales a floating-point \p num by \f$2^\text{exp}\f$. + * + * Overflow returns ±HUGE_VAL (or its float and long double forms), underflow returns the + * correctly rounded result, possibly a subnormal or zero. ±0, ±∞, and NaN pass through + * unchanged, and \p num is returned as-is when \p exp is 0. On a range error, and unless + * errno was disabled in cmake, \c errno may be set to \c ERANGE and \c FE_OVERFLOW or + * \c FE_UNDERFLOW raised, depending on the platform. + * + * @tparam T Floating-point type. + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> @@ -105,30 +69,16 @@ namespace ccm } /** - * @brief Overload for integer \p num, multiplying it by 2 raised to the power \p exp. - * - * The integer \p num is converted to \c double before the multiplication, and the result - * is returned as a \c double. - * - * Parameters - * - \p num: Integer value to be scaled (cast to \c double internally). - * - \p exp: Integer exponent by which \p num is multiplied (power of two). + * @brief Scales an integer \p num by \f$2^\text{exp}\f$, converting it to \c double first. * - * Return value - * - If no errors occur, returns the product \f$\text{num} \times 2^\text{exp}\f$ as a \c double. - * - If a range error due to overflow occurs, ±HUGE_VAL is returned. - * - If a range error due to underflow occurs, the correct result (after rounding) is returned, - * which may be 0 or a denormalized value. - * - * Error handling - * - Same as the floating-point overload. If errors occur, \c errno may be set to \c ERANGE and - * floating-point exceptions such as \c FE_OVERFLOW or \c FE_UNDERFLOW may be raised if errno has not been disabled. - * - * Notes - * - This overload is equivalent to calling \c ldexp(static_cast(num), exp). - * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. + * Equivalent to \c ldexp(static_cast(num), exp), so it shares the + * floating-point overload's behavior after the conversion. Overflow returns ±HUGE_VAL. * + * @tparam Integer Integer type. + * @param num Value to scale, converted to \c double. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> @@ -136,52 +86,31 @@ namespace ccm { return ccm::ldexp(static_cast(num), exp); } /** - * @brief Multiplies a float \p num by 2 raised to the power \p exp. - * - * Equivalent to calling \c ldexp(num, exp). - * - * Parameters - * - \p num: Floating-point (float) value to be scaled. - * - \p exp: Integer exponent by which \p num is multiplied (power of two). - * - * Return value - * - The product \f$\text{num} \times 2^\text{exp}\f$ as a float. - * - May return ±HUGE_VALF on overflow, or a denormalized value or 0 on underflow. + * @brief Scales a \c float \p num by \f$2^\text{exp}\f$. Equivalent to \c ldexp(num, exp). * - * Error handling - * - As with the other overloads, \c errno may be set to \c ERANGE and floating-point exceptions - * (e.g., \c FE_OVERFLOW, \c FE_UNDERFLOW) may occur depending on the platform if errno has not been disabled. + * Overflow returns ±HUGE_VALF, underflow a subnormal or zero. * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. - + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c float. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr float ldexpf(float num, int exp) noexcept { return ccm::ldexp(num, exp); } /** - * @brief Multiplies a long double \p num by 2 raised to the power \p exp. - * - * Parameters - * - \p num: A \c long double value to be scaled by a power of two. - * - \p exp: An integer exponent by which \p num is multiplied (i.e., \f$2^\text{exp}\f$). - * - * Return value - * - The product \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. - * - May return ±HUGE_VALL on overflow, or a denormalized value or 0 on underflow. - * - * Error handling - * - If range errors occur, \c errno may be set to \c ERANGE, and floating-point exceptions - * (\c FE_OVERFLOW, \c FE_UNDERFLOW) may be raised, depending on the implementation and if errno has not been disabled. - * - If \p num is ±0 or ±∞, the function returns it unmodified. - * - If \p exp is 0, \p num is returned unmodified. - * - If \p num is NaN, NaN is returned. - * - * Notes - * - This overload uses native \c long double support for multiplying by a power of two. + * @brief Scales a \c long double \p num by \f$2^\text{exp}\f$ using native long double support. * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. + * Overflow returns ±HUGE_VALL, underflow a subnormal or zero. ±0, ±∞, and NaN pass through + * unchanged, and \p num is returned as-is when \p exp is 0. On a range error, and unless errno + * was disabled in cmake, \c errno may be set to \c ERANGE and \c FE_OVERFLOW or \c FE_UNDERFLOW + * raised, depending on the platform. * + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr long double ldexpl(long double num, int exp) noexcept diff --git a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp index 0999be1d..5a6305bc 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp @@ -29,7 +29,7 @@ // Deterministic mode routes every multiply_add through the IEEE-correct __builtin_fma (hardware // where present, a software fma() call otherwise) so the result is bit-identical on FMA and // non-FMA targets, and forces the if constexpr(target_cpu_has_fma) kernel branches to a single -// path. This does not assert that the CPU has an FMA instruction; it selects the correctly-rounded +// path. This does not assert that the CPU has an FMA instruction. It selects the correctly-rounded // fused operation regardless of hardware. #if defined(CCM_CONFIG_DETERMINISTIC) && !defined(CCMATH_TARGET_CPU_HAS_FMA) #define CCMATH_TARGET_CPU_HAS_FMA diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp index 2660042a..dcdb59bd 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp @@ -327,7 +327,7 @@ namespace ccm::gen::impl if (b.is_zero()) { continue; } const std::uint64_t significand = static_cast(b.get_explicit_mantissa()); const int shift = (b.get_explicit_exponent() - static_cast(FPBits_t::fraction_length)) + kSumScale; - if (shift < 0) { continue; } // below the accumulator resolution; never reached for the fed terms + if (shift < 0) { continue; } // below the accumulator resolution, never reached for the fed terms const Wide term = Wide(significand) << static_cast(shift); if (b.sign().is_neg()) { neg += term; } else @@ -348,7 +348,7 @@ namespace ccm::gen::impl constexpr int rsqrt_residual_sign(double ch, double cl, double x) noexcept { // sign(c^2*x - 1) is invariant under ch -> ch*2^-k, cl -> cl*2^-k, x -> x*2^2k. Scale so - // ch lands in [1,2); then c^2 and c^2*x stay in range (a raw 1/sqrt(x) for tiny x is + // ch lands in [1,2). Then c^2 and c^2*x stay in range (a raw 1/sqrt(x) for tiny x is // huge and c^2 would overflow to infinity, breaking the sign and the caller's walk). const int k = static_cast(support::fp::FPBits(ch).get_biased_exponent()) - support::fp::FPBits::exponent_bias; ch = support::helpers::internal_ldexp(ch, -k); @@ -412,7 +412,7 @@ namespace ccm::gen::impl // path mirrors powf_double_double but keeps a full double-double log2(x) (binary64 // allows |y| up to ~2^53, so the powf lo6_hi single-double shortcut is unsafe). A // normal final value collapses with a single binary64 addition, correctly rounded in - // every mode; a subnormal value (only reachable through the 2^-512 underflow scale) is + // every mode. A subnormal value (only reachable through the 2^-512 underflow scale) is // instead rounded straight onto the subnormal grid to avoid a double rounding. constexpr double pow_double_double(unsigned idx_x, double dx, double e_x, double y6, std::uint64_t sign, double scale) noexcept // NOLINT(bugprone-exception-escape) @@ -508,7 +508,7 @@ namespace ccm::gen::impl // A single binary64 addition of a faithful double-double rounds once in the // active mode, yielding the correctly-rounded result for a normal final value. When // scale != 1 the reconstruction is built at normal magnitude and shifted by a - // power-of-two scale; that shift is exact while the result stays normal, so the lone + // power-of-two scale. That shift is exact while the result stays normal, so the lone // addition is still correctly rounded there. const double collapsed = rr.hi + rr.lo; double result = collapsed * scale; @@ -517,14 +517,14 @@ namespace ccm::gen::impl // time, so the collapse above double-rounds (off by one ULP under FE_TONEAREST). A // round-to-odd intermediate cannot rescue it: a 53-bit double leaves only one guard // bit over a 52-bit subnormal, while round-to-odd needs two. Instead round the - // double-double straight onto the subnormal grid in one active-mode rounding; the + // double-double straight onto the subnormal grid in one active-mode rounding. The // following power-of-two shift is then exact. Normal results keep the fast path // untouched (a normal reconstruction times a power-of-two scale is already exact). if (scale < 1.0) { if (const FPBits_t rb(result); rb.is_subnormal() || rb.is_zero()) { - // Every subnormal shares the lsb 2^-1074; with scale = 2^-512 the pre-scale grid + // Every subnormal shares the lsb 2^-1074. With scale = 2^-512 the pre-scale grid // is 2^-562, so scaling the pair up by 2^562 makes that grid the unit and the // magnitude at most 2^51 (exact, no overflow). Round the integer count of grid // units in the active mode, then reattach the 2^-1074 weight exactly. @@ -836,7 +836,7 @@ namespace ccm::gen::impl pow_int_detail::ScaledPow sp = pow_int_detail::ipow_scaled(base, mag); // Fold a negative result's sign into the pair before the final rounding. Negating // a magnitude that was already rounded in the active mode flips directed rounding - // (-round_up(|v|) is round_down(-|v|)); rounding the signed value is correct. + // (-round_up(|v|) is round_down(-|v|)). Rounding the signed value is correct. if (sign != 0) { sp.mantissa.hi = -sp.mantissa.hi; @@ -875,12 +875,12 @@ namespace ccm::gen::impl // the same exponent-tracked accumulator as the integer path so the smallest and largest // reachable magnitudes keep full precision (a bare double-double drops its lo limb for a // small base under a large odd power, e.g. 1/sqrt(x) for x just above a tiny power of two). - // Genuine half-integers only; integer exponents are fully owned by the integer path above + // Genuine half-integers only. Integer exponents are fully owned by the integer path above // (which also carries the result sign for a negative base). const double two_exp = exp * 2.0; if (is_integer(two_exp) && !is_integer(exp) && FPBits_t(two_exp).abs().get_val() <= 2048.0) { - // pow(x, +/-1/2) are sqrt and 1/sqrt. sqrt is the correctly rounded sqrt_gen; 1/sqrt + // pow(x, +/-1/2) are sqrt and 1/sqrt. sqrt is the correctly rounded sqrt_gen. 1/sqrt // is correctly rounded by an exact residual test (a bare double-double 1/sqrt sits a // directed-mode ULP short for x just past a power of two). The double-double squaring // below cannot resolve those two, so they are intercepted first. @@ -892,7 +892,7 @@ namespace ccm::gen::impl const pow_int_detail::ScaledPow sp = pow_int_detail::ipow_scaled(pow_int_detail::dd_sqrt(base), mag); if (m < 0) { - // Negative half-integer: reciprocal of the odd power then an exact scale; a + // Negative half-integer: reciprocal of the odd power then an exact scale. A // non-normal result falls through to the kernel. const double r = support::helpers::internal_ldexp(pow_int_detail::reciprocal(sp.mantissa), static_cast(-sp.exp2)); if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) { return r; } diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp index a0b7c8ce..62e3cbab 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp @@ -124,7 +124,7 @@ namespace ccm::gen::impl const DVec lo6_hi = pp::fma(y6, e_x + lr_hi, -hm); const DVec lo6 = pp::fma(y6, pp::fma(dx, p, lr_mid), lo6_hi); - // Clamp 2^(hi+mid) exponent into double range; out of range lanes round at the cast. + // Clamp 2^(hi+mid) exponent into double range. Out of range lanes round at the cast. I64 hm_i(hm); hm_i = pp::simd_select(hm_i > I64(1 << 15), I64(1 << 15), hm_i); hm_i = pp::simd_select(hm_i < I64(-(1 << 15)), I64(-(1 << 15)), hm_i); diff --git a/include/ccmath/internal/math/runtime/pp/declaration.hpp b/include/ccmath/internal/math/runtime/pp/declaration.hpp index a520f791..b06a3b54 100644 --- a/include/ccmath/internal/math/runtime/pp/declaration.hpp +++ b/include/ccmath/internal/math/runtime/pp/declaration.hpp @@ -105,7 +105,7 @@ namespace ccm::pp { return n > 0 && (n & (n - 1)) == 0; } } // namespace detail - // [simd.abi] ABI tags. ScalarAbi holds a single element; VecAbi holds N + // [simd.abi] ABI tags. ScalarAbi holds a single element. VecAbi holds N // lanes backed by a compiler vector extension (or an array fallback). struct ScalarAbi { diff --git a/include/ccmath/internal/math/runtime/pp/flags.hpp b/include/ccmath/internal/math/runtime/pp/flags.hpp index 19f60fb9..4f1311a2 100644 --- a/include/ccmath/internal/math/runtime/pp/flags.hpp +++ b/include/ccmath/internal/math/runtime/pp/flags.hpp @@ -17,7 +17,7 @@ #include // Load/store flags. The C++26 surface spells these as simd_flags<...> with the -// simd_flag_default / simd_flag_aligned / simd_flag_overaligned objects; the +// simd_flag_default / simd_flag_aligned / simd_flag_overaligned objects. The // Parallelism TS spells the equivalents element_aligned / vector_aligned. Both // names are provided. An aligned flag promises the pointer is suitably aligned, // which is communicated to the optimizer (see flags_align_request / @@ -71,7 +71,7 @@ namespace ccm::pp // NOLINTEND(readability-avoid-nested-conditional-operator) // Communicate a known alignment to the optimizer. The pointer value is - // unchanged; the compiler may then use aligned moves / assume alignment. + // unchanged. The compiler may then use aligned moves / assume alignment. template CCM_ALWAYS_INLINE T const *assume_aligned_ptr(T const *p) { diff --git a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp index 467f7c20..87bb142c 100644 --- a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp +++ b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp @@ -18,7 +18,7 @@ // SSE2 intrinsic backend for compilers without the GNU/Clang vector extensions // (i.e. MSVC cl.exe). It specializes the 128-bit native widths (float x4, // double x2, and the int32 x4 / int64 x2 used by their masks). Hot operations -// map to intrinsics; the rest spill to a small stack array, run +// map to intrinsics. The rest spill to a small stack array, run // the scalar logic, and reload (correct, used only for cold paths). Anything not // specialized here keeps the portable array fallback from vec_ext.hpp, so the // scalar fallback is always available. Validated on Clang via @@ -446,7 +446,7 @@ namespace ccm::pp } }; - // Mainly the mask type for double; SSE2 has few 64-bit integer ops, so most + // Mainly the mask type for double. SSE2 has few 64-bit integer ops, so most // arithmetic/compares spill. template <> struct SimdTraits> @@ -575,8 +575,8 @@ namespace ccm::pp } }; - // 256-bit native widths. Floating-point ops need only AVX; integer mask and - // bitwise ops are done in the float domain (AVX-safe); 256-bit integer + // 256-bit native widths. Floating-point ops need only AVX. Integer mask and + // bitwise ops are done in the float domain (AVX-safe). 256-bit integer // arithmetic / compares use AVX2 intrinsics when available and spill otherwise. #if CCMATH_SIMD_HAVE_AVX namespace detail diff --git a/include/ccmath/internal/math/runtime/pp/pp.hpp b/include/ccmath/internal/math/runtime/pp/pp.hpp index 979448e5..f8667576 100644 --- a/include/ccmath/internal/math/runtime/pp/pp.hpp +++ b/include/ccmath/internal/math/runtime/pp/pp.hpp @@ -11,7 +11,7 @@ #pragma once // Umbrella for the C++17 port of the C++26 std::simd surface (ccm::pp). This is -// a standalone library; it does not replace the live ccm::intrin SIMD system +// a standalone library. It does not replace the live ccm::intrin SIMD system // until the project decides to switch over. #include "ccmath/internal/math/runtime/pp/conversion.hpp" diff --git a/include/ccmath/internal/math/runtime/pp/reference.hpp b/include/ccmath/internal/math/runtime/pp/reference.hpp index c217c7fd..da903b46 100644 --- a/include/ccmath/internal/math/runtime/pp/reference.hpp +++ b/include/ccmath/internal/math/runtime/pp/reference.hpp @@ -16,7 +16,7 @@ // Mutable lane proxy returned by basic_simd::operator[]. This is an extension // over the C++26 surface (which exposes read-only element access) kept for -// ergonomics; it mirrors libc++'s reference.h. +// ergonomics. It mirrors libc++'s reference.h. namespace ccm::pp::detail { diff --git a/include/ccmath/internal/math/runtime/pp/simd.hpp b/include/ccmath/internal/math/runtime/pp/simd.hpp index d622ac97..0b70eae0 100644 --- a/include/ccmath/internal/math/runtime/pp/simd.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd.hpp @@ -25,7 +25,7 @@ namespace ccm::pp { // [simd.class] A data-parallel value of N lanes of T. C++26 names ported to - // C++17; storage and operations come from the per-ABI SimdTraits backend. + // C++17. Storage and operations come from the per-ABI SimdTraits backend. template class basic_simd { @@ -85,7 +85,7 @@ namespace ccm::pp Traits::store(data_, ptr); } - // Element access. Const returns by value (C++26); the mutable overload + // Element access. Const returns by value (C++26). The mutable overload // returns a lane proxy (TS-style extension). CCM_ALWAYS_INLINE value_type operator[](detail::SimdSizeType i) const { return Traits::get(data_, i); } CCM_ALWAYS_INLINE detail::SimdReference operator[](detail::SimdSizeType i) { return { &data_, i }; } diff --git a/include/ccmath/internal/math/runtime/pp/simd_config.hpp b/include/ccmath/internal/math/runtime/pp/simd_config.hpp index 6c0a48e3..a6498f78 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_config.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_config.hpp @@ -20,7 +20,7 @@ #define CCMATH_SIMD_HAVE_MMX 0 #endif -// GNU/Clang/ICX/NVHPC spell these __SSE__ / __SSE2__ / __x86_64__; MSVC does not, +// GNU/Clang/ICX/NVHPC spell these __SSE__ / __SSE2__ / __x86_64__. MSVC does not, // so also recognise its _M_X64 (x64 implies SSE2) and _M_IX86_FP levels. #if defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && _M_IX86_FP >= 1) #define CCMATH_SIMD_HAVE_SSE 1 diff --git a/include/ccmath/internal/math/runtime/pp/simd_math.hpp b/include/ccmath/internal/math/runtime/pp/simd_math.hpp index b3a6d0d4..6f99e753 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_math.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_math.hpp @@ -69,7 +69,7 @@ namespace ccm::pp CCM_ALWAYS_INLINE basic_simd max(basic_simd const & a, basic_simd const & b) { return basic_simd::from_member(SimdTraits::op_max(a.get(), b.get())); } - // abs: floating point reuses the fabs primitive; integral negates negatives. + // abs: floating point reuses the fabs primitive. Integral negates negatives. template , int> = 0> CCM_ALWAYS_INLINE basic_simd abs(basic_simd const & v) { return basic_simd::from_member(SimdTraits::op_fabs(v.get())); } diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index 7ac5d65a..4810a6f0 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -52,7 +52,7 @@ namespace ccm::pp // Per-lane scalar math primitives, used by the scalar backend and as the // portable fallback for the vec_ext backend. Compiler builtins are used on - // GNU/Clang (no , no errno, foldable); other toolchains (e.g. MSVC), + // GNU/Clang (no , no errno, foldable). Other toolchains (e.g. MSVC), // or builds that define CCM_PP_FORCE_PORTABLE, fall back to the C runtime // entry points declared here. #if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) diff --git a/include/ccmath/internal/math/runtime/pp/vec_ext.hpp b/include/ccmath/internal/math/runtime/pp/vec_ext.hpp index d9f93ebd..1fe43c44 100644 --- a/include/ccmath/internal/math/runtime/pp/vec_ext.hpp +++ b/include/ccmath/internal/math/runtime/pp/vec_ext.hpp @@ -29,7 +29,7 @@ // Compilers that support the GNU/Clang vector extensions: GCC, Clang, Intel's // modern oneAPI compiler (ICX, defines __clang__), and the NVIDIA HPC compiler // (nvc++, defines __GNUC__). They are listed explicitly for clarity even though -// ICX/nvc++ already imply __clang__/__GNUC__. Anything else (notably MSVC cl.exe) +// ICX/nvc++ already imply __clang__/__GNUC__. Anything else (in particular MSVC cl.exe) // takes the intrinsic backend or the scalar array fallback. #if !defined(CCM_PP_FORCE_ARRAY_BACKEND) && !defined(CCM_PP_FORCE_PORTABLE) && !defined(CCM_PP_FORCE_MSVC_INTRIN) && \ (defined(__clang__) || defined(__GNUC__) || defined(__INTEL_LLVM_COMPILER) || defined(__NVCOMPILER)) @@ -111,7 +111,7 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return -a; } // A same-size vector comparison yields a vector of the matching signed - // integer with all-ones / zero lanes; the cast reinterprets it as our + // integer with all-ones / zero lanes. The cast reinterprets it as our // canonical MaskMember type. CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return reinterpret_mask(a == b); } CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return reinterpret_mask(a != b); } @@ -199,7 +199,7 @@ namespace ccm::pp CCM_ALWAYS_INLINE static detail::vec_builtin_t bitcast(SimdMember v) { return reinterpret_cast>(v); } - // Math primitives. Clang lowers these to packed instructions; GCC takes the + // Math primitives. Clang lowers these to packed instructions. GCC takes the // per-lane fallback. CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { diff --git a/include/ccmath/internal/types/dyadic_float.hpp b/include/ccmath/internal/types/dyadic_float.hpp index 3b7d0a3a..fd734372 100644 --- a/include/ccmath/internal/types/dyadic_float.hpp +++ b/include/ccmath/internal/types/dyadic_float.hpp @@ -20,10 +20,9 @@ * @defgroup types * @brief Internal high-precision dyadic floating-point type for CCMath. * - * This file contains the definition and implementation details for a high-precision - * floating-point data type in “dyadic” form. - * It is used internally by CCMath for dyadic floating-point computations - * that don't fit inside the standard floating-point types. + * A high-precision floating-point type in dyadic form, that is a mantissa scaled by a + * power of two. CCMath uses it internally for computations that do not fit inside the + * standard floating-point types. * * @see ccmath/internal/support/fp/fp_bits.hpp * @see ccmath/internal/support/type_traits.hpp @@ -377,10 +376,9 @@ namespace ccm::types volatile T two = static_cast(2.0); T r = two * d_hi; - // TODO: Whether rounding down the absolute value to max_normal should - // also raise FE_OVERFLOW and set ERANGE is debatable. - // TODO: Check what MSVC does for this case to match them when compiling with MSVC. - // This behavior matches Clang. + // TODO(IanP): decide whether rounding the absolute value down to max_normal + // should also raise FE_OVERFLOW and set ERANGE. The current behavior matches Clang. + // TODO(IanP): check what MSVC does here and match it under MSVC. if constexpr (ShouldSignalExceptions && support::fp::FPBits(r).is_inf()) { support::fenv::set_errno_if_required(ERANGE); } return r; @@ -420,11 +418,11 @@ namespace ccm::types // d_lo is denormal, but the output is normal. int scale_up_exponent = 1 - exp_lo; T scale_up_factor = support::fp::FPBits::create_value( - Sign::POS, static_cast(support::fp::FPBits::exponent_bias + scale_up_exponent), implicit_mask) - .get_val(); - T scale_down_factor = support::fp::FPBits::create_value( - Sign::POS, static_cast(support::fp::FPBits::exponent_bias - scale_up_exponent), implicit_mask) - .get_val(); + Sign::POS, static_cast(support::fp::FPBits::exponent_bias + scale_up_exponent), implicit_mask) + .get_val(); + T scale_down_factor = support::fp::FPBits::create_value( + Sign::POS, static_cast(support::fp::FPBits::exponent_bias - scale_up_exponent), implicit_mask) + .get_val(); d_lo = support::fp::FPBits::create_value(sign, static_cast(exp_lo + scale_up_exponent), implicit_mask).get_val(); @@ -449,10 +447,9 @@ namespace ccm::types // long double. r_bits -= implicit_mask; - // TODO: IEEE Std 754-2019 lets implementers choose whether to check for - // "tininess" before or after rounding for base-2 formats, as long as - // the same choice is made for all operations. - // Our choice to check after rounding might not be the same as the hardware's. + // TODO(IanP): IEEE Std 754-2019 lets implementers check for tininess before or + // after rounding for base-2 formats, as long as the same choice holds for all + // operations. We check after rounding, which might not match the hardware. if constexpr (ShouldSignalExceptions && round_and_sticky) { support::fenv::set_errno_if_required(ERANGE); diff --git a/include/ccmath/internal/types/fp_types.hpp b/include/ccmath/internal/types/fp_types.hpp index 8a5b92e6..6c13a478 100644 --- a/include/ccmath/internal/types/fp_types.hpp +++ b/include/ccmath/internal/types/fp_types.hpp @@ -32,11 +32,11 @@ namespace ccm using float_t = float; using double_t = double; #elif CCM_FLT_EVAL_METHOD == 1 - typedef double float_t; - typedef double double_t; + using float_t = double; + using double_t = double; #elif CCM_FLT_EVAL_METHOD == 2 - typedef long double float_t; - typedef long double double_t; + using float_t = long double; + using double_t = long double; #else #error "Unknown CCM_FLT_EVAL_METHOD" #endif diff --git a/include/ccmath/math/expo/log10.hpp b/include/ccmath/math/expo/log10.hpp index 36901115..1cb20d8f 100644 --- a/include/ccmath/math/expo/log10.hpp +++ b/include/ccmath/math/expo/log10.hpp @@ -34,7 +34,7 @@ namespace ccm * @brief Computes the base-10 logarithm of a floating-point value. * @tparam T Floating-point type. * @param num Floating-point value. - * @return Base-10 logarithm of num; returns -inf for zero and NaN for negative inputs. + * @return Base-10 logarithm of num. Returns -inf for zero and NaN for negative inputs. * @see https://en.cppreference.com/w/cpp/numeric/math/log10 */ template , bool> = true> diff --git a/include/ccmath/math/expo/log1p.hpp b/include/ccmath/math/expo/log1p.hpp index f7268990..abe283c7 100644 --- a/include/ccmath/math/expo/log1p.hpp +++ b/include/ccmath/math/expo/log1p.hpp @@ -33,7 +33,7 @@ namespace ccm * @brief Computes log(1 + num) with improved accuracy near zero. * @tparam T Floating-point type. * @param num Floating-point value. - * @return Natural logarithm of 1 + num; returns -inf for num == -1 and NaN for num < -1. + * @return Natural logarithm of 1 + num. Returns -inf for num == -1 and NaN for num < -1. * @see https://en.cppreference.com/w/cpp/numeric/math/log1p */ template , bool> = true> diff --git a/include/ccmath/math/fmanip/ldexp.hpp b/include/ccmath/math/fmanip/ldexp.hpp index 348b0257..f8faaeb0 100644 --- a/include/ccmath/math/fmanip/ldexp.hpp +++ b/include/ccmath/math/fmanip/ldexp.hpp @@ -45,55 +45,19 @@ namespace ccm { /** - * @brief Multiplies a floating-point or integer value \p num by 2 raised to the power \p exp. - * - * Additional overloads accept an integer \p num and treat it as double. - * - * Parameters - * - \p num: Floating-point or integer value to be scaled. - * - \p exp: Integer exponent by which \p num is multiplied (power of two). - * - * Return value - * - If no errors occur, returns the product \f$\text{num} \times 2^\text{exp}\f$. - * - If a range error due to overflow occurs, ±HUGE_VAL, ±HUGE_VALF, or ±HUGE_VALL is returned. - * - If a range error due to underflow occurs, the correct result (after rounding) is returned, - * which may be 0 or a denormalized value. - * - * Error handling - * - Errors are reported according to the platform’s math error-handling rules and if errno has not been disabled. - * - If range errors occur, \c errno may be set to \c ERANGE and floating-point exceptions such - * as \c FE_OVERFLOW or \c FE_UNDERFLOW may be raised, depending on the implementation. - * - If \p num is ±0 or ±∞, the function returns it unmodified. - * - If \p exp is 0, \p num is returned unmodified. - * - If \p num is NaN, NaN is returned. - * - * Notes - * - On binary systems where FLT_RADIX is 2, this function can be viewed as a specialized way - * of scaling \p num by a power of two. - * - Using arithmetic operators with powers of two may be faster on some platforms, but - * this function ensures a clear, consistent interface for exponent shifting. - * - Overloads are available for all integer types, converting \p num to \c double first. - * - * Example - * \code{.cpp} - * #include - * #include - * - * int main() { - * double val = 1.75; - * int expVal = 3; - * double result = ccm::ldexp(val, expVal); - * std::printf("ldexp(%f, %d) = %f\n", val, expVal, result); - * - * int i = 5; - * double resultInt = ccm::ldexp(i, 2); - * std::printf("ldexp(%d, %d) = %f\n", i, 2, resultInt); - * return 0; - * } - * \endcode - * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. - * + * @brief Scales a floating-point \p num by \f$2^\text{exp}\f$. + * + * Overflow returns ±HUGE_VAL (or its float and long double forms), underflow returns the + * correctly rounded result, possibly a subnormal or zero. ±0, ±∞, and NaN pass through + * unchanged, and \p num is returned as-is when \p exp is 0. On a range error, and unless + * errno was disabled in cmake, \c errno may be set to \c ERANGE and \c FE_OVERFLOW or + * \c FE_UNDERFLOW raised, depending on the platform. + * + * @tparam T Floating-point type. + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> @@ -108,30 +72,16 @@ namespace ccm } /** - * @brief Overload for integer \p num, multiplying it by 2 raised to the power \p exp. - * - * The integer \p num is converted to \c double before the multiplication, and the result - * is returned as a \c double. - * - * Parameters - * - \p num: Integer value to be scaled (cast to \c double internally). - * - \p exp: Integer exponent by which \p num is multiplied (power of two). + * @brief Scales an integer \p num by \f$2^\text{exp}\f$, converting it to \c double first. * - * Return value - * - If no errors occur, returns the product \f$\text{num} \times 2^\text{exp}\f$ as a \c double. - * - If a range error due to overflow occurs, ±HUGE_VAL is returned. - * - If a range error due to underflow occurs, the correct result (after rounding) is returned, - * which may be 0 or a denormalized value. - * - * Error handling - * - Same as the floating-point overload. If errors occur, \c errno may be set to \c ERANGE and - * floating-point exceptions such as \c FE_OVERFLOW or \c FE_UNDERFLOW may be raised if errno has not been disabled. - * - * Notes - * - This overload is equivalent to calling \c ldexp(static_cast(num), exp). - * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. + * Equivalent to \c ldexp(static_cast(num), exp), so it shares the + * floating-point overload's behavior after the conversion. Overflow returns ±HUGE_VAL. * + * @tparam Integer Integer type. + * @param num Value to scale, converted to \c double. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> @@ -139,52 +89,31 @@ namespace ccm { return ccm::ldexp(static_cast(num), exp); } /** - * @brief Multiplies a float \p num by 2 raised to the power \p exp. - * - * Equivalent to calling \c ldexp(num, exp). - * - * Parameters - * - \p num: Floating-point (float) value to be scaled. - * - \p exp: Integer exponent by which \p num is multiplied (power of two). - * - * Return value - * - The product \f$\text{num} \times 2^\text{exp}\f$ as a float. - * - May return ±HUGE_VALF on overflow, or a denormalized value or 0 on underflow. + * @brief Scales a \c float \p num by \f$2^\text{exp}\f$. Equivalent to \c ldexp(num, exp). * - * Error handling - * - As with the other overloads, \c errno may be set to \c ERANGE and floating-point exceptions - * (e.g., \c FE_OVERFLOW, \c FE_UNDERFLOW) may occur depending on the platform if errno has not been disabled. + * Overflow returns ±HUGE_VALF, underflow a subnormal or zero. * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. - + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c float. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr float ldexpf(float num, int exp) noexcept { return ccm::ldexp(num, exp); } /** - * @brief Multiplies a long double \p num by 2 raised to the power \p exp. - * - * Parameters - * - \p num: A \c long double value to be scaled by a power of two. - * - \p exp: An integer exponent by which \p num is multiplied (i.e., \f$2^\text{exp}\f$). - * - * Return value - * - The product \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. - * - May return ±HUGE_VALL on overflow, or a denormalized value or 0 on underflow. - * - * Error handling - * - If range errors occur, \c errno may be set to \c ERANGE, and floating-point exceptions - * (\c FE_OVERFLOW, \c FE_UNDERFLOW) may be raised, depending on the implementation and if errno has not been disabled. - * - If \p num is ±0 or ±∞, the function returns it unmodified. - * - If \p exp is 0, \p num is returned unmodified. - * - If \p num is NaN, NaN is returned. - * - * Notes - * - This overload uses native \c long double support for multiplying by a power of two. + * @brief Scales a \c long double \p num by \f$2^\text{exp}\f$ using native long double support. * - * @attention During compile time evaluation, errno may or may not be set depending on the compiler. Generally assume it is not. + * Overflow returns ±HUGE_VALL, underflow a subnormal or zero. ±0, ±∞, and NaN pass through + * unchanged, and \p num is returned as-is when \p exp is 0. On a range error, and unless errno + * was disabled in cmake, \c errno may be set to \c ERANGE and \c FE_OVERFLOW or \c FE_UNDERFLOW + * raised, depending on the platform. * + * @param num Value to scale. + * @param exp Power of two to scale by. + * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. + * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr long double ldexpl(long double num, int exp) noexcept diff --git a/include/ccmath/math/trig/impl/inv_trig_impl.hpp b/include/ccmath/math/trig/impl/inv_trig_impl.hpp index 901028d9..3c71659e 100644 --- a/include/ccmath/math/trig/impl/inv_trig_impl.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_impl.hpp @@ -85,11 +85,11 @@ namespace ccm::internal::impl { const T ax = ccm::fabs(x); - // asin(x) = x + x^3/6 + O(x^5); the cubic coefficient is +1/6 (this fast path previously + // asin(x) = x + x^3/6 + O(x^5). The cubic coefficient is +1/6 (this fast path previously // used -1/6, which underran by ~x^3/3 over the tiny-|x| band). if (ax < static_cast(0x1.0p-14)) { return x + x * x * x * static_cast(0x1.5555555555555p-3); } - // Float evaluates in double then rounds once; the same-type path is in the else so it is + // Float evaluates in double then rounds once. The same-type path is in the else so it is // discarded rather than left unreachable for the float instantiation (MSVC C4702). if constexpr (sizeof(T) == sizeof(float)) { @@ -149,7 +149,7 @@ namespace ccm::internal::impl template constexpr T atan_impl(T x) noexcept { - // The |x|<=1 path composes sqrt, a division, and asin; in float those roundings compound to + // The |x|<=1 path composes sqrt, a division, and asin. In float those roundings compound to // ~5 ULP. Evaluate float atan through the double kernel and round once (double atan is within // a couple of double ULP, so the float result is effectively correctly rounded). The double // path lives in the else so it is discarded for float rather than left as unreachable code, diff --git a/include/ccmath/math/trig/impl/sincos_double_data.hpp b/include/ccmath/math/trig/impl/sincos_double_data.hpp index 3b237b07..70871719 100644 --- a/include/ccmath/math/trig/impl/sincos_double_data.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_data.hpp @@ -17,7 +17,7 @@ namespace ccm::internal::sincos_double_data // All small-range constants are genuine binary64 (the float kernel's constants promoted to // double would carry only ~24 bits and make the double kernel float-grade). Generated by - // Sollya at 250-bit precision; see + // Sollya at 250-bit precision. // sin(k * pi/8) for k = 0..15, correctly rounded to double. constexpr std::array SIN_K_PI_OVER_8 = { @@ -27,13 +27,13 @@ namespace ccm::internal::sincos_double_data // Cody-Waite three-part -pi/8 for the small-range reduction. The high part has 31 significant // bits so k * MPI[0] is exact for |k| < 2^22, which covers the small-range threshold (|x| < 2^20, - // |k| < 2^21.4); the lower parts carry pi/8 to ~115 bits total. + // |k| < 2^21.4). The lower parts carry pi/8 to ~115 bits total. constexpr std::array MPI = { -0x1.921fb544p-2, -0x1.0b4611a8p-36, 0x1.d9cceba3f91f2p-68 }; // 8/pi as a double. constexpr double ONE_OVER_PI = 0x1.45f306dc9c883p+1; - // sin(y)/y on [-pi/16, pi/16] = SIN_POLY[0] + y^2 (SIN_POLY[1] + y^2 (...)); Taylor coefficients + // sin(y)/y on [-pi/16, pi/16] = SIN_POLY[0] + y^2 (SIN_POLY[1] + y^2 (...)). Taylor coefficients // rounded to double (realized error ~2^-63, well within the 4-ULP contract). sin(y) = y * poly. constexpr std::array SIN_POLY = { 0x1.0p0, // 1 @@ -44,7 +44,7 @@ namespace ccm::internal::sincos_double_data -0x1.ae64567f544e4p-26, // -1/39916800 }; - // cos(y) on [-pi/16, pi/16] = COS_POLY[0] + y^2 (COS_POLY[1] + ...); Sollya minimax, error ~2^-68. + // cos(y) on [-pi/16, pi/16] = COS_POLY[0] + y^2 (COS_POLY[1] + ...). Sollya minimax, error ~2^-68. constexpr std::array COS_POLY = { 0x1.0p0, -0x1.0p-1, 0x1.55555555554fp-5, -0x1.6c16c16b84c68p-10, 0x1.a019f88d4441ep-16, -0x1.27a8a5f9090d7p-22, }; diff --git a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp index 3aea5288..ec4612d1 100644 --- a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp +++ b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp @@ -27,7 +27,7 @@ namespace ccm::internal::impl::sincos_ph { // Fractional bits of 8/pi, frac(8/pi) = sum FRAC_8_OVER_PI[i] * 2^(-64 (i+1)). Generated by MPFR - // at 4096-bit precision; 32 words (2048 bits) cover the binary64 exponent range with guard bits. + // at 4096-bit precision. 32 words (2048 bits) cover the binary64 exponent range with guard bits. inline constexpr std::array FRAC_8_OVER_PI = { 0x8be60db9391054a7ULL, 0xf09d5f47d4d37703ULL, 0x6d8a5664f10e4107ULL, 0xf9458eaf7aef1586ULL, 0xdc91b8e909374b80ULL, 0x1924bba827464873ULL, 0xf877ac72c4a69cfbULL, 0xa208d7d4baed1213ULL, 0xa671c09ad17df904ULL, 0xe64758e60d4ce7d2ULL, 0x72117e2ef7e4a0ecULL, 0x7fe25fff7816603fULL, @@ -63,8 +63,8 @@ namespace ccm::internal::impl::sincos_ph return U128{ hi, lo }; } - // Add a 64-bit word into the 256-bit accumulator (acc[0] low .. acc[3] high) at limb `word`, - // propagating carries; the accumulator wraps mod 2^256, which is exactly the mod-16 we want. + // Add a 64-bit word into the 256-bit accumulator (acc[0] low .. acc[3] high) at limb word, + // propagating carries. The accumulator wraps mod 2^256, which is exactly the mod-16 we want. constexpr void add_at_word(std::uint64_t acc[4], std::uint64_t val, int word) noexcept { while (val != 0 && word < 4) @@ -76,7 +76,7 @@ namespace ccm::internal::impl::sincos_ph } } - // Add (w << bitpos) into the 256-bit accumulator; bitpos may be negative (low bits are dropped, + // Add (w << bitpos) into the 256-bit accumulator, bitpos may be negative (low bits are dropped, // they fall below the accumulator's 2^-252 resolution). constexpr void add_word_at(std::uint64_t acc[4], std::uint64_t w, long bitpos) noexcept { @@ -111,7 +111,7 @@ namespace ccm::internal::impl::sincos_ph const long binary_exp = static_cast(e) - 52; // x = mantissa * 2^binary_exp // acc holds (x * 8/pi) mod 16 scaled by 2^252: top 4 bits (255..252) are the integer mod 16, - // the rest is the fraction. 8/pi = 2 + frac(8/pi); the integer part 2 contributes 2*x. + // the rest is the fraction. 8/pi = 2 + frac(8/pi). The integer part 2 contributes 2*x. std::uint64_t acc[4] = { 0ULL, 0ULL, 0ULL, 0ULL }; add_word_at(acc, mantissa, binary_exp + 1 + 252); for (int i = 0; i < 32; ++i) diff --git a/showcase/README.md b/showcase/README.md index bc68de33..f9c6f8c6 100644 --- a/showcase/README.md +++ b/showcase/README.md @@ -7,7 +7,7 @@ Publish-quality comparisons of CCMath against other constexpr math libraries. Ph ```bash ./showcase/run build # configure + build (CLion preset when available) ./showcase/run doctor # verify binaries -./showcase/run all # full suite → showcase/reports/ +./showcase/run all # full suite, reports in showcase/reports/ ``` Run `./showcase/run help` for every command. @@ -28,13 +28,13 @@ See `showcase/item/README.md` for the full configuration list. ## CCMath paths under test -| Path | What it measures | -|------|------------------| -| `ccmath/public_default` | Production entry point (`ccm::sqrt`, `ccm::sin`) | -| `ccmath/generic_gen` | Generic kernel (`ccm::gen::sqrt_gen`, `ccm::gen::sin_gen`) | -| `ccmath/runtime_rt` | Runtime dispatcher (`ccm::rt::sqrt_rt`, `ccm::rt::sin_rt`) | -| `ccmath/runtime_simd` | SIMD fast path when compiled in | -| `ccmath/runtime_builtin` | Direct compiler builtin when available | +| Path | What it measures | +|--------------------------|------------------------------------------------------------| +| `ccmath/public_default` | Production entry point (`ccm::sqrt`, `ccm::sin`) | +| `ccmath/generic_gen` | Generic kernel (`ccm::gen::sqrt_gen`, `ccm::gen::sin_gen`) | +| `ccmath/runtime_rt` | Runtime dispatcher (`ccm::rt::sqrt_rt`, `ccm::rt::sin_rt`) | +| `ccmath/runtime_simd` | SIMD fast path when compiled in | +| `ccmath/runtime_builtin` | Direct compiler builtin when available | External backends in Phase 1: **GCEM** (accuracy + perf + compile), **std** (perf only). @@ -48,10 +48,10 @@ The generic_gen path calls the raw generic kernel directly. For sin this bypasse ## CMake presets -| Preset | Use | -|--------|-----| +| Preset | Use | +|--------------------------------|---------------------------------------------| | `clion-clang-release-showcase` | CLion (single-config, `out/clang-showcase`) | -| `ninja-clang-release-showcase` | Terminal / CI-style (multi-config Release) | +| `ninja-clang-release-showcase` | Terminal / CI-style (multi-config Release) | ```bash cmake --preset clion-clang-release-showcase @@ -64,10 +64,10 @@ Or use `./showcase/run build`, which picks the CLion preset when present. Output lands under `showcase/reports/` (gitignored): -- `perf/` — Google Benchmark JSON -- `accuracy/` — per-path failure logs -- `compile/` — probe timings and object sizes -- `summary.json` — merged suite output +- `perf/`: Google Benchmark JSON +- `accuracy/`: per-path failure logs +- `compile/`: probe timings and object sizes +- `summary.json`: merged suite output ## Fairness notes diff --git a/showcase/item/README.md b/showcase/item/README.md index f88b7e8e..af70cb83 100644 --- a/showcase/item/README.md +++ b/showcase/item/README.md @@ -4,31 +4,32 @@ CLion picks up any `*.run.xml` file under the project tree. These live under `sh ## One-time setup -1. **CMake profile:** Settings → CMake → Profiles → add preset **CLion Clang Release + showcase** (`clion-clang-release-showcase`). Output directory: `out/clang-showcase`. +1. **CMake profile:** Settings > CMake > Profiles > add preset **CLion Clang Release + showcase** ( + `clion-clang-release-showcase`). Output directory: `out/clang-showcase`. 2. **Build:** Run **showcase / build** (or `./showcase/run build` from the repo root). 3. **Check:** Run **showcase / doctor** to confirm binaries were found. ## Run configurations -| Group | Name | What it runs | -|-------|------|----------------| -| setup | showcase / build | Configure + build showcase targets | -| setup | showcase / doctor | Print build dir and binary paths | -| suite | showcase / suite / all | Perf + accuracy + compile + summary | -| suite | showcase / suite / perf | Google Benchmark JSON for sqrt and sin | -| suite | showcase / suite / accuracy | MPFR campaigns for all paths | -| suite | showcase / suite / compile | Compile-time probe matrix | -| perf | showcase / perf / sqrt | Benchmark sqrt (all backends and paths) | -| perf | showcase / perf / sin | Benchmark sin | -| accuracy | showcase / accuracy / sqrt (all) | MPFR, all CCMath paths + GCEM | -| accuracy | showcase / accuracy / sin (all) | MPFR, all CCMath paths + GCEM | -| accuracy | showcase / accuracy / sqrt public | MPFR, ccmath public_default only | -| accuracy | showcase / accuracy / sqrt generic | MPFR, ccmath generic_gen only | -| accuracy | showcase / accuracy / sqrt rt | MPFR, ccmath runtime_rt only | -| accuracy | showcase / accuracy / sin public | MPFR, ccmath public_default only | -| accuracy | showcase / accuracy / sin generic | MPFR, ccmath generic_gen only | -| accuracy | showcase / accuracy / gcem sqrt | MPFR, GCEM sqrt only | -| accuracy | showcase / accuracy / gcem sin | MPFR, GCEM sin only | +| Group | Name | What it runs | +|----------|------------------------------------|-----------------------------------------| +| setup | showcase / build | Configure + build showcase targets | +| setup | showcase / doctor | Print build dir and binary paths | +| suite | showcase / suite / all | Perf + accuracy + compile + summary | +| suite | showcase / suite / perf | Google Benchmark JSON for sqrt and sin | +| suite | showcase / suite / accuracy | MPFR campaigns for all paths | +| suite | showcase / suite / compile | Compile-time probe matrix | +| perf | showcase / perf / sqrt | Benchmark sqrt (all backends and paths) | +| perf | showcase / perf / sin | Benchmark sin | +| accuracy | showcase / accuracy / sqrt (all) | MPFR, all CCMath paths + GCEM | +| accuracy | showcase / accuracy / sin (all) | MPFR, all CCMath paths + GCEM | +| accuracy | showcase / accuracy / sqrt public | MPFR, ccmath public_default only | +| accuracy | showcase / accuracy / sqrt generic | MPFR, ccmath generic_gen only | +| accuracy | showcase / accuracy / sqrt rt | MPFR, ccmath runtime_rt only | +| accuracy | showcase / accuracy / sin public | MPFR, ccmath public_default only | +| accuracy | showcase / accuracy / sin generic | MPFR, ccmath generic_gen only | +| accuracy | showcase / accuracy / gcem sqrt | MPFR, GCEM sqrt only | +| accuracy | showcase / accuracy / gcem sin | MPFR, GCEM sin only | Reports go to `showcase/reports/` (gitignored). diff --git a/tests/conformance/fenv_exception_test.cpp b/tests/conformance/fenv_exception_test.cpp index 7fdbf7b1..5df4738a 100644 --- a/tests/conformance/fenv_exception_test.cpp +++ b/tests/conformance/fenv_exception_test.cpp @@ -31,10 +31,7 @@ namespace template void consume(T value) - { - volatile T sink = value; - (void)sink; - } + { [[maybe_unused]] volatile T sink = value; } } // namespace TEST(CcmathFenvExceptionTests, DomainErrorsRaiseInvalidLikeStd) diff --git a/tests/conformance/fma_software_fenv_test.cpp b/tests/conformance/fma_software_fenv_test.cpp index 63cb24a9..8a555f2a 100644 --- a/tests/conformance/fma_software_fenv_test.cpp +++ b/tests/conformance/fma_software_fenv_test.cpp @@ -31,10 +31,7 @@ namespace template void consume(T value) - { - volatile T sink = value; - (void)sink; - } + { [[maybe_unused]] volatile T sink = value; } } // namespace TEST(CcmathSoftwareFmaFenvTests, InvalidFlagsMatchStd) diff --git a/tests/conformance/pow_errno_fenv_test.cpp b/tests/conformance/pow_errno_fenv_test.cpp index a593a505..2066b773 100644 --- a/tests/conformance/pow_errno_fenv_test.cpp +++ b/tests/conformance/pow_errno_fenv_test.cpp @@ -28,10 +28,7 @@ namespace template void consume(T value) - { - volatile T sink = value; - (void)sink; - } + { [[maybe_unused]] volatile T sink = value; } CCM_NEVER_INLINE double invoke_pow_gen(double base, double exp) { return ccm::gen::pow_gen(base, exp); } @@ -118,7 +115,7 @@ TEST(CcmathPowFenvTests, GenericPowfBaseTwoCorrectlyRoundedAtRangeThresholdsAllM constexpr float kMax = std::numeric_limits::max(); constexpr float kMinSub = std::numeric_limits::denorm_min(); - // 2^128 and 2^129 overflow; 2^-150 and below sit beneath the smallest subnormal 2^-149. + // 2^128 and 2^129 overflow. 2^-150 and below sit beneath the smallest subnormal 2^-149. constexpr std::array kCases = { { { 128.0F, kInf, kInf, kMax, kMax }, { 129.0F, kInf, kInf, kMax, kMax }, diff --git a/tests/shared/oracle/big_int_gmp.cpp b/tests/shared/oracle/big_int_gmp.cpp index a6e0bf38..8e464a24 100644 --- a/tests/shared/oracle/big_int_gmp.cpp +++ b/tests/shared/oracle/big_int_gmp.cpp @@ -48,7 +48,7 @@ namespace [[nodiscard]] Big make_big(const std::array& words) { return Big(words); } - // GMP bit counts are mp_bitcnt_t (32-bit unsigned long on Windows); every count we pass is + // GMP bit counts are mp_bitcnt_t (32-bit unsigned long on Windows). Every count we pass is // bounded well below that, so the narrowing cast is safe. [[nodiscard]] constexpr mp_bitcnt_t to_bitcnt(std::size_t bits) { return static_cast(bits); } diff --git a/tests/shared/oracle/coremath_pow_common.hpp b/tests/shared/oracle/coremath_pow_common.hpp index 26f317db..70b5a523 100644 --- a/tests/shared/oracle/coremath_pow_common.hpp +++ b/tests/shared/oracle/coremath_pow_common.hpp @@ -26,7 +26,7 @@ namespace ccm::test::oracle // Higher-precision correctly-rounded float reference, used only to cross-check // disagreements with the primary float oracle (cr_powf). cr_powf has a fast path // that flushes results extremely close to 1.0 to exactly 1.0 in every rounding - // mode (ignoring directed rounding) and mishandles (-1)^(large even); the double + // mode (ignoring directed rounding) and mishandles (-1)^(large even). The double // oracle cr_pow does not. We evaluate cr_pow under round-up and round-down, take // the round-to-odd double, and cast it to float in the active rounding mode, which // yields the correctly rounded float without double rounding. @@ -80,10 +80,10 @@ namespace ccm::test::oracle // True when the function output disagrees with the primary oracle but is in fact // correctly rounded (confirmed by a higher-precision cross-check), i.e. the oracle - // is wrong for this case. float cross-checks against the round-to-odd double oracle; + // is wrong for this case. float cross-checks against the round-to-odd double oracle, // double resolves the near-1 region where cr_pow ignores directed rounding. template - inline bool function_matches_high_precision_truth(T base, T exponent, T actual) + inline bool function_matches_high_precision_truth([[maybe_unused]] T base, [[maybe_unused]] T exponent, [[maybe_unused]] T actual) { if constexpr (std::is_same_v) { @@ -97,9 +97,6 @@ namespace ccm::test::oracle } else { - (void)base; - (void)exponent; - (void)actual; return false; } } diff --git a/tests/shared/oracle/mpfr_oracle_harness.hpp b/tests/shared/oracle/mpfr_oracle_harness.hpp index 2f5ecfe7..15ed1367 100644 --- a/tests/shared/oracle/mpfr_oracle_harness.hpp +++ b/tests/shared/oracle/mpfr_oracle_harness.hpp @@ -219,7 +219,7 @@ namespace ccm::test::oracle if (is_exceptional_or_zero_result(actual) || is_exceptional_or_zero_result(fallback_expected)) { - // MPFR is the correctly-rounded oracle; std::pow is only the fallback for IEEE + // MPFR is the correctly-rounded oracle. std::pow is only the fallback for IEEE // special-case conventions MPFR may not model (signed zeros, inf/NaN from C rules). // A result that matches the correctly-rounded MPFR value is never a failure, even // when the platform std::pow disagrees: e.g. pow(2, -1074) is exactly the smallest diff --git a/tests/shared/oracle/oracle_campaign_common.hpp b/tests/shared/oracle/oracle_campaign_common.hpp index dc6d6099..de5cefb1 100644 --- a/tests/shared/oracle/oracle_campaign_common.hpp +++ b/tests/shared/oracle/oracle_campaign_common.hpp @@ -147,10 +147,9 @@ namespace ccm::test::oracle inline bool is_modeled_generic_pow_case(T base, T exponent) { return std::isfinite(base) && std::isfinite(exponent) && base > T{}; } - inline bool uses_public_mpfr_oracle(ccm::test::pow_path::validation_path path) + inline bool uses_public_mpfr_oracle([[maybe_unused]] ccm::test::pow_path::validation_path path) { #if defined(CCM_CONFIG_TEST_DISABLE_RUNTIME_BUILTIN_POW) - (void)path; return false; #else return path == ccm::test::pow_path::validation_path::public_default; @@ -307,7 +306,7 @@ namespace ccm::test::oracle } // Echo hard-failure records to stdout so CI logs surface the offending input without an - // uploaded artifact. The JSON event log keeps the full record; this is the at-a-glance view. + // uploaded artifact. The JSON event log keeps the full record. This is the at-a-glance view. template inline void print_hard_failures(const std::vector>& events) { diff --git a/tests/shared/utils/math_samples.hpp b/tests/shared/utils/math_samples.hpp index 0120c53a..6a6c6aba 100644 --- a/tests/shared/utils/math_samples.hpp +++ b/tests/shared/utils/math_samples.hpp @@ -135,7 +135,7 @@ namespace ccm::test::samples ternary_input{ std::numeric_limits::quiet_NaN(), 1.0F, 1.0F }, ternary_input{ 1.0F, 1.0F, std::numeric_limits::quiet_NaN() }, // Double-rounding killers: double(x)*double(y) + double(z) cast back to float rounds twice - // and is off by 1 ulp; only a correctly-rounded fused kernel matches. See fma.hpp. + // and is off by 1 ulp. Only a correctly-rounded fused kernel matches. See fma.hpp. ternary_input{ 0x1.001p+0F, 0x1.001p+0F, 0x1.0p-53F }, // 1+2^-12 squared + 2^-53 -> 0x3f801001 ternary_input{ 0x1.001p+0F, 0x1.001p+0F, -0x1.0p-53F }, // 1+2^-12 squared - 2^-53 -> 0x3f801000 ternary_input{ 0x1.8p+1F, 0x1.000002p+0F, -0x1.0p-149F }, // 3*(1+2^-23) - 2^-149 diff --git a/tests/src/math/power/pow_test.cpp b/tests/src/math/power/pow_test.cpp index e533bbf4..a0776905 100644 --- a/tests/src/math/power/pow_test.cpp +++ b/tests/src/math/power/pow_test.cpp @@ -35,10 +35,7 @@ namespace template void consume(T value) - { - volatile T sink = value; - (void)sink; - } + { [[maybe_unused]] volatile T sink = value; } } // namespace // [cmath.syn]: Synopsis-level overloads are constexpr entry points. diff --git a/tests/src/math/power/power_ulp_accuracy_test.cpp b/tests/src/math/power/power_ulp_accuracy_test.cpp index f0b7517d..8a4ee48c 100644 --- a/tests/src/math/power/power_ulp_accuracy_test.cpp +++ b/tests/src/math/power/power_ulp_accuracy_test.cpp @@ -72,7 +72,7 @@ TEST(CcmathPowerUlpTests, PowGenDoubleRegressionCases) { 945971881662.053466796875, 15.38309228199631562 }, // Base just above 1 with a huge exponent lands the result deep in the over/underflow // scaled band (here ~2^-735). That region used to drop to the fast single-double path - // and lost ~38 ULP; it must now ride the accurate double-double reconstruction. + // and lost ~38 ULP. It must now ride the accurate double-double reconstruction. { 0x1.00000000000ffp+0, -0x1.0p53 }, { 0x1.000000000010p+0, -0x1.0p53 }, // Symmetric overflow-adjacent band (~2^+735). @@ -313,7 +313,7 @@ TEST(CcmathPowerUlpTests, PowfSmallIntegerLoopBoundary) // [c.math]/1: inputs whose phase-1 Ziv test fails, forcing the double-double fallback and its // round-to-odd reconstruction. Mined by replaying the phase-1 kernel and kept only when the -// fallback path is taken on FMA targets; expected bits are MPFR correctly rounded results. +// fallback path is taken on FMA targets. Expected bits are MPFR correctly rounded results. TEST(CcmathPowerUlpTests, PowfZivFallbackRegressionCases) { struct ZivCase diff --git a/tests/src/math/trig/trig_kernel_regression_test.cpp b/tests/src/math/trig/trig_kernel_regression_test.cpp index b3decb3f..0b0cb8c5 100644 --- a/tests/src/math/trig/trig_kernel_regression_test.cpp +++ b/tests/src/math/trig/trig_kernel_regression_test.cpp @@ -11,7 +11,7 @@ // Regression coverage for the generic trig kernels (the path used for constexpr and the // non-builtin runtime). Pins the defects fixed in the trig overhaul: the large-argument // reduction collapse, the float-grade double kernel, the sign of a signed zero, the asin -// tiny-|x| cubic sign, and the float atan accuracy. Goldens are MPFR correctly-rounded values; +// tiny-|x| cubic sign, and the float atan accuracy. Goldens are MPFR correctly-rounded values, // the accuracy contract is 4 ULP. #include "ccmath/internal/math/generic/func/trig/cos_gen.hpp" @@ -75,7 +75,7 @@ namespace TEST(CcmathTrigKernelRegression, DoubleWithinContract) { - // {arg_bits, golden_bits} from MPFR. sin(1.5) was float-grade; the large args collapsed. + // {arg_bits, golden_bits} from MPFR. sin(1.5) was float-grade. The large args collapsed. EXPECT_LE(d_ulp(ccm::gen::sin_gen(d_from(0x3ff8000000000000ULL)), d_from(0x3fefeb7a9b2c6d8bULL)), 4); // sin(1.5) EXPECT_LE(d_ulp(ccm::gen::sin_gen(d_from(0x4170000000000000ULL)), d_from(0xbfe8f22f8433d6eeULL)), 4); // sin(2^24) EXPECT_LE(d_ulp(ccm::gen::sin_gen(d_from(0x430c6bf526340000ULL)), d_from(0x3feb76f88136cebaULL)), 4); // sin(1e15) diff --git a/tools/asmlab/CMakeLists.txt b/tools/asmlab/CMakeLists.txt index 12f2d769..18f0218a 100644 --- a/tools/asmlab/CMakeLists.txt +++ b/tools/asmlab/CMakeLists.txt @@ -10,7 +10,7 @@ endif () find_package(Python3 COMPONENTS Interpreter QUIET) if (NOT Python3_FOUND) - message(WARNING "asmlab: Python3 not found; convenience targets will be skipped") + message(WARNING "asmlab: Python3 not found, convenience targets will be skipped") return() endif () diff --git a/tools/asmlab/scripts/run_meta.py b/tools/asmlab/scripts/run_meta.py index 2508844f..8a9b066e 100644 --- a/tools/asmlab/scripts/run_meta.py +++ b/tools/asmlab/scripts/run_meta.py @@ -8,6 +8,7 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """Record git, compiler, and tool versions for an asmlab run.""" +import contextlib import datetime from datetime import timezone import json @@ -50,13 +51,11 @@ def _host_cpu(): if res.returncode == 0: return res.stdout.strip() if sysname == "Linux": - try: - with open("/proc/cpuinfo") as fh: - for line in fh: - if line.startswith("model name"): - return line.split(":", 1)[1].strip() - except OSError: - pass + # Fall back to platform.processor() if /proc/cpuinfo cannot be read. + with contextlib.suppress(OSError), open("/proc/cpuinfo") as fh: + for line in fh: + if line.startswith("model name"): + return line.split(":", 1)[1].strip() return platform.processor() or "unknown" diff --git a/tools/asmlab/scripts/source_map.py b/tools/asmlab/scripts/source_map.py index a3754e1e..bb7685be 100644 --- a/tools/asmlab/scripts/source_map.py +++ b/tools/asmlab/scripts/source_map.py @@ -12,6 +12,7 @@ and maps instructions to source locations. Writes source_map.json and source_map.md. """ +import contextlib import json import re import sys @@ -212,13 +213,11 @@ def _read_snippet(path, line, cache): candidates.append(Path(path)) for p in candidates: if p.exists(): - try: + with contextlib.suppress(OSError): lines = p.read_text().splitlines() if 1 <= line <= len(lines): cache[key] = lines[line - 1].strip() return cache[key] - except OSError: - pass cache[key] = "" return "" diff --git a/tools/gen_secondary_builds.py b/tools/gen_secondary_builds.py index 3b5e8da6..d759eace 100644 --- a/tools/gen_secondary_builds.py +++ b/tools/gen_secondary_builds.py @@ -258,7 +258,7 @@ def premake_defines_block(options: list[dict[str, Any]]) -> str: trigger = premake_trigger_name(option["meson_option"]) default = "true" if option["meson_default"] else "false" define = option["define"] - # Deterministic mode forces the runtime SIMD path off; the generic kernels are the only + # Deterministic mode forces the runtime SIMD path off. The generic kernels are the only # cross-hardware-stable evaluation. condition = f'_ccmath_option_enabled("{trigger}", {default})' if deterministic_trigger is not None and option["key"] == "runtime_simd": From b6220fcd083067b7b0a35770d846051f08278f5e Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 08:13:07 -0400 Subject: [PATCH 20/52] Comment cleanup --- include/ccmath/math/trig/impl/inv_trig_impl.hpp | 7 +++++++ include/ccmath/math/trig/impl/sincos_double_data.hpp | 2 +- include/ccmath/math/trig/impl/sincos_float_data.hpp | 3 ++- include/ccmath/math/trig/impl/sincos_float_impl.hpp | 2 ++ include/ccmath/math/trig/impl/sincos_payne_hanek.hpp | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/ccmath/math/trig/impl/inv_trig_impl.hpp b/include/ccmath/math/trig/impl/inv_trig_impl.hpp index 3c71659e..7b44e002 100644 --- a/include/ccmath/math/trig/impl/inv_trig_impl.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_impl.hpp @@ -32,6 +32,8 @@ namespace ccm::internal::impl { namespace inv_trig_detail { + // Evaluates the asin(x)/x approximation polynomial from inv_trig_data (argument x^2) in + // parallel Estrin form, for |x| <= 1/2. template constexpr T asin_eval(T xsq) noexcept { @@ -51,6 +53,8 @@ namespace ccm::internal::impl return ccm::support::polyeval(x8, d0, d1, d2); } + // Two regions. For |x| <= 1/2, acos(x) = pi/2 - asin(x) via the asin polynomial. For + // |x| > 1/2, acos(|x|) = 2 asin(sqrt((1-|x|)/2)), reflected to pi - r when x < 0. template constexpr T acos_kernel(T x) noexcept { @@ -200,6 +204,9 @@ namespace ccm::internal::impl } } + // Resolves the atan2 special values (NaN, infinite, or zero operands, including signed zero) + // first, then reduces the general case to atan(y/x) with a quadrant correction by the sign + // of x. template constexpr T atan2_impl(T y, T x) noexcept { diff --git a/include/ccmath/math/trig/impl/sincos_double_data.hpp b/include/ccmath/math/trig/impl/sincos_double_data.hpp index 70871719..7e7173e1 100644 --- a/include/ccmath/math/trig/impl/sincos_double_data.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_data.hpp @@ -30,7 +30,7 @@ namespace ccm::internal::sincos_double_data // |k| < 2^21.4). The lower parts carry pi/8 to ~115 bits total. constexpr std::array MPI = { -0x1.921fb544p-2, -0x1.0b4611a8p-36, 0x1.d9cceba3f91f2p-68 }; - // 8/pi as a double. + // 8/pi as a double, the reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. constexpr double ONE_OVER_PI = 0x1.45f306dc9c883p+1; // sin(y)/y on [-pi/16, pi/16] = SIN_POLY[0] + y^2 (SIN_POLY[1] + y^2 (...)). Taylor coefficients diff --git a/include/ccmath/math/trig/impl/sincos_float_data.hpp b/include/ccmath/math/trig/impl/sincos_float_data.hpp index 5a68d3e4..430dc5d6 100644 --- a/include/ccmath/math/trig/impl/sincos_float_data.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_data.hpp @@ -23,9 +23,10 @@ namespace ccm::internal::sincos_float_data 0.0f, -0x1.87de2ap-2f, -0x1.6a09e6p-1f, -0x1.d906bcp-1f, -1.0f, -0x1.d906bcp-1f, -0x1.6a09e6p-1f, -0x1.87de2ap-2f, }; - // Multi-part pi/8 for small range reduction. + // Cody-Waite three-part -pi/8 for the small-range reduction. constexpr std::array MPI = { -0x1.921f8p-2f, -0x1.aa22p-21f, -0x1.68c234p-41f }; + // 8/pi as a float, the reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. constexpr float ONE_OVER_PI = 0x1.45f306p+1f; // Large-|x| reduction is handled by the shared Payne-Hanek path in sincos_payne_hanek.hpp. diff --git a/include/ccmath/math/trig/impl/sincos_float_impl.hpp b/include/ccmath/math/trig/impl/sincos_float_impl.hpp index 562f13e8..6da19f20 100644 --- a/include/ccmath/math/trig/impl/sincos_float_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_impl.hpp @@ -82,6 +82,8 @@ namespace ccm::internal::impl const float sin_k = data::SIN_K_PI_OVER_8[k & 15]; const float cos_k = data::SIN_K_PI_OVER_8[(k + 4) & 15]; + // sin(y) = y + y^3 p1(y^2) and cos(y) = 1 + y^2 q1(y^2) on |y| < pi/16, with float-grade + // two-term tails (the double kernel uses higher-order polynomials). const float y_sq = y * y; const float p1 = support::multiply_add(y_sq, 0x1.111112p-7f, -0x1.555556p-3f); const float q1 = support::multiply_add(y_sq, 0x1.54b8bep-5f, -0x1.ffffc4p-2f); diff --git a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp index ec4612d1..947e1bd0 100644 --- a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp +++ b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp @@ -46,7 +46,7 @@ namespace ccm::internal::impl::sincos_ph std::uint64_t lo; }; - // 64 x 64 -> 128 unsigned multiply, portable and constexpr (no __int128). + // 64-bit by 64-bit unsigned multiply to a 128-bit result, portable and constexpr (no __int128). constexpr U128 mul64(std::uint64_t a, std::uint64_t b) noexcept { const std::uint64_t a_lo = a & 0xffffffffULL; From 38db28dcc524c5300c29dda2f2bf3926ca336ac3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:31:42 +0000 Subject: [PATCH 21/52] ci: apply clang-format --- include/ccmath/internal/types/dyadic_float.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/ccmath/internal/types/dyadic_float.hpp b/include/ccmath/internal/types/dyadic_float.hpp index fd734372..2ceb7b3c 100644 --- a/include/ccmath/internal/types/dyadic_float.hpp +++ b/include/ccmath/internal/types/dyadic_float.hpp @@ -418,11 +418,11 @@ namespace ccm::types // d_lo is denormal, but the output is normal. int scale_up_exponent = 1 - exp_lo; T scale_up_factor = support::fp::FPBits::create_value( - Sign::POS, static_cast(support::fp::FPBits::exponent_bias + scale_up_exponent), implicit_mask) - .get_val(); - T scale_down_factor = support::fp::FPBits::create_value( - Sign::POS, static_cast(support::fp::FPBits::exponent_bias - scale_up_exponent), implicit_mask) - .get_val(); + Sign::POS, static_cast(support::fp::FPBits::exponent_bias + scale_up_exponent), implicit_mask) + .get_val(); + T scale_down_factor = support::fp::FPBits::create_value( + Sign::POS, static_cast(support::fp::FPBits::exponent_bias - scale_up_exponent), implicit_mask) + .get_val(); d_lo = support::fp::FPBits::create_value(sign, static_cast(exp_lo + scale_up_exponent), implicit_mask).get_val(); From 9382438c5ed393b7d339339a99fbf980f57bdb07 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 10:56:44 -0400 Subject: [PATCH 22/52] Improve general code quality --- .clang-tidy | 3 +- fuzzing/shared/input.hpp | 5 ++- .../generic/func/power/pow_impl/pow_impl.hpp | 4 +- .../internal/math/runtime/pp/utility.hpp | 43 ------------------- include/ccmath/internal/support/bits.hpp | 3 +- .../support/common_math_constants.hpp | 7 ++- .../support/fp/except_value_utils.hpp | 3 +- include/ccmath/math/misc/impl/gamma_data.hpp | 6 ++- .../ccmath/math/trig/impl/inv_trig_data.hpp | 4 +- .../math/trig/impl/sincos_double_data.hpp | 4 +- .../math/trig/impl/sincos_double_impl.hpp | 2 +- .../math/trig/impl/sincos_float_data.hpp | 4 +- .../math/trig/impl/sincos_float_impl.hpp | 2 +- .../math/trig/impl/sincos_payne_hanek.hpp | 8 ++-- .../math/basic/fma_correct_rounding_test.cpp | 13 +++--- .../internal/types/normalized_float_test.cpp | 4 +- .../math/power/pow_correct_rounding_test.cpp | 9 ++-- 17 files changed, 47 insertions(+), 77 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index ec405b73..428eaa7f 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,7 +7,6 @@ bugprone-*, modernize-*, -modernize-use-trailing-return-type, -modernize-use-constraints, --modernize-avoid-c-arrays, -modernize-use-nodiscard, -modernize-use-auto, @@ -18,6 +17,7 @@ readability-*, -readability-identifier-length, -readability-magic-numbers, -readability-function-cognitive-complexity, +-readability-uppercase-literal-suffix, cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, @@ -49,6 +49,7 @@ llvm-namespace-comment, hicpp-exception-baseclass, hicpp-multiway-paths-covered, hicpp-no-assembler, +-hicpp-uppercase-literal-suffix, misc-const-correctness, misc-definitions-in-headers, misc-header-include-cycle, diff --git a/fuzzing/shared/input.hpp b/fuzzing/shared/input.hpp index 4b10ad8d..d377dfd9 100644 --- a/fuzzing/shared/input.hpp +++ b/fuzzing/shared/input.hpp @@ -12,6 +12,7 @@ #include "ccmath/internal/support/fp/fp_bits.hpp" +#include #include #include #include @@ -31,8 +32,8 @@ namespace ccm::fuzz { if constexpr (std::numeric_limits::digits == 64 && sizeof(T) >= 10) { - unsigned char bytes[sizeof(T)]; - std::memcpy(bytes, &value, sizeof(T)); + std::array bytes{}; + std::memcpy(bytes.data(), &value, sizeof(T)); const bool integer_bit = (bytes[7] & 0x80U) != 0; const unsigned exponent = (static_cast(bytes[9] & 0x7FU) << 8) | bytes[8]; // Canonical iff the integer bit is set exactly when the biased exponent is non-zero. diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp index dcdb59bd..8de59c2b 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp @@ -362,8 +362,8 @@ namespace ccm::gen::impl const DoubleDouble t1 = two_prod(chh.lo, x); const DoubleDouble t2 = two_prod(cross, x); const DoubleDouble t3 = two_prod(clsq, x); - const double terms[9] = { t0.hi, t0.lo, t1.hi, t1.lo, t2.hi, t2.lo, t3.hi, t3.lo, -1.0 }; - return exact_sign_of_sum(terms, 9); + const std::array terms = { t0.hi, t0.lo, t1.hi, t1.lo, t2.hi, t2.lo, t3.hi, t3.lo, -1.0 }; + return exact_sign_of_sum(terms.data(), static_cast(terms.size())); } // Correctly rounded 1/sqrt(x) for finite x > 0, in every rounding mode. The exact residual diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index 4810a6f0..e706e207 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -16,10 +16,6 @@ #include #include -#if defined(_MSC_VER) && !((defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE)) - #include -#endif - namespace ccm::pp { // Per-(T, Abi) storage and operation set. Specialized by the scalar and @@ -94,45 +90,6 @@ namespace ccm::pp return __builtin_pow(a, b); } } -#elif defined(_MSC_VER) - #define CCM_PP_S_UNARY(NAME, CF, CD) \ - template \ - CCM_ALWAYS_INLINE T NAME(T x) \ - { \ - if constexpr (std::is_same_v) { return ::CF(x); } \ - else \ - { \ - return ::CD(x); \ - } \ - } - CCM_PP_S_UNARY(s_sqrt, sqrtf, sqrt) - CCM_PP_S_UNARY(s_floor, floorf, floor) - CCM_PP_S_UNARY(s_ceil, ceilf, ceil) - CCM_PP_S_UNARY(s_trunc, truncf, trunc) - CCM_PP_S_UNARY(s_round, roundf, round) - CCM_PP_S_UNARY(s_fabs, fabsf, fabs) - CCM_PP_S_UNARY(s_exp, expf, exp) - CCM_PP_S_UNARY(s_log, logf, log) - #undef CCM_PP_S_UNARY - - template - CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) - { - if constexpr (std::is_same_v) { return ::fmaf(a, b, c); } - else - { - return ::fma(a, b, c); - } - } - template - CCM_ALWAYS_INLINE T s_pow(T a, T b) - { - if constexpr (std::is_same_v) { return ::powf(a, b); } - else - { - return ::pow(a, b); - } - } #else extern "C" { diff --git a/include/ccmath/internal/support/bits.hpp b/include/ccmath/internal/support/bits.hpp index fa268eaf..80b97083 100644 --- a/include/ccmath/internal/support/bits.hpp +++ b/include/ccmath/internal/support/bits.hpp @@ -21,6 +21,7 @@ #include "ccmath/internal/support/is_constant_evaluated.hpp" #include "ccmath/internal/support/type_traits.hpp" +#include #include #include #include @@ -55,7 +56,7 @@ namespace ccm::support // from just the 80 bits the format actually uses, never touching the padding. struct byte_buffer { - unsigned char bytes[sizeof(From)]; + std::array bytes; }; const byte_buffer buffer = __builtin_bit_cast(byte_buffer, from); diff --git a/include/ccmath/internal/support/common_math_constants.hpp b/include/ccmath/internal/support/common_math_constants.hpp index cd921a2b..c6122cc1 100644 --- a/include/ccmath/internal/support/common_math_constants.hpp +++ b/include/ccmath/internal/support/common_math_constants.hpp @@ -75,12 +75,15 @@ namespace ccm::support::constants 0x1.5ee02a9241675p-1, 0x1.60e32f44788d8p-1 }; - // Range reduction constants for logarithms. + // Range reduction constants for logarithms, adapted from LLVM-libc + // (libc/src/__support/math/common_constants.h). The algorithm is described at + // https://libc.llvm.org/headers/math/log.html. // r(0) = 1, r(127) = 0.5 // r(k) = 2^-8 * ceil(2^8 * (1 - 2^-8) / (1 + k*2^-7)) // The constants are chosen so that v = fma(r, m_x, -1) is exact in single // precision, and -2^-8 <= v < 2^-7. - // TODO(IanP): Add reference to paper these constants are derived from once lntue has published the paper. + // TODO(IanP): Add the formal paper reference once it is published. The citation is still + // pending upstream too, as TODO(lntue) in LLVM-libc common_constants.h. alignas(32) constexpr std::array R = { 0x1p0, 0x1.fcp-1, 0x1.f8p-1, 0x1.f4p-1, 0x1.fp-1, 0x1.ecp-1, 0x1.e8p-1, 0x1.e4p-1, 0x1.ep-1, 0x1.dep-1, 0x1.dap-1, 0x1.d6p-1, 0x1.d4p-1, 0x1.dp-1, 0x1.ccp-1, 0x1.cap-1, 0x1.c6p-1, 0x1.c4p-1, 0x1.cp-1, 0x1.bep-1, 0x1.bap-1, 0x1.b8p-1, 0x1.b4p-1, 0x1.b2p-1, 0x1.aep-1, 0x1.acp-1, diff --git a/include/ccmath/internal/support/fp/except_value_utils.hpp b/include/ccmath/internal/support/fp/except_value_utils.hpp index 17138f8e..0159b8fb 100644 --- a/include/ccmath/internal/support/fp/except_value_utils.hpp +++ b/include/ccmath/internal/support/fp/except_value_utils.hpp @@ -21,6 +21,7 @@ #include "ccmath/internal/support/is_constant_evaluated.hpp" #include "ccmath/internal/support/type_traits.hpp" +#include #include #include @@ -42,7 +43,7 @@ namespace ccm::support::fp StorageType rnd_tonearest_offset; }; - Mapping values[N]; + std::array values; [[nodiscard]] constexpr std::optional lookup(StorageType x_bits) const { diff --git a/include/ccmath/math/misc/impl/gamma_data.hpp b/include/ccmath/math/misc/impl/gamma_data.hpp index 602b317d..9367f74a 100644 --- a/include/ccmath/math/misc/impl/gamma_data.hpp +++ b/include/ccmath/math/misc/impl/gamma_data.hpp @@ -15,13 +15,15 @@ #pragma once +#include + namespace ccm::internal::impl::data { // Polynomial approximating tgamma(d + 2.875) on [-0.5, 0.5] generated by // Sollya with: P = fpminimax(tgamma(x + 2.875), [|0,1,2,3,4,5,6,7|], [|D...|], [-0.5, 0.5]) - inline constexpr double k_gamma_coeffs[8] = { 0x1.c9a76b7de6f60p+0, 0x1.8f2754874fe83p+0, 0x1.0d11cba0c229fp+0, 0x1.e1f47c012064dp-2, - 0x1.828b9f2afb74fp-3, 0x1.e1cc49d830795p-5, 0x1.2c9af3e686836p-6, 0x1.1bf60c7e311f6p-8 }; + inline constexpr std::array k_gamma_coeffs = { 0x1.c9a76b7de6f60p+0, 0x1.8f2754874fe83p+0, 0x1.0d11cba0c229fp+0, 0x1.e1f47c012064dp-2, + 0x1.828b9f2afb74fp-3, 0x1.e1cc49d830795p-5, 0x1.2c9af3e686836p-6, 0x1.1bf60c7e311f6p-8 }; inline constexpr double k_gamma_base = 0x1.7p+1; diff --git a/include/ccmath/math/trig/impl/inv_trig_data.hpp b/include/ccmath/math/trig/impl/inv_trig_data.hpp index 11786c79..3d1e0fa6 100644 --- a/include/ccmath/math/trig/impl/inv_trig_data.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_data.hpp @@ -12,13 +12,15 @@ #pragma once +#include + namespace ccm::internal::inv_trig_data { inline constexpr double k_pi = 0x1.921fb54442d18p+1; inline constexpr double k_pi_over_2 = 0x1.921fb54442d18p+0; // P(x^2) - 1 where P(x^2) approximates asin(x)/x on [0, 0.5]. - inline constexpr double k_asin_coeffs[12] = { + inline constexpr std::array k_asin_coeffs = { 0x1.555555555538p-3, 0x1.333333336fd5bp-4, 0x1.6db6db41ce4bcp-5, 0x1.f1c72c66896dep-6, 0x1.6e89f0a0ac64bp-6, 0x1.1c6c111de4074p-6, 0x1.c6fa84b5699acp-7, 0x1.8ed60a3e6dd19p-7, 0x1.ab3a090750049p-8, 0x1.405213cd1ef46p-6, -0x1.0a5a381f73f65p-6, 0x1.05985a32a9045p-5, }; diff --git a/include/ccmath/math/trig/impl/sincos_double_data.hpp b/include/ccmath/math/trig/impl/sincos_double_data.hpp index 7e7173e1..95d5e563 100644 --- a/include/ccmath/math/trig/impl/sincos_double_data.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_data.hpp @@ -30,8 +30,8 @@ namespace ccm::internal::sincos_double_data // |k| < 2^21.4). The lower parts carry pi/8 to ~115 bits total. constexpr std::array MPI = { -0x1.921fb544p-2, -0x1.0b4611a8p-36, 0x1.d9cceba3f91f2p-68 }; - // 8/pi as a double, the reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. - constexpr double ONE_OVER_PI = 0x1.45f306dc9c883p+1; + // Reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. + constexpr double EIGHT_OVER_PI = 0x1.45f306dc9c883p+1; // sin(y)/y on [-pi/16, pi/16] = SIN_POLY[0] + y^2 (SIN_POLY[1] + y^2 (...)). Taylor coefficients // rounded to double (realized error ~2^-63, well within the 4-ULP contract). sin(y) = y * poly. diff --git a/include/ccmath/math/trig/impl/sincos_double_impl.hpp b/include/ccmath/math/trig/impl/sincos_double_impl.hpp index 661d38fb..26a58456 100644 --- a/include/ccmath/math/trig/impl/sincos_double_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_impl.hpp @@ -34,7 +34,7 @@ namespace ccm::internal::impl constexpr unsigned sincos_range_reduction_small(double x, double & u) { - const double prod_hi = x * data::ONE_OVER_PI; + const double prod_hi = x * data::EIGHT_OVER_PI; const double k = support::fp::nearest_integer(prod_hi); const double y_hi = support::multiply_add(k, data::MPI[0], x); diff --git a/include/ccmath/math/trig/impl/sincos_float_data.hpp b/include/ccmath/math/trig/impl/sincos_float_data.hpp index 430dc5d6..4d26940d 100644 --- a/include/ccmath/math/trig/impl/sincos_float_data.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_data.hpp @@ -26,8 +26,8 @@ namespace ccm::internal::sincos_float_data // Cody-Waite three-part -pi/8 for the small-range reduction. constexpr std::array MPI = { -0x1.921f8p-2f, -0x1.aa22p-21f, -0x1.68c234p-41f }; - // 8/pi as a float, the reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. - constexpr float ONE_OVER_PI = 0x1.45f306p+1f; + // Reciprocal of pi/8. The reduction computes k = round(x * 8/pi) from it. + constexpr float EIGHT_OVER_PI = 0x1.45f306p+1f; // Large-|x| reduction is handled by the shared Payne-Hanek path in sincos_payne_hanek.hpp. diff --git a/include/ccmath/math/trig/impl/sincos_float_impl.hpp b/include/ccmath/math/trig/impl/sincos_float_impl.hpp index 6da19f20..c845771a 100644 --- a/include/ccmath/math/trig/impl/sincos_float_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_impl.hpp @@ -34,7 +34,7 @@ namespace ccm::internal::impl constexpr unsigned sincosf_range_reduction_small(float x, float & u) { - const float prod_hi = x * data::ONE_OVER_PI; + const float prod_hi = x * data::EIGHT_OVER_PI; const float k = support::fp::nearest_integer(prod_hi); const float y_hi = support::multiply_add(k, data::MPI[0], x); diff --git a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp index 947e1bd0..3ec7c50e 100644 --- a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp +++ b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp @@ -65,7 +65,7 @@ namespace ccm::internal::impl::sincos_ph // Add a 64-bit word into the 256-bit accumulator (acc[0] low .. acc[3] high) at limb word, // propagating carries. The accumulator wraps mod 2^256, which is exactly the mod-16 we want. - constexpr void add_at_word(std::uint64_t acc[4], std::uint64_t val, int word) noexcept + constexpr void add_at_word(std::array& acc, std::uint64_t val, int word) noexcept { while (val != 0 && word < 4) { @@ -78,7 +78,7 @@ namespace ccm::internal::impl::sincos_ph // Add (w << bitpos) into the 256-bit accumulator, bitpos may be negative (low bits are dropped, // they fall below the accumulator's 2^-252 resolution). - constexpr void add_word_at(std::uint64_t acc[4], std::uint64_t w, long bitpos) noexcept + constexpr void add_word_at(std::array& acc, std::uint64_t w, long bitpos) noexcept { if (w == 0) { return; } if (bitpos < 0) @@ -99,7 +99,7 @@ namespace ccm::internal::impl::sincos_ph } // Returns k (low bits of the pi/8 multiple) and sets y to the reduced argument in [-pi/16, pi/16]. - constexpr unsigned payne_hanek_reduce(double x, double & y) noexcept + constexpr unsigned payne_hanek_reduce(double x, double& y) noexcept { using FPBits = support::fp::FPBits; @@ -112,7 +112,7 @@ namespace ccm::internal::impl::sincos_ph // acc holds (x * 8/pi) mod 16 scaled by 2^252: top 4 bits (255..252) are the integer mod 16, // the rest is the fraction. 8/pi = 2 + frac(8/pi). The integer part 2 contributes 2*x. - std::uint64_t acc[4] = { 0ULL, 0ULL, 0ULL, 0ULL }; + std::array acc = { 0ULL, 0ULL, 0ULL, 0ULL }; add_word_at(acc, mantissa, binary_exp + 1 + 252); for (int i = 0; i < 32; ++i) { diff --git a/tests/src/math/basic/fma_correct_rounding_test.cpp b/tests/src/math/basic/fma_correct_rounding_test.cpp index 06830c82..563b527b 100644 --- a/tests/src/math/basic/fma_correct_rounding_test.cpp +++ b/tests/src/math/basic/fma_correct_rounding_test.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -55,12 +56,12 @@ namespace struct FloatCase { std::uint32_t x, y, z; - std::uint32_t expected[4]; // FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD + std::array expected; // FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; struct DoubleCase { std::uint64_t x, y, z; - std::uint64_t expected[4]; + std::array expected; }; // Goldens generated with MPFR mpfr_fma at the target precision plus mpfr_subnormalize. @@ -111,8 +112,8 @@ namespace { 0x4000000000000001ULL, 0x4000000000000000ULL, 0x4000000000000001ULL, 0x4000000000000000ULL } }, }; - constexpr int kModes[4] = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; - constexpr const char* kName[4] = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; + constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; + constexpr std::array kName = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; } // namespace TEST(CcmathFmaCorrectRounding, FloatAllModes) @@ -123,7 +124,7 @@ TEST(CcmathFmaCorrectRounding, FloatAllModes) const float x = f_from_bits(c.x); const float y = f_from_bits(c.y); const float z = f_from_bits(c.z); - for (int m = 0; m < 4; ++m) + for (std::size_t m = 0; m < kModes.size(); ++m) { std::fesetround(kModes[m]); const float got = ccm::fma(x, y, z); @@ -143,7 +144,7 @@ TEST(CcmathFmaCorrectRounding, DoubleAllModes) const double x = d_from_bits(c.x); const double y = d_from_bits(c.y); const double z = d_from_bits(c.z); - for (int m = 0; m < 4; ++m) + for (std::size_t m = 0; m < kModes.size(); ++m) { std::fesetround(kModes[m]); const double got = ccm::fma(x, y, z); diff --git a/tests/src/math/internal/types/normalized_float_test.cpp b/tests/src/math/internal/types/normalized_float_test.cpp index 97919648..88986172 100644 --- a/tests/src/math/internal/types/normalized_float_test.cpp +++ b/tests/src/math/internal/types/normalized_float_test.cpp @@ -20,9 +20,9 @@ namespace { using FPBits = ccm::support::fp::FPBits; - constexpr ccm::support::fp::ExceptValues kFloatExcepts{ { + constexpr ccm::support::fp::ExceptValues kFloatExcepts{ { { { 0x3f800000U, 0x3f000000U, 0U, 0U, 0U }, - } }; + } } }; } // namespace TEST(CcmathInternalTypesTests, NormalizedFloatMul2MatchesLdexp) diff --git a/tests/src/math/power/pow_correct_rounding_test.cpp b/tests/src/math/power/pow_correct_rounding_test.cpp index aa65969d..7d16ab0c 100644 --- a/tests/src/math/power/pow_correct_rounding_test.cpp +++ b/tests/src/math/power/pow_correct_rounding_test.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -48,7 +49,7 @@ namespace { std::uint64_t base_bits; std::uint64_t exp_bits; - std::uint64_t expected[4]; // FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD + std::array expected; // FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; // Goldens generated with MPFR mpfr_pow at 53-bit target precision plus mpfr_subnormalize. @@ -80,8 +81,8 @@ namespace { 0x3b5fffffffffffffULL, 0x402c000000000000ULL, { 0x000ffffffffffff9ULL, 0x000ffffffffffff9ULL, 0x000ffffffffffffaULL, 0x000ffffffffffff9ULL } }, }; - constexpr int kModes[4] = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; - constexpr const char* kNames[4] = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; + constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; + constexpr std::array kNames = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; } // namespace TEST(CcmathPowCorrectRounding, BinaryDoubleAllModes) @@ -91,7 +92,7 @@ TEST(CcmathPowCorrectRounding, BinaryDoubleAllModes) { const double base = from_bits(c.base_bits); const double exp = from_bits(c.exp_bits); - for (int m = 0; m < 4; ++m) + for (std::size_t m = 0; m < kModes.size(); ++m) { std::fesetround(kModes[m]); const double got = ccm::gen::pow_gen(base, exp); From c66e944cafc6965497cce2461d1ab43432b818be Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 13:36:02 -0400 Subject: [PATCH 23/52] Improve general code quality --- .../internal/math/runtime/pp/msvc_intrin.hpp | 437 +++++++++--------- .../math/runtime/simd/instructions/neon.hpp | 4 +- include/ccmath/internal/types/big_int.hpp | 3 +- 3 files changed, 223 insertions(+), 221 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp index 87bb142c..2c9a79df 100644 --- a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp +++ b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp @@ -28,6 +28,7 @@ #include + #include #include #include @@ -47,16 +48,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(float v) { return _mm_set1_ps(v); } CCM_ALWAYS_INLINE static float get(SimdMember const &m, detail::SimdSizeType i) { - float a[4]; - _mm_storeu_ps(a, m); + std::array a; + _mm_storeu_ps(a.data(), m); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, float v) { - float a[4]; - _mm_storeu_ps(a, m); + std::array a; + _mm_storeu_ps(a.data(), m); a[i] = v; - m = _mm_loadu_ps(a); + m = _mm_loadu_ps(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(float const *p) { return _mm_loadu_ps(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, float *p) { _mm_storeu_ps(p, m); } @@ -77,16 +78,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi32(b ? -1 : 0); } CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { - std::int32_t a[4]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); return a[i] != 0; } CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { - std::int32_t a[4]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); a[i] = b ? -1 : 0; - m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a)); + m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a.data())); } CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } @@ -96,8 +97,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int32_t a[4]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); detail::SimdSizeType c = 0; for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -115,10 +116,10 @@ namespace ccm::pp #define CCM_PP_MSVC_F32_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ - float a[4]; \ - _mm_storeu_ps(a, v); \ + std::array a; \ + _mm_storeu_ps(a.data(), v); \ for (int i = 0; i < 4; ++i) { a[i] = detail::SFN(a[i]); } \ - return _mm_loadu_ps(a); \ + return _mm_loadu_ps(a.data()); \ } CCM_PP_MSVC_F32_ROUND(op_floor, s_floor) CCM_PP_MSVC_F32_ROUND(op_ceil, s_ceil) @@ -127,31 +128,31 @@ namespace ccm::pp #undef CCM_PP_MSVC_F32_ROUND CCM_ALWAYS_INLINE static SimdMember op_fma(SimdMember a, SimdMember b, SimdMember c) { - float aa[4], bb[4], cc[4]; - _mm_storeu_ps(aa, a); - _mm_storeu_ps(bb, b); - _mm_storeu_ps(cc, c); + std::array aa, bb, cc; + _mm_storeu_ps(aa.data(), a); + _mm_storeu_ps(bb.data(), b); + _mm_storeu_ps(cc.data(), c); for (int i = 0; i < 4; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } - return _mm_loadu_ps(aa); + return _mm_loadu_ps(aa.data()); } CCM_ALWAYS_INLINE static float hadd(SimdMember v) { - float a[4]; - _mm_storeu_ps(a, v); + std::array a; + _mm_storeu_ps(a.data(), v); return a[0] + a[1] + a[2] + a[3]; } CCM_ALWAYS_INLINE static float hmin(SimdMember v) { - float a[4]; - _mm_storeu_ps(a, v); + std::array a; + _mm_storeu_ps(a.data(), v); float s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static float hmax(SimdMember v) { - float a[4]; - _mm_storeu_ps(a, v); + std::array a; + _mm_storeu_ps(a.data(), v); float s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -160,8 +161,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - float a[4]; - _mm_storeu_ps(a, v); + std::array a; + _mm_storeu_ps(a.data(), v); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -189,16 +190,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(double v) { return _mm_set1_pd(v); } CCM_ALWAYS_INLINE static double get(SimdMember const &m, detail::SimdSizeType i) { - double a[2]; - _mm_storeu_pd(a, m); + std::array a; + _mm_storeu_pd(a.data(), m); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, double v) { - double a[2]; - _mm_storeu_pd(a, m); + std::array a; + _mm_storeu_pd(a.data(), m); a[i] = v; - m = _mm_loadu_pd(a); + m = _mm_loadu_pd(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(double const *p) { return _mm_loadu_pd(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, double *p) { _mm_storeu_pd(p, m); } @@ -219,16 +220,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi64x(b ? -1 : 0); } CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { - std::int64_t a[2]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); return a[i] != 0; } CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { - std::int64_t a[2]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); a[i] = b ? -1 : 0; - m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a)); + m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a.data())); } CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } @@ -238,8 +239,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int64_t a[2]; - _mm_storeu_si128(reinterpret_cast<__m128i *>(a), m); + std::array a; + _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); return (a[0] != 0 ? 1 : 0) + (a[1] != 0 ? 1 : 0); } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) @@ -255,11 +256,11 @@ namespace ccm::pp #define CCM_PP_MSVC_F64_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ - double a[2]; \ - _mm_storeu_pd(a, v); \ + std::array a; \ + _mm_storeu_pd(a.data(), v); \ a[0] = detail::SFN(a[0]); \ a[1] = detail::SFN(a[1]); \ - return _mm_loadu_pd(a); \ + return _mm_loadu_pd(a.data()); \ } CCM_PP_MSVC_F64_ROUND(op_floor, s_floor) CCM_PP_MSVC_F64_ROUND(op_ceil, s_ceil) @@ -268,38 +269,38 @@ namespace ccm::pp #undef CCM_PP_MSVC_F64_ROUND CCM_ALWAYS_INLINE static SimdMember op_fma(SimdMember a, SimdMember b, SimdMember c) { - double aa[2], bb[2], cc[2]; - _mm_storeu_pd(aa, a); - _mm_storeu_pd(bb, b); - _mm_storeu_pd(cc, c); + std::array aa, bb, cc; + _mm_storeu_pd(aa.data(), a); + _mm_storeu_pd(bb.data(), b); + _mm_storeu_pd(cc.data(), c); aa[0] = detail::s_fma(aa[0], bb[0], cc[0]); aa[1] = detail::s_fma(aa[1], bb[1], cc[1]); - return _mm_loadu_pd(aa); + return _mm_loadu_pd(aa.data()); } CCM_ALWAYS_INLINE static double hadd(SimdMember v) { - double a[2]; - _mm_storeu_pd(a, v); + std::array a; + _mm_storeu_pd(a.data(), v); return a[0] + a[1]; } CCM_ALWAYS_INLINE static double hmin(SimdMember v) { - double a[2]; - _mm_storeu_pd(a, v); + std::array a; + _mm_storeu_pd(a.data(), v); return a[0] < a[1] ? a[0] : a[1]; } CCM_ALWAYS_INLINE static double hmax(SimdMember v) { - double a[2]; - _mm_storeu_pd(a, v); + std::array a; + _mm_storeu_pd(a.data(), v); return a[0] > a[1] ? a[0] : a[1]; } template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - double a[2]; - _mm_storeu_pd(a, v); + std::array a; + _mm_storeu_pd(a.data(), v); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 2; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -338,16 +339,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) { return _mm_set1_epi32(v); } CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const &m, detail::SimdSizeType i) { - std::int32_t a[4]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int32_t v) { - std::int32_t a[4]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); a[i] = v; - m = detail::si128_load(a); + m = detail::si128_load(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const *p) { return detail::si128_load(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int32_t *p) { detail::si128_store(m, p); } @@ -374,8 +375,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int32_t a[4]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); detail::SimdSizeType c = 0; for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -387,11 +388,11 @@ namespace ccm::pp #define CCM_PP_MSVC_I32_BIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ - std::int32_t x[4], y[4]; \ - detail::si128_store(a, x); \ - detail::si128_store(b, y); \ + std::array x, y; \ + detail::si128_store(a, x.data()); \ + detail::si128_store(b, y.data()); \ for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ - return detail::si128_load(x); \ + return detail::si128_load(x.data()); \ } CCM_PP_MSVC_I32_BIN(mul, *) CCM_PP_MSVC_I32_BIN(div, /) @@ -407,22 +408,22 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } CCM_ALWAYS_INLINE static std::int32_t hadd(SimdMember v) { - std::int32_t a[4]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); return static_cast(a[0] + a[1] + a[2] + a[3]); } CCM_ALWAYS_INLINE static std::int32_t hmin(SimdMember v) { - std::int32_t a[4]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); std::int32_t s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static std::int32_t hmax(SimdMember v) { - std::int32_t a[4]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); std::int32_t s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -431,8 +432,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - std::int32_t a[4]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -462,16 +463,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) { return _mm_set1_epi64x(v); } CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const &m, detail::SimdSizeType i) { - std::int64_t a[2]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int64_t v) { - std::int64_t a[2]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); a[i] = v; - m = detail::si128_load(a); + m = detail::si128_load(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const *p) { return detail::si128_load(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int64_t *p) { detail::si128_store(m, p); } @@ -483,12 +484,12 @@ namespace ccm::pp #define CCM_PP_MSVC_I64_CMP(NAME, OP) \ CCM_ALWAYS_INLINE static MaskMember NAME(SimdMember a, SimdMember b) \ { \ - std::int64_t x[2], y[2], r[2]; \ - detail::si128_store(a, x); \ - detail::si128_store(b, y); \ + std::array x, y, r; \ + detail::si128_store(a, x.data()); \ + detail::si128_store(b, y.data()); \ r[0] = (x[0] OP y[0]) ? -1 : 0; \ r[1] = (x[1] OP y[1]) ? -1 : 0; \ - return detail::si128_load(r); \ + return detail::si128_load(r.data()); \ } CCM_PP_MSVC_I64_CMP(eq, ==) CCM_PP_MSVC_I64_CMP(ne, !=) @@ -509,8 +510,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int64_t a[2]; - detail::si128_store(m, a); + std::array a; + detail::si128_store(m, a.data()); return (a[0] != 0 ? 1 : 0) + (a[1] != 0 ? 1 : 0); } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) @@ -523,12 +524,12 @@ namespace ccm::pp #define CCM_PP_MSVC_I64_BIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ - std::int64_t x[2], y[2]; \ - detail::si128_store(a, x); \ - detail::si128_store(b, y); \ + std::array x, y; \ + detail::si128_store(a, x.data()); \ + detail::si128_store(b, y.data()); \ x[0] = static_cast(x[0] OP y[0]); \ x[1] = static_cast(x[1] OP y[1]); \ - return detail::si128_load(x); \ + return detail::si128_load(x.data()); \ } CCM_PP_MSVC_I64_BIN(mul, *) CCM_PP_MSVC_I64_BIN(div, /) @@ -540,28 +541,28 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } CCM_ALWAYS_INLINE static std::int64_t hadd(SimdMember v) { - std::int64_t a[2]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); return static_cast(a[0] + a[1]); } CCM_ALWAYS_INLINE static std::int64_t hmin(SimdMember v) { - std::int64_t a[2]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); return a[0] < a[1] ? a[0] : a[1]; } CCM_ALWAYS_INLINE static std::int64_t hmax(SimdMember v) { - std::int64_t a[2]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); return a[0] > a[1] ? a[0] : a[1]; } template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - std::int64_t a[2]; - detail::si128_store(v, a); + std::array a; + detail::si128_store(v, a.data()); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 2; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -611,16 +612,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(float v) { return _mm256_set1_ps(v); } CCM_ALWAYS_INLINE static float get(SimdMember const &m, detail::SimdSizeType i) { - float a[8]; - _mm256_storeu_ps(a, m); + std::array a; + _mm256_storeu_ps(a.data(), m); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, float v) { - float a[8]; - _mm256_storeu_ps(a, m); + std::array a; + _mm256_storeu_ps(a.data(), m); a[i] = v; - m = _mm256_loadu_ps(a); + m = _mm256_loadu_ps(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(float const *p) { return _mm256_loadu_ps(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, float *p) { _mm256_storeu_ps(p, m); } @@ -641,16 +642,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi32(b ? -1 : 0); } CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); return a[i] != 0; } CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); a[i] = b ? -1 : 0; - m = detail::si256_load(a); + m = detail::si256_load(a.data()); } CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } @@ -660,8 +661,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; for (int i = 0; i < 8; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -675,10 +676,10 @@ namespace ccm::pp #define CCM_PP_MSVC_F32X8_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ - float a[8]; \ - _mm256_storeu_ps(a, v); \ + std::array a; \ + _mm256_storeu_ps(a.data(), v); \ for (int i = 0; i < 8; ++i) { a[i] = detail::SFN(a[i]); } \ - return _mm256_loadu_ps(a); \ + return _mm256_loadu_ps(a.data()); \ } CCM_PP_MSVC_F32X8_ROUND(op_floor, s_floor) CCM_PP_MSVC_F32X8_ROUND(op_ceil, s_ceil) @@ -690,34 +691,34 @@ namespace ccm::pp #if CCMATH_SIMD_HAVE_FMA return _mm256_fmadd_ps(a, b, c); #else - float aa[8], bb[8], cc[8]; - _mm256_storeu_ps(aa, a); - _mm256_storeu_ps(bb, b); - _mm256_storeu_ps(cc, c); + std::array aa, bb, cc; + _mm256_storeu_ps(aa.data(), a); + _mm256_storeu_ps(bb.data(), b); + _mm256_storeu_ps(cc.data(), c); for (int i = 0; i < 8; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } - return _mm256_loadu_ps(aa); + return _mm256_loadu_ps(aa.data()); #endif } CCM_ALWAYS_INLINE static float hadd(SimdMember v) { - float a[8]; - _mm256_storeu_ps(a, v); + std::array a; + _mm256_storeu_ps(a.data(), v); float s = 0; for (int i = 0; i < 8; ++i) { s += a[i]; } return s; } CCM_ALWAYS_INLINE static float hmin(SimdMember v) { - float a[8]; - _mm256_storeu_ps(a, v); + std::array a; + _mm256_storeu_ps(a.data(), v); float s = a[0]; for (int i = 1; i < 8; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static float hmax(SimdMember v) { - float a[8]; - _mm256_storeu_ps(a, v); + std::array a; + _mm256_storeu_ps(a.data(), v); float s = a[0]; for (int i = 1; i < 8; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -725,8 +726,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - float a[8]; - _mm256_storeu_ps(a, v); + std::array a; + _mm256_storeu_ps(a.data(), v); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 8; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -754,16 +755,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(double v) { return _mm256_set1_pd(v); } CCM_ALWAYS_INLINE static double get(SimdMember const &m, detail::SimdSizeType i) { - double a[4]; - _mm256_storeu_pd(a, m); + std::array a; + _mm256_storeu_pd(a.data(), m); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, double v) { - double a[4]; - _mm256_storeu_pd(a, m); + std::array a; + _mm256_storeu_pd(a.data(), m); a[i] = v; - m = _mm256_loadu_pd(a); + m = _mm256_loadu_pd(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(double const *p) { return _mm256_loadu_pd(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, double *p) { _mm256_storeu_pd(p, m); } @@ -784,16 +785,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi64x(b ? -1 : 0); } CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); return a[i] != 0; } CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); a[i] = b ? -1 : 0; - m = detail::si256_load(a); + m = detail::si256_load(a.data()); } CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } @@ -803,8 +804,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -818,10 +819,10 @@ namespace ccm::pp #define CCM_PP_MSVC_F64X4_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ - double a[4]; \ - _mm256_storeu_pd(a, v); \ + std::array a; \ + _mm256_storeu_pd(a.data(), v); \ for (int i = 0; i < 4; ++i) { a[i] = detail::SFN(a[i]); } \ - return _mm256_loadu_pd(a); \ + return _mm256_loadu_pd(a.data()); \ } CCM_PP_MSVC_F64X4_ROUND(op_floor, s_floor) CCM_PP_MSVC_F64X4_ROUND(op_ceil, s_ceil) @@ -833,32 +834,32 @@ namespace ccm::pp #if CCMATH_SIMD_HAVE_FMA return _mm256_fmadd_pd(a, b, c); #else - double aa[4], bb[4], cc[4]; - _mm256_storeu_pd(aa, a); - _mm256_storeu_pd(bb, b); - _mm256_storeu_pd(cc, c); + std::array aa, bb, cc; + _mm256_storeu_pd(aa.data(), a); + _mm256_storeu_pd(bb.data(), b); + _mm256_storeu_pd(cc.data(), c); for (int i = 0; i < 4; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } - return _mm256_loadu_pd(aa); + return _mm256_loadu_pd(aa.data()); #endif } CCM_ALWAYS_INLINE static double hadd(SimdMember v) { - double a[4]; - _mm256_storeu_pd(a, v); + std::array a; + _mm256_storeu_pd(a.data(), v); return a[0] + a[1] + a[2] + a[3]; } CCM_ALWAYS_INLINE static double hmin(SimdMember v) { - double a[4]; - _mm256_storeu_pd(a, v); + std::array a; + _mm256_storeu_pd(a.data(), v); double s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static double hmax(SimdMember v) { - double a[4]; - _mm256_storeu_pd(a, v); + std::array a; + _mm256_storeu_pd(a.data(), v); double s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -866,8 +867,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - double a[4]; - _mm256_storeu_pd(a, v); + std::array a; + _mm256_storeu_pd(a.data(), v); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -895,16 +896,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) { return _mm256_set1_epi32(v); } CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const &m, detail::SimdSizeType i) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int32_t v) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); a[i] = v; - m = detail::si256_load(a); + m = detail::si256_load(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const *p) { return detail::si256_load(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int32_t *p) { detail::si256_store(m, p); } @@ -920,30 +921,30 @@ namespace ccm::pp #define CCM_PP_I32X8_SPILL2(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ - std::int32_t x[8], y[8]; \ - detail::si256_store(a, x); \ - detail::si256_store(b, y); \ + std::array x, y; \ + detail::si256_store(a, x.data()); \ + detail::si256_store(b, y.data()); \ for (int i = 0; i < 8; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ - return detail::si256_load(x); \ + return detail::si256_load(x.data()); \ } CCM_PP_I32X8_SPILL2(add, +) CCM_PP_I32X8_SPILL2(sub, -) #undef CCM_PP_I32X8_SPILL2 CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { - std::int32_t x[8]; - detail::si256_store(a, x); + std::array x; + detail::si256_store(a, x.data()); for (int i = 0; i < 8; ++i) { x[i] = static_cast(-x[i]); } - return detail::si256_load(x); + return detail::si256_load(x.data()); } #define CCM_PP_I32X8_SPILLCMP(NAME, OP) \ CCM_ALWAYS_INLINE static MaskMember NAME(SimdMember a, SimdMember b) \ { \ - std::int32_t x[8], y[8], r[8]; \ - detail::si256_store(a, x); \ - detail::si256_store(b, y); \ + std::array x, y, r; \ + detail::si256_store(a, x.data()); \ + detail::si256_store(b, y.data()); \ for (int i = 0; i < 8; ++i) { r[i] = (x[i] OP y[i]) ? -1 : 0; } \ - return detail::si256_load(r); \ + return detail::si256_load(r.data()); \ } CCM_PP_I32X8_SPILLCMP(eq, ==) CCM_PP_I32X8_SPILLCMP(gt, >) @@ -965,8 +966,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int32_t a[8]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; for (int i = 0; i < 8; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -981,11 +982,11 @@ namespace ccm::pp #define CCM_PP_I32X8_SPILLBIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ - std::int32_t x[8], y[8]; \ - detail::si256_store(a, x); \ - detail::si256_store(b, y); \ + std::array x, y; \ + detail::si256_store(a, x.data()); \ + detail::si256_store(b, y.data()); \ for (int i = 0; i < 8; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ - return detail::si256_load(x); \ + return detail::si256_load(x.data()); \ } CCM_PP_I32X8_SPILLBIN(mul, *) CCM_PP_I32X8_SPILLBIN(div, /) @@ -997,24 +998,24 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } CCM_ALWAYS_INLINE static std::int32_t hadd(SimdMember v) { - std::int32_t a[8]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); std::int32_t s = 0; for (int i = 0; i < 8; ++i) { s = static_cast(s + a[i]); } return s; } CCM_ALWAYS_INLINE static std::int32_t hmin(SimdMember v) { - std::int32_t a[8]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); std::int32_t s = a[0]; for (int i = 1; i < 8; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static std::int32_t hmax(SimdMember v) { - std::int32_t a[8]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); std::int32_t s = a[0]; for (int i = 1; i < 8; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -1022,8 +1023,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - std::int32_t a[8]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 8; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; @@ -1051,16 +1052,16 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) { return _mm256_set1_epi64x(v); } CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const &m, detail::SimdSizeType i) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); return a[i]; } CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int64_t v) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); a[i] = v; - m = detail::si256_load(a); + m = detail::si256_load(a.data()); } CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const *p) { return detail::si256_load(p); } CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int64_t *p) { detail::si256_store(m, p); } @@ -1075,35 +1076,35 @@ namespace ccm::pp #else CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { - std::int64_t x[4], y[4]; - detail::si256_store(a, x); - detail::si256_store(b, y); + std::array x, y; + detail::si256_store(a, x.data()); + detail::si256_store(b, y.data()); for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] + y[i]); } - return detail::si256_load(x); + return detail::si256_load(x.data()); } CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { - std::int64_t x[4], y[4]; - detail::si256_store(a, x); - detail::si256_store(b, y); + std::array x, y; + detail::si256_store(a, x.data()); + detail::si256_store(b, y.data()); for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] - y[i]); } - return detail::si256_load(x); + return detail::si256_load(x.data()); } CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { - std::int64_t x[4]; - detail::si256_store(a, x); + std::array x; + detail::si256_store(a, x.data()); for (int i = 0; i < 4; ++i) { x[i] = static_cast(-x[i]); } - return detail::si256_load(x); + return detail::si256_load(x.data()); } #define CCM_PP_I64X4_SPILLCMP(NAME, OP) \ CCM_ALWAYS_INLINE static MaskMember NAME(SimdMember a, SimdMember b) \ { \ - std::int64_t x[4], y[4], r[4]; \ - detail::si256_store(a, x); \ - detail::si256_store(b, y); \ + std::array x, y, r; \ + detail::si256_store(a, x.data()); \ + detail::si256_store(b, y.data()); \ for (int i = 0; i < 4; ++i) { r[i] = (x[i] OP y[i]) ? -1 : 0; } \ - return detail::si256_load(r); \ + return detail::si256_load(r.data()); \ } CCM_PP_I64X4_SPILLCMP(eq, ==) CCM_PP_I64X4_SPILLCMP(gt, >) @@ -1125,8 +1126,8 @@ namespace ccm::pp CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { - std::int64_t a[4]; - detail::si256_store(m, a); + std::array a; + detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } return c; @@ -1141,11 +1142,11 @@ namespace ccm::pp #define CCM_PP_I64X4_SPILLBIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ - std::int64_t x[4], y[4]; \ - detail::si256_store(a, x); \ - detail::si256_store(b, y); \ + std::array x, y; \ + detail::si256_store(a, x.data()); \ + detail::si256_store(b, y.data()); \ for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ - return detail::si256_load(x); \ + return detail::si256_load(x.data()); \ } CCM_PP_I64X4_SPILLBIN(mul, *) CCM_PP_I64X4_SPILLBIN(div, /) @@ -1157,22 +1158,22 @@ namespace ccm::pp CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } CCM_ALWAYS_INLINE static std::int64_t hadd(SimdMember v) { - std::int64_t a[4]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); return static_cast(a[0] + a[1] + a[2] + a[3]); } CCM_ALWAYS_INLINE static std::int64_t hmin(SimdMember v) { - std::int64_t a[4]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); std::int64_t s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } return s; } CCM_ALWAYS_INLINE static std::int64_t hmax(SimdMember v) { - std::int64_t a[4]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); std::int64_t s = a[0]; for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } return s; @@ -1180,8 +1181,8 @@ namespace ccm::pp template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { - std::int64_t a[4]; - detail::si256_store(v, a); + std::array a; + detail::si256_store(v, a.data()); typename SimdTraits>::SimdMember r; for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } return r; diff --git a/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp b/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp index 819778ea..2df8b22b 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp @@ -65,8 +65,8 @@ namespace ccm::intrin CCM_ALWAYS_INLINE simd(float value) : m_value(vdupq_n_f32(value)) {} CCM_ALWAYS_INLINE simd(float a, float b, float c, float d) { - float values[4] = { a, b, c, d }; - m_value = vld1q_f32(values); + std::array values = { a, b, c, d }; + m_value = vld1q_f32(values.data()); } CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) diff --git a/include/ccmath/internal/types/big_int.hpp b/include/ccmath/internal/types/big_int.hpp index c475c1b9..91ad22aa 100644 --- a/include/ccmath/internal/types/big_int.hpp +++ b/include/ccmath/internal/types/big_int.hpp @@ -696,7 +696,8 @@ namespace ccm::types */ template // NOLINTNEXTLINE(google-explicit-constructor) - Cannot be marked explicit. - constexpr BigInt(const WordType (&nums)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays) - We are intentionally using C-style arrays here. + constexpr BigInt(const WordType (&nums)[N]) // NOLINT(modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays) - We are + // intentionally using C-style arrays here. { static_assert(N == WORD_COUNT); for (std::size_t i = 0; i < WORD_COUNT; ++i) { val[i] = nums[i]; } From 61f12eede687db04820755e1b664f514164f36c2 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 16:00:13 -0400 Subject: [PATCH 24/52] Fix MSVC ccm::pp build by hoisting libm decls to global scope --- .../internal/math/runtime/pp/utility.hpp | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index e706e207..08d17210 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -16,6 +16,36 @@ #include #include +// C runtime math entry points for the portable scalar fallback below. They are +// declared at global scope to match math.h. Declaring them extern "C" inside a +// namespace makes MSVC reject any translation unit that also pulls in cmath +// (C2375 redefinition, C2733 cannot overload an extern "C" function). +#if (!defined(__GNUC__) && !defined(__clang__)) || defined(CCM_PP_FORCE_PORTABLE) +extern "C" +{ + float sqrtf(float); + double sqrt(double); + float floorf(float); + double floor(double); + float ceilf(float); + double ceil(double); + float truncf(float); + double trunc(double); + float roundf(float); + double round(double); + float fabsf(float); + double fabs(double); + float expf(float); + double exp(double); + float logf(float); + double log(double); + float fmaf(float, float, float); + double fma(double, double, double); + float powf(float, float); + double pow(double, double); +} +#endif + namespace ccm::pp { // Per-(T, Abi) storage and operation set. Specialized by the scalar and @@ -50,7 +80,7 @@ namespace ccm::pp // portable fallback for the vec_ext backend. Compiler builtins are used on // GNU/Clang (no , no errno, foldable). Other toolchains (e.g. MSVC), // or builds that define CCM_PP_FORCE_PORTABLE, fall back to the C runtime - // entry points declared here. + // entry points declared at global scope above. #if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) #define CCM_PP_S_UNARY(NAME, BF, BD) \ template \ @@ -91,29 +121,6 @@ namespace ccm::pp } } #else - extern "C" - { - float sqrtf(float); - double sqrt(double); - float floorf(float); - double floor(double); - float ceilf(float); - double ceil(double); - float truncf(float); - double trunc(double); - float roundf(float); - double round(double); - float fabsf(float); - double fabs(double); - float expf(float); - double exp(double); - float logf(float); - double log(double); - float fmaf(float, float, float); - double fma(double, double, double); - float powf(float, float); - double pow(double, double); - } #define CCM_PP_S_UNARY(NAME, CF, CD) \ template \ CCM_ALWAYS_INLINE T NAME(T x) \ From 15e975a61825141773c750b5392e6f16f4aed6f6 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 16:00:23 -0400 Subject: [PATCH 25/52] Align pow_impl exact-sign block to satisfy clang-format --- .../math/generic/func/power/pow_impl/pow_impl.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp index 8de59c2b..faef6b0b 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp @@ -355,13 +355,13 @@ namespace ccm::gen::impl cl = support::helpers::internal_ldexp(cl, -k); x = support::helpers::internal_ldexp(x, 2 * k); - const DoubleDouble chh = two_prod(ch, ch); - const double cross = 2.0 * (ch * cl); - const double clsq = cl * cl; - const DoubleDouble t0 = two_prod(chh.hi, x); - const DoubleDouble t1 = two_prod(chh.lo, x); - const DoubleDouble t2 = two_prod(cross, x); - const DoubleDouble t3 = two_prod(clsq, x); + const DoubleDouble chh = two_prod(ch, ch); + const double cross = 2.0 * (ch * cl); + const double clsq = cl * cl; + const DoubleDouble t0 = two_prod(chh.hi, x); + const DoubleDouble t1 = two_prod(chh.lo, x); + const DoubleDouble t2 = two_prod(cross, x); + const DoubleDouble t3 = two_prod(clsq, x); const std::array terms = { t0.hi, t0.lo, t1.hi, t1.lo, t2.hi, t2.lo, t3.hi, t3.lo, -1.0 }; return exact_sign_of_sum(terms.data(), static_cast(terms.size())); } From 288fab5e552609dbf82a55f0f22a72061804e805 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 21:21:24 -0400 Subject: [PATCH 26/52] Fix MSVC ccm::pp simd test by ordering cmath before pp.hpp --- .../internal/math/runtime/pp/utility.hpp | 55 ++++++++----------- tests/src/math/internal/simd/pp_simd_test.cpp | 10 +++- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index 08d17210..e706e207 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -16,36 +16,6 @@ #include #include -// C runtime math entry points for the portable scalar fallback below. They are -// declared at global scope to match math.h. Declaring them extern "C" inside a -// namespace makes MSVC reject any translation unit that also pulls in cmath -// (C2375 redefinition, C2733 cannot overload an extern "C" function). -#if (!defined(__GNUC__) && !defined(__clang__)) || defined(CCM_PP_FORCE_PORTABLE) -extern "C" -{ - float sqrtf(float); - double sqrt(double); - float floorf(float); - double floor(double); - float ceilf(float); - double ceil(double); - float truncf(float); - double trunc(double); - float roundf(float); - double round(double); - float fabsf(float); - double fabs(double); - float expf(float); - double exp(double); - float logf(float); - double log(double); - float fmaf(float, float, float); - double fma(double, double, double); - float powf(float, float); - double pow(double, double); -} -#endif - namespace ccm::pp { // Per-(T, Abi) storage and operation set. Specialized by the scalar and @@ -80,7 +50,7 @@ namespace ccm::pp // portable fallback for the vec_ext backend. Compiler builtins are used on // GNU/Clang (no , no errno, foldable). Other toolchains (e.g. MSVC), // or builds that define CCM_PP_FORCE_PORTABLE, fall back to the C runtime - // entry points declared at global scope above. + // entry points declared here. #if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) #define CCM_PP_S_UNARY(NAME, BF, BD) \ template \ @@ -121,6 +91,29 @@ namespace ccm::pp } } #else + extern "C" + { + float sqrtf(float); + double sqrt(double); + float floorf(float); + double floor(double); + float ceilf(float); + double ceil(double); + float truncf(float); + double trunc(double); + float roundf(float); + double round(double); + float fabsf(float); + double fabs(double); + float expf(float); + double exp(double); + float logf(float); + double log(double); + float fmaf(float, float, float); + double fma(double, double, double); + float powf(float, float); + double pow(double, double); + } #define CCM_PP_S_UNARY(NAME, CF, CD) \ template \ CCM_ALWAYS_INLINE T NAME(T x) \ diff --git a/tests/src/math/internal/simd/pp_simd_test.cpp b/tests/src/math/internal/simd/pp_simd_test.cpp index 90317243..e596dad4 100644 --- a/tests/src/math/internal/simd/pp_simd_test.cpp +++ b/tests/src/math/internal/simd/pp_simd_test.cpp @@ -8,11 +8,19 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ +// clang-format off +// must come before pp.hpp. It pulls in the UCRT math declarations first, +// so the ccm::pp MSVC scalar fallback, which forward declares the same C runtime +// entry points, redeclares them rather than declaring them ahead of the UCRT. +// Including pp.hpp first makes MSVC reject the later UCRT declarations (C2375 +// redefinition, C2733 cannot overload an extern "C" function). +#include + #include "ccmath/internal/math/runtime/pp/pp.hpp" +// clang-format on #include -#include #include #include #include From cc61fb1f4b934bacd2e6239e7e40c2ee3e0b2042 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 21:50:55 -0400 Subject: [PATCH 27/52] Fix MSVC ccm::pp link by routing float s_fabs through double fabs --- .../ccmath/internal/math/runtime/pp/utility.hpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index e706e207..167a163e 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -103,7 +103,6 @@ namespace ccm::pp double trunc(double); float roundf(float); double round(double); - float fabsf(float); double fabs(double); float expf(float); double exp(double); @@ -129,11 +128,23 @@ namespace ccm::pp CCM_PP_S_UNARY(s_ceil, ceilf, ceil) CCM_PP_S_UNARY(s_trunc, truncf, trunc) CCM_PP_S_UNARY(s_round, roundf, round) - CCM_PP_S_UNARY(s_fabs, fabsf, fabs) CCM_PP_S_UNARY(s_exp, expf, exp) CCM_PP_S_UNARY(s_log, logf, log) #undef CCM_PP_S_UNARY + // MSVC does not always provide a linkable fabsf, so the float path routes + // through the double fabs. The widening to double and the narrowing back + // are both exact because fabs only clears the sign bit. + template + CCM_ALWAYS_INLINE T s_fabs(T x) + { + if constexpr (std::is_same_v) { return static_cast(fabs(static_cast(x))); } + else + { + return fabs(x); + } + } + template CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { From 30f6e638d99895f1e38b10c48daeb416f2ab3189 Mon Sep 17 00:00:00 2001 From: ianpike Date: Tue, 16 Jun 2026 22:18:22 -0400 Subject: [PATCH 28/52] Another MSVC fix --- .../internal/math/runtime/pp/utility.hpp | 126 ++++++++---------- 1 file changed, 52 insertions(+), 74 deletions(-) diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index 167a163e..cc364743 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -46,50 +46,14 @@ namespace ccm::pp CCM_ALWAYS_INLINE constexpr void unroll_ic(F && f) { unroll_ic_impl(std::forward(f), std::make_index_sequence(N)>{}); } - // Per-lane scalar math primitives, used by the scalar backend and as the - // portable fallback for the vec_ext backend. Compiler builtins are used on - // GNU/Clang (no , no errno, foldable). Other toolchains (e.g. MSVC), - // or builds that define CCM_PP_FORCE_PORTABLE, fall back to the C runtime - // entry points declared here. + // Per-lane scalar math primitives for the scalar backend and the vec_ext + // portable fallback. GNU/Clang use foldable, errno-free compiler builtins + // (no ). Other toolchains (e.g. MSVC) or CCM_PP_FORCE_PORTABLE builds + // call the C runtime entry points declared below. CCM_PP_F and CCM_PP_D + // select the float and double backend for a given function base name. #if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) - #define CCM_PP_S_UNARY(NAME, BF, BD) \ - template \ - CCM_ALWAYS_INLINE T NAME(T x) \ - { \ - if constexpr (std::is_same_v) { return BF(x); } \ - else \ - { \ - return BD(x); \ - } \ - } - CCM_PP_S_UNARY(s_sqrt, __builtin_sqrtf, __builtin_sqrt) - CCM_PP_S_UNARY(s_floor, __builtin_floorf, __builtin_floor) - CCM_PP_S_UNARY(s_ceil, __builtin_ceilf, __builtin_ceil) - CCM_PP_S_UNARY(s_trunc, __builtin_truncf, __builtin_trunc) - CCM_PP_S_UNARY(s_round, __builtin_roundf, __builtin_round) - CCM_PP_S_UNARY(s_fabs, __builtin_fabsf, __builtin_fabs) - CCM_PP_S_UNARY(s_exp, __builtin_expf, __builtin_exp) - CCM_PP_S_UNARY(s_log, __builtin_logf, __builtin_log) - #undef CCM_PP_S_UNARY - - template - CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) - { - if constexpr (std::is_same_v) { return __builtin_fmaf(a, b, c); } - else - { - return __builtin_fma(a, b, c); - } - } - template - CCM_ALWAYS_INLINE T s_pow(T a, T b) - { - if constexpr (std::is_same_v) { return __builtin_powf(a, b); } - else - { - return __builtin_pow(a, b); - } - } + #define CCM_PP_F(BASE) __builtin_##BASE##f + #define CCM_PP_D(BASE) __builtin_##BASE #else extern "C" { @@ -113,56 +77,70 @@ namespace ccm::pp float powf(float, float); double pow(double, double); } - #define CCM_PP_S_UNARY(NAME, CF, CD) \ - template \ - CCM_ALWAYS_INLINE T NAME(T x) \ + #define CCM_PP_F(BASE) BASE##f + #define CCM_PP_D(BASE) BASE +#endif + +#define CCM_PP_S_UNARY(NAME, BASE) \ + template \ + CCM_ALWAYS_INLINE T NAME(T x) \ + { \ + if constexpr (std::is_same_v) { return CCM_PP_F(BASE)(x); } \ + else \ { \ - if constexpr (std::is_same_v) { return CF(x); } \ - else \ - { \ - return CD(x); \ - } \ - } - CCM_PP_S_UNARY(s_sqrt, sqrtf, sqrt) - CCM_PP_S_UNARY(s_floor, floorf, floor) - CCM_PP_S_UNARY(s_ceil, ceilf, ceil) - CCM_PP_S_UNARY(s_trunc, truncf, trunc) - CCM_PP_S_UNARY(s_round, roundf, round) - CCM_PP_S_UNARY(s_exp, expf, exp) - CCM_PP_S_UNARY(s_log, logf, log) - #undef CCM_PP_S_UNARY + return CCM_PP_D(BASE)(x); \ + } \ + } + CCM_PP_S_UNARY(s_sqrt, sqrt) + CCM_PP_S_UNARY(s_floor, floor) + CCM_PP_S_UNARY(s_ceil, ceil) + CCM_PP_S_UNARY(s_trunc, trunc) + CCM_PP_S_UNARY(s_round, round) + CCM_PP_S_UNARY(s_exp, exp) + CCM_PP_S_UNARY(s_log, log) +#undef CCM_PP_S_UNARY - // MSVC does not always provide a linkable fabsf, so the float path routes - // through the double fabs. The widening to double and the narrowing back - // are both exact because fabs only clears the sign bit. template - CCM_ALWAYS_INLINE T s_fabs(T x) + CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { - if constexpr (std::is_same_v) { return static_cast(fabs(static_cast(x))); } + if constexpr (std::is_same_v) { return CCM_PP_F(fma)(a, b, c); } else { - return fabs(x); + return CCM_PP_D(fma)(a, b, c); } } - template - CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) + CCM_ALWAYS_INLINE T s_pow(T a, T b) { - if constexpr (std::is_same_v) { return fmaf(a, b, c); } + if constexpr (std::is_same_v) { return CCM_PP_F(pow)(a, b); } else { - return fma(a, b, c); + return CCM_PP_D(pow)(a, b); } } + +#undef CCM_PP_F +#undef CCM_PP_D + + // MSVC does not always provide a linkable fabsf, so on the portable path the + // float case routes through the double fabs, which is exact because fabs only + // clears the sign bit. template - CCM_ALWAYS_INLINE T s_pow(T a, T b) + CCM_ALWAYS_INLINE T s_fabs(T x) { - if constexpr (std::is_same_v) { return powf(a, b); } +#if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) + if constexpr (std::is_same_v) { return __builtin_fabsf(x); } else { - return pow(a, b); + return __builtin_fabs(x); + } +#else + if constexpr (std::is_same_v) { return static_cast(fabs(static_cast(x))); } + else + { + return fabs(x); } - } #endif + } } // namespace detail } // namespace ccm::pp From 1928475e533e16e10c498eb7516f28378783b153 Mon Sep 17 00:00:00 2001 From: ianpike Date: Wed, 17 Jun 2026 02:45:34 -0400 Subject: [PATCH 29/52] Make Meson and Premake builds independent of CMake --- cmake/gen/ccmath/meson.build | 15 +++ cmake/gen/meson.build | 6 +- premake5.lua | 45 ++++++++- tools/gen_secondary_builds.py | 177 ++++++++++++++++++++++++++++------ 4 files changed, 211 insertions(+), 32 deletions(-) create mode 100644 cmake/gen/ccmath/meson.build diff --git a/cmake/gen/ccmath/meson.build b/cmake/gen/ccmath/meson.build new file mode 100644 index 00000000..ed947d2e --- /dev/null +++ b/cmake/gen/ccmath/meson.build @@ -0,0 +1,15 @@ +# Generated by tools/gen_secondary_builds.py. Do not edit by hand. +# Produces /cmake/gen/ccmath/version.hpp from the version template, so the +# 'ccmath' include prefix resolves without a CMake configure or a checked-in header. + +_ccmath_version_parts = meson.project_version().split('.') +_ccmath_version_conf = configuration_data() +_ccmath_version_conf.set('CCMATH_VERSION_MAJOR', _ccmath_version_parts[0]) +_ccmath_version_conf.set('CCMATH_VERSION_MINOR', _ccmath_version_parts[1]) +_ccmath_version_conf.set('CCMATH_VERSION_PATCH', _ccmath_version_parts[2]) + +ccmath_version_hpp = configure_file( + input: meson.project_source_root() / 'cmake/in/version.hpp.in', + output: 'version.hpp', + configuration: _ccmath_version_conf, +) diff --git a/cmake/gen/meson.build b/cmake/gen/meson.build index 3d3ce501..44c8cefc 100644 --- a/cmake/gen/meson.build +++ b/cmake/gen/meson.build @@ -58,14 +58,18 @@ if get_option('enable_runtime_simd') and not get_option('deterministic') and cpp _ccmath_defines += '-DCCM_CONFIG_RT_SIMD_HAS_SVML' endif +# Generate ccmath/version.hpp natively so Meson consumers never depend on a CMake configure. +subdir('ccmath') + ccmath_inc = include_directories( '../../include', - '../../out/secondary/include', + '.', ) ccmath_dep = declare_dependency( include_directories: ccmath_inc, compile_args: _ccmath_defines, + sources: ccmath_version_hpp, ) meson.override_dependency('ccmath', ccmath_dep) diff --git a/premake5.lua b/premake5.lua index 656192f2..06fcf623 100644 --- a/premake5.lua +++ b/premake5.lua @@ -4,7 +4,7 @@ -- BEGIN GENERATED LIBRARY MANIFEST -- Generated by tools/gen_secondary_builds.py from ccmath-build-manifest.json. --- CMake is the primary build system. +-- CMake is the primary build system, but this module is self-contained. -- -- Consumer usage: -- include("path/to/ccmath/premake5.lua") @@ -44,6 +44,43 @@ newoption { ccmath = ccmath or {} +-- Resolve paths against ccmath's own location, not the consumer's. +local _ccmath_root = _SCRIPT_DIR + +-- Generate ccmath/version.hpp here so Premake consumers never depend on a CMake configure. +local _ccmath_version_header = [==[ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef CCMATH_VERSION_HPP +#define CCMATH_VERSION_HPP + +#define CCMATH_VERSION_MAJOR 0 +#define CCMATH_VERSION_MINOR 3 +#define CCMATH_VERSION_PATCH 0 +#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) +#define CCMATH_VERSION CCMATH_TO_VERSION(CCMATH_VERSION_MAJOR, CCMATH_VERSION_MINOR, CCMATH_VERSION_PATCH) + +#endif // CCMATH_VERSION_HPP +]==] + +local function _ccmath_write_version_header() + local generated = path.join(_ccmath_root, "out/secondary/include/ccmath") + os.mkdir(generated) + local handle = io.open(path.join(generated, "version.hpp"), "w") + handle:write(_ccmath_version_header) + handle:close() +end + +_ccmath_write_version_header() + local _ccmath_include_dirs = { "include", "out/secondary/include" } local function _ccmath_option_enabled(trigger, default_value) @@ -59,7 +96,11 @@ local function _ccmath_is_msvc() end function ccmath.include_dirs() - return _ccmath_include_dirs + local resolved = {} + for _, dir in ipairs(_ccmath_include_dirs) do + table.insert(resolved, path.getabsolute(path.join(_ccmath_root, dir))) + end + return resolved end function ccmath.defines() diff --git a/tools/gen_secondary_builds.py b/tools/gen_secondary_builds.py index d759eace..a295a0eb 100644 --- a/tools/gen_secondary_builds.py +++ b/tools/gen_secondary_builds.py @@ -310,32 +310,66 @@ def premake_buildoptions_block(options: list[dict[str, Any]]) -> str: return "\n".join(lines) +def premake_version_writer(version_header: str) -> list[str]: + return [ + "-- Generate ccmath/version.hpp here so Premake consumers never depend on a CMake configure.", + "local _ccmath_version_header = [==[", + version_header.rstrip("\n"), + "]==]", + "", + "local function _ccmath_write_version_header()", + ' local generated = path.join(_ccmath_root, "out/secondary/include/ccmath")', + " os.mkdir(generated)", + ' local handle = io.open(path.join(generated, "version.hpp"), "w")', + " handle:write(_ccmath_version_header)", + " handle:close()", + "end", + "", + "_ccmath_write_version_header()", + ] + + def premake_content( manifest: dict[str, Any], source_dir: Path, generated_include_dir: Path, base_dir: Path, + version_header: str | None = None, ) -> str: source_include = relative_posix_path(source_dir / "include", base_dir) generated_include = relative_posix_path(generated_include_dir, base_dir) - return "\n".join( + parts = [ + "-- Generated by tools/gen_secondary_builds.py from ccmath-build-manifest.json.", + "-- CMake is the primary build system, but this module is self-contained.", + "--", + "-- Consumer usage:", + '-- include("path/to/ccmath/premake5.lua")', + '-- project "my-app"', + '-- kind "ConsoleApp"', + '-- language "C++"', + '-- files { "main.cpp" }', + "-- ccmath.use()", + "", + premake_options(manifest["options"]).rstrip(), + "", + "ccmath = ccmath or {}", + "", + ] + + if version_header is not None: + parts.extend( + [ + "-- Resolve paths against ccmath's own location, not the consumer's.", + "local _ccmath_root = _SCRIPT_DIR", + "", + ] + ) + parts.extend(premake_version_writer(version_header)) + parts.append("") + + parts.extend( [ - "-- Generated by tools/gen_secondary_builds.py from ccmath-build-manifest.json.", - "-- CMake is the primary build system.", - "--", - "-- Consumer usage:", - '-- include("path/to/ccmath/premake5.lua")', - '-- project "my-app"', - '-- kind "ConsoleApp"', - '-- language "C++"', - '-- files { "main.cpp" }', - "-- ccmath.use()", - "", - premake_options(manifest["options"]).rstrip(), - "", - "ccmath = ccmath or {}", - "", f'local _ccmath_include_dirs = {{ "{source_include}", "{generated_include}" }}', "", "local function _ccmath_option_enabled(trigger, default_value)", @@ -350,10 +384,34 @@ def premake_content( ' return string.match(_ACTION or "", "vs") ~= nil', "end", "", - "function ccmath.include_dirs()", - " return _ccmath_include_dirs", - "end", - "", + ] + ) + + if version_header is not None: + parts.extend( + [ + "function ccmath.include_dirs()", + " local resolved = {}", + " for _, dir in ipairs(_ccmath_include_dirs) do", + " table.insert(resolved, path.getabsolute(path.join(_ccmath_root, dir)))", + " end", + " return resolved", + "end", + "", + ] + ) + else: + parts.extend( + [ + "function ccmath.include_dirs()", + " return _ccmath_include_dirs", + "end", + "", + ] + ) + + parts.extend( + [ premake_defines_block(manifest["options"]).rstrip(), "", premake_buildoptions_block(manifest["options"]).rstrip(), @@ -366,6 +424,7 @@ def premake_content( "", ] ) + return "\n".join(parts) def root_meson_wrapper(manifest: dict[str, Any]) -> str: @@ -408,6 +467,7 @@ def root_premake_wrapper(manifest: dict[str, Any], source_dir: Path) -> str: source_dir, source_dir / ROOT_SECONDARY_INCLUDE, source_dir, + version_header=render_version_header(manifest, source_dir), ) return "\n".join( [ @@ -426,18 +486,74 @@ def root_premake_wrapper(manifest: dict[str, Any], source_dir: Path) -> str: def cmake_gen_meson(manifest: dict[str, Any], source_dir: Path) -> str: gen_subdir = source_dir / "cmake" / "gen" source_include = relative_posix_path(source_dir / "include", gen_subdir) - generated_include = relative_posix_path(source_dir / ROOT_SECONDARY_INCLUDE, gen_subdir) + options = manifest["options"] + + lines = [ + "cpp = meson.get_compiler('cpp')", + "", + "_ccmath_builtin_bit_cast_probe = '''", + BIT_CAST_PROBE.rstrip(), + "'''", + "", + "if not cpp.compiles(_ccmath_builtin_bit_cast_probe, name : 'ccmath builtin bit_cast support')", + " error('CCMath requires compiler support for __builtin_bit_cast')", + "endif", + "", + "_ccmath_defines = []", + ] + lines.extend(meson_define_lines(options)) + lines.extend(meson_runtime_probe_lines(options)) + lines.extend( + [ + "", + "# Generate ccmath/version.hpp natively so Meson consumers never depend on a CMake configure.", + "subdir('ccmath')", + "", + "ccmath_inc = include_directories(", + f" '{source_include}',", + " '.',", + ")", + "", + "ccmath_dep = declare_dependency(", + " include_directories: ccmath_inc,", + " compile_args: _ccmath_defines,", + " sources: ccmath_version_hpp,", + ")", + "", + "meson.override_dependency('ccmath', ccmath_dep)", + ] + ) return "\n".join( [ "# Generated by tools/gen_secondary_builds.py. Do not edit by hand.", "# Exposes CCMath as a Meson dependency only.", "", - meson_dependency_block( - source_include=source_include, - generated_include=generated_include, - options=manifest["options"], - ).rstrip(), + "\n".join(lines), + "", + ] + ) + + +def cmake_gen_meson_version(manifest: dict[str, Any]) -> str: + version_template = manifest["version_template"] + return "\n".join( + [ + "# Generated by tools/gen_secondary_builds.py. Do not edit by hand.", + "# Produces /cmake/gen/ccmath/version.hpp from the version template, so the", + "# 'ccmath' include prefix resolves without a CMake configure or a checked-in header.", + "", + "_ccmath_version_parts = meson.project_version().split('.')", + "_ccmath_version_conf = configuration_data()", + "_ccmath_version_conf.set('CCMATH_VERSION_MAJOR', _ccmath_version_parts[0])", + "_ccmath_version_conf.set('CCMATH_VERSION_MINOR', _ccmath_version_parts[1])", + "_ccmath_version_conf.set('CCMATH_VERSION_PATCH', _ccmath_version_parts[2])", + "", + "ccmath_version_hpp = configure_file(", + f" input: meson.project_source_root() / '{version_template}',", + " output: 'version.hpp',", + " configuration: _ccmath_version_conf,", + ")", "", ] ) @@ -467,15 +583,18 @@ def write_outputs( ) if update_root: - root_generated_include = source_dir / ROOT_SECONDARY_INCLUDE - write_version_header(root_generated_include, version_header) - gen_dir = source_dir / "cmake" / "gen" gen_dir.mkdir(parents=True, exist_ok=True) (gen_dir / "meson.build").write_text( cmake_gen_meson(manifest, source_dir), encoding="utf-8", ) + gen_version_dir = gen_dir / "ccmath" + gen_version_dir.mkdir(parents=True, exist_ok=True) + (gen_version_dir / "meson.build").write_text( + cmake_gen_meson_version(manifest), + encoding="utf-8", + ) (source_dir / "meson_options.txt").write_text(options_txt, encoding="utf-8") (source_dir / "meson.build").write_text( root_meson_wrapper(manifest), From d5f3959d83f6e5f061d2090f78415c353367fd67 Mon Sep 17 00:00:00 2001 From: ianpike Date: Wed, 17 Jun 2026 02:46:25 -0400 Subject: [PATCH 30/52] Widen version encoding to three digits per component --- cmake/in/version.hpp.in | 3 ++- premake5.lua | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/in/version.hpp.in b/cmake/in/version.hpp.in index ce6713af..cf6b4f22 100644 --- a/cmake/in/version.hpp.in +++ b/cmake/in/version.hpp.in @@ -14,7 +14,8 @@ #define CCMATH_VERSION_MAJOR @CCMATH_VERSION_MAJOR@ #define CCMATH_VERSION_MINOR @CCMATH_VERSION_MINOR@ #define CCMATH_VERSION_PATCH @CCMATH_VERSION_PATCH@ -#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) +// MAJOR.MINOR.PATCH packed into one integer, up to 3 digits (0 to 999) per component. +#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 1000000 + (MINOR) * 1000 + (PATCH)) #define CCMATH_VERSION CCMATH_TO_VERSION(CCMATH_VERSION_MAJOR, CCMATH_VERSION_MINOR, CCMATH_VERSION_PATCH) #endif // CCMATH_VERSION_HPP \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index 06fcf623..05a3d5a2 100644 --- a/premake5.lua +++ b/premake5.lua @@ -65,7 +65,8 @@ local _ccmath_version_header = [==[ #define CCMATH_VERSION_MAJOR 0 #define CCMATH_VERSION_MINOR 3 #define CCMATH_VERSION_PATCH 0 -#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) +// MAJOR.MINOR.PATCH packed into one integer, up to 3 digits (0 to 999) per component. +#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 1000000 + (MINOR) * 1000 + (PATCH)) #define CCMATH_VERSION CCMATH_TO_VERSION(CCMATH_VERSION_MAJOR, CCMATH_VERSION_MINOR, CCMATH_VERSION_PATCH) #endif // CCMATH_VERSION_HPP From 3593fa3c17c457d0ebb69b1065f4784c41863670 Mon Sep 17 00:00:00 2001 From: ianpike Date: Wed, 17 Jun 2026 17:32:09 -0400 Subject: [PATCH 31/52] Fix constexpr math on domain and special inputs --- .../internal/support/fenv/fenv_support.hpp | 2 +- .../ccmath/math/trig/impl/inv_trig_impl.hpp | 4 +- .../math/trig/impl/sincos_double_impl.hpp | 8 ++- .../math/trig/impl/sincos_float_impl.hpp | 8 ++- tests/src/math/constexpr_smoke_test.cpp | 72 +++++++++++++++++++ 5 files changed, 89 insertions(+), 5 deletions(-) diff --git a/include/ccmath/internal/support/fenv/fenv_support.hpp b/include/ccmath/internal/support/fenv/fenv_support.hpp index cda22458..6590cbeb 100644 --- a/include/ccmath/internal/support/fenv/fenv_support.hpp +++ b/include/ccmath/internal/support/fenv/fenv_support.hpp @@ -172,7 +172,7 @@ namespace ccm::support::fenv return 0; } - inline void set_errno_if_required(const int err) + constexpr void set_errno_if_required(const int err) { if (is_constant_evaluated()) { return; } if constexpr (!is_errno_enabled()) { return; } diff --git a/include/ccmath/math/trig/impl/inv_trig_impl.hpp b/include/ccmath/math/trig/impl/inv_trig_impl.hpp index 7b44e002..e6e36961 100644 --- a/include/ccmath/math/trig/impl/inv_trig_impl.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_impl.hpp @@ -124,7 +124,7 @@ namespace ccm::internal::impl { ccm::support::fenv::set_errno_if_required(EDOM); ccm::support::fenv::raise_except_if_required(FE_INVALID); - return x + fp_bits_t::quiet_nan().get_val(); + return fp_bits_t::quiet_nan().get_val(); } const T ax = ccm::fabs(x); @@ -132,7 +132,7 @@ namespace ccm::internal::impl { ccm::support::fenv::set_errno_if_required(EDOM); ccm::support::fenv::raise_except_if_required(FE_INVALID); - return x + fp_bits_t::quiet_nan().get_val(); + return fp_bits_t::quiet_nan().get_val(); } if (ax == static_cast(1)) { return x < static_cast(0) ? static_cast(ccm::numbers::pi_v) : static_cast(0); } diff --git a/include/ccmath/math/trig/impl/sincos_double_impl.hpp b/include/ccmath/math/trig/impl/sincos_double_impl.hpp index 26a58456..4413451b 100644 --- a/include/ccmath/math/trig/impl/sincos_double_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_impl.hpp @@ -71,8 +71,14 @@ namespace ccm::internal::impl { support::fenv::set_errno_if_required(EDOM); support::fenv::raise_except_if_required(FE_INVALID); + // sin/cos of an infinity is a domain error, so return a canonical quiet NaN. + // Forming it as x + quiet_nan is ill-formed in a constant expression, which + // rejects floating-point arithmetic that produces a NaN. + return FPBits::quiet_nan().get_val(); } - return x + FPBits::quiet_nan().get_val(); + + // x is a quiet NaN here. Return it unchanged so its sign and payload survive. + return x; } k = sincos_ph::payne_hanek_reduce(x, y); diff --git a/include/ccmath/math/trig/impl/sincos_float_impl.hpp b/include/ccmath/math/trig/impl/sincos_float_impl.hpp index c845771a..f1c83a23 100644 --- a/include/ccmath/math/trig/impl/sincos_float_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_impl.hpp @@ -70,8 +70,14 @@ namespace ccm::internal::impl { support::fenv::set_errno_if_required(EDOM); support::fenv::raise_except_if_required(FE_INVALID); + // sin/cos of an infinity is a domain error, so return a canonical quiet NaN. + // Forming it as x + quiet_nan is ill-formed in a constant expression, which + // rejects floating-point arithmetic that produces a NaN. + return FPBits::quiet_nan().get_val(); } - return x + FPBits::quiet_nan().get_val(); + + // x is a quiet NaN here. Return it unchanged so its sign and payload survive. + return x; } double yd = 0.0; diff --git a/tests/src/math/constexpr_smoke_test.cpp b/tests/src/math/constexpr_smoke_test.cpp index c838806f..1d86b842 100644 --- a/tests/src/math/constexpr_smoke_test.cpp +++ b/tests/src/math/constexpr_smoke_test.cpp @@ -8,11 +8,19 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ +#include "ccmath/internal/math/generic/func/trig/cos_gen.hpp" +#include "ccmath/internal/math/generic/func/trig/sin_gen.hpp" +#include "ccmath/internal/support/bits.hpp" +#include "ccmath/internal/support/fenv/fenv_support.hpp" + #include #include +#include +#include #include +#include #include namespace @@ -116,3 +124,67 @@ TEST(CcmathMathConstexprSmokeTest, AllImplementedFunctions) static_assert(ccm::lgamma(1.0) == 0.0); static_assert(ccm::lerp(0.0, 10.0, 0.5) == 5.0); } + +TEST(CcmathMathConstexprSmokeTest, DomainAndSpecialInputs) +{ + // Regression for issue 135. Domain and special inputs must be usable in a constant + // expression and return the correct special value, raising no errno at compile time. + // GCC-proper evaluates these through the __builtin_ family, which refuses domain + // inputs at compile time (a separate limitation), so guard that path out here. +#if !(defined(__GNUC__) && !defined(__clang__)) + static_assert(ccm::log(0.0) == -std::numeric_limits::infinity()); + static_assert(ccm::isnan(ccm::log(-1.0))); + static_assert(ccm::isnan(ccm::acos(2.0))); + static_assert(ccm::isnan(ccm::pow(-1.0, 0.5))); + static_assert(ccm::tgamma(0.0) == std::numeric_limits::infinity()); + static_assert(ccm::ilogb(0.0) == FP_ILOGB0); + + // sin/cos of an infinity is a domain error. The generic kernel must yield a NaN that is + // usable in a constant expression. The old code built it as x + quiet_nan, which constant + // evaluation rejects because the addition produces a NaN. + static_assert(ccm::isnan(ccm::sin(std::numeric_limits::infinity()))); + static_assert(ccm::isnan(ccm::cos(std::numeric_limits::infinity()))); + static_assert(ccm::isnan(ccm::sin(std::numeric_limits::infinity()))); + static_assert(ccm::isnan(ccm::cos(std::numeric_limits::infinity()))); +#endif + SUCCEED(); +} + +TEST(CcmathMathConstexprSmokeTest, SincosInfinityAndNanPayload) +{ + // Runtime companion to the sin/cos infinity static_asserts above, exercising the generic + // kernel directly (the runtime dispatch otherwise routes through __builtin sin/cos). An + // infinity is a domain error: the result is a NaN, FE_INVALID is raised, and errno is EDOM. + // A quiet-NaN input must pass through unchanged so its sign and payload survive. + const double inf = std::numeric_limits::infinity(); + const float inf_f = std::numeric_limits::infinity(); + + EXPECT_TRUE(ccm::isnan(ccm::gen::sin_gen(inf))); + EXPECT_TRUE(ccm::isnan(ccm::gen::cos_gen(inf))); + EXPECT_TRUE(ccm::isnan(ccm::gen::sin_gen(inf_f))); + EXPECT_TRUE(ccm::isnan(ccm::gen::cos_gen(inf_f))); + + // A quiet NaN with the sign bit set and a distinctive payload must come back bit for bit. + const std::uint64_t qnan_bits = 0xfff8'0000'1234'5678ULL; + const double qnan_in = ccm::support::bit_cast(qnan_bits); + EXPECT_EQ(ccm::support::bit_cast(ccm::gen::sin_gen(qnan_in)), qnan_bits); + EXPECT_EQ(ccm::support::bit_cast(ccm::gen::cos_gen(qnan_in)), qnan_bits); + + const std::uint32_t qnan_bits_f = 0xffc0'abcdU; + const float qnan_in_f = ccm::support::bit_cast(qnan_bits_f); + EXPECT_EQ(ccm::support::bit_cast(ccm::gen::sin_gen(qnan_in_f)), qnan_bits_f); + EXPECT_EQ(ccm::support::bit_cast(ccm::gen::cos_gen(qnan_in_f)), qnan_bits_f); + + // FE_INVALID and errno are each reported only when the build's error-handling mode asks for + // them. -fno-math-errno (the default on some toolchains) keeps the fp exception but drops the + // errno write, so gate each check on the mode it belongs to rather than on a single flag. + using ccm::support::fenv::ccm_math_err_mode; + using ccm::support::fenv::get_mode; + const int handling = ccm::support::fenv::ccm_math_err_handling(); + + std::feclearexcept(FE_ALL_EXCEPT); + errno = 0; + EXPECT_TRUE(ccm::isnan(ccm::gen::sin_gen(inf))); + if ((handling & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { EXPECT_NE(std::fetestexcept(FE_INVALID), 0); } + if ((handling & get_mode(ccm_math_err_mode::eErrno)) != 0) { EXPECT_EQ(errno, EDOM); } +} From 324010797dbb97793fad58cb0d6e43a3e46a8d77 Mon Sep 17 00:00:00 2001 From: ianpike Date: Wed, 17 Jun 2026 17:32:41 -0400 Subject: [PATCH 32/52] Use generic kernels for MSVC runtime math fallback --- .../math/runtime/func/basic/fmod_rt.hpp | 5 - .../math/runtime/func/basic/remainder_rt.hpp | 5 - .../math/runtime/func/detail/CMakeLists.txt | 2 +- .../math/runtime/func/detail/msvc_libm.hpp | 151 ------------------ .../math/runtime/func/expo/exp2_rt.hpp | 8 - .../math/runtime/func/expo/log10_rt.hpp | 14 +- .../math/runtime/func/expo/log2_rt.hpp | 14 +- .../math/runtime/func/expo/log_rt.hpp | 14 +- .../math/runtime/func/misc/gamma_rt.hpp | 5 - .../math/runtime/func/misc/lgamma_rt.hpp | 5 - .../math/runtime/func/trig/cos_rt.hpp | 11 +- .../math/runtime/func/trig/sin_rt.hpp | 11 +- .../math/runtime/func/trig/tan_rt.hpp | 11 +- .../system_math_header_coexistence_test.cpp | 84 ++++++++++ tests/unit/CMakeLists.txt | 4 + 15 files changed, 107 insertions(+), 237 deletions(-) delete mode 100644 include/ccmath/internal/math/runtime/func/detail/msvc_libm.hpp create mode 100644 tests/src/math/system_math_header_coexistence_test.cpp diff --git a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp index 5353a102..fe6f99c8 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/basic/fmod.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" @@ -22,14 +21,10 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T fmod_rt(T x, T y) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::fmod_call(x, y); -#else if constexpr (ccm::builtin::has_runtime_fmod) { return ccm::builtin::fmod_rt(x, y); } else { return static_cast(x - (detail::trunc_scalar(x / y) * y)); } -#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp index 0064b532..e39e2145 100644 --- a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/basic/remainder.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/predef/unlikely.hpp" @@ -25,9 +24,6 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T remainder_rt(T x, T y) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::remainder_call(x, y); -#else if constexpr (ccm::builtin::has_runtime_remainder) { return ccm::builtin::remainder_rt(x, y); } else { @@ -42,6 +38,5 @@ namespace ccm::rt } return static_cast(x - (detail::trunc_scalar(x / y) * y)); } -#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/detail/CMakeLists.txt b/include/ccmath/internal/math/runtime/func/detail/CMakeLists.txt index 5ab57ceb..047417ce 100644 --- a/include/ccmath/internal/math/runtime/func/detail/CMakeLists.txt +++ b/include/ccmath/internal/math/runtime/func/detail/CMakeLists.txt @@ -1 +1 @@ -ccm_add_headers(msvc_libm.hpp trunc_scalar.hpp) +ccm_add_headers(trunc_scalar.hpp) diff --git a/include/ccmath/internal/math/runtime/func/detail/msvc_libm.hpp b/include/ccmath/internal/math/runtime/func/detail/msvc_libm.hpp deleted file mode 100644 index e02f2d34..00000000 --- a/include/ccmath/internal/math/runtime/func/detail/msvc_libm.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#pragma once - -#include - -namespace ccm::rt::detail::msvc_libm -{ -#if defined(_MSC_VER) && !defined(__clang__) - extern "C" float exp2f(float); - extern "C" double exp2(double); - extern "C" float logf(float); - extern "C" double log(double); - extern "C" float log2f(float); - extern "C" double log2(double); - extern "C" float log10f(float); - extern "C" double log10(double); - extern "C" float sinf(float); - extern "C" double sin(double); - extern "C" float cosf(float); - extern "C" double cos(double); - extern "C" float tanf(float); - extern "C" double tan(double); - extern "C" float fmodf(float, float); - extern "C" double fmod(double, double); - extern "C" float remainderf(float, float); - extern "C" double remainder(double, double); - extern "C" float tgammaf(float); - extern "C" double tgamma(double); - extern "C" float lgammaf(float); - extern "C" double lgamma(double); - - template - [[nodiscard]] inline T exp2_call(T x) noexcept - { - if constexpr (std::is_same_v) { return exp2f(x); } - else - { - return static_cast(exp2(static_cast(x))); - } - } - - template - [[nodiscard]] inline T log_call(T x) noexcept - { - if constexpr (std::is_same_v) { return logf(x); } - else - { - return static_cast(log(static_cast(x))); - } - } - - template - [[nodiscard]] inline T log2_call(T x) noexcept - { - if constexpr (std::is_same_v) { return log2f(x); } - else - { - return static_cast(log2(static_cast(x))); - } - } - - template - [[nodiscard]] inline T log10_call(T x) noexcept - { - if constexpr (std::is_same_v) { return log10f(x); } - else - { - return static_cast(log10(static_cast(x))); - } - } - - template - [[nodiscard]] inline T sin_call(T x) noexcept - { - if constexpr (std::is_same_v) { return sinf(x); } - else - { - return static_cast(sin(static_cast(x))); - } - } - - template - [[nodiscard]] inline T cos_call(T x) noexcept - { - if constexpr (std::is_same_v) { return cosf(x); } - else - { - return static_cast(cos(static_cast(x))); - } - } - - template - [[nodiscard]] inline T tan_call(T x) noexcept - { - if constexpr (std::is_same_v) { return tanf(x); } - else - { - return static_cast(tan(static_cast(x))); - } - } - - template - [[nodiscard]] inline T fmod_call(T x, T y) noexcept - { - if constexpr (std::is_same_v) { return fmodf(x, y); } - else - { - return static_cast(fmod(static_cast(x), static_cast(y))); - } - } - - template - [[nodiscard]] inline T remainder_call(T x, T y) noexcept - { - if constexpr (std::is_same_v) { return remainderf(x, y); } - else - { - return static_cast(remainder(static_cast(x), static_cast(y))); - } - } - - template - [[nodiscard]] inline T gamma_call(T x) noexcept - { - if constexpr (std::is_same_v) { return tgammaf(x); } - else - { - return static_cast(tgamma(static_cast(x))); - } - } - - template - [[nodiscard]] inline T lgamma_call(T x) noexcept - { - if constexpr (std::is_same_v) { return lgammaf(x); } - else - { - return static_cast(lgamma(static_cast(x))); - } - } -#endif -} // namespace ccm::rt::detail::msvc_libm diff --git a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp index 8c53889a..8f7e0865 100644 --- a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/exp2.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/expo/impl/exp2_double_impl.hpp" #include "ccmath/math/expo/impl/exp2_float_impl.hpp" @@ -24,12 +23,6 @@ namespace ccm::rt [[nodiscard]] inline T exp2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::exp2_float, ccm::internal::exp2_double); }; -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC routes to libm, which is not correctly rounded outside round to nearest. - // Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } - return detail::msvc_libm::exp2_call(num); -#else if constexpr (ccm::builtin::has_runtime_exp2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -41,6 +34,5 @@ namespace ccm::rt { return simd_impl::unary_via_scalar_abi(num, scalar); } -#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp index 39414495..3821823f 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log10.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -25,12 +24,6 @@ namespace ccm::rt [[nodiscard]] inline T log10_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log10_float, ccm::internal::log10_double); }; -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC routes to libm, which is not correctly rounded outside round to nearest. - // Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } - return detail::msvc_libm::log10_call(num); -#else if constexpr (ccm::builtin::has_runtime_log10) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -40,12 +33,11 @@ namespace ccm::rt } else { - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log10(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp index a900b128..9e216c9e 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log2.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -26,12 +25,6 @@ namespace ccm::rt [[nodiscard]] inline T log2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log2_float, ccm::internal::log2_double); }; -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC routes to libm, which is not correctly rounded outside round to nearest. - // Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } - return detail::msvc_libm::log2_call(num); -#else if constexpr (ccm::builtin::has_runtime_log2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -41,12 +34,11 @@ namespace ccm::rt } else { - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log2(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp index 77528124..83d7bee1 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -26,12 +25,6 @@ namespace ccm::rt [[nodiscard]] inline T log_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log_float, ccm::internal::log_double); }; -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC routes to libm, which is not correctly rounded outside round to nearest. - // Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } - return detail::msvc_libm::log_call(num); -#else if constexpr (ccm::builtin::has_runtime_log) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -41,12 +34,11 @@ namespace ccm::rt } else { - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp index be6616af..e98b72ea 100644 --- a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/misc/tgamma.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/misc/impl/gamma_impl.hpp" @@ -22,15 +21,11 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T gamma_rt(T num) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::gamma_call(num); -#else if constexpr (ccm::builtin::has_runtime_gamma) { return ccm::builtin::gamma_rt(num); } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::gamma_float, ccm::internal::gamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); } -#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp index 1b3084e7..415deb0c 100644 --- a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp @@ -11,7 +11,6 @@ #pragma once #include "ccmath/internal/math/generic/builtins/misc/lgamma.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/misc/impl/lgamma_impl.hpp" @@ -22,15 +21,11 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T lgamma_rt(T num) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::lgamma_call(num); -#else if constexpr (ccm::builtin::has_runtime_lgamma) { return ccm::builtin::lgamma_rt(num); } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::lgamma_float, ccm::internal::lgamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); } -#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp index e26d4219..8a3a58c2 100644 --- a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp @@ -12,7 +12,6 @@ #include "ccmath/internal/math/generic/builtins/trig/cos.hpp" #include "ccmath/internal/math/generic/func/trig/cos_gen.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -24,19 +23,15 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T cos_rt(T num) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::cos_call(num); -#else if constexpr (ccm::builtin::has_runtime_cos) { return ccm::builtin::cos_rt(num); } else { const auto scalar = [](T value) { return gen::cos_gen(value); }; - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::cos(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp index 9142df45..0045632a 100644 --- a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp @@ -12,7 +12,6 @@ #include "ccmath/internal/math/generic/builtins/trig/sin.hpp" #include "ccmath/internal/math/generic/func/trig/sin_gen.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -24,19 +23,15 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T sin_rt(T num) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::sin_call(num); -#else if constexpr (ccm::builtin::has_runtime_sin) { return ccm::builtin::sin_rt(num); } else { const auto scalar = [](T value) { return gen::sin_gen(value); }; - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::sin(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp index 8990b7a2..a98ebd9b 100644 --- a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp @@ -12,7 +12,6 @@ #include "ccmath/internal/math/generic/builtins/trig/tan.hpp" #include "ccmath/internal/math/generic/func/trig/tan_gen.hpp" -#include "ccmath/internal/math/runtime/func/detail/msvc_libm.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -24,19 +23,15 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T tan_rt(T num) noexcept { -#if defined(_MSC_VER) && !defined(__clang__) - return detail::msvc_libm::tan_call(num); -#else if constexpr (ccm::builtin::has_runtime_tan) { return ccm::builtin::tan_rt(num); } else { const auto scalar = [](T value) { return gen::tan_gen(value); }; - #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) +#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::tan(v); }, scalar); - #else +#else return simd_impl::unary_via_scalar_abi(num, scalar); - #endif - } #endif + } } } // namespace ccm::rt diff --git a/tests/src/math/system_math_header_coexistence_test.cpp b/tests/src/math/system_math_header_coexistence_test.cpp new file mode 100644 index 00000000..e1a2ed78 --- /dev/null +++ b/tests/src/math/system_math_header_coexistence_test.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +// Regression for the MSVC extern "C" libm linkage collision (errors C2733 and C2375). +// +// The runtime kernels used to forward declare libm functions (exp2, log, log2, log10, sin, cos, +// tan, fmod, remainder, tgamma, lgamma and their float variants) as extern "C" inside a namespace. +// A function with C language linkage declared in a namespace names the same entity as the global +// one, so once a consumer pulled in the UCRT declarations from the system math headers alongside +// ccmath, MSVC rejected the two declarations as conflicting. +// +// The system math headers are included first on purpose, then ccmath, which is the order a downstream +// consumer hits. If the forward declarations ever come back this translation unit stops compiling on +// MSVC. The runtime checks confirm the kernels still resolve and return the expected values. + +// cmath must be parsed before ccmath so the UCRT declarations are already in scope (cmath pulls in +// corecrt_math.h, the header named in the original error), which is what triggered the collision. +// Keep this order fixed and do not let the formatter sort it. +// clang-format off +#include + +#include +// clang-format on + +// Pull in the runtime headers that carried the forward declarations directly, so the guard holds even +// if the umbrella header stops including one of them. +#include "ccmath/internal/math/runtime/func/basic/fmod_rt.hpp" +#include "ccmath/internal/math/runtime/func/basic/remainder_rt.hpp" +#include "ccmath/internal/math/runtime/func/expo/exp2_rt.hpp" +#include "ccmath/internal/math/runtime/func/expo/log10_rt.hpp" +#include "ccmath/internal/math/runtime/func/expo/log2_rt.hpp" +#include "ccmath/internal/math/runtime/func/expo/log_rt.hpp" +#include "ccmath/internal/math/runtime/func/misc/gamma_rt.hpp" +#include "ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp" +#include "ccmath/internal/math/runtime/func/trig/cos_rt.hpp" +#include "ccmath/internal/math/runtime/func/trig/sin_rt.hpp" +#include "ccmath/internal/math/runtime/func/trig/tan_rt.hpp" + +#include + +namespace +{ + // Force a genuine runtime call so the runtime dispatch is exercised rather than constant folded. + template + T runtime(T value) + { + volatile T sink = value; + return sink; + } +} // namespace + +TEST(CcmathSystemMathHeaderCoexistence, RuntimeKernelsResolveAlongsideSystemMath) +{ + EXPECT_DOUBLE_EQ(ccm::exp2(runtime(3.0)), 8.0); + EXPECT_DOUBLE_EQ(ccm::log(runtime(1.0)), 0.0); + EXPECT_DOUBLE_EQ(ccm::log2(runtime(8.0)), 3.0); + EXPECT_DOUBLE_EQ(ccm::log10(runtime(100.0)), 2.0); + EXPECT_DOUBLE_EQ(ccm::sin(runtime(0.0)), 0.0); + EXPECT_DOUBLE_EQ(ccm::cos(runtime(0.0)), 1.0); + EXPECT_DOUBLE_EQ(ccm::tan(runtime(0.0)), 0.0); + EXPECT_DOUBLE_EQ(ccm::fmod(runtime(7.0), runtime(3.0)), 1.0); + EXPECT_DOUBLE_EQ(ccm::remainder(runtime(7.0), runtime(3.0)), 1.0); + EXPECT_DOUBLE_EQ(ccm::tgamma(runtime(5.0)), 24.0); + EXPECT_DOUBLE_EQ(ccm::lgamma(runtime(1.0)), 0.0); + + EXPECT_FLOAT_EQ(ccm::exp2f(runtime(3.0F)), 8.0F); + EXPECT_FLOAT_EQ(ccm::tgammaf(runtime(5.0F)), 24.0F); +} + +TEST(CcmathSystemMathHeaderCoexistence, RuntimeEntryPointsResolveAlongsideSystemMath) +{ + // The runtime entry points are what included the previously conflicting headers, so call them too. + EXPECT_DOUBLE_EQ(ccm::rt::exp2_rt(runtime(3.0)), 8.0); + EXPECT_DOUBLE_EQ(ccm::rt::sin_rt(runtime(0.0)), 0.0); + EXPECT_DOUBLE_EQ(ccm::rt::gamma_rt(runtime(5.0)), 24.0); + EXPECT_DOUBLE_EQ(ccm::rt::remainder_rt(runtime(7.0), runtime(3.0)), 1.0); +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7300f6d5..02e65429 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -46,6 +46,10 @@ ccmath_add_gtest_suite(ccmath-simple-ext-constexpr SOURCES ../src/ext/constexpr_smoke_test.cpp LABELS simple) +ccmath_add_gtest_suite(ccmath-simple-system-math-coexistence + SOURCES ../src/math/system_math_header_coexistence_test.cpp + LABELS simple) + if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) set(_lerp_source ../src/math/misc/lerp_test_std.cpp) else () From d6cdc22fb0028f6db9aec7dea42438746bea4189 Mon Sep 17 00:00:00 2001 From: ianpike Date: Wed, 17 Jun 2026 19:27:40 -0400 Subject: [PATCH 33/52] Use the system libm for MSVC runtime math --- cmake/config/ProjectCompileDefinitions.cmake | 9 ++ .../math/runtime/func/basic/fmod_rt.hpp | 5 + .../math/runtime/func/basic/remainder_rt.hpp | 5 + .../math/runtime/func/detail/system_math.hpp | 151 ++++++++++++++++++ .../math/runtime/func/expo/exp2_rt.hpp | 7 + .../math/runtime/func/expo/log10_rt.hpp | 13 +- .../math/runtime/func/expo/log2_rt.hpp | 13 +- .../math/runtime/func/expo/log_rt.hpp | 13 +- .../math/runtime/func/misc/gamma_rt.hpp | 5 + .../math/runtime/func/misc/lgamma_rt.hpp | 5 + .../math/runtime/func/trig/cos_rt.hpp | 11 +- .../math/runtime/func/trig/sin_rt.hpp | 11 +- .../math/runtime/func/trig/tan_rt.hpp | 11 +- 13 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 include/ccmath/internal/math/runtime/func/detail/system_math.hpp diff --git a/cmake/config/ProjectCompileDefinitions.cmake b/cmake/config/ProjectCompileDefinitions.cmake index 284038ba..6489e6b2 100644 --- a/cmake/config/ProjectCompileDefinitions.cmake +++ b/cmake/config/ProjectCompileDefinitions.cmake @@ -11,3 +11,12 @@ if (CCMATH_ENABLE_DETERMINISTIC) $<$>:-ffp-contract=off> ) endif () + +# On MSVC the runtime path calls the system libm (via ) for the expensive transcendentals, +# which has no foldable __builtin and no SVML, so the generic kernels would otherwise be used and are +# not accurate enough. This is on by default for MSVC. Disabling it keeps the build free of +# and falls back to the generic kernels. +option(CCMATH_DISABLE_SYSTEM_MATH "Do not call the system libm on MSVC (keeps the build free of )" OFF) +if (CCMATH_DISABLE_SYSTEM_MATH) + target_compile_definitions(ccmath INTERFACE CCM_CONFIG_DISABLE_SYSTEM_MATH) +endif () diff --git a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp index fe6f99c8..2b68f332 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/basic/fmod.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" @@ -21,10 +22,14 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T fmod_rt(T x, T y) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::fmod_call(x, y); +#else if constexpr (ccm::builtin::has_runtime_fmod) { return ccm::builtin::fmod_rt(x, y); } else { return static_cast(x - (detail::trunc_scalar(x / y) * y)); } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp index e39e2145..786e52e8 100644 --- a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/basic/remainder.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/predef/unlikely.hpp" @@ -24,6 +25,9 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T remainder_rt(T x, T y) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::remainder_call(x, y); +#else if constexpr (ccm::builtin::has_runtime_remainder) { return ccm::builtin::remainder_rt(x, y); } else { @@ -38,5 +42,6 @@ namespace ccm::rt } return static_cast(x - (detail::trunc_scalar(x / y) * y)); } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/detail/system_math.hpp b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp new file mode 100644 index 00000000..fc1502bc --- /dev/null +++ b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +// On MSVC there is no foldable __builtin math and no SVML transcendental path, so the runtime scalar +// fallback would otherwise use ccmath's generic kernels, which are not accurate enough to match the +// system libm. Including the real and calling the global C runtime entry points gives UCRT +// speed and accuracy. Using the actual UCRT header (rather than forward declaring the symbols) means +// a consumer that also includes just sees the same include-guarded declarations, so there is +// no C2733/C2375/C4273 linkage conflict. +// +// CCM_CONFIG_SYSTEM_MATH is on by default for MSVC-proper. Define CCM_CONFIG_DISABLE_SYSTEM_MATH to +// turn it off and keep the build free of (the generic kernels are used instead). Define +// CCM_CONFIG_FORCE_SYSTEM_MATH to exercise this path on other compilers for testing. +#if !defined(CCM_CONFIG_DISABLE_SYSTEM_MATH) && (defined(CCM_CONFIG_FORCE_SYSTEM_MATH) || (defined(_MSC_VER) && !defined(__clang__))) + #define CCM_CONFIG_SYSTEM_MATH 1 +#endif + +#if defined(CCM_CONFIG_SYSTEM_MATH) + + #include + + #include + +namespace ccm::rt::detail::sys +{ + // Expensive transcendentals. The runtime path uses these only under FE_TONEAREST, since libm is + // not correctly rounded in the directed modes (the kernel handles those, like the builtin path). + template + [[nodiscard]] inline T exp2_call(T x) + { + if constexpr (std::is_same_v) { return ::exp2f(x); } + else + { + return ::exp2(static_cast(x)); + } + } + + template + [[nodiscard]] inline T log_call(T x) + { + if constexpr (std::is_same_v) { return ::logf(x); } + else + { + return ::log(static_cast(x)); + } + } + + template + [[nodiscard]] inline T log2_call(T x) + { + if constexpr (std::is_same_v) { return ::log2f(x); } + else + { + return ::log2(static_cast(x)); + } + } + + template + [[nodiscard]] inline T log10_call(T x) + { + if constexpr (std::is_same_v) { return ::log10f(x); } + else + { + return ::log10(static_cast(x)); + } + } + + template + [[nodiscard]] inline T sin_call(T x) + { + if constexpr (std::is_same_v) { return ::sinf(x); } + else + { + return ::sin(static_cast(x)); + } + } + + template + [[nodiscard]] inline T cos_call(T x) + { + if constexpr (std::is_same_v) { return ::cosf(x); } + else + { + return ::cos(static_cast(x)); + } + } + + template + [[nodiscard]] inline T tan_call(T x) + { + if constexpr (std::is_same_v) { return ::tanf(x); } + else + { + return ::tan(static_cast(x)); + } + } + + template + [[nodiscard]] inline T gamma_call(T x) + { + if constexpr (std::is_same_v) { return ::tgammaf(x); } + else + { + return ::tgamma(static_cast(x)); + } + } + + template + [[nodiscard]] inline T lgamma_call(T x) + { + if constexpr (std::is_same_v) { return ::lgammaf(x); } + else + { + return ::lgamma(static_cast(x)); + } + } + + // fmod and remainder are exact in every rounding mode, so they are routed unconditionally. The + // generic *_rt formula (x - trunc(x / y) * y) loses all precision for large quotients, and the + // exact kernel cannot be reached here without pulling the nearest dispatch (and pp) back in. + template + [[nodiscard]] inline T fmod_call(T x, T y) + { + if constexpr (std::is_same_v) { return ::fmodf(x, y); } + else + { + return ::fmod(static_cast(x), static_cast(y)); + } + } + + template + [[nodiscard]] inline T remainder_call(T x, T y) + { + if constexpr (std::is_same_v) { return ::remainderf(x, y); } + else + { + return ::remainder(static_cast(x), static_cast(y)); + } + } +} // namespace ccm::rt::detail::sys + +#endif // CCM_CONFIG_SYSTEM_MATH diff --git a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp index 8f7e0865..3383ff02 100644 --- a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/exp2.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/expo/impl/exp2_double_impl.hpp" #include "ccmath/math/expo/impl/exp2_float_impl.hpp" @@ -23,6 +24,11 @@ namespace ccm::rt [[nodiscard]] inline T exp2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::exp2_float, ccm::internal::exp2_double); }; +#if defined(CCM_CONFIG_SYSTEM_MATH) + // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + return detail::sys::exp2_call(num); +#else if constexpr (ccm::builtin::has_runtime_exp2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -34,5 +40,6 @@ namespace ccm::rt { return simd_impl::unary_via_scalar_abi(num, scalar); } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp index 3821823f..6b6c0f1c 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log10.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -24,6 +25,11 @@ namespace ccm::rt [[nodiscard]] inline T log10_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log10_float, ccm::internal::log10_double); }; +#if defined(CCM_CONFIG_SYSTEM_MATH) + // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + return detail::sys::log10_call(num); +#else if constexpr (ccm::builtin::has_runtime_log10) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -33,11 +39,12 @@ namespace ccm::rt } else { -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log10(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp index 9e216c9e..ffb236ec 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log2.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -25,6 +26,11 @@ namespace ccm::rt [[nodiscard]] inline T log2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log2_float, ccm::internal::log2_double); }; +#if defined(CCM_CONFIG_SYSTEM_MATH) + // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + return detail::sys::log2_call(num); +#else if constexpr (ccm::builtin::has_runtime_log2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -34,11 +40,12 @@ namespace ccm::rt } else { -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log2(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp index 83d7bee1..eca1fd71 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/expo/log.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -25,6 +26,11 @@ namespace ccm::rt [[nodiscard]] inline T log_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log_float, ccm::internal::log_double); }; +#if defined(CCM_CONFIG_SYSTEM_MATH) + // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + return detail::sys::log_call(num); +#else if constexpr (ccm::builtin::has_runtime_log) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to @@ -34,11 +40,12 @@ namespace ccm::rt } else { -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp index e98b72ea..706b11a2 100644 --- a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/misc/tgamma.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/misc/impl/gamma_impl.hpp" @@ -21,11 +22,15 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T gamma_rt(T num) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::gamma_call(num); +#else if constexpr (ccm::builtin::has_runtime_gamma) { return ccm::builtin::gamma_rt(num); } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::gamma_float, ccm::internal::gamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp index 415deb0c..dedec1d2 100644 --- a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp @@ -11,6 +11,7 @@ #pragma once #include "ccmath/internal/math/generic/builtins/misc/lgamma.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/math/misc/impl/lgamma_impl.hpp" @@ -21,11 +22,15 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T lgamma_rt(T num) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::lgamma_call(num); +#else if constexpr (ccm::builtin::has_runtime_lgamma) { return ccm::builtin::lgamma_rt(num); } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::lgamma_float, ccm::internal::lgamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp index 8a3a58c2..98af643c 100644 --- a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp @@ -12,6 +12,7 @@ #include "ccmath/internal/math/generic/builtins/trig/cos.hpp" #include "ccmath/internal/math/generic/func/trig/cos_gen.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -23,15 +24,19 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T cos_rt(T num) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::cos_call(num); +#else if constexpr (ccm::builtin::has_runtime_cos) { return ccm::builtin::cos_rt(num); } else { const auto scalar = [](T value) { return gen::cos_gen(value); }; -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::cos(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp index 0045632a..93da86d2 100644 --- a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp @@ -12,6 +12,7 @@ #include "ccmath/internal/math/generic/builtins/trig/sin.hpp" #include "ccmath/internal/math/generic/func/trig/sin_gen.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -23,15 +24,19 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T sin_rt(T num) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::sin_call(num); +#else if constexpr (ccm::builtin::has_runtime_sin) { return ccm::builtin::sin_rt(num); } else { const auto scalar = [](T value) { return gen::sin_gen(value); }; -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::sin(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp index a98ebd9b..2ea3dda4 100644 --- a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp @@ -12,6 +12,7 @@ #include "ccmath/internal/math/generic/builtins/trig/tan.hpp" #include "ccmath/internal/math/generic/func/trig/tan_gen.hpp" +#include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" #include "ccmath/internal/math/runtime/func/svml_dispatch.hpp" #include "ccmath/internal/math/runtime/simd/func/catalog.hpp" @@ -23,15 +24,19 @@ namespace ccm::rt template , bool> = true> [[nodiscard]] inline T tan_rt(T num) noexcept { +#if defined(CCM_CONFIG_SYSTEM_MATH) + return detail::sys::tan_call(num); +#else if constexpr (ccm::builtin::has_runtime_tan) { return ccm::builtin::tan_rt(num); } else { const auto scalar = [](T value) { return gen::tan_gen(value); }; -#if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) + #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_trig_svml_or_impl(num, [](auto v) { return intrin::tan(v); }, scalar); -#else + #else return simd_impl::unary_via_scalar_abi(num, scalar); -#endif + #endif } +#endif } } // namespace ccm::rt From 904194c3822b8511797c4f1320375c249baed5a7 Mon Sep 17 00:00:00 2001 From: ianpike Date: Thu, 18 Jun 2026 11:47:38 -0400 Subject: [PATCH 34/52] Remove the see tag pointing to cppref --- include/ccmath/ext/align.hpp | 3 --- include/ccmath/internal/math/common/fmanip/ldexp.hpp | 4 ---- include/ccmath/math/basic/fabs.hpp | 9 --------- include/ccmath/math/basic/fdim.hpp | 5 ----- include/ccmath/math/basic/fmod.hpp | 5 ----- include/ccmath/math/basic/max.hpp | 7 ------- include/ccmath/math/basic/min.hpp | 5 ----- include/ccmath/math/basic/nan.hpp | 3 --- include/ccmath/math/basic/remainder.hpp | 4 ---- include/ccmath/math/basic/remquo.hpp | 4 ---- include/ccmath/math/expo/exp.hpp | 4 ---- include/ccmath/math/expo/exp2.hpp | 4 ---- include/ccmath/math/expo/expm1.hpp | 4 ---- include/ccmath/math/expo/log10.hpp | 4 ---- include/ccmath/math/expo/log1p.hpp | 4 ---- include/ccmath/math/expo/log2.hpp | 4 ---- include/ccmath/math/fmanip/copysign.hpp | 4 ---- include/ccmath/math/fmanip/frexp.hpp | 3 --- include/ccmath/math/fmanip/ilogb.hpp | 3 --- include/ccmath/math/fmanip/ldexp.hpp | 4 ---- include/ccmath/math/fmanip/logb.hpp | 3 --- include/ccmath/math/fmanip/modf.hpp | 3 --- include/ccmath/math/fmanip/nextafter.hpp | 4 ---- include/ccmath/math/fmanip/nexttoward.hpp | 4 ---- include/ccmath/math/fmanip/scalbn.hpp | 8 -------- include/ccmath/math/misc/gamma.hpp | 4 ---- include/ccmath/math/misc/lerp.hpp | 2 -- include/ccmath/math/misc/lgamma.hpp | 4 ---- include/ccmath/math/nearest/ceil.hpp | 4 ---- include/ccmath/math/nearest/floor.hpp | 4 ---- include/ccmath/math/nearest/nearbyint.hpp | 4 ---- include/ccmath/math/nearest/rint.hpp | 4 ---- include/ccmath/math/nearest/round.hpp | 4 ---- include/ccmath/math/nearest/trunc.hpp | 4 ---- include/ccmath/math/power/cbrt.hpp | 3 --- include/ccmath/math/power/hypot.hpp | 4 ---- include/ccmath/math/power/pow.hpp | 4 ---- include/ccmath/math/power/sqrt.hpp | 4 ---- include/ccmath/math/trig/acos.hpp | 4 ---- include/ccmath/math/trig/asin.hpp | 4 ---- include/ccmath/math/trig/atan.hpp | 4 ---- include/ccmath/math/trig/atan2.hpp | 3 --- include/ccmath/math/trig/cos.hpp | 4 ---- include/ccmath/math/trig/sin.hpp | 4 ---- include/ccmath/math/trig/tan.hpp | 4 ---- 45 files changed, 185 deletions(-) diff --git a/include/ccmath/ext/align.hpp b/include/ccmath/ext/align.hpp index 4249997a..d2690a7f 100644 --- a/include/ccmath/ext/align.hpp +++ b/include/ccmath/ext/align.hpp @@ -35,7 +35,6 @@ namespace ccm::ext * @tparam mode Alignment direction (eUP or eDOWN). * @param value Value to align. * @return value rounded to the nearest aligned multiple according to mode. - * @see https://en.cppreference.com/w/cpp/memory/align */ template , bool> = true> constexpr T align(T value) noexcept @@ -92,7 +91,6 @@ namespace ccm::ext * @tparam alignment Alignment boundary as a compile-time constant. * @param value Value to align. * @return value rounded up to the alignment boundary. - * @see https://en.cppreference.com/w/cpp/memory/align */ template , bool> = true> constexpr T align_up(T value) noexcept @@ -104,7 +102,6 @@ namespace ccm::ext * @tparam alignment Alignment boundary as a compile-time constant. * @param value Value to align. * @return value rounded down to the alignment boundary. - * @see https://en.cppreference.com/w/cpp/memory/align */ template , bool> = true> constexpr T align_down(T value) noexcept diff --git a/include/ccmath/internal/math/common/fmanip/ldexp.hpp b/include/ccmath/internal/math/common/fmanip/ldexp.hpp index ad45c068..cb05ba81 100644 --- a/include/ccmath/internal/math/common/fmanip/ldexp.hpp +++ b/include/ccmath/internal/math/common/fmanip/ldexp.hpp @@ -56,7 +56,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> constexpr T ldexp(T num, int exp) noexcept @@ -79,7 +78,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> constexpr double ldexp(Integer num, int exp) noexcept @@ -94,7 +92,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c float. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr float ldexpf(float num, int exp) noexcept { return ccm::ldexp(num, exp); } @@ -111,7 +108,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr long double ldexpl(long double num, int exp) noexcept { return ccm::ldexp(num, exp); } diff --git a/include/ccmath/math/basic/fabs.hpp b/include/ccmath/math/basic/fabs.hpp index c39eb7e8..d5c8e2a4 100644 --- a/include/ccmath/math/basic/fabs.hpp +++ b/include/ccmath/math/basic/fabs.hpp @@ -23,7 +23,6 @@ namespace ccm * @tparam T Numeric type. * @param num Floating-point or integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> @@ -37,7 +36,6 @@ namespace ccm * @tparam T Numeric type. * @param num Floating-point or integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> @@ -53,7 +51,6 @@ namespace ccm * @tparam T Unsigned integer type. * @param num Unsigned value. * @return num unchanged. An unsigned value is already non-negative, so its absolute value is itself. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto abs(T num) -> std::enable_if_t, T> @@ -69,7 +66,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto fabs(T num) -> std::enable_if_t, T> @@ -80,7 +76,6 @@ namespace ccm * @tparam Integer Integer type. * @param num Integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ template constexpr auto fabs(Integer num) -> std::enable_if_t, double> @@ -90,7 +85,6 @@ namespace ccm * @brief Computes the absolute value of a number. * @param num Floating-point value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ constexpr float fabsf(float num) { return ccm::abs(num); } @@ -99,7 +93,6 @@ namespace ccm * @brief Computes the absolute value of a number. * @param num Floating-point value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ constexpr long double fabsl(long double num) { return ccm::abs(num); } @@ -108,7 +101,6 @@ namespace ccm * @brief Computes the absolute value of a number. * @param num Integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ constexpr long labs(long num) { return ccm::abs(num); } @@ -117,7 +109,6 @@ namespace ccm * @brief Computes the absolute value of a number. * @param num Integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fabs */ constexpr long long llabs(long long num) { return ccm::abs(num); } diff --git a/include/ccmath/math/basic/fdim.hpp b/include/ccmath/math/basic/fdim.hpp index d0bcdb91..cbc6e823 100644 --- a/include/ccmath/math/basic/fdim.hpp +++ b/include/ccmath/math/basic/fdim.hpp @@ -23,7 +23,6 @@ namespace ccm * @param x A floating-point or integer values * @param y A floating-point or integer values * @return If successful, returns the positive difference between x and y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fdim */ template , bool> = true> constexpr T fdim(T x, T y) @@ -43,7 +42,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return If successful, returns the positive difference between x and y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fdim */ template && std::is_floating_point_v, bool> = true> constexpr auto fdim(T x, U y) @@ -59,7 +57,6 @@ namespace ccm * @param x An integral value. * @param y An integral value. * @return If successful, returns the positive difference between x and y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fdim */ template , bool> = true> constexpr double fdim(Integer x, Integer y) @@ -70,7 +67,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return If successful, returns the positive difference between x and y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fdim */ constexpr float fdimf(float x, float y) { return ccm::fdim(x, y); } @@ -80,7 +76,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return If successful, returns the positive difference between x and y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fdim */ constexpr long double fdiml(long double x, long double y) { return ccm::fdim(x, y); } diff --git a/include/ccmath/math/basic/fmod.hpp b/include/ccmath/math/basic/fmod.hpp index 461a062f..ba9f8ba7 100644 --- a/include/ccmath/math/basic/fmod.hpp +++ b/include/ccmath/math/basic/fmod.hpp @@ -84,7 +84,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmod */ template , bool> = true> constexpr Real fmod(Real x, Real y) @@ -96,7 +95,6 @@ namespace ccm * @param x An integral value. * @param y An integral value. * @return The floating-point remainder of the division operation x/y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmod */ template , bool> = true> constexpr double fmod(Integer x, Integer y) @@ -109,7 +107,6 @@ namespace ccm * @param x A floating-point or integral value. * @param y A floating-point or integral value. * @return The floating-point remainder of the division operation x/y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmod */ template constexpr auto fmod(T x, T y) @@ -120,7 +117,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmod */ constexpr float fmodf(float x, float y) { return fmod(x, y); } @@ -130,7 +126,6 @@ namespace ccm * @param x A floating-point value. * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmod */ constexpr long double fmodl(long double x, long double y) { return fmod(x, y); } diff --git a/include/ccmath/math/basic/max.hpp b/include/ccmath/math/basic/max.hpp index fbf9c72a..b8ec47c8 100644 --- a/include/ccmath/math/basic/max.hpp +++ b/include/ccmath/math/basic/max.hpp @@ -28,7 +28,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ template constexpr T max(T x, T y) noexcept @@ -52,7 +51,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return Larger of x and y after converting both values to their common type. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ template constexpr auto max(T x, U y) noexcept @@ -70,7 +68,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ template constexpr T fmax(T x, T y) noexcept @@ -83,7 +80,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ template constexpr auto fmax(T x, U y) noexcept @@ -100,7 +96,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ template , bool> = true> constexpr double fmax(Integer x, Integer y) noexcept @@ -111,7 +106,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ constexpr float fmaxf(float x, float y) noexcept { return fmax(x, y); } @@ -121,7 +115,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmax */ constexpr long double fmaxl(long double x, long double y) noexcept { return fmax(x, y); } diff --git a/include/ccmath/math/basic/min.hpp b/include/ccmath/math/basic/min.hpp index 0a337d87..c4623681 100644 --- a/include/ccmath/math/basic/min.hpp +++ b/include/ccmath/math/basic/min.hpp @@ -27,7 +27,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmin */ template constexpr T min(const T x, const T y) noexcept @@ -51,7 +50,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmin */ template constexpr T min(const T x, const U y) noexcept @@ -69,7 +67,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmin */ template , bool> = true> constexpr Real fmin(const Real x, const Real y) noexcept @@ -82,7 +79,6 @@ namespace ccm * @param x Left-hand side of the comparison. * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmin */ template constexpr auto fmin(const T x, const U y) noexcept @@ -100,7 +96,6 @@ namespace ccm * @param x Right-hand side of the comparison. * @param y Left-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. - * @see https://en.cppreference.com/w/cpp/numeric/math/fmin */ template , bool> = true> constexpr Integer fmin(const Integer x, const Integer y) noexcept diff --git a/include/ccmath/math/basic/nan.hpp b/include/ccmath/math/basic/nan.hpp index 00bad0ef..9dd7ee20 100644 --- a/include/ccmath/math/basic/nan.hpp +++ b/include/ccmath/math/basic/nan.hpp @@ -20,7 +20,6 @@ namespace ccm * @brief not-a-number (NaN) function * @param arg Narrow character string identifying the contents of a NaN * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. - * @see https://en.cppreference.com/w/cpp/numeric/math/nan */ constexpr double nan(const char * arg) noexcept { return internal::impl::nan_double_impl(arg); } @@ -29,7 +28,6 @@ namespace ccm * @brief not-a-number (NaN) function * @param arg Narrow character string identifying the contents of a NaN * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. - * @see https://en.cppreference.com/w/cpp/numeric/math/nan */ constexpr float nanf(const char * arg) noexcept { return internal::impl::nan_float_impl(arg); } @@ -39,7 +37,6 @@ namespace ccm * @param arg Narrow character string identifying the contents of a NaN * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. * @note This function is not yet supported for long double and can only return either 0.0 or a quiet NaN based on if quiet NaNs are supported. - * @see https://en.cppreference.com/w/cpp/numeric/math/nan */ constexpr long double nanl(const char * arg) noexcept { diff --git a/include/ccmath/math/basic/remainder.hpp b/include/ccmath/math/basic/remainder.hpp index e3486883..44a35170 100644 --- a/include/ccmath/math/basic/remainder.hpp +++ b/include/ccmath/math/basic/remainder.hpp @@ -25,7 +25,6 @@ namespace ccm * @param x Dividend. * @param y Divisor. * @return The remainder of the division of x by y. - * @see https://en.cppreference.com/w/cpp/numeric/math/remainder */ template , bool> = true> constexpr T remainder(T x, T y) @@ -57,7 +56,6 @@ namespace ccm * @param x Dividend. * @param y Divisor. * @return The remainder of the division of x by y as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/remainder */ template , bool> = true> constexpr double remainder(Integer x, Integer y) @@ -68,7 +66,6 @@ namespace ccm * @param x Dividend. * @param y Divisor. * @return The remainder of the division of x by y. - * @see https://en.cppreference.com/w/cpp/numeric/math/remainder */ constexpr float remainderf(float x, float y) { return ccm::remainder(x, y); } @@ -78,7 +75,6 @@ namespace ccm * @param x Dividend. * @param y Divisor. * @return The remainder of the division of x by y. - * @see https://en.cppreference.com/w/cpp/numeric/math/remainder */ constexpr long double remainderl(long double x, long double y) { return ccm::remainder(x, y); } diff --git a/include/ccmath/math/basic/remquo.hpp b/include/ccmath/math/basic/remquo.hpp index b977f9b1..d0eb7c14 100644 --- a/include/ccmath/math/basic/remquo.hpp +++ b/include/ccmath/math/basic/remquo.hpp @@ -42,7 +42,6 @@ namespace ccm * return quotient; * } * @endcode - * @see https://en.cppreference.com/w/cpp/numeric/math/remquo */ template , bool> = true> constexpr T remquo(T x, T y, int * quo) @@ -82,7 +81,6 @@ namespace ccm * return quotient; * } * @endcode - * @see https://en.cppreference.com/w/cpp/numeric/math/remquo */ template && std::is_arithmetic_v, bool> = true> constexpr std::common_type_t remquo(Arithmetic1 x, Arithmetic2 y, int * quo) @@ -117,7 +115,6 @@ namespace ccm * return quotient; * } * @endcode - * @see https://en.cppreference.com/w/cpp/numeric/math/remquo */ constexpr float remquof(float x, float y, int * quo) { return ccm::remquo(x, y, quo); } @@ -148,7 +145,6 @@ namespace ccm * return quotient; * } * @endcode - * @see https://en.cppreference.com/w/cpp/numeric/math/remquo */ constexpr long double remquol(long double x, long double y, int * quo) { diff --git a/include/ccmath/math/expo/exp.hpp b/include/ccmath/math/expo/exp.hpp index b0b50b42..0617fe41 100644 --- a/include/ccmath/math/expo/exp.hpp +++ b/include/ccmath/math/expo/exp.hpp @@ -27,7 +27,6 @@ namespace ccm * @tparam T floating-point or integer type * @param num floating-point or integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp */ template , bool> = true> constexpr T exp(T num) @@ -46,7 +45,6 @@ namespace ccm * @tparam Integer integer type * @param num integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp */ template , bool> = true> constexpr double exp(Integer num) @@ -56,7 +54,6 @@ namespace ccm * @brief Computes e raised to the given power * @param num floating-point value * @return If no errors occur, the base-e exponential of num (e^num) is returned as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp */ constexpr float expf(float num) { return ccm::exp(num); } @@ -65,7 +62,6 @@ namespace ccm * @brief Computes e raised to the given power * @param num floating-point value * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp */ constexpr long double expl(long double num) { return ccm::exp(num); } diff --git a/include/ccmath/math/expo/exp2.hpp b/include/ccmath/math/expo/exp2.hpp index 155d7f84..306e5d81 100644 --- a/include/ccmath/math/expo/exp2.hpp +++ b/include/ccmath/math/expo/exp2.hpp @@ -30,7 +30,6 @@ namespace ccm * @tparam T Floating-point or integer type * @param num Floating-point or integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp2 */ template , bool> = true> constexpr T exp2(T num) @@ -49,7 +48,6 @@ namespace ccm * @tparam Integer Integer type * @param num Integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp2 */ template , bool> = true> constexpr double exp2(Integer num) @@ -59,7 +57,6 @@ namespace ccm * @brief Returns 2 raised to the given power (2^x) * @param num Floating-point value * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a float. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp2 */ constexpr float exp2f(float num) { return ccm::exp2(num); } @@ -68,7 +65,6 @@ namespace ccm * @brief Returns 2 raised to the given power (2^x) * @param num Floating-point value * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/exp2 */ constexpr long double exp2l(long double num) { return ccm::exp2(num); } diff --git a/include/ccmath/math/expo/expm1.hpp b/include/ccmath/math/expo/expm1.hpp index cb8cc8f1..5a7a48c4 100644 --- a/include/ccmath/math/expo/expm1.hpp +++ b/include/ccmath/math/expo/expm1.hpp @@ -29,7 +29,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return exp(num) - 1. - * @see https://en.cppreference.com/w/cpp/numeric/math/expm1 */ template , bool> = true> constexpr T expm1(T num) @@ -48,7 +47,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return exp(num) - 1 as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/expm1 */ template , bool> = true> constexpr double expm1(Integer num) @@ -58,7 +56,6 @@ namespace ccm * @brief Computes exp(num) - 1 for float. * @param num Floating-point value. * @return exp(num) - 1 as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/expm1 */ constexpr float expm1f(float num) { return ccm::expm1(num); } @@ -67,7 +64,6 @@ namespace ccm * @brief Computes exp(num) - 1 for long double. * @param num Floating-point value. * @return exp(num) - 1 as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/expm1 */ constexpr long double expm1l(long double num) { return ccm::expm1(num); } diff --git a/include/ccmath/math/expo/log10.hpp b/include/ccmath/math/expo/log10.hpp index 1cb20d8f..d0f4b9f6 100644 --- a/include/ccmath/math/expo/log10.hpp +++ b/include/ccmath/math/expo/log10.hpp @@ -35,7 +35,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Base-10 logarithm of num. Returns -inf for zero and NaN for negative inputs. - * @see https://en.cppreference.com/w/cpp/numeric/math/log10 */ template , bool> = true> constexpr T log10(T num) @@ -80,7 +79,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return Base-10 logarithm of num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log10 */ template , bool> = true> constexpr double log10(Integer num) @@ -90,7 +88,6 @@ namespace ccm * @brief Computes the base-10 logarithm of a float. * @param num Floating-point value. * @return Base-10 logarithm as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/log10 */ constexpr float log10f(float num) { return ccm::log10(num); } @@ -99,7 +96,6 @@ namespace ccm * @brief Computes the base-10 logarithm of a long double. * @param num Floating-point value. * @return Base-10 logarithm as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log10 */ constexpr long double log10l(long double num) { return ccm::log10(num); } diff --git a/include/ccmath/math/expo/log1p.hpp b/include/ccmath/math/expo/log1p.hpp index abe283c7..c80cb49f 100644 --- a/include/ccmath/math/expo/log1p.hpp +++ b/include/ccmath/math/expo/log1p.hpp @@ -34,7 +34,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Natural logarithm of 1 + num. Returns -inf for num == -1 and NaN for num < -1. - * @see https://en.cppreference.com/w/cpp/numeric/math/log1p */ template , bool> = true> constexpr T log1p(T num) @@ -77,7 +76,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return Natural logarithm of 1 + num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log1p */ template , bool> = true> constexpr double log1p(Integer num) @@ -87,7 +85,6 @@ namespace ccm * @brief Computes log(1 + num) for float. * @param num Floating-point value. * @return Natural logarithm of 1 + num as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/log1p */ constexpr float log1pf(float num) { return ccm::log1p(num); } @@ -96,7 +93,6 @@ namespace ccm * @brief Computes log(1 + num) for long double. * @param num Floating-point value. * @return Natural logarithm of 1 + num as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log1p */ constexpr long double log1pl(long double num) { return ccm::log1p(num); } diff --git a/include/ccmath/math/expo/log2.hpp b/include/ccmath/math/expo/log2.hpp index 8a8e30db..24b0bd59 100644 --- a/include/ccmath/math/expo/log2.hpp +++ b/include/ccmath/math/expo/log2.hpp @@ -35,7 +35,6 @@ namespace ccm * @tparam T The type of the number. * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number. - * @see https://en.cppreference.com/w/cpp/numeric/math/log2 */ template , bool> = true> constexpr T log2(T num) noexcept @@ -95,7 +94,6 @@ namespace ccm * @tparam Integer The type of the integer. * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log2 */ template , bool> = true> constexpr double log2(Integer num) noexcept @@ -105,7 +103,6 @@ namespace ccm * @brief Returns the base 2 logarithm of a number. * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number as a float. - * @see https://en.cppreference.com/w/cpp/numeric/math/log2 */ constexpr float log2f(float num) { return ccm::log2(num); } @@ -114,7 +111,6 @@ namespace ccm * @brief Returns the base 2 logarithm of a number. * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/log2 */ constexpr long double log2l(long double num) { return ccm::log2(num); } diff --git a/include/ccmath/math/fmanip/copysign.hpp b/include/ccmath/math/fmanip/copysign.hpp index 5d7bf3b4..ef9b0055 100644 --- a/include/ccmath/math/fmanip/copysign.hpp +++ b/include/ccmath/math/fmanip/copysign.hpp @@ -25,7 +25,6 @@ namespace ccm * @param mag A floating-point or integer value * @param sgn A floating-point or integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/copysign */ template , bool> = true> constexpr T copysign(T mag, T sgn) @@ -52,7 +51,6 @@ namespace ccm * @param mag A integer value * @param sgn A integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/copysign */ template , bool> = true> constexpr double copysign(Integer mag, Integer sgn) @@ -63,7 +61,6 @@ namespace ccm * @param mag A floating-point. * @param sgn A floating-point. * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/copysign */ constexpr float copysignf(float mag, float sgn) { return ccm::copysign(mag, sgn); } @@ -73,7 +70,6 @@ namespace ccm * @param mag A floating-point. * @param sgn A floating-point. * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/copysign */ constexpr long double copysignl(long double mag, long double sgn) { return ccm::copysign(mag, sgn); } diff --git a/include/ccmath/math/fmanip/frexp.hpp b/include/ccmath/math/fmanip/frexp.hpp index 0d732b52..aa3cd02f 100644 --- a/include/ccmath/math/fmanip/frexp.hpp +++ b/include/ccmath/math/fmanip/frexp.hpp @@ -25,7 +25,6 @@ namespace ccm * @param x Floating-point value to decompose. * @param exp Pointer that receives the extracted exponent. * @return Normalized fraction in the range [-1, -0.5) or [0.5, 1), or zero when x is zero. - * @see https://en.cppreference.com/w/cpp/numeric/math/frexp */ template , bool> = true> constexpr T frexp(T x, int * exp) @@ -47,7 +46,6 @@ namespace ccm * @param x Floating-point value to decompose. * @param exp Pointer that receives the extracted exponent. * @return Normalized fraction for x. - * @see https://en.cppreference.com/w/cpp/numeric/math/frexp */ constexpr float frexpf(float x, int * exp) { return ccm::frexp(x, exp); } @@ -60,7 +58,6 @@ namespace ccm * @param x Floating-point value to decompose. * @param exp Pointer that receives the extracted exponent. * @return Normalized fraction for x. - * @see https://en.cppreference.com/w/cpp/numeric/math/frexp */ constexpr long double frexpl(long double x, int * exp) { return ccm::frexp(x, exp); } diff --git a/include/ccmath/math/fmanip/ilogb.hpp b/include/ccmath/math/fmanip/ilogb.hpp index dc296857..fb2e6ea9 100644 --- a/include/ccmath/math/fmanip/ilogb.hpp +++ b/include/ccmath/math/fmanip/ilogb.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Unbiased base-2 exponent as int (or implementation-defined special values for zero/NaN). - * @see https://en.cppreference.com/w/cpp/numeric/math/ilogb */ template , bool> = true> constexpr int ilogb(T num) noexcept @@ -41,7 +40,6 @@ namespace ccm * @brief Extracts the unbiased integer exponent of a float. * @param num Floating-point value. * @return Unbiased base-2 exponent as int. - * @see https://en.cppreference.com/w/cpp/numeric/math/ilogb */ constexpr int ilogbf(float num) noexcept { return ccm::ilogb(num); } @@ -50,7 +48,6 @@ namespace ccm * @brief Extracts the unbiased integer exponent of a long double. * @param num Floating-point value. * @return Unbiased base-2 exponent as int. - * @see https://en.cppreference.com/w/cpp/numeric/math/ilogb */ constexpr int ilogbl(long double num) noexcept { return ccm::ilogb(num); } diff --git a/include/ccmath/math/fmanip/ldexp.hpp b/include/ccmath/math/fmanip/ldexp.hpp index f8faaeb0..37c6cb7f 100644 --- a/include/ccmath/math/fmanip/ldexp.hpp +++ b/include/ccmath/math/fmanip/ldexp.hpp @@ -58,7 +58,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> constexpr T ldexp(T num, int exp) @@ -82,7 +81,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ template , bool> = true> constexpr double ldexp(Integer num, int exp) @@ -97,7 +95,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c float. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr float ldexpf(float num, int exp) noexcept { return ccm::ldexp(num, exp); } @@ -114,7 +111,6 @@ namespace ccm * @param exp Power of two to scale by. * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c long double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. - * @see https://en.cppreference.com/w/cpp/numeric/math/ldexp */ constexpr long double ldexpl(long double num, int exp) noexcept { return ccm::ldexp(num, exp); } diff --git a/include/ccmath/math/fmanip/logb.hpp b/include/ccmath/math/fmanip/logb.hpp index 218c20e4..1165c1a5 100644 --- a/include/ccmath/math/fmanip/logb.hpp +++ b/include/ccmath/math/fmanip/logb.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Unbiased exponent of num in the return type T. - * @see https://en.cppreference.com/w/cpp/numeric/math/logb */ template , bool> = true> constexpr T logb(T num) noexcept @@ -41,7 +40,6 @@ namespace ccm * @brief Extracts the unbiased exponent of a float. * @param num Floating-point value. * @return Unbiased exponent as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/logb */ constexpr float logbf(float num) noexcept { return ccm::logb(num); } @@ -50,7 +48,6 @@ namespace ccm * @brief Extracts the unbiased exponent of a long double. * @param num Floating-point value. * @return Unbiased exponent as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/logb */ constexpr long double logbl(long double num) noexcept { return ccm::logb(num); } diff --git a/include/ccmath/math/fmanip/modf.hpp b/include/ccmath/math/fmanip/modf.hpp index bf3bcc0d..44e08add 100644 --- a/include/ccmath/math/fmanip/modf.hpp +++ b/include/ccmath/math/fmanip/modf.hpp @@ -25,7 +25,6 @@ namespace ccm * @param x Floating-point value to split. * @param iptr Pointer that receives the integral part. * @return Fractional part of x, with the same sign as x. - * @see https://en.cppreference.com/w/cpp/numeric/math/modf */ template , bool> = true> constexpr T modf(T x, T * iptr) noexcept @@ -54,7 +53,6 @@ namespace ccm * @param x Floating-point value to split. * @param iptr Pointer that receives the integral part. * @return Fractional part of x. - * @see https://en.cppreference.com/w/cpp/numeric/math/modf */ constexpr float modff(float x, float * iptr) noexcept { return ccm::modf(x, iptr); } @@ -64,7 +62,6 @@ namespace ccm * @param x Floating-point value to split. * @param iptr Pointer that receives the integral part. * @return Fractional part of x. - * @see https://en.cppreference.com/w/cpp/numeric/math/modf */ constexpr long double modfl(long double x, long double * iptr) noexcept { return ccm::modf(x, iptr); } diff --git a/include/ccmath/math/fmanip/nextafter.hpp b/include/ccmath/math/fmanip/nextafter.hpp index 85f0f454..7e635755 100644 --- a/include/ccmath/math/fmanip/nextafter.hpp +++ b/include/ccmath/math/fmanip/nextafter.hpp @@ -25,7 +25,6 @@ namespace ccm * @param from Starting value. * @param to Direction target. * @return Next representable value after from in the direction of to. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ template , bool> = true> constexpr T nextafter(T from, T to) noexcept @@ -45,7 +44,6 @@ namespace ccm * @param from Starting value. * @param to Direction target. * @return Next representable value in the promoted common type. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ template && std::is_arithmetic_v, bool> = true> constexpr auto nextafter(Arithmetic1 from, Arithmetic2 to) noexcept @@ -56,7 +54,6 @@ namespace ccm * @param from Starting value. * @param to Direction target. * @return Next representable float value. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ constexpr float nextafterf(float from, float to) noexcept { return ccm::nextafter(from, to); } @@ -66,7 +63,6 @@ namespace ccm * @param from Starting value. * @param to Direction target. * @return Next representable long double value. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ constexpr long double nextafterl(long double from, long double to) noexcept { return ccm::nextafter(from, to); } diff --git a/include/ccmath/math/fmanip/nexttoward.hpp b/include/ccmath/math/fmanip/nexttoward.hpp index 42f54cce..f5faa001 100644 --- a/include/ccmath/math/fmanip/nexttoward.hpp +++ b/include/ccmath/math/fmanip/nexttoward.hpp @@ -25,7 +25,6 @@ namespace ccm * @param from Starting value. * @param to Direction target as long double. * @return Next representable value after from toward to. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ template , bool> = true> constexpr T nexttoward(T from, long double to) noexcept @@ -44,7 +43,6 @@ namespace ccm * @param from Starting integer value. * @param to Direction target as long double. * @return Next representable double value after converting from to floating-point. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ template , bool> = true> constexpr double nexttoward(Integer from, long double to) noexcept @@ -55,7 +53,6 @@ namespace ccm * @param from Starting value. * @param to Direction target as long double. * @return Next representable float value. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ constexpr float nexttowardf(float from, long double to) noexcept { return gen::nextafter_gen(from, to); } @@ -65,7 +62,6 @@ namespace ccm * @param from Starting value. * @param to Direction target as long double. * @return Next representable long double value. - * @see https://en.cppreference.com/w/cpp/numeric/math/nextafter */ constexpr long double nexttowardl(long double from, long double to) noexcept { return gen::nextafter_gen(from, to); } diff --git a/include/ccmath/math/fmanip/scalbn.hpp b/include/ccmath/math/fmanip/scalbn.hpp index 2194954a..1fe006ca 100644 --- a/include/ccmath/math/fmanip/scalbn.hpp +++ b/include/ccmath/math/fmanip/scalbn.hpp @@ -30,7 +30,6 @@ namespace ccm * @param num Floating-point or integer value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ template , bool> = true> constexpr T scalbn(T num, int exp) noexcept @@ -53,7 +52,6 @@ namespace ccm * @param num Floating-point or integer value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ template , bool> = true> constexpr T scalbn(T num, long exp) noexcept @@ -76,7 +74,6 @@ namespace ccm * @param num Integer value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ template , bool> = true> constexpr double scalbn(Integer num, int exp) noexcept @@ -88,7 +85,6 @@ namespace ccm * @param num Integer value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ template , bool> = true> constexpr double scalbn(Integer num, long exp) noexcept @@ -107,7 +103,6 @@ namespace ccm * @param num Floating-point value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ constexpr float scalbnf(float num, int exp) noexcept { return ccm::scalbn(num, exp); } @@ -117,7 +112,6 @@ namespace ccm * @param num Floating-point value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ constexpr float scalbnf(float num, long exp) noexcept { return ccm::scalbn(num, exp); } @@ -127,7 +121,6 @@ namespace ccm * @param num Floating-point value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ constexpr long double scalbnl(long double num, int exp) noexcept { return ccm::scalbn(num, exp); } @@ -137,7 +130,6 @@ namespace ccm * @param num Floating-point value. * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/scalbn */ constexpr long double scalbnl(long double num, long exp) noexcept { return ccm::scalbn(num, exp); } diff --git a/include/ccmath/math/misc/gamma.hpp b/include/ccmath/math/misc/gamma.hpp index 3d43373d..a84b71d4 100644 --- a/include/ccmath/math/misc/gamma.hpp +++ b/include/ccmath/math/misc/gamma.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T floating-point or integer type * @param num floating-point or integer value * @return If no errors occur, the gamma function value of num is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/tgamma */ template , bool> = true> constexpr T tgamma(T num) @@ -46,7 +45,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return Gamma function value of num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/tgamma */ template , bool> = true> constexpr double tgamma(Integer num) @@ -64,7 +62,6 @@ namespace ccm * @brief Computes the gamma function for float. * @param num Floating-point value. * @return Gamma function value of num as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/tgamma */ constexpr float tgammaf(float num) { return ccm::tgamma(num); } @@ -73,7 +70,6 @@ namespace ccm * @brief Computes the gamma function for long double. * @param num Floating-point value. * @return Gamma function value of num as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/tgamma */ constexpr long double tgammal(long double num) { return ccm::tgamma(num); } diff --git a/include/ccmath/math/misc/lerp.hpp b/include/ccmath/math/misc/lerp.hpp index 5f0773f8..c0e13459 100644 --- a/include/ccmath/math/misc/lerp.hpp +++ b/include/ccmath/math/misc/lerp.hpp @@ -22,7 +22,6 @@ namespace ccm * @param b End value. * @param t Interpolation factor. * @return Interpolated value equivalent to a + t * (b - a) with stability handling for edge cases. - * @see https://en.cppreference.com/w/cpp/numeric/math/lerp */ template constexpr T lerp(T a, T b, T t) noexcept @@ -48,7 +47,6 @@ namespace ccm * @param b End value. * @param t Interpolation factor. * @return Interpolated value in the common type of T, U, and V. - * @see https://en.cppreference.com/w/cpp/numeric/math/lerp */ template constexpr std::enable_if_t && std::is_arithmetic_v && std::is_arithmetic_v, std::common_type_t> diff --git a/include/ccmath/math/misc/lgamma.hpp b/include/ccmath/math/misc/lgamma.hpp index 915a256f..bde9d02a 100644 --- a/include/ccmath/math/misc/lgamma.hpp +++ b/include/ccmath/math/misc/lgamma.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T floating-point or integer type * @param num floating-point or integer value * @return If no errors occur, the natural logarithm of the absolute value of the gamma function at num is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/lgamma */ template , bool> = true> constexpr T lgamma(T num) @@ -42,7 +41,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return Natural logarithm of |gamma(num)| as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/lgamma */ template , bool> = true> constexpr double lgamma(Integer num) @@ -52,7 +50,6 @@ namespace ccm * @brief Computes log-gamma for float. * @param num Floating-point value. * @return Natural logarithm of |gamma(num)| as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/lgamma */ constexpr float lgammaf(float num) { return ccm::lgamma(num); } @@ -61,7 +58,6 @@ namespace ccm * @brief Computes log-gamma for long double. * @param num Floating-point value. * @return Natural logarithm of |gamma(num)| as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/lgamma */ constexpr long double lgammal(long double num) { return ccm::lgamma(num); } diff --git a/include/ccmath/math/nearest/ceil.hpp b/include/ccmath/math/nearest/ceil.hpp index 47217492..92fee8b0 100644 --- a/include/ccmath/math/nearest/ceil.hpp +++ b/include/ccmath/math/nearest/ceil.hpp @@ -27,7 +27,6 @@ namespace ccm * @tparam T The type of the number. * @param num A floating-point or integer value. * @return The smallest integer value not less than num. - * @see https://en.cppreference.com/w/cpp/numeric/math/ceil */ template , bool> = true> constexpr T ceil(T num) noexcept @@ -54,7 +53,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return num converted to double. - * @see https://en.cppreference.com/w/cpp/numeric/math/ceil */ template , bool> = true> constexpr double ceil(Integer num) noexcept @@ -64,7 +62,6 @@ namespace ccm * @brief Computes the ceiling of a float value. * @param num Floating-point value. * @return Smallest integer value not less than num, as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/ceil */ constexpr float ceilf(float num) noexcept { return ccm::ceil(num); } @@ -73,7 +70,6 @@ namespace ccm * @brief Computes the ceiling of a double value. * @param num Floating-point value. * @return Smallest integer value not less than num, as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/ceil */ constexpr long double ceill(long double num) noexcept { return ccm::ceil(num); } diff --git a/include/ccmath/math/nearest/floor.hpp b/include/ccmath/math/nearest/floor.hpp index 1293888d..162a363c 100644 --- a/include/ccmath/math/nearest/floor.hpp +++ b/include/ccmath/math/nearest/floor.hpp @@ -26,7 +26,6 @@ namespace ccm * @tparam T The type of the number. * @param num A floating-point or integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/floor */ template , bool> = true> constexpr T floor(T num) noexcept @@ -52,7 +51,6 @@ namespace ccm * @brief Computes the largest integer value not greater than num. * @param num A integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/floor */ template , bool> = true> constexpr double floor(Integer num) noexcept @@ -64,7 +62,6 @@ namespace ccm * @brief Computes the largest integer value not greater than num. * @param num A floating-point value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/floor */ constexpr float floorf(float num) noexcept { return ccm::floor(num); } @@ -73,7 +70,6 @@ namespace ccm * @brief Computes the largest integer value not greater than num. * @param num A floating-point value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/floor */ constexpr long double floorl(long double num) noexcept { return ccm::floor(num); } diff --git a/include/ccmath/math/nearest/nearbyint.hpp b/include/ccmath/math/nearest/nearbyint.hpp index 6e3be124..ff3208d6 100644 --- a/include/ccmath/math/nearest/nearbyint.hpp +++ b/include/ccmath/math/nearest/nearbyint.hpp @@ -29,7 +29,6 @@ namespace ccm * @tparam T The type of the number. * @param num A floating-point value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input floating point value unmodified. - * @see https://en.cppreference.com/w/cpp/numeric/math/nearbyint */ template , bool> = true> constexpr T nearbyint(T num) noexcept @@ -45,7 +44,6 @@ namespace ccm * @tparam Integer The type of the number. * @param num An integral value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. - * @see https://en.cppreference.com/w/cpp/numeric/math/nearbyint */ template , bool> = true> constexpr double nearbyint(Integer num) noexcept @@ -55,7 +53,6 @@ namespace ccm * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. * @param num A float value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. - * @see https://en.cppreference.com/w/cpp/numeric/math/nearbyint */ constexpr float nearbyintf(float num) noexcept { return ccm::nearbyint(num); } @@ -64,7 +61,6 @@ namespace ccm * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. * @param num A long double value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. - * @see https://en.cppreference.com/w/cpp/numeric/math/nearbyint */ constexpr long double nearbyintl(long double num) noexcept { return ccm::nearbyint(num); } diff --git a/include/ccmath/math/nearest/rint.hpp b/include/ccmath/math/nearest/rint.hpp index 591bef51..77a91d74 100644 --- a/include/ccmath/math/nearest/rint.hpp +++ b/include/ccmath/math/nearest/rint.hpp @@ -29,7 +29,6 @@ namespace ccm * @tparam T The type of the number. * @param num A floating-point or integer value. * @return The rounded value. - * @see https://en.cppreference.com/w/cpp/numeric/math/rint */ template , bool> = true> constexpr T rint(T num) noexcept @@ -45,7 +44,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return num converted to double. - * @see https://en.cppreference.com/w/cpp/numeric/math/rint */ template , bool> = true> constexpr double rint(Integer num) noexcept @@ -55,7 +53,6 @@ namespace ccm * @brief Rounds a float to an integer value using the current rounding mode. * @param num Floating-point value. * @return Rounded value as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/rint */ constexpr float rintf(float num) noexcept { return ccm::rint(num); } @@ -64,7 +61,6 @@ namespace ccm * @brief Rounds a long double to an integer value using the current rounding mode. * @param num Floating-point value. * @return Rounded value as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/rint */ constexpr long double rintl(long double num) noexcept { return ccm::rint(num); } diff --git a/include/ccmath/math/nearest/round.hpp b/include/ccmath/math/nearest/round.hpp index 7b6b011c..169a7950 100644 --- a/include/ccmath/math/nearest/round.hpp +++ b/include/ccmath/math/nearest/round.hpp @@ -26,7 +26,6 @@ namespace ccm * @tparam T The type of the number. * @param num A floating-point or integer value. * @return The rounded value. - * @see https://en.cppreference.com/w/cpp/numeric/math/round */ template , bool> = true> constexpr T round(T num) noexcept @@ -52,7 +51,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Integer value. * @return num converted to double. - * @see https://en.cppreference.com/w/cpp/numeric/math/round */ template , bool> = true> constexpr double round(Integer num) noexcept @@ -62,7 +60,6 @@ namespace ccm * @brief Rounds a float to the nearest integer value, halfway cases away from zero. * @param num Floating-point value. * @return Rounded value as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/round */ constexpr float roundf(float num) noexcept { return ccm::round(num); } @@ -71,7 +68,6 @@ namespace ccm * @brief Rounds a double to the nearest integer value, halfway cases away from zero. * @param num Floating-point value. * @return Rounded value as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/round */ constexpr long double roundl(long double num) noexcept { return ccm::round(num); } diff --git a/include/ccmath/math/nearest/trunc.hpp b/include/ccmath/math/nearest/trunc.hpp index dcba1131..dff21968 100644 --- a/include/ccmath/math/nearest/trunc.hpp +++ b/include/ccmath/math/nearest/trunc.hpp @@ -23,7 +23,6 @@ namespace ccm * @tparam T The type of the input. * @param num The value to truncate. * @return Returns a truncated value. - * @see https://en.cppreference.com/w/cpp/numeric/math/trunc */ template , bool> = true> constexpr T trunc(T num) noexcept @@ -71,7 +70,6 @@ namespace ccm * @tparam Integer The type of the input. * @param num The value to truncate. * @return Returns a truncated value. - * @see https://en.cppreference.com/w/cpp/numeric/math/trunc */ template , bool> = true> constexpr double trunc(Integer num) noexcept @@ -81,7 +79,6 @@ namespace ccm * @brief Specialization for float that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to x. * @param num The float to truncate. * @return Returns a truncated float. - * @see https://en.cppreference.com/w/cpp/numeric/math/trunc */ constexpr float truncf(float num) noexcept { return ccm::trunc(num); } @@ -91,7 +88,6 @@ namespace ccm * x. * @param num The long double to truncate. * @return Returns a truncated long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/trunc */ constexpr long double truncl(long double num) noexcept { return ccm::trunc(num); } diff --git a/include/ccmath/math/power/cbrt.hpp b/include/ccmath/math/power/cbrt.hpp index eb4f5f88..e6191256 100644 --- a/include/ccmath/math/power/cbrt.hpp +++ b/include/ccmath/math/power/cbrt.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Cube root of num. - * @see https://en.cppreference.com/w/cpp/numeric/math/cbrt */ template , bool> = true> constexpr T cbrt(T num) noexcept @@ -41,7 +40,6 @@ namespace ccm * @brief Computes the cube root of a float. * @param num Floating-point value. * @return Cube root of num as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/cbrt */ constexpr float cbrtf(float num) noexcept { return ccm::cbrt(num); } @@ -50,7 +48,6 @@ namespace ccm * @brief Computes the cube root of a long double. * @param num Floating-point value. * @return Cube root of num as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/cbrt */ constexpr long double cbrtl(long double num) noexcept { return ccm::cbrt(num); } diff --git a/include/ccmath/math/power/hypot.hpp b/include/ccmath/math/power/hypot.hpp index 2d3c5ea9..ed5e55e5 100644 --- a/include/ccmath/math/power/hypot.hpp +++ b/include/ccmath/math/power/hypot.hpp @@ -25,7 +25,6 @@ namespace ccm * @param x First value. * @param y Second value. * @return sqrt(x*x + y*y) computed with scaling behavior suitable for floating-point range. - * @see https://en.cppreference.com/w/cpp/numeric/math/hypot */ template , bool> = true> constexpr T hypot(T x, T y) noexcept @@ -45,7 +44,6 @@ namespace ccm * @param y Second value. * @param z Third value. * @return sqrt(x*x + y*y + z*z) computed with the same scaling behavior as the 2-argument overload. - * @see https://en.cppreference.com/w/cpp/numeric/math/hypot */ constexpr float hypot(float x, float y, float z) noexcept { return ccm::hypot(ccm::hypot(x, y), z); } @@ -71,7 +69,6 @@ namespace ccm * @param x First value. * @param y Second value. * @return Hypotenuse length as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/hypot */ constexpr float hypotf(float x, float y) noexcept { return ccm::hypot(x, y); } @@ -81,7 +78,6 @@ namespace ccm * @param x First value. * @param y Second value. * @return Hypotenuse length as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/hypot */ constexpr long double hypotl(long double x, long double y) noexcept { return ccm::hypot(x, y); } diff --git a/include/ccmath/math/power/pow.hpp b/include/ccmath/math/power/pow.hpp index 4e30ff22..8f9cd3d4 100644 --- a/include/ccmath/math/power/pow.hpp +++ b/include/ccmath/math/power/pow.hpp @@ -45,7 +45,6 @@ namespace ccm * @param base Base value. * @param exp Exponent value. * @return base raised to exp. - * @see https://en.cppreference.com/w/cpp/numeric/math/pow */ template , bool> = true> constexpr T pow(T base, T exp) @@ -83,7 +82,6 @@ namespace ccm * @param base Base value. * @param exp Exponent value. * @return base raised to exp in the promoted floating-point type. - * @see https://en.cppreference.com/w/cpp/numeric/math/pow */ template (base, exp); } @@ -114,7 +111,6 @@ namespace ccm * @note Native long-double precision is available only when long double is x87 binary80. * On other platforms the default path evaluates pow in double precision and casts * the result to long double. That fallback is not native long-double accuracy yet. - * @see https://en.cppreference.com/w/cpp/numeric/math/pow */ constexpr long double powl(long double base, long double exp) { return ccm::pow(base, exp); } diff --git a/include/ccmath/math/power/sqrt.hpp b/include/ccmath/math/power/sqrt.hpp index 75a4aac3..e78b81bd 100644 --- a/include/ccmath/math/power/sqrt.hpp +++ b/include/ccmath/math/power/sqrt.hpp @@ -25,7 +25,6 @@ namespace ccm * @tparam T Floating-point type or integer type. * @param num Floating-point or integer number. * @return If no errors occur, square root of num (√num), is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/sqrt */ template , bool> = true> constexpr T sqrt(T num) @@ -43,7 +42,6 @@ namespace ccm * @tparam Integer Integer type. * @param num Integer number. * @return If no errors occur, square root of num (√num), is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/sqrt */ template , bool> = true> constexpr double sqrt(Integer num) @@ -53,7 +51,6 @@ namespace ccm * @brief Calculates the square root of a number. * @param num Floating-point number. * @return If no errors occur, square root of num (√num), is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/sqrt */ constexpr float sqrtf(float num) { return ccm::sqrt(num); } @@ -62,7 +59,6 @@ namespace ccm * @brief Calculates the square root of a number. * @param num Floating-point number. * @return If no errors occur, square root of num (√num), is returned. - * @see https://en.cppreference.com/w/cpp/numeric/math/sqrt */ constexpr long double sqrtl(long double num) { return ccm::sqrt(num); } diff --git a/include/ccmath/math/trig/acos.hpp b/include/ccmath/math/trig/acos.hpp index 5d04147f..8f59448c 100644 --- a/include/ccmath/math/trig/acos.hpp +++ b/include/ccmath/math/trig/acos.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value, typically in [-1, 1]. * @return Inverse cosine of num in radians. - * @see https://en.cppreference.com/w/cpp/numeric/math/acos */ template , bool> = true> constexpr T acos(T num) @@ -50,7 +49,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Input value. * @return Inverse cosine in radians as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/acos */ template , bool> = true> constexpr double acos(Integer num) @@ -60,7 +58,6 @@ namespace ccm * @brief Computes inverse cosine for float. * @param num Input value. * @return Inverse cosine in radians as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/acos */ constexpr float acosf(float num) { return ccm::acos(num); } @@ -69,7 +66,6 @@ namespace ccm * @brief Computes inverse cosine for long double. * @param num Input value. * @return Inverse cosine in radians as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/acos */ constexpr long double acosl(long double num) { return ccm::acos(num); } diff --git a/include/ccmath/math/trig/asin.hpp b/include/ccmath/math/trig/asin.hpp index 3a736e2e..e959c710 100644 --- a/include/ccmath/math/trig/asin.hpp +++ b/include/ccmath/math/trig/asin.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value, typically in [-1, 1]. * @return Inverse sine of num in radians. - * @see https://en.cppreference.com/w/cpp/numeric/math/asin */ template , bool> = true> constexpr T asin(T num) @@ -50,7 +49,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Input value. * @return Inverse sine in radians as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/asin */ template , bool> = true> constexpr double asin(Integer num) @@ -60,7 +58,6 @@ namespace ccm * @brief Computes inverse sine for float. * @param num Input value. * @return Inverse sine in radians as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/asin */ constexpr float asinf(float num) { return ccm::asin(num); } @@ -69,7 +66,6 @@ namespace ccm * @brief Computes inverse sine for long double. * @param num Input value. * @return Inverse sine in radians as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/asin */ constexpr long double asinl(long double num) { return ccm::asin(num); } diff --git a/include/ccmath/math/trig/atan.hpp b/include/ccmath/math/trig/atan.hpp index 83dc1379..906ef43a 100644 --- a/include/ccmath/math/trig/atan.hpp +++ b/include/ccmath/math/trig/atan.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Floating-point value. * @return Inverse tangent of num in radians. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan */ template , bool> = true> constexpr T atan(T num) @@ -50,7 +49,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Input value. * @return Inverse tangent in radians as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan */ template , bool> = true> constexpr double atan(Integer num) @@ -60,7 +58,6 @@ namespace ccm * @brief Computes inverse tangent for float. * @param num Input value. * @return Inverse tangent in radians as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan */ constexpr float atanf(float num) { return ccm::atan(num); } @@ -69,7 +66,6 @@ namespace ccm * @brief Computes inverse tangent for long double. * @param num Input value. * @return Inverse tangent in radians as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan */ constexpr long double atanl(long double num) { return ccm::atan(num); } diff --git a/include/ccmath/math/trig/atan2.hpp b/include/ccmath/math/trig/atan2.hpp index de4598c1..c88dd370 100644 --- a/include/ccmath/math/trig/atan2.hpp +++ b/include/ccmath/math/trig/atan2.hpp @@ -25,7 +25,6 @@ namespace ccm * @param y Numerator component. * @param x Denominator component. * @return Angle in radians in the correct quadrant. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan2 */ template , bool> = true> constexpr T atan2(T y, T x) @@ -51,7 +50,6 @@ namespace ccm * @param y Numerator component. * @param x Denominator component. * @return Angle in radians as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan2 */ constexpr float atan2f(float y, float x) { return ccm::atan2(y, x); } @@ -61,7 +59,6 @@ namespace ccm * @param y Numerator component. * @param x Denominator component. * @return Angle in radians as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/atan2 */ constexpr long double atan2l(long double y, long double x) { return ccm::atan2(y, x); } diff --git a/include/ccmath/math/trig/cos.hpp b/include/ccmath/math/trig/cos.hpp index 84c5553b..37a070f8 100644 --- a/include/ccmath/math/trig/cos.hpp +++ b/include/ccmath/math/trig/cos.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Angle in radians. * @return Cosine of num. - * @see https://en.cppreference.com/w/cpp/numeric/math/cos */ template , bool> = true> constexpr T cos(T num) @@ -42,7 +41,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Angle in radians. * @return Cosine of num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/cos */ template , bool> = true> constexpr double cos(Integer num) @@ -52,7 +50,6 @@ namespace ccm * @brief Computes the cosine of a float angle in radians. * @param num Angle in radians. * @return Cosine as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/cos */ constexpr float cosf(float num) { return ccm::cos(num); } @@ -61,7 +58,6 @@ namespace ccm * @brief Computes the cosine of a long double angle in radians. * @param num Angle in radians. * @return Cosine as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/cos */ constexpr long double cosl(long double num) { return ccm::cos(num); } diff --git a/include/ccmath/math/trig/sin.hpp b/include/ccmath/math/trig/sin.hpp index 387e41b8..ffd50ed2 100644 --- a/include/ccmath/math/trig/sin.hpp +++ b/include/ccmath/math/trig/sin.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Angle in radians. * @return Sine of num. - * @see https://en.cppreference.com/w/cpp/numeric/math/sin */ template , bool> = true> constexpr T sin(T num) @@ -42,7 +41,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Angle in radians. * @return Sine of num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/sin */ template , bool> = true> constexpr double sin(Integer num) @@ -52,7 +50,6 @@ namespace ccm * @brief Computes the sine of a float angle in radians. * @param num Angle in radians. * @return Sine as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/sin */ constexpr float sinf(float num) { return ccm::sin(num); } @@ -61,7 +58,6 @@ namespace ccm * @brief Computes the sine of a long double angle in radians. * @param num Angle in radians. * @return Sine as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/sin */ constexpr long double sinl(long double num) { return ccm::sin(num); } diff --git a/include/ccmath/math/trig/tan.hpp b/include/ccmath/math/trig/tan.hpp index 94925313..63231089 100644 --- a/include/ccmath/math/trig/tan.hpp +++ b/include/ccmath/math/trig/tan.hpp @@ -24,7 +24,6 @@ namespace ccm * @tparam T Floating-point type. * @param num Angle in radians. * @return Tangent of num. - * @see https://en.cppreference.com/w/cpp/numeric/math/tan */ template , bool> = true> constexpr T tan(T num) @@ -42,7 +41,6 @@ namespace ccm * @tparam Integer Integral type. * @param num Angle in radians. * @return Tangent of num as double. - * @see https://en.cppreference.com/w/cpp/numeric/math/tan */ template , bool> = true> constexpr double tan(Integer num) @@ -52,7 +50,6 @@ namespace ccm * @brief Computes the tangent of a float angle in radians. * @param num Angle in radians. * @return Tangent as float. - * @see https://en.cppreference.com/w/cpp/numeric/math/tan */ constexpr float tanf(float num) { return ccm::tan(num); } @@ -61,7 +58,6 @@ namespace ccm * @brief Computes the tangent of a long double angle in radians. * @param num Angle in radians. * @return Tangent as long double. - * @see https://en.cppreference.com/w/cpp/numeric/math/tan */ constexpr long double tanl(long double num) { return ccm::tan(num); } From d0ba1691544609a25d83465a46b8326b88ebb9c2 Mon Sep 17 00:00:00 2001 From: ianpike Date: Thu, 18 Jun 2026 15:12:25 -0400 Subject: [PATCH 35/52] Support hosts without cfenv and add a fenv-disable flag --- cmake/config/BuildManifest.cmake | 9 + cmake/gen/meson.build | 3 + .../generic/func/fmanip/impl/ilogb_impl.hpp | 2 +- .../generic/func/fmanip/impl/logb_impl.hpp | 3 +- .../internal/support/fenv/CMakeLists.txt | 1 + .../internal/support/fenv/fenv_support.hpp | 87 ++------ .../internal/support/fenv/host_fenv.hpp | 208 ++++++++++++++++++ .../internal/support/fenv/rounding_mode.hpp | 12 +- include/ccmath/internal/support/fp/cast.hpp | 2 +- .../support/fp/directional_rounding_utils.hpp | 2 +- .../support/fp/except_value_utils.hpp | 2 +- include/ccmath/internal/support/fp/fma.hpp | 2 +- .../support/helpers/internal_ldexp.hpp | 3 +- .../ccmath/internal/types/dyadic_float.hpp | 2 +- .../ccmath/math/expo/impl/exp2_float_impl.hpp | 2 +- .../math/misc/impl/gamma_double_impl.hpp | 2 +- .../math/misc/impl/gamma_float_impl.hpp | 3 +- .../math/misc/impl/lgamma_double_impl.hpp | 3 +- include/ccmath/math/nearest/rint.hpp | 2 +- .../ccmath/math/trig/impl/inv_trig_impl.hpp | 2 +- .../math/trig/impl/sincos_double_impl.hpp | 2 +- .../math/trig/impl/sincos_float_impl.hpp | 2 +- meson_options.txt | 1 + premake5.lua | 11 + tests/conformance/fenv_disabled_test.cpp | 60 +++++ tests/rigorous/CMakeLists.txt | 5 + 26 files changed, 336 insertions(+), 97 deletions(-) create mode 100644 include/ccmath/internal/support/fenv/host_fenv.hpp create mode 100644 tests/conformance/fenv_disabled_test.cpp diff --git a/cmake/config/BuildManifest.cmake b/cmake/config/BuildManifest.cmake index faa50673..af89f100 100644 --- a/cmake/config/BuildManifest.cmake +++ b/cmake/config/BuildManifest.cmake @@ -12,6 +12,7 @@ set(CCMATH_LIBRARY_MANIFEST_VERSION_TEMPLATE cmake/in/version.hpp.in) set(CCMATH_LIBRARY_MANIFEST_OPTION_KEYS runtime_simd disable_errno + disable_fenv reduced_precision_powl deterministic ) @@ -32,6 +33,14 @@ set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_DEFINE CCM_CONFIG_DISABLE_ERRNO set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_DESCRIPTION "Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)") +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_VAR CCMATH_DISABLE_FENV) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_MESON_OPTION disable_fenv) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DEFINE CCM_CONFIG_DISABLE_FENV) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DESCRIPTION + "Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)") + set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_VAR CCMATH_DISABLE_REDUCED_PRECISION_POWL) set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_DEFAULT OFF) set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_INVERT FALSE) diff --git a/cmake/gen/meson.build b/cmake/gen/meson.build index 44c8cefc..ca8d7e38 100644 --- a/cmake/gen/meson.build +++ b/cmake/gen/meson.build @@ -24,6 +24,9 @@ endif if get_option('disable_errno') _ccmath_defines += '-DCCM_CONFIG_DISABLE_ERRNO' endif +if get_option('disable_fenv') + _ccmath_defines += '-DCCM_CONFIG_DISABLE_FENV' +endif if get_option('disable_reduced_precision_powl') _ccmath_defines += '-DCCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL' endif diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp index ffd30100..1e04a0ef 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp @@ -13,10 +13,10 @@ #include "ccmath/internal/math/generic/func/fmanip/impl/frexp_impl.hpp" #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include -#include #include #include diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp index d9bde56d..95637c2d 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp @@ -13,10 +13,9 @@ #include "ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp" #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" -#include - namespace ccm::internal::impl { template diff --git a/include/ccmath/internal/support/fenv/CMakeLists.txt b/include/ccmath/internal/support/fenv/CMakeLists.txt index ce66ef44..cafd6f72 100644 --- a/include/ccmath/internal/support/fenv/CMakeLists.txt +++ b/include/ccmath/internal/support/fenv/CMakeLists.txt @@ -1,5 +1,6 @@ ccm_add_headers( fenv_support.hpp + host_fenv.hpp rounding_mode.hpp ) diff --git a/include/ccmath/internal/support/fenv/fenv_support.hpp b/include/ccmath/internal/support/fenv/fenv_support.hpp index 6590cbeb..8aee39e8 100644 --- a/include/ccmath/internal/support/fenv/fenv_support.hpp +++ b/include/ccmath/internal/support/fenv/fenv_support.hpp @@ -14,11 +14,11 @@ #pragma once #include "ccmath/internal/predef/attributes/never_inline.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" #include -#include #include #if defined(_MSC_VER) && !defined(__clang__) @@ -51,69 +51,6 @@ namespace ccm::support::fenv::detail } } // namespace ccm::support::fenv::detail -namespace ccm::support::fenv::internal -{ - inline int clear_except(const int err_code) - { return std::feclearexcept(err_code); } - - inline int test_except(const int err_code) - { return std::fetestexcept(err_code); } - - inline int get_except() - { - // Only GNU-based compilers support this function. -#ifdef __USE_GNU - return ::fegetexcept(); -#else - return 0; -#endif - } - - inline int set_except([[maybe_unused]] int err_code) - { - // Only GNU-based compilers support this function. -#ifdef __USE_GNU - return ::fesetexcept(err_code); -#else - return 0; -#endif - } - - inline int raise_except(const int err_code) - { return std::feraiseexcept(err_code); } - - inline int enable_except([[maybe_unused]] int err_code) - { - // Only GNU-based compilers support this function. -#ifdef __USE_GNU - return ::feenableexcept(err_code); -#else - return 0; -#endif - } - inline int disable_except([[maybe_unused]] int err_code) - { -// Only GNU-based compilers support this function. -#ifdef __USE_GNU - return ::fedisableexcept(err_code); -#else - return 0; -#endif - } - - inline int get_round() - { return get_rounding_mode(); } - - inline int set_round(const int rounding_mode) - { return std::fesetround(rounding_mode); } - - inline int get_env(std::fenv_t *envp) - { return std::fegetenv(envp); } - - inline int set_env(const std::fenv_t *envp) - { return std::fesetenv(envp); } -} // namespace ccm::support::fenv::internal - namespace ccm::support::fenv { enum class ccm_math_err_mode : std::uint8_t @@ -139,12 +76,20 @@ namespace ccm::support::fenv { #if defined(__FAST_MATH__) || defined(CCM_CONFIG_DISABLE_ERRNO) return 0; -#elif defined(__NO_MATH_ERRNO__) - return get_mode(ccm_math_err_mode::eErrnoExcept); -#elif defined(__NVPTX__) || defined(__AMDGPU__) - return get_mode(ccm_math_err_mode::eErrno); #else - return get_mode(ccm_math_err_mode::eErrno) | get_mode(ccm_math_err_mode::eErrnoExcept); + #if defined(__NO_MATH_ERRNO__) + int mode = get_mode(ccm_math_err_mode::eErrnoExcept); + #elif defined(__NVPTX__) || defined(__AMDGPU__) + int mode = get_mode(ccm_math_err_mode::eErrno); + #else + int mode = get_mode(ccm_math_err_mode::eErrno) | get_mode(ccm_math_err_mode::eErrnoExcept); + #endif + #if !defined(CCM_CONFIG_HAS_FENV) + // No host floating-point environment, so fp-exception signaling is not available. errno + // writes stay on, since they go through and do not need the fenv. + mode &= ~get_mode(ccm_math_err_mode::eErrnoExcept); + #endif + return mode; #endif } @@ -155,7 +100,7 @@ namespace ccm::support::fenv if constexpr (is_errno_enabled()) { - if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return internal::set_except(excepts); } + if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return host::set_except(excepts); } } // ReSharper disable once CppDFAUnreachableCode // This is unreachable code if the above constexpr if statement is true which is desired. return 0; @@ -166,7 +111,7 @@ namespace ccm::support::fenv if (is_constant_evaluated()) { return 0; } // We cannot raise fenv exceptions in a constexpr context. So we return. if constexpr (is_errno_enabled()) { - if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return internal::raise_except(excepts); } + if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return host::raise_except(excepts); } } // ReSharper disable once CppDFAUnreachableCode // This is unreachable code if the above constexpr if statement is true which is desired. return 0; diff --git a/include/ccmath/internal/support/fenv/host_fenv.hpp b/include/ccmath/internal/support/fenv/host_fenv.hpp new file mode 100644 index 00000000..ad16d3b9 --- /dev/null +++ b/include/ccmath/internal/support/fenv/host_fenv.hpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +// Single point of truth for whether ccmath can use the host floating-point environment. +// +// Most hosts (GCC, Clang, MSVC, Intel and the like) ship the C++ header and +// CCM_CONFIG_HAS_FENV is defined. Some freestanding or console toolchains (the PS5 / Prospero SDK, +// for one) ship no . When that happens ccmath falls back to the C header if it is +// present, which keeps the runtime rounding-mode reads and exception signaling working. Only when +// neither header exists does ccmath assume round-to-nearest at runtime with no +// floating-point-exception signaling. +// +// Define CCM_CONFIG_DISABLE_FENV to force that last fallback on a host that does have the headers. +// It completely disables the host floating-point environment and drops every / +// include from the library. The directed rounding modes then degrade to round-to-nearest at runtime, +// which is non-standard, the same tradeoff CCM_CONFIG_DISABLE_ERRNO makes for errno. Define +// CCM_CONFIG_FORCE_FENV_H to exercise the tier on a host that also has (testing +// only). +// +// The detection rules out a missing __has_include first so that __has_include(...) is only used +// once the compiler is known to provide it, which keeps every supported compiler (including MSVC) +// happy. + +#if defined(CCM_CONFIG_DISABLE_FENV) +// Forced off: leave CCM_CONFIG_HAS_FENV undefined so the no-fenv tier below is selected. +#elif !defined(__has_include) + // A pre-C++17 compiler with no __has_include. Assume the standard header is present. + #define CCM_CONFIG_HAS_FENV 1 + #define CCM_CONFIG_FENV_USE_CFENV 1 +#elif defined(CCM_CONFIG_FORCE_FENV_H) + #if __has_include() + #define CCM_CONFIG_HAS_FENV 1 + #define CCM_CONFIG_FENV_USE_FENV_H 1 + #endif +#elif __has_include() + #define CCM_CONFIG_HAS_FENV 1 + #define CCM_CONFIG_FENV_USE_CFENV 1 +#elif __has_include() + #define CCM_CONFIG_HAS_FENV 1 + #define CCM_CONFIG_FENV_USE_FENV_H 1 +#endif + +#if defined(CCM_CONFIG_FENV_USE_CFENV) + #include +#elif defined(CCM_CONFIG_FENV_USE_FENV_H) + #include +#else + // No host floating-point environment. Provide fallback FE_* constants so the call sites still + // compile. The values are not handed to a host fenv call in this mode, but they have to stay + // distinct so that switches on the rounding mode keep working. The values match the common x86 + // glibc layout for the sake of familiarity. + #ifndef FE_TONEAREST + #define FE_TONEAREST 0x0000 + #endif + #ifndef FE_DOWNWARD + #define FE_DOWNWARD 0x0400 + #endif + #ifndef FE_UPWARD + #define FE_UPWARD 0x0800 + #endif + #ifndef FE_TOWARDZERO + #define FE_TOWARDZERO 0x0C00 + #endif + #ifndef FE_INVALID + #define FE_INVALID 0x01 + #endif + #ifndef FE_DIVBYZERO + #define FE_DIVBYZERO 0x04 + #endif + #ifndef FE_OVERFLOW + #define FE_OVERFLOW 0x08 + #endif + #ifndef FE_UNDERFLOW + #define FE_UNDERFLOW 0x10 + #endif + #ifndef FE_INEXACT + #define FE_INEXACT 0x20 + #endif + #ifndef FE_ALL_EXCEPT + #define FE_ALL_EXCEPT (FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT) + #endif +#endif + +// Thin wrappers over the host floating-point environment. This is the only place in ccmath that +// names the host fenv functions, so every consumer reaches them through here regardless of which +// tier resolved above. +namespace ccm::support::fenv::host +{ +#if defined(CCM_CONFIG_HAS_FENV) + + #if defined(CCM_CONFIG_FENV_USE_CFENV) + #define CCM_FENV_HOST_QUAL std + #else + #define CCM_FENV_HOST_QUAL + #endif + + using fenv_t = CCM_FENV_HOST_QUAL ::fenv_t; + + inline int get_round() + { return CCM_FENV_HOST_QUAL ::fegetround(); } + + inline int set_round(const int rounding_mode) + { return CCM_FENV_HOST_QUAL ::fesetround(rounding_mode); } + + inline int raise_except(const int err_code) + { return CCM_FENV_HOST_QUAL ::feraiseexcept(err_code); } + + inline int clear_except(const int err_code) + { return CCM_FENV_HOST_QUAL ::feclearexcept(err_code); } + + inline int test_except(const int err_code) + { return CCM_FENV_HOST_QUAL ::fetestexcept(err_code); } + + inline int get_env(fenv_t * envp) + { return CCM_FENV_HOST_QUAL ::fegetenv(envp); } + + inline int set_env(const fenv_t * envp) + { return CCM_FENV_HOST_QUAL ::fesetenv(envp); } + + #undef CCM_FENV_HOST_QUAL + + // fesetexcept, fegetexcept, feenableexcept and fedisableexcept are GNU extensions that live in + // the global namespace, so they are always called unqualified and guarded on __USE_GNU. Other + // toolchains (MSVC included) return zero from these instead. + inline int set_except([[maybe_unused]] int err_code) + { + #ifdef __USE_GNU + return ::fesetexcept(err_code); + #else + return 0; + #endif + } + + inline int get_except() + { + #ifdef __USE_GNU + return ::fegetexcept(); + #else + return 0; + #endif + } + + inline int enable_except([[maybe_unused]] int err_code) + { + #ifdef __USE_GNU + return ::feenableexcept(err_code); + #else + return 0; + #endif + } + + inline int disable_except([[maybe_unused]] int err_code) + { + #ifdef __USE_GNU + return ::fedisableexcept(err_code); + #else + return 0; + #endif + } + +#else // No host floating-point environment. Report round-to-nearest and make every write a no-op. + + using fenv_t = int; + + inline int get_round() + { return FE_TONEAREST; } + + inline int set_round([[maybe_unused]] int rounding_mode) + { return 0; } + + inline int raise_except([[maybe_unused]] int err_code) + { return 0; } + + inline int clear_except([[maybe_unused]] int err_code) + { return 0; } + + inline int test_except([[maybe_unused]] int err_code) + { return 0; } + + inline int get_env([[maybe_unused]] fenv_t * envp) + { return 0; } + + inline int set_env([[maybe_unused]] const fenv_t * envp) + { return 0; } + + inline int set_except([[maybe_unused]] int err_code) + { return 0; } + + inline int get_except() + { return 0; } + + inline int enable_except([[maybe_unused]] int err_code) + { return 0; } + + inline int disable_except([[maybe_unused]] int err_code) + { return 0; } + +#endif +} // namespace ccm::support::fenv::host diff --git a/include/ccmath/internal/support/fenv/rounding_mode.hpp b/include/ccmath/internal/support/fenv/rounding_mode.hpp index f568af37..d817212e 100644 --- a/include/ccmath/internal/support/fenv/rounding_mode.hpp +++ b/include/ccmath/internal/support/fenv/rounding_mode.hpp @@ -10,9 +10,9 @@ #pragma once +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" -#include #include // Rounding mode assumed during constant evaluation. The C++ constant evaluator always performs @@ -35,19 +35,19 @@ namespace ccm::support::fenv namespace internal { inline bool rt_rounding_mode_is_round_up() - { return std::fegetround() == FE_UPWARD; } + { return host::get_round() == FE_UPWARD; } inline bool rt_rounding_mode_is_round_down() - { return std::fegetround() == FE_DOWNWARD; } + { return host::get_round() == FE_DOWNWARD; } inline bool rt_rounding_mode_is_round_to_nearest() - { return std::fegetround() == FE_TONEAREST; } + { return host::get_round() == FE_TONEAREST; } inline bool rt_rounding_mode_is_round_to_zero() - { return std::fegetround() == FE_TOWARDZERO; } + { return host::get_round() == FE_TOWARDZERO; } inline int rt_get_rounding_mode() - { return std::fegetround(); } + { return host::get_round(); } } // namespace internal /** diff --git a/include/ccmath/internal/support/fp/cast.hpp b/include/ccmath/internal/support/fp/cast.hpp index 4e015f64..4a4bca55 100644 --- a/include/ccmath/internal/support/fp/cast.hpp +++ b/include/ccmath/internal/support/fp/cast.hpp @@ -17,11 +17,11 @@ #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/type_traits.hpp" #include "ccmath/internal/types/dyadic_float.hpp" -#include #include namespace ccm::support::fp diff --git a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp index 740dc0b4..b2fcd046 100644 --- a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp +++ b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp @@ -10,10 +10,10 @@ #pragma once +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "fp_bits.hpp" -#include #include #include diff --git a/include/ccmath/internal/support/fp/except_value_utils.hpp b/include/ccmath/internal/support/fp/except_value_utils.hpp index 0159b8fb..62fb846f 100644 --- a/include/ccmath/internal/support/fp/except_value_utils.hpp +++ b/include/ccmath/internal/support/fp/except_value_utils.hpp @@ -16,13 +16,13 @@ #pragma once #include "ccmath/internal/predef/unlikely.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" #include "ccmath/internal/support/type_traits.hpp" #include -#include #include namespace ccm::support::fp diff --git a/include/ccmath/internal/support/fp/fma.hpp b/include/ccmath/internal/support/fp/fma.hpp index ebef73db..ddb935b4 100644 --- a/include/ccmath/internal/support/fp/fma.hpp +++ b/include/ccmath/internal/support/fp/fma.hpp @@ -14,6 +14,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" @@ -21,7 +22,6 @@ #include "ccmath/internal/types/dyadic_float.hpp" #include "ccmath/internal/types/sign.hpp" -#include #include #include #include diff --git a/include/ccmath/internal/support/helpers/internal_ldexp.hpp b/include/ccmath/internal/support/helpers/internal_ldexp.hpp index d79657fa..ecb3f4bd 100644 --- a/include/ccmath/internal/support/helpers/internal_ldexp.hpp +++ b/include/ccmath/internal/support/helpers/internal_ldexp.hpp @@ -17,12 +17,11 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/types/normalized_float.hpp" -#include - namespace ccm::support::helpers { template diff --git a/include/ccmath/internal/types/dyadic_float.hpp b/include/ccmath/internal/types/dyadic_float.hpp index 2ceb7b3c..941dfa63 100644 --- a/include/ccmath/internal/types/dyadic_float.hpp +++ b/include/ccmath/internal/types/dyadic_float.hpp @@ -33,6 +33,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fenv/rounding_mode.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/multiply_add.hpp" @@ -41,7 +42,6 @@ #include "ccmath/internal/types/big_int.hpp" #include -#include #include #include diff --git a/include/ccmath/math/expo/impl/exp2_float_impl.hpp b/include/ccmath/math/expo/impl/exp2_float_impl.hpp index 0ecbccca..ed3d2d7a 100644 --- a/include/ccmath/math/expo/impl/exp2_float_impl.hpp +++ b/include/ccmath/math/expo/impl/exp2_float_impl.hpp @@ -14,12 +14,12 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/types/fp_types.hpp" #include "ccmath/math/expo/impl/exp2_data.hpp" #include -#include #include #include diff --git a/include/ccmath/math/misc/impl/gamma_double_impl.hpp b/include/ccmath/math/misc/impl/gamma_double_impl.hpp index 6a0f98a3..978124e1 100644 --- a/include/ccmath/math/misc/impl/gamma_double_impl.hpp +++ b/include/ccmath/math/misc/impl/gamma_double_impl.hpp @@ -18,13 +18,13 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/fp/nearest_integer.hpp" #include "ccmath/internal/support/multiply_add.hpp" #include "ccmath/math/misc/impl/gamma_data.hpp" #include "ccmath/math/nearest/floor.hpp" -#include #include namespace ccm::internal::impl diff --git a/include/ccmath/math/misc/impl/gamma_float_impl.hpp b/include/ccmath/math/misc/impl/gamma_float_impl.hpp index aa8c6317..f7800cc2 100644 --- a/include/ccmath/math/misc/impl/gamma_float_impl.hpp +++ b/include/ccmath/math/misc/impl/gamma_float_impl.hpp @@ -17,12 +17,11 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/math/misc/impl/gamma_double_impl.hpp" #include "ccmath/math/nearest/floor.hpp" -#include - namespace ccm::internal::impl { constexpr float gamma_float_impl(float x) noexcept diff --git a/include/ccmath/math/misc/impl/lgamma_double_impl.hpp b/include/ccmath/math/misc/impl/lgamma_double_impl.hpp index 8f95c2a5..237bb9af 100644 --- a/include/ccmath/math/misc/impl/lgamma_double_impl.hpp +++ b/include/ccmath/math/misc/impl/lgamma_double_impl.hpp @@ -17,6 +17,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/math/basic/fabs.hpp" #include "ccmath/math/expo/log.hpp" @@ -26,8 +27,6 @@ #include "ccmath/math/numbers.hpp" #include "ccmath/math/trig/sin.hpp" -#include - namespace ccm::internal::impl { namespace lgamma_detail diff --git a/include/ccmath/math/nearest/rint.hpp b/include/ccmath/math/nearest/rint.hpp index 77a91d74..5f6bec82 100644 --- a/include/ccmath/math/nearest/rint.hpp +++ b/include/ccmath/math/nearest/rint.hpp @@ -13,13 +13,13 @@ #include "ccmath/internal/math/runtime/func/nearest/rint_rt.hpp" #include +#include #include #include #include #include #include -#include #include namespace ccm diff --git a/include/ccmath/math/trig/impl/inv_trig_impl.hpp b/include/ccmath/math/trig/impl/inv_trig_impl.hpp index e6e36961..51f48951 100644 --- a/include/ccmath/math/trig/impl/inv_trig_impl.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_impl.hpp @@ -14,6 +14,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/multiply_add.hpp" #include "ccmath/internal/support/poly_eval.hpp" @@ -25,7 +26,6 @@ #include "ccmath/math/trig/impl/inv_trig_data.hpp" #include -#include #include namespace ccm::internal::impl diff --git a/include/ccmath/math/trig/impl/sincos_double_impl.hpp b/include/ccmath/math/trig/impl/sincos_double_impl.hpp index 4413451b..684e4c82 100644 --- a/include/ccmath/math/trig/impl/sincos_double_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_impl.hpp @@ -15,6 +15,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/fp/nearest_integer.hpp" #include "ccmath/internal/support/multiply_add.hpp" @@ -22,7 +23,6 @@ #include "ccmath/math/trig/impl/sincos_payne_hanek.hpp" #include -#include #include namespace ccm::internal::impl diff --git a/include/ccmath/math/trig/impl/sincos_float_impl.hpp b/include/ccmath/math/trig/impl/sincos_float_impl.hpp index f1c83a23..fc1d5b4d 100644 --- a/include/ccmath/math/trig/impl/sincos_float_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_impl.hpp @@ -15,6 +15,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/bits.hpp" #include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/fp/nearest_integer.hpp" #include "ccmath/internal/support/multiply_add.hpp" @@ -22,7 +23,6 @@ #include "ccmath/math/trig/impl/sincos_payne_hanek.hpp" #include -#include #include namespace ccm::internal::impl diff --git a/meson_options.txt b/meson_options.txt index 69990f63..83578db3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,5 +3,6 @@ option('enable_runtime_simd', type: 'boolean', value: true, description: 'Enable SIMD optimization for runtime evaluation (does not affect compile-time)') option('disable_errno', type: 'boolean', value: false, description: 'Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)') +option('disable_fenv', type: 'boolean', value: false, description: 'Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)') option('disable_reduced_precision_powl', type: 'boolean', value: false, description: 'Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback') option('deterministic', type: 'boolean', value: false, description: 'Produce bit-identical cross-hardware math: route transcendentals through the generic kernels (no libm), force the correctly-rounded FMA path, disable runtime SIMD, and evaluate long double in double precision (GCC/Clang)') diff --git a/premake5.lua b/premake5.lua index 05a3d5a2..b0dcc190 100644 --- a/premake5.lua +++ b/premake5.lua @@ -28,6 +28,13 @@ newoption { default = "false", } +newoption { + trigger = "ccmath-disable-fenv", + description = "Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)", + allowed = { { "true", "Enable" }, { "false", "Disable" } }, + default = "false", +} + newoption { trigger = "ccmath-disable-reduced-precision-powl", description = "Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback", @@ -115,6 +122,10 @@ function ccmath.defines() table.insert(defs, "CCM_CONFIG_DISABLE_ERRNO") end + if _ccmath_option_enabled("ccmath-disable-fenv", false) then + table.insert(defs, "CCM_CONFIG_DISABLE_FENV") + end + if _ccmath_option_enabled("ccmath-disable-reduced-precision-powl", false) then table.insert(defs, "CCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL") end diff --git a/tests/conformance/fenv_disabled_test.cpp b/tests/conformance/fenv_disabled_test.cpp new file mode 100644 index 00000000..a06bd5d0 --- /dev/null +++ b/tests/conformance/fenv_disabled_test.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +// Built with CCM_CONFIG_DISABLE_FENV so that host_fenv.hpp resolves to its no-fenv tier, the same +// state a host with no and no (such as the PS5 toolchain) ends up in. The suite +// confirms that the headers compile without the host fenv and that the documented fallback contract +// holds: runtime rounding pinned to round-to-nearest, fp-exception signaling reduced to a no-op, and +// errno handling still compilable. + +#include "ccmath/internal/support/fenv/fenv_support.hpp" +#include "ccmath/internal/support/fenv/host_fenv.hpp" +#include "ccmath/internal/support/fenv/rounding_mode.hpp" +#include "ccmath/math/nearest/rint.hpp" + +#include + +#if defined(CCM_CONFIG_HAS_FENV) + #error "fenv_disabled_test must be built with CCM_CONFIG_DISABLE_FENV so no host fenv is selected" +#endif + +namespace fenv = ccm::support::fenv; + +TEST(CcmathFenvDisabledTests, RoundingModeIsPinnedToNearest) +{ + // Without a host fenv the runtime read cannot observe the hardware mode, so every query helper + // reports the round-to-nearest default. The volatile sink keeps the call on the runtime read + // rather than letting the constant evaluator fold it. + volatile int rm = 0; + rm = fenv::get_rounding_mode(); + EXPECT_EQ(static_cast(rm), FE_TONEAREST); + + EXPECT_TRUE(fenv::rounding_mode_is_round_to_nearest()); + EXPECT_FALSE(fenv::rounding_mode_is_round_up()); + EXPECT_FALSE(fenv::rounding_mode_is_round_down()); + EXPECT_FALSE(fenv::rounding_mode_is_round_to_zero()); +} + +TEST(CcmathFenvDisabledTests, ExceptionSignalingIsNoOp) +{ + // The fp-exception entry points still exist and compile, but they do nothing, and the error + // handling mask does not carry the exception bit when the host fenv is unavailable. + EXPECT_EQ(fenv::raise_except_if_required(FE_OVERFLOW | FE_INVALID), 0); + EXPECT_EQ(fenv::set_except_if_required(FE_INEXACT), 0); + EXPECT_EQ(fenv::ccm_math_err_handling() & fenv::get_mode(fenv::ccm_math_err_mode::eErrnoExcept), 0); +} + +TEST(CcmathFenvDisabledTests, RuntimeMathEvaluatesUnderNearest) +{ + // A function that reads the rounding mode must still evaluate and produce the round-to-nearest, + // ties-to-even result. + const volatile double x = 2.5; + EXPECT_EQ(ccm::rint(static_cast(x)), 2.0); +} diff --git a/tests/rigorous/CMakeLists.txt b/tests/rigorous/CMakeLists.txt index ac6ee73d..4efda3bd 100644 --- a/tests/rigorous/CMakeLists.txt +++ b/tests/rigorous/CMakeLists.txt @@ -6,6 +6,7 @@ file(MAKE_DIRECTORY ${CCMATH_ORACLE_LOG_DIR}) ccmath_add_conformance_suite(ccmath-rigorous-conformance APPLY_POW_VALIDATION EXCLUDE + conformance/fenv_disabled_test.cpp conformance/fma_software_fenv_test.cpp conformance/powl_platform_test.cpp) @@ -13,6 +14,10 @@ ccmath_add_rigorous_gtest_suite(ccmath-rigorous-fma-software SOURCES ../conformance/fma_software_fenv_test.cpp COMPILE_DEFINITIONS CCM_CONFIG_DISABLE_RUNTIME_BUILTIN_FMA) +ccmath_add_rigorous_gtest_suite(ccmath-rigorous-fenv-disabled + SOURCES ../conformance/fenv_disabled_test.cpp + COMPILE_DEFINITIONS CCM_CONFIG_DISABLE_FENV) + ccmath_add_rigorous_module_suite(ccmath-rigorous-power-boundaries MODULE power APPLY_POW_VALIDATION From ad11be2065716a830715d1fc4a667630f8f4c67d Mon Sep 17 00:00:00 2001 From: ianpike Date: Thu, 18 Jun 2026 17:30:12 -0400 Subject: [PATCH 36/52] Improvements to documentation --- CONTRIBUTING.md | 279 +++++++++++-------------- README.md | 379 +++++++++++----------------------- tests/shared/oracle/README.md | 34 +++ 3 files changed, 269 insertions(+), 423 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a7704a8..62e8566a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,77 +4,122 @@ For help understanding how to implement elementary functions in CCMath, see [Approximating Functions: A Practical Guide to Sollya and Function Implementation](docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf). PRs go to the `main` branch. Bug reports and design questions belong in -[Issues](https://github.com/Rinzii/ccmath/issues) or -[Discord](https://discord.gg/p3mVxAbdmc). +[Issues](https://github.com/Rinzii/ccmath/issues). By contributing you confirm the change is yours to license and compatible with Apache-2.0 WITH LLVM-exception. +## Project layout + +A quick map of the repository: + +- `include/ccmath/` is the header-only library. Each function sits under its family + (`math/power/`, `math/trig/`, and so on), and the internals, including the + `builtin`, `gen`, and `rt` kernels, sit under `include/ccmath/internal/`. +- `tests/` holds the test suites, with the ULP and conformance harnesses under + `tests/shared/utils` and the MPFR oracles under `tests/shared/oracle`. +- `docs/` has the longer guides, and `cmake/`, `CMakeLists.txt`, and `CMakePresets.json` + drive the build. + ## Core tenets -1. Prefer constexpr evaluation where the standard allows it (`gen` paths, `static_assert` smoke). -2. Match `` observable behavior. Correctly rounded in all four rounding modes is the goal, with documented compromises up to 4 ULP or fewer modes allowed to ship sooner. See [Accuracy and rounding goals](#accuracy-and-rounding-goals). -3. Keep runtime performance in the same ballpark as the platform libm on covered paths. -4. Follow the existing dispatch layout (`builtin` / `gen` / `rt`, or impl-direct where the family already does). -5. Prefer portable generic kernels over platform forks when both satisfy the standard. -6. Mirror supported compiler behavior where the standard leaves details unspecified (for example `fpclassify` payload bits). -7. Add tests in the same change. Cover standards-mandated edge cases and ULP checks on public APIs. -8. Doxygen on exported entry points (`@brief`, `@param`, `@return`). -9. Keep the public header layout aligned with [cppreference ``](https://en.cppreference.com/w/cpp/header/cmath). +1. Always follow the C and C++ standard requirements for ``. Match the specified semantics, domain and range + handling, special values, and error reporting (errno and the floating-point exception flags). +2. Aim for a correctly rounded result in all four rounding modes, with documented compromises up to 4 ULP or fewer modes + allowed to ship sooner. See [Accuracy and rounding goals](#accuracy-and-rounding-goals). +3. Prefer constexpr evaluation where the standard allows it (`gen` kernels, `static_assert` smoke). +4. Keep runtime performance in the same ballpark as the platform libm on the cases it covers. +5. Follow the existing dispatch layout (`builtin` / `gen` / `rt`, or impl-direct where the family already does). +6. Prefer portable generic kernels over platform forks when both satisfy the standard. +7. Mirror supported compiler behavior where the standard leaves details unspecified (for example `fpclassify` payload + bits). +8. Add tests in the same change. Cover standards-mandated edge cases and ULP checks on public APIs. +9. Doxygen on exported entry points (`@brief`, `@param`, `@return`). +10. Keep the public header layout and naming conventions aligned with [cppreference + ``](https://en.cppreference.com/w/cpp/header/cmath). ## Accuracy and rounding goals The end goal for every function is a correctly rounded result, bit-for-bit the infinitely precise value rounded to the destination type, in all four IEEE rounding modes (`FE_TONEAREST`, `FE_DOWNWARD`, `FE_UPWARD`, `FE_TOWARDZERO`). That -is the bar the library is held to over time. +is the bar the library is held to. -We accept documented compromises when they get working, in-contract functionality -out sooner: +We accept documented compromises that ship in-contract functionality +sooner: -- A function that is not yet correctly rounded may land with a stated, tested +- A function that is not yet correctly rounded may ship with a stated, tested error bound of no more than 4 ULP. The ULP suites under `tests/shared/utils` check against the platform libm, and an MPFR oracle checks against the correctly rounded value where one is targeted. -- A function may support fewer than the four rounding modes. Landing +- A function may support fewer than the four rounding modes. Shipping `FE_TONEAREST` first, with the directed modes to follow, is fine. A compromise only counts if it is written down. State the current ULP bound and the supported rounding modes at the function, say what is missing, and leave a -`TODO():` plus a tracking issue so the gap stays visible. An -undocumented shortfall is a bug. A documented one is a known limitation we can -close later. +`TODO:` plus a tracking issue so the gap stays visible. An undocumented one +is a bug. A documented one is a known limitation we can close later. Accuracy and standards conformance are the top priority. Performance is the next -priority after that. Once a function is correct it should stay in the same -ballpark as the platform libm on covered paths, and the usual shape is a fast -path with an accurate fallback rather than a single slow path. When the two -genuinely conflict, correctness wins, but reach for a layered design before +priority after that. Once a function is correct it should stay in the same ballpark +as the platform libm where it is implemented, and the usual shape is a fast common +case backed by an accurate fallback rather than one slow implementation. When the +two genuinely conflict, correctness wins, but reach for a layered design before trading away speed. -## First PR +## Build from the command line -```bash -git clone https://github.com//ccmath.git -cd ccmath -git checkout main -git checkout -b -``` +### Prerequisites -Configure, build, and run the simple test preset: +- CMake 3.18+ +- Git +- Ninja (Linux and macOS) +- Clang or GCC (Linux and macOS) +- Visual Studio 2022 (Windows) + +### Linux and macOS ```bash +git clone https://github.com/Rinzii/ccmath.git +cd ccmath +git checkout main cmake --preset=ninja-gcc-debug cmake --build --preset=build-ninja-gcc-debug ctest --preset=test-ninja-gcc-debug --output-on-failure ``` -Commit with a one-line imperative subject (no trailing period). Open a PR against -`main` with a short summary of what changed and how you tested it. +Other configure presets are listed in `CMakePresets.json` (`ninja-clang-debug`, +`ninja-clang-release`, and similar). Pass `-DCMAKE_CXX_STANDARD=17` or `20` on the +configure line when you need a specific standard. -Implementation work should stay under `include/ccmath/` using the patterns already -present in the function family you touch. Do not add `` includes under -`include/`. +### Windows (Visual Studio 2022) + +```powershell +git clone https://github.com/Rinzii/ccmath.git +cd ccmath +git checkout main +cmake --preset=vs22-debug +cmake --build --preset=build-vs22-debug +ctest --preset=test-vs22-debug --output-on-failure +``` + +Preset names vary. Read `CMakePresets.json` before assuming a name exists locally. + +## First PR + +Fork the repo, then branch off `main`: + +```bash +git clone https://github.com//ccmath.git +cd ccmath +git checkout main +git checkout -b +``` + +Build and test with the presets above. Implementation work stays under +`include/ccmath/`, follows the patterns already present in the function family you +touch, and does not add `` includes. When the change is ready, open a PR +against `main` as described in [Opening a pull request](#opening-a-pull-request). ## Issue and pull request workflow @@ -87,63 +132,36 @@ force-push `main`. ### Releases and tags -Releases are annotated tags in the form `vMAJOR.MINOR.PATCH`, for example -`v0.2.0`. Release candidates use the semver pre-release form -`vMAJOR.MINOR.PATCH-rc.N`, for example `v0.3.0-rc.1`. While the project is below -`1.0`, a minor bump may carry breaking API changes. - -### Labels - -Issues and PRs use a prefixed label taxonomy. Maintainers apply most of these -during triage. The groups are: - -- `T:` type of work (`T: Bugfix`, `T: New Feature`, `T: Performance`, `T: Refactor`, `T: Cleanup`, `T: Documentation`, `T: Tests`, `T: Chore`) -- `A:` area, covering both function families (`A: Trig`, `A: Power`, `A: Expo`, and the rest) and numeric or implementation axes (`A: Correctly Rounded`, `A: Accuracy/ULP`, `A: Range Reduction`, `A: Rounding Modes`, `A: Edge Cases`, `A: Constexpr`, `A: Runtime Kernel`, `A: SIMD`, `A: Deterministic`, `A: Numeric Proof`, `A: Build`, `A: Tooling`) -- `D:` difficulty, from `D3: Low` for approachable work up to `D0: Irrational` for hard-to-round problems that may take unreasonable effort -- `P:` priority, from `P1: Critical` to `P4: Low` -- `S:` status through review (`S: Untriaged`, `S: Needs Discussion`, `S: Needs Review`, `S: Approved`, `S: Awaiting Changes`, `S: Blocked`, `S: Merge Conflict`, `S: Stale`, `S: DO NOT MERGE`, `S: Won't Fix`) -- `Feature:` approval state for feature requests (`Feature: Unapproved`, `Feature: Approved`) -- `Bug:` reproduction state (`Bug: Needs Replicating`, `Bug: Replicated`) -- `Platform:`, `Arch:`, and `Compiler:` for environment-specific reports -- `Branch:` for the branch a PR targets (`Branch: main`, `Branch: release`) -- `Changes:` for the surface a PR touches (`Changes: Public API`, `Changes: Headers Only`) -- `size/` for PR size, from `size/XS` to `size/XL` - -A contributor looking for a starting point should filter on `D3: Low` and -`good first issue`. - -The label set is managed as code in `.github/labels.yml` and synced on merge to -`main`. Change that file in a pull request rather than editing labels in the -GitHub UI, otherwise the next sync reverts the change. +Releases are annotated tags `vMAJOR.MINOR.PATCH`, with release candidates as +`vMAJOR.MINOR.PATCH-rc.N`. Below `1.0`, a minor bump may carry breaking API changes. ### Filing an issue Bug reports use the form under `.github/ISSUE_TEMPLATE`. For a numeric defect, -give the exact function, the affected code path (constexpr, runtime, SIMD, or +give the exact function, the affected evaluation (constexpr, runtime, SIMD, or deterministic mode), the scalar type, and the exact input, expected, and actual values. Hex float literals such as `0x1.0p+2` avoid precision loss. State the active rounding mode and the source of truth for the expected result (MPFR, the -C++ standard, or a reference libm). New bug reports start as `T: Bugfix` and -`S: Untriaged`. - -### Agree the approach first - -Hard numeric work is design-reviewed on the issue before it is implemented. If an -issue sits at the hard end of the `D:` scale (`D0` or `D1`) or carries -`A: Correctly Rounded`, `A: Accuracy/ULP`, `A: Rounding Modes`, or -`A: Range Reduction`, comment on the issue with your intended approach and the -exact inputs you plan to validate against, and wait for a maintainer to ack it -before opening a non-draft PR. A draft PR holding only the acceptance tests and a -validation plan is a good way to start that conversation, and a maintainer may -move the issue to `S: Needs Discussion` while it is settled. The point is to -catch a wrong method in a short comment rather than in a finished kernel, so you -do not pour effort into an approach that has to be redone. +C++ standard, or a reference libm). + +### Agree on the approach first + +Hard numeric work gets a design review on the issue before you implement it. For a +correctly-rounded function, or work on accuracy and ULP, rounding modes, or range +reduction, comment on the issue with your intended approach and the exact inputs you +plan to validate against, and wait for a maintainer to sign off before opening a +non-draft PR. A draft PR holding only the acceptance tests and a validation plan is a +good way to start that conversation. The point is to catch a wrong method in a short +comment rather than in a finished kernel, so you do not pour effort into an approach +that has to be redone. Likewise, hold off on implementing a new feature until a +maintainer approves the request. ### Opening a pull request -PRs target `main` unless the fix is specific to a release branch. Keep the -change minimal and aligned with the function family you touch. Add tests in the -same change. Use a one-line imperative subject with no trailing period. +PRs target `main` unless the fix is specific to a release branch. Keep the change +minimal and aligned with the function family you touch. Use a one-line imperative +subject with no trailing period. Open the PR from a topic branch, since a PR from +your fork's `main`, `master`, or `develop` is closed automatically. Before you request review, read your own diff once outside the editor, make sure the warning-as-error build and the test presets are green, and write a short @@ -154,9 +172,23 @@ say it is done. When a reviewer misreads the code, prefer making the code or a why comment clearer over explaining only in the thread, since the next reader sees the code and not the discussion. +### Cite your sources + +Most numeric work builds on existing research, so share what you drew on. In the +issue or PR, name the algorithm and the reference behind it, whether that is a paper, +a worked derivation, or a reference implementation you studied for its behavior. Link +any analysis or proof artifact too, a Sollya script, a Gappa proof, or the MPFR +comparison you validated against. A reviewer can check a method far faster when its +source is named than when they have to reconstruct it from the code, and a future +maintainer can follow the reasoning rather than guess at it. + +Also, if any code is adapted from another source rather than written from scratch, name +that source in a comment on the code itself, not only in the PR. Only adapt from a source +whose license is compatible with Apache-2.0 WITH LLVM-exception. + ### Testing math changes -Tests land in the same change as the code. A few specifics matter for math +Tests go in the same change as the code. A few specifics matter for math kernels and are easy to get wrong: - Exercise the code you changed. A public call such as `ccm::pow` can resolve to @@ -176,85 +208,8 @@ kernels and are easy to get wrong: `tests/shared/utils/fenv_fixture.hpp`. The ULP and conformance suites live under `tests/shared/utils`. -### Reviewing a pull request - -A review is a dialogue, not a verdict. Ask rather than demand, assume the author -acted in good faith, and talk about the code as ours rather than yours. Give real -credit when something is done well. - -Mark each comment as blocking or a nit so the author knows what gates the merge. -A wrong result, a standards violation, a red build, and a claim the change does -not actually deliver are blocking. Style, naming, and scoping are nits, and any -reviewer can raise those, while contract and architecture calls are left to a -maintainer. - -Back a numeric claim with evidence. A failing input and its correctly-rounded -value from MPFR, a worst-case grid result, or a link to the algorithm reference -carries more than an assertion. Read the tests first, since the most common gap -here is a test that never reaches the kernel or only checks a single point. Do -not gatekeep past the accuracy policy. In-contract and honest is the bar, not -sub-ULP perfection everywhere. If a thread keeps going back and forth, move it to -a synchronous chat and write up the outcome. - -### Automation - -Several labels are applied for you, so you do not need to set them by hand: - -- `A:` area labels from the files a PR changes, plus `Changes: Public API` -- `A:` and `Arch:` labels on a bug report, read from the function family and architecture answers in the issue form -- `size/` from the number of lines changed -- `Branch: main` or `Branch: release` from the base branch -- `S: Untriaged` on new issues and PRs that open without labels, cleared once a maintainer applies another label by hand (labels added by automation do not clear it) -- `S: Merge Conflict` when a PR develops conflicts -- `S: Needs Review`, `S: Approved`, and `S: Awaiting Changes` as a maintainer review progresses -- `S: Stale` after 60 days of inactivity, removed on the next update -- `Feature: Unapproved` on any issue labeled `T: New Feature`, until an admin adds `Feature: Approved`, which clears it - -A feature request stays `Feature: Unapproved` until a maintainer with admin -rights approves it. Hold off on implementation work until a request is approved. - -PRs opened from a fork's `main`, `master`, or `develop` branch are closed -automatically with a note to move the work to a topic branch. - -## Build from the command line - -### Prerequisites - -- CMake 3.18+ -- Git -- Ninja (Linux and macOS) -- Clang or GCC (Linux and macOS) -- Visual Studio 2022 (Windows) - -### Linux and macOS - -```bash -git clone https://github.com/Rinzii/ccmath.git -cd ccmath -git checkout main -cmake --preset=ninja-gcc-debug -cmake --build --preset=build-ninja-gcc-debug -ctest --preset=test-ninja-gcc-debug --output-on-failure -``` - -Other configure presets are listed in `CMakePresets.json` (`ninja-clang-debug`, -`ninja-clang-release`, and similar). Pass `-DCMAKE_CXX_STANDARD=17` or `20` on the -configure line when you need a specific standard. - -### Windows (Visual Studio 2022) - -```powershell -git clone https://github.com/Rinzii/ccmath.git -cd ccmath -git checkout main -cmake --preset=vs22-debug -cmake --build --preset=build-vs22-debug -ctest --preset=test-vs22-debug --output-on-failure -``` - -Preset names vary. Read `CMakePresets.json` before assuming a name exists locally. - ## More detail +- [CONVENTIONS.md](CONVENTIONS.md) for the code-level conventions - [docs/STATUS.md](docs/STATUS.md) for module completion - [SECURITY.md](SECURITY.md) for vulnerability reports diff --git a/README.md b/README.md index b4bedb4b..fb098852 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # CCMath -## A constexpr-first `` library for C++17 and later +**A constexpr-first `` for C++17 and later** [![Release](https://img.shields.io/github/v/release/Rinzii/ccmath?sort=semver&label=release)](https://github.com/Rinzii/ccmath/releases/latest) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Rinzii/ccmath/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Rinzii/ccmath) @@ -15,18 +15,39 @@ | `main` | [![Linux main](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml?query=branch%3Amain) | [![macOS main](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml?query=branch%3Amain) | [![Windows main](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml?query=branch%3Amain) | | `release/0.3.x` | [![Linux release](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml?query=branch%3Arelease%2F0.3.x) | [![macOS release](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml?query=branch%3Arelease%2F0.3.x) | [![Windows release](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml?query=branch%3Arelease%2F0.3.x) | -CCMath is a header-only library with a ``-style API. The same entry points constexpr-evaluate where the standard and compiler allow, dispatch to portable runtime implementations elsewhere, and can use SIMD on selected functions when the target supports it. +CCMath is a header-only library with a ``-style API. The same entry points constexpr-evaluate where the standard +and the compiler allow, dispatch to portable runtime implementations everywhere else, and pick up SIMD on selected +functions when the target supports it. -This README describes what CCMath is working toward. Some claims here reflect goals still in progress. See [STATUS.md](docs/STATUS.md) for current implementation status by module and function. +This README also describes what CCMath is working toward, so some of these claims are goals rather than finished +work. [STATUS.md](docs/STATUS.md) tracks where each module and function actually stands today. For the +approximation-driven function work, the implementation guide lives +in [APPROXIMATING_FUNCTIONS.pdf](docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf). Questions and design +discussion are welcome on [Discord](https://discord.gg/p3mVxAbdmc). -For approximation-driven function work, see the implementation guide -in [APPROXIMATING_FUNCTIONS.pdf](docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf). +--- + +## Contents + +- [Why CCMath](#why-ccmath) +- [Quick start](#quick-start) +- [Accuracy and conformance](#accuracy-and-conformance) +- [Performance](#performance) +- [Supported platforms and compilers](#supported-platforms-and-compilers) +- [Installation](#installation) +- [Validation](#validation) +- [Contributing](#contributing) +- [Projects using CCMath](#projects-using-ccmath) +- [License](#license) +- [Acknowledgments](#acknowledgments) --- -## Why CCMath? +## Why CCMath -Most `` implementations prioritize different tradeoffs. Few combine standards-oriented semantics, constexpr evaluation, full runtime paths, SIMD, and portable in-tree code. +Most `` implementations are built around different tradeoffs, and few of them combine standards-oriented +semantics, constexpr evaluation, full runtime execution, SIMD, and portable in-tree code in one place. That combination +is what CCMath is after. | Library | Standards-Oriented | Constexpr | Runtime | SIMD | Portable Implementation | |-------------------|--------------------|-----------|---------|------------------|-------------------------| @@ -37,225 +58,117 @@ Most `` implementations prioritize different tradeoffs. Few combine stand | SLEEF | Partial | No | Yes | Yes | Yes | | CCMath | Yes | Yes | Yes | Yes | Yes | -CCMath targets: - -- Standards conformance -- Compile-time evaluation -- Runtime execution -- SIMD acceleration -- Numerical correctness -- Competitive performance -- Cross-platform portability - -These goals overlap with the comparison columns plus numerical validation. Per-function progress is tracked in [STATUS.md](docs/STATUS.md). +Numerical correctness and performance are not columns in the table, and each gets its own section below. Per-function +progress is tracked in [STATUS.md](docs/STATUS.md). --- -## Design Principles - -### Standards Conformance - -CCMath is designed to closely follow the behavior specified by the C and C++ standards for mathematical functions. - -This includes: +## Quick start -- Function semantics -- Domain handling -- Range handling -- NaN propagation -- Infinity behavior -- Floating-point classification -- Rounding behavior -- Edge-case handling +There is no separate constexpr API. When constant evaluation is possible the public symbol participates in it, and when +it is not, the same symbol dispatches to a runtime implementation. You write `ccm::sqrt` once and it works in both +contexts. -Implemented functions follow the rules the C and C++ standards set for ``. - -### Compile-Time Evaluation - -```cpp -constexpr auto value = ccm::sin(0.5); - -static_assert(value > 0.0); -``` - -There is no separate constexpr API. When constant evaluation is possible, the public symbol participates in it. - -### Runtime Execution +The monolithic header pulls in everything: ```cpp -double result = ccm::exp(x); -``` - -When constant evaluation is not possible, the same symbols dispatch to runtime paths. - -### SIMD Acceleration - -Selected functions pick up SIMD on supported targets: - -- SSE2 -- SSE4 -- AVX2 -- ARM NEON - -### Numerical Correctness - -The goal for every function is a correctly rounded result under all four IEEE rounding modes. Coverage is function-by-function: some paths are validated today under all modes, while other functions still target round-to-nearest ties-to-even first, or land within a documented bound of no more than 4 ULP while the correctly rounded version is in progress. - -ULP harnesses, all-mode rounding probes, worst-case grids, and cross-compiler CI matrices live in-tree. The validation section below describes how they run in practice. - -### Performance - -After numerical correctness, performance is the next priority. Once a function is correct it is expected to stay in the same ballpark as the platform libm on covered paths, with a fast path plus an accurate fallback rather than a single slow path. Benchmarks comparing `ccm::*` against the platform libm live in-tree. - ---- - -## Function Coverage - -Work spans trigonometric, exponential, power, rounding, classification, and utility categories from the C and C++ mathematical library. Several modules are still in flight. [STATUS.md](docs/STATUS.md) lists what ships today and what remains open. - ---- - -## Validation and Verification - -Validation follows patterns from LLVM-libc, CORE-MATH, and glibc. CI covers: - -### Continuous Integration - -#### Platforms - -- Linux -- macOS -- Windows - -#### Compilers - -- GCC -- Clang -- Apple Clang -- MSVC - -#### Language Standards - -- C++17 -- C++20 -- C++23 -- C++26 / latest - -#### Build Configurations - -- Debug -- Release - -### Simple Validation Suite - -The `simple` suite runs on every CI job: - -```bash -ctest -L simple -``` - -This suite exercises public `ccm::*` entry points and includes: - -- Smoke testing -- Standards edge cases -- ULP checks against the active platform libm -- Regression testing - -### Rigorous Validation Suite +#include +#include -The `rigorous` suite runs in dedicated CI jobs: +int main() { + constexpr double result = ccm::sqrt(25.0); -```bash -ctest -L rigorous + std::cout << result << '\n'; +} ``` -Rigorous jobs compare against MPFR and CORE-MATH oracles. +You can also include a single function when that is all you need: -#### MPFR Validation +```cpp +#include +#include -Within a rigorous build, MPFR-backed tests use the `mpfr` label: +int main() { + constexpr double result = ccm::log(12.0); -```bash -ctest -L mpfr + std::cout << result << '\n'; +} ``` -Includes: +Both the compile-time and the runtime forms go through the same entry point: -- Extended function corpora -- Exceptional-value matrices -- Adversarial search - -Linux and macOS use system MPFR/GMP. - -Windows uses MPFR provided through vcpkg. - -#### CORE-MATH Validation - -Within a rigorous build, CORE-MATH-backed tests use the `coremath` label: +```cpp +// Compile time: usable in a constant expression. +constexpr double a = ccm::sin(0.5); +static_assert(a > 0.0); -```bash -ctest -L coremath +// Runtime: the same call on a value the compiler cannot fold. +double exp_at(double x) { return ccm::exp(x); } ``` -Correctly-rounded reference comparisons under all four IEEE rounding modes on finite inputs. - -#### Proof Verification - -When available, CCMath uses: - -- Gappa -- Sollya - -to refresh and verify proof artifacts for supported implementations. +--- -### Fuzz Testing +## Accuracy and conformance -Fuzz smoke tests run in dedicated macOS and Linux CI jobs with full LLVM Clang (libFuzzer requires the fuzzer sanitizer runtime). Windows matrix jobs use MSVC and are outside libFuzzer coverage today. +Accuracy is the first priority, ahead of performance. The goal for every function is a correctly rounded result under +all four IEEE-754 rounding modes. Coverage is function-by-function, so some already meet that bar today while others are +still getting there. -### Static Analysis and Security +| Status | What it means | +|------------------------------|------------------------------------------------------------------------------------| +| Correctly rounded, all modes | Bit-exact under all four IEEE-754 rounding modes | +| Correctly rounded, nearest | Bit-exact under round-to-nearest ties-to-even, other modes not yet covered | +| Bounded error | Within a documented bound of no more than 4 ULP until correct rounding is finished | -CodeQL, OpenSSF Scorecard, warning-as-error builds, and strict compiler warning gates run in CI. +Where each function sits on that scale is listed in [STATUS.md](docs/STATUS.md). A gap that is not written down there is +a bug. -### Style and Consistency +Alongside accuracy, CCMath is designed to follow the behavior the C and C++ standards pin down for ``. That +covers function semantics, domain and range handling, NaN propagation, infinity behavior, floating-point classification, +rounding behavior, and the edge cases the standards specify. Implemented functions follow those rules exactly. -clang-format runs in CI on every platform matrix job and on pull requests that touch C++ sources. clang-tidy is available locally via lint.sh and lint.bat. +We cover the trigonometric, exponential, power, rounding, classification, and utility categories of the C and C++ +mathematical library, though several functions are not finished yet. --- -## Usage +## Performance -### Monolithic Include - -```cpp -#include -#include +After accuracy, performance is the next thing we care about. The idea is that once a function is correct it should still +keep pace with the platform libm, so the common case stays quick and we only fall back to the fully accurate version +when we have to. A lot of functions also pick up SIMD where the hardware supports it, currently SSE2, SSE4, AVX2, and +ARM NEON. We keep benchmarks against the platform libm in the tree, so a regression is easy to catch. -int main() { - constexpr double result = ccm::sqrt(25.0); +--- - std::cout << result << '\n'; -} -``` +## Supported platforms and compilers -### Per-Function Include +CCMath builds and is tested across Linux, macOS, and Windows, under GCC, Clang, Apple Clang, and MSVC, on C++17 through +C++26 in both Debug and Release. The CI matrix that enforces this is described under [validation](#validation). -```cpp -#include -#include +| Compiler | Minimum version | +|----------------|-----------------| +| GCC | 11.1 | +| Clang | 9.0.0 | +| Apple Clang | 14.0.3 | +| MSVC | 19.26 | +| Intel DPC++ | 2022.0.0 | +| NVIDIA HPC SDK | 22.7 | -int main() { - constexpr double result = ccm::log(12.0); - - std::cout << result << '\n'; -} -``` +> [!NOTE] +> Minimum compiler versions are not final yet, so treat them as guidance for now. --- ## Installation +The library itself has no required dependencies, so consuming it is just a matter of putting the headers where your +compiler can find them. + ### CMake +CMake is the primary build system. + ```cmake include(FetchContent) @@ -274,30 +187,26 @@ target_link_libraries( ) ``` -You may also vendor the headers directly. +You can also vendor the headers directly. ### Secondary build systems (Meson and Premake) -CMake is the primary and authoritative build system. Meson and Premake support library consumption only: include paths, compile definitions, the generated `version.hpp`, and the library-required feature validation that a consumer needs. They do not define examples, smoke binaries, tests, benchmarks, fuzzing targets, or third-party dependency fetches. - -After configuring CMake, regenerate the secondary build files: - -```bash -cmake --preset ninja-clang-debug -DCCMATH_BUILD_TESTS=OFF -DCCMATH_BUILD_EXAMPLES=OFF -cmake --build out/build/ninja/ninja-clang-debug --target ccmath-generate-secondary-builds -``` +CMake is the primary build system, but the repository also ships ready-to-use `meson.build` and `premake5.lua`. Both are +self-contained, since each generates `ccmath/version.hpp` itself, so a Meson or Premake consumer never needs a CMake +configure first. They expose include directories and compile definitions only, not examples, tests, benchmarks, or +fuzzing targets. -Meson consumer usage after regeneration: +Meson, with ccmath as a subproject: ```meson ccmath_dep = dependency('ccmath', fallback : ['ccmath', 'ccmath_dep']) executable('my-app', 'main.cpp', dependencies : ccmath_dep) ``` -Premake consumer usage after regeneration: +Premake: ```lua -include("../ccmath/premake5.lua") +include("path/to/ccmath/premake5.lua") project "my-app" kind "ConsoleApp" @@ -306,76 +215,30 @@ project "my-app" ccmath.use() ``` -The generated root `meson.build` exposes `ccmath_dep` and overrides the `ccmath` dependency name for Meson consumers. The generated root `premake5.lua` exposes `ccmath.use()`, `ccmath.include_dirs()`, and `ccmath.defines()` for Premake consumers. Library-facing options are declared once in `cmake/config/BuildManifest.cmake` and exported through `ccmath-build-manifest.json`. - -### Vendored integration testing - -[`integration/vendored-consumer`](integration/vendored-consumer) simulates an external project that vendors CCMath headers under `third_party/ccmath`. CI copies the public headers and generated `version.hpp`, then builds the consumer with CMake, Meson, and Premake. - -```bash -cmake --preset ninja-clang-debug -DCCMATH_BUILD_TESTS=OFF -DCCMATH_BUILD_EXAMPLES=OFF -bash tools/integration/prepare_vendored_tree.sh out/build/ninja/ninja-clang-debug - -cmake -S integration/vendored-consumer -B integration/vendored-consumer/build-cmake -G Ninja -cmake --build integration/vendored-consumer/build-cmake - -meson setup integration/vendored-consumer/build-meson integration/vendored-consumer -meson compile -C integration/vendored-consumer/build-meson - -cd integration/vendored-consumer && premake5 gmake && make config=debug -``` - --- -## Rigorous Testing Setup +## Validation -Typical local configuration: +CCMath is validated thoroughly. The approach borrows from LLVM-libc, CORE-MATH, and glibc: every public entry point is +checked for standards edge cases and ULP error against the platform libm. Beyond that, a rigorous suite cross-checks the +harder cases against MPFR and CORE-MATH oracles under all four IEEE rounding modes, Gappa and Sollya back the proof +artifacts, and differential fuzzing catches kernel disagreements. All of it runs in CI across Linux, macOS, and Windows, +under GCC, Clang, Apple Clang, and MSVC, on C++17 through C++26, alongside CodeQL, OpenSSF Scorecard, and +warning-as-error builds. -```bash -cmake -B build-rigorous -G Ninja \ - -DCCMATH_BUILD_RIGOROUS_TESTS=ON \ - -DCCMATH_ENABLE_MPFR_TESTS=ON \ - -DCCMATH_ENABLE_COREMATH_TESTS=ON \ - -DCCMATH_COREMATH_ROOT=/path/to/coremath-prefix -``` - -Build a CORE-MATH prefix using: - -```bash -tools/coremath/build_prefix.sh -``` - -On Windows, pass: - -```bash --DVCPKG_MANIFEST_DIR=cmake/vcpkg -``` - -along with a vcpkg toolchain file to obtain MPFR. - -See the workflow definitions in [`.github/workflows`](.github/workflows) for the exact CI configurations. - -The library itself has no required dependencies. - -Optional packages used for testing, benchmarking, and fuzzing are documented in [thirdparty/README.md](thirdparty/README.md). +How to run any of these suites locally is documented in [CONTRIBUTING.md](CONTRIBUTING.md), and per-function accuracy +status is tracked in [STATUS.md](docs/STATUS.md). --- -## Supported Compilers - -- GCC 11.1+ -- Clang 9.0.0+ -- Apple Clang 14.0.3+ -- MSVC 19.26+ -- Intel DPC++ 2022.0.0+ -- NVIDIA HPC SDK 22.7+ +## Contributing -> [!NOTE] -> Minimum compiler versions are still being refined and should be considered guidance rather than a finalized support matrix. +See [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), and [SECURITY.md](SECURITY.md). Bug +reports and design questions go to GitHub Issues or Discord. --- -## Projects Using CCMath +## Projects using CCMath The following projects publicly use CCMath: @@ -385,12 +248,6 @@ If your project uses CCMath and is publicly available, open a PR to add it to th --- -## Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), and [SECURITY.md](SECURITY.md). Bug reports and design questions go to GitHub Issues or Discord. - ---- - ## License Apache License v2.0 with LLVM Exceptions. See [LICENSE](LICENSE). diff --git a/tests/shared/oracle/README.md b/tests/shared/oracle/README.md index 4c623de6..55abf396 100644 --- a/tests/shared/oracle/README.md +++ b/tests/shared/oracle/README.md @@ -12,6 +12,40 @@ Add a thin main (or a runner under tests/shared/oracle/runners/), then in the re Pow uses runners/mpfr_pow_runner.hpp and runners/coremath_pow_runner.hpp. +## Unary elementary functions + +mpfr_unary.cpp is one executable that measures every unary elementary function against a +correctly-rounded MPFR reference, selected with --function and --type. mpfr_unary_common.hpp holds +the shared run_campaign, which samples binary64 over a per-function domain and can sweep binary32 +exhaustively with --exhaustive. Adding a function is one case in the dispatch in mpfr_unary.cpp. +tools/measure_ulp.sh drives the full sweep and tools/ulp_status.py folds the summaries into +docs/ULP_STATUS.md. + +Every run prints a ulp_histogram line bucketing the finite scored cases by ULP distance (0, 1, 2, 3, +4, then 5 and beyond), so the summary shows how close to correctly rounded a function is, not only its +worst case. The same buckets are written to the summary JSON. + +--impl picks which implementation is measured: gen for the generic kernel (the default and the thing +ccmath owns), public for the ccm:: public path, or libm for the system math library. The public path +lowers to libm under FE_TONEAREST on clang and gcc, so it matches libm in round-to-nearest and shows +its own behaviour in the directed modes. The choice is recorded in the output line and the summary +JSON, so gen and libm numbers are never confused. + +To aim a deep check at one region, clip any argument and the corpus switches to a confined region +sample. The bounds are --lo and --hi for a value range, or --scale=log with --min-exp and --max-exp +to sample evenly across binades. An unsuffixed flag applies to every argument and a suffix picks one +argument, so for pow --lo0 and --hi0 clip the base while --min-exp1 and --max-exp1 clip the exponent. +Add --confine to drop the structured edges and sample the region only, and --progress for a live +percent, throughput, and ETA line on stderr during long runs. A sampled sweep drops exact-bit repeat +inputs by default, so no value is measured twice and the average is not double counted. Pass +--allow-duplicates to keep them, which skips the dedup sort on a very large sample over a vast space +such as pow, where repeats are statistically absent anyway. + +Exhaustive binary32 can cover a sub-range instead of all 2^32 patterns. A finite --lo and --hi with +--exhaustive sweeps every float in that range, and --exhaustive-range=START:END sweeps an explicit +inclusive interval of bit patterns (decimal or 0x hex). This makes a full sweep of a single binade, +such as every float in [1, 2], a sub-second check. + ## Configure MPFR: CCMATH_ENABLE_MPFR_TESTS=ON. CORE-MATH: CCMATH_ENABLE_COREMATH_TESTS=ON. From 8d97ef49e0b724f6fff7f248fdf2f31c2d672139 Mon Sep 17 00:00:00 2001 From: ianpike Date: Thu, 18 Jun 2026 17:32:06 -0400 Subject: [PATCH 37/52] Improvements to documentation --- CONVENTIONS.md | 184 +++ docs/ULP_STATUS.md | 1157 ++++------------- .../internal/support/fenv/host_fenv.hpp | 7 + 3 files changed, 475 insertions(+), 873 deletions(-) create mode 100644 CONVENTIONS.md diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 00000000..c6b6b1b6 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,184 @@ +# CCMath conventions + +The code-level companion to [CONTRIBUTING.md](CONTRIBUTING.md), which has the tenets, +accuracy policy, and PR workflow. These are the patterns you will see across the +headers, and the ones easy to get wrong. + +## Project layout + +```mermaid +graph LR + A["include/ccmath/"] --> B["ccmath.hpp (umbrella header)"] + A --> C["math/ (public headers, per family)"] + A --> D["internal/ (private, may change any commit)"] + D --> D1["config/ (CCM_CONFIG_* flags)"] + D --> D2["predef/ (compiler, platform wrappers)"] + D --> D3["support/ (fp, fenv, helpers)"] + D --> D4["types/ (internal aliases)"] + D --> D5["math/"] + D5 --> K1["generic/builtins/ : ccm::builtin"] + D5 --> K2["generic/func/ : ccm::gen (*_gen)"] + D5 --> K3["runtime/func/ : ccm::rt (*_rt)"] +``` + +Public headers live under `math/`, one per function by family. Everything else is +private under `internal/`. Families: `basic`, `compare`, `expo`, `fmanip`, `hyper`, +`misc`, `nearest`, `power`, `special`, `trig`, matching +[cppreference ``](https://en.cppreference.com/w/cpp/header/cmath). The key part +is the three-kernel split at the bottom: nearly every function has a `builtin`, a +`gen`, and an `rt` kernel, and which runs depends on where you call it from. + +## Dispatch + +The public header owns the choice, and you should not add layers between it and the +kernels. Every public `ccm` function is `constexpr` and routes on one question: is +this a constant expression? + +```mermaid +flowchart TD + A["ccm::fn(...) public, constexpr"] --> B{"is_constant_evaluated()?"} + B -- "compile time" --> C{"constexpr builtin available?"} + C -- "yes" --> D["ccm::builtin::*_ct"] + C -- "no" --> E["ccm::gen::*_gen"] + B -- "runtime" --> F["ccm::rt::*_rt"] +``` + +At compile time, use the constexpr builtin if one exists (constant evaluation rounds +to nearest, so it is correct), otherwise the generic kernel. At runtime, always `rt`, +since a builtin lowers to libm and we trust it only under `FE_TONEAREST`. That is also +why each non-generic runtime kernel sits in its own `*_rt.hpp` and guards the rounding +mode. `include/ccmath/math/power/pow.hpp` is the shape to copy: + +```cpp +template , bool> = true> +constexpr T pow(T base, T exp) +{ + if constexpr (ccm::builtin::has_constexpr_pow) + { + if (support::is_constant_evaluated()) { return ccm::builtin::pow_ct(base, exp); } + return rt::pow_rt(base, exp); + } + else + { + if (support::is_constant_evaluated()) { return gen::pow_gen(base, exp); } + return rt::pow_rt(base, exp); + } +} +``` + +## Standards conformance + +CCMath is a drop-in ``: observable behavior matches what the C and C++ +standards specify, so swapping `std::` for `ccm::` changes no result. That is why the +edge cases get the same care as ordinary inputs. + +The arithmetic overloads apply the [c.math]/3 promotion rule, promoting mixed inputs +to the common floating type and integers to `double`, so `ccm::pow(2, 3.0)` matches +the standard call (`pow.hpp` keeps this in `detail::cmath_pow_result_t`). Special +values follow IEEE 754 and Annex F: NaN propagation, the standard-pinned sign of zero, +and the infinities. Where the standard leaves a result implementation-defined (some +`fmod` NaN cases, the `fpclassify` payload bits), we match the supported compilers +rather than invent one. + +Errors follow the C `math_errhandling` model, `errno` plus a floating-point exception +flag, so existing error checks keep working (mechanics under +[Errors and rounding](#errors-and-rounding)). Recent C++ revisions also made `` +usable in constant expressions, which is what CCMath already does, so there is no +separate constexpr API. + +## Namespaces + +The public API is `ccm`, names matching ``. The three dispatch layers each get +a namespace, and the rest is internal support. + +| Namespace | Role | Public | +|--------------------------------------------------------------|-----------------------------------------------------------|--------| +| `ccm` | public API, names match `` (`pow`, `powf`, `powl`) | yes | +| `ccm::builtin` | compiler-builtin wrappers (`*_ct`) | no | +| `ccm::gen` | generic kernels (`*_gen`) | no | +| `ccm::rt` | runtime kernels (`*_rt`) | no | +| `ccm::detail` | trait and overload glue local to a public header | no | +| `ccm::support` (`::fp`, `::fenv`, `::helpers`), `ccm::types` | internal utilities | no | + +If it is not in `ccm` proper, it is not API and can change in any commit. + +## Naming and macros + +Names follow `` and the surrounding code. Functions are snake_case (`pow`, +`pow_gen`), types PascalCase (`FPBits`, `NormalizedFloat`), and aliases snake_case +with a `_t` suffix (`fp_bits_t`, `half_width_t`). Template parameters are `T` for the +primary type, descriptive PascalCase otherwise (`Integer`, `Real`). + +Macros use `CCMATH_` for public, versioning, and compiler config, and `CCM_` for +internal use and the `CCM_CONFIG_*` flags. No other prefixes. Leave a `TODO:` only +when a gap genuinely cannot be closed in the change. + +## Support utilities + +Check `ccm::support` before hand-rolling anything low-level. `FPBits` (`fp_bits_t`) +takes a float apart into sign, exponent, and mantissa, and builds specials like +`quiet_nan()`, so a raw `bit_cast` is rarely what you want. Use +`ccm::support::is_constant_evaluated()` for the compile-time branch, not the `std::` +one, since C++17 is the floor. Fused multiply-add and the error-free transforms on it +live in `ccm::support::multiply_add`. + +## Errors and rounding + +CCMath never throws. A domain, pole, or range error sets `errno` and raises the +matching floating-point exception flag through the `ccm::support::fenv` helpers +(`set_errno_if_required`, `raise_except_if_required`), which are config-guarded and +stay quiet during constant evaluation. Report errors that way, not with a C++ +exception. + +Kernels honor all four IEEE rounding modes at runtime, so read the active mode and +adjust the bit work or the final rounding around it. Never call `fesetround` inside a +kernel, and do not use `#pragma STDC FENV_ACCESS`. The accuracy bar and the +documented-compromise policy are in +[CONTRIBUTING.md](CONTRIBUTING.md#accuracy-and-rounding-goals). + +## File conventions + +Each header opens with the SPDX license block (Apache-2.0 WITH LLVM-exception) and +`#pragma once`. Project headers come first, as quoted includes from the include +root (`"ccmath/..."`), then standard headers in angle brackets. No `` +under `include/`: CCMath is the `` here, so use the internal `builtin`, `gen`, +and `rt` kernels. + +## C++ usage + +C++17 is the minimum, so gate any newer feature on the standard it needs, not on what +a given compiler accepts. Casts are C++-style only (`static_cast`, `reinterpret_cast`, +`const_cast`, `bit_cast`), never the C form. Otherwise it is ordinary modern C++: the +`_v` and `_t` aliases, `enum class`, `using` over `typedef`, and brace initialization. + +Two ccmath specifics. `[[nodiscard]]` goes on internal helpers and accessors, not the +public entry points, which mirror the unmarked `` signatures. Compiler and +platform checks go through the `internal/predef` wrappers (`has_builtin`, +`has_attribute`, `likely`, `unlikely`, `assume`), not raw `__GNUC__` or `_MSC_VER`. +Everything builds clean under `-Werror` with aggressive warnings. + +## Comments + +Public entry points get Doxygen: `@brief`, `@param` or `@tparam`, and `@return`. +Implementation comments are for the why, not the what: the constraint, the tradeoff, +the reason a builtin is or is not trusted. `pow.hpp` is a good model. + +## Formatting + +clang-format is authoritative. Run `lint.sh` or `lint.bat` (which also runs +clang-tidy) before pushing. The settings worth knowing: + +| Setting | Value | +|-----------------------|------------------| +| Base style | LLVM | +| Braces | Allman | +| Indent | tabs, width 4 | +| Column limit | 160 | +| Namespace indentation | all | +| Pointer alignment | middle (`T * x`) | + +## Build systems + +CMake is the primary build. Meson and Premake are self-contained for consumers and +need no CMake configure first. Library options are declared once in +`cmake/config/BuildManifest.cmake` and flow to the secondary builds from there. diff --git a/docs/ULP_STATUS.md b/docs/ULP_STATUS.md index 70057152..3ffa0b90 100644 --- a/docs/ULP_STATUS.md +++ b/docs/ULP_STATUS.md @@ -1,578 +1,282 @@ -## Table of Contents +# ULP status + +The goal for ccmath is correctly rounded results in all four IEEE 754 rounding modes for every +function. We are not there yet, and this page is meant to be an honest picture of where each +function actually stands rather than a promise. It reports what was measured, how it was measured, +and what is still left to do. + +The companion page ROUNDING_STATUS.md tracks which rounding modes each function handles at runtime. +This page is about accuracy, the distance between the ccm result and the true correctly rounded +value. + +## How to read this + +Every function falls into one of five classes. + +| Class | Meaning | +|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Exact | Produces the exact representable result, so 0 ULP in all four modes by construction. The nearest-integer, sign, and scaling families are here. | +| Correctly rounded | The unique nearest representable value, 0.5 ULP, confirmed by an all-modes campaign. sqrt is correctly rounded by IEEE 754, and fma is exact-then-rounded. | +| Bounded | Measured to be within a small number of ULP of the correctly rounded value. Correct rounding is the goal, and these functions are not there yet. | +| Not a rounding function | Returns a bool, an int, or a classification, so ULP does not apply. The comparison family is here. | +| Not implemented | The header exists but there is no implementation yet. The hyperbolic and special-function families are here. | + +A few notes on the numbers in the tables below. + +These numbers are for ccmath's own generic kernels, not for whatever the public function lowers to +at runtime. On a clang or gcc build the public functions hand the runtime round-to-nearest case to +a compiler builtin, which is the system libm and is often more accurate than the figures here. We +measure the generic kernel on purpose, because it is ccmath's own code and it is what runs at +compile time, in the directed rounding modes, in deterministic mode, and anywhere a builtin is not +available. So treat these as the portable worst case ccmath is responsible for, not as what a clang +or gcc runtime build delivers in round-to-nearest. + +Max ULP is the largest error observed between the kernel result and the correctly rounded value +over the measured corpus. A value of 0 means every result was the nearest representable value. The +reference is MPFR evaluated at high precision and rounded once to the target type, so it is the +true correctly rounded answer and not another library's result. + +The accuracy families, which are Exponential, Power, Trigonometric, and Miscellaneous, break each +function into its three type variants: the float f-suffix variant (expf, powf, sinf), the plain double +variant (exp, pow, sin), and the long double variant (expl, powl, sinl). The other families are exact +or not implemented, which holds for every type, so they stay one row each. + +Only binary32 is measured so far. Every binary32 number comes from an exhaustive sweep over all bit +patterns, which makes its max a proven worst case in round-to-nearest. binary64 is a larger job and is +measured next, and long double after that, so both show up as pending until then. + +Each variant row names the exact kernel it runs in the Tested kernel column. The gen kernels are +templates, so the same symbol instantiates at each type, for example ccm::gen::pow_gen and +ccm::gen::pow_gen. The asin, acos, atan, tgamma, and lgamma kernels come as a separate float +and double function with no dedicated long double kernel, so their long double variant uses the double +kernel. These are ccmath's own generic kernels under --impl=gen, the implementation the public function +falls back to in the directed modes, at compile time, and in deterministic mode. The same harness can +point at the public path or the system libm with --impl=public or --impl=libm, but those are not what +this page reports. + +The numbers are measured in round-to-nearest, the default mode. Which rounding modes each function +handles at runtime is tracked separately in ROUNDING_STATUS.md. + +To refresh these tables, run the sweep and then the generator. + +``` +tools/measure_ulp.sh --exhaustive-float +python3 tools/ulp_status.py +``` + +The function classes and the list of measured functions live in tools/ulp_status_registry.json. +A function with no completed campaign summary shows up as pending, so the tables never claim a +number that was not actually measured. + +## Accuracy by function + + + +> ccmath generic kernels (--impl=gen), round-to-nearest. MPFR correctly-rounded reference. The accuracy families break +> each function into its float, double, and long double variant (expf, exp, expl). binary32 is measured by an exhaustive +> sweep over all bit patterns, so its max is a proven worst case. binary64 and long double are pending. The Tested kernel +> column names the exact symbol that runs. Measured on apple with clang 17.0.0 (clang-1700.4.4.1). Last refreshed +> 2026-06-18T12:49:45Z. ### Basic -- [fabs](#fabs) -- [fdim](#fdim) -- [fma](#fma) -- [(f)max](#fmax) -- [(f)min](#fmin) -- [remainder](#remainder) -- [remquo](#remquo) -- [nan](#nan) - -### Compare -- [fpclassify](#fpclassify) -- [isfinite](#isfinite) -- [isgreater](#isgreater) -- [isgreaterequal](#isgreaterequal) -- [isinf](#isinf) -- [isless](#isless) -- [islessequal](#islessequal) -- [islessgreater](#islessgreater) -- [isnan](#isnan) -- [isnormal](#isnormal) -- [isunordered](#isunordered) -- [signbit](#signbit) + +| Function | Class | Max ULP | Avg ULP | Notes | +|-----------|-------------------------|---------|---------|-----------------------------------------------------------------------------------------------------------| +| fabs | Exact | 0 | 0 | | +| fdim | Exact | 0 | 0 | | +| fma | Correctly rounded | 0 | 0 | Native FMA where trusted, exact software fallback otherwise. Validated by the MPFR fma all-mode campaign. | +| fmax | Exact | 0 | 0 | | +| fmin | Exact | 0 | 0 | | +| fmod | Exact | 0 | 0 | Exact remainder by construction. | +| remainder | Exact | 0 | 0 | | +| remquo | Exact | 0 | 0 | | +| nan | Not a rounding function | n/a | n/a | Constructs a NaN payload. No rounded result. | + +### Comparison + +| Function | Class | Max ULP | Avg ULP | Notes | +|----------------|-------------------------|---------|---------|-------| +| fpclassify | Not a rounding function | n/a | n/a | | +| isfinite | Not a rounding function | n/a | n/a | | +| isgreater | Not a rounding function | n/a | n/a | | +| isgreaterequal | Not a rounding function | n/a | n/a | | +| isinf | Not a rounding function | n/a | n/a | | +| isless | Not a rounding function | n/a | n/a | | +| islessequal | Not a rounding function | n/a | n/a | | +| islessgreater | Not a rounding function | n/a | n/a | | +| isnan | Not a rounding function | n/a | n/a | | +| isnormal | Not a rounding function | n/a | n/a | | +| isunordered | Not a rounding function | n/a | n/a | | +| signbit | Not a rounding function | n/a | n/a | | ### Exponential -- [exp](#exp) -- [exp2](#exp2) -- [expm1](#expm1) -- [log](#log) -- [log1p](#log1p) -- [log2](#log2) -- [log10](#log10) - -### Float Manipulation -- [copysign](#copysign) -- [frexp](#frexp) -- [ilogb](#ilogb) -- [ldexp](#ldexp) -- [logb](#logb) -- [modf](#modf) -- [nextafter](#nextafter) -- [nexttoward](#nexttoward) -- [scalbn](#scalbn) + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|---------|---------|----------|----------------------------------|-------| +| expf (binary32) | Bounded | 1 | 7.62e-05 | ccm::gen::exp_gen | | +| exp (binary64) | Bounded | pending | pending | ccm::gen::exp_gen | | +| expl (long double) | Bounded | pending | pending | ccm::gen::exp_gen | | +| exp2f (binary32) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| exp2 (binary64) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| exp2l (long double) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| expm1f (binary32) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| expm1 (binary64) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| expm1l (long double) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| logf (binary32) | Bounded | pending | pending | ccm::gen::log_gen | | +| log (binary64) | Bounded | pending | pending | ccm::gen::log_gen | | +| logl (long double) | Bounded | pending | pending | ccm::gen::log_gen | | +| log1pf (binary32) | Bounded | 2 | 0.00453 | ccm::gen::log1p_gen | | +| log1p (binary64) | Bounded | pending | pending | ccm::gen::log1p_gen | | +| log1pl (long double) | Bounded | pending | pending | ccm::gen::log1p_gen | | +| log2f (binary32) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log2 (binary64) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log2l (long double) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log10f (binary32) | Bounded | pending | pending | ccm::gen::log10_gen | | +| log10 (binary64) | Bounded | pending | pending | ccm::gen::log10_gen | | +| log10l (long double) | Bounded | pending | pending | ccm::gen::log10_gen | | + +### Float manipulation + +| Function | Class | Max ULP | Avg ULP | Notes | +|------------|-------------------------|---------|---------|--------------------------------| +| copysign | Exact | 0 | 0 | | +| frexp | Exact | 0 | 0 | | +| ilogb | Not a rounding function | n/a | n/a | Returns an integer exponent. | +| ldexp | Exact | 0 | 0 | | +| logb | Exact | 0 | 0 | | +| modf | Exact | 0 | 0 | | +| nextafter | Exact | 0 | 0 | | +| nexttoward | Exact | 0 | 0 | | +| nextup | Exact | 0 | 0 | | +| nextdown | Exact | 0 | 0 | | +| scalbn | Exact | 0 | 0 | long double is slow but exact. | ### Hyperbolic -- [acosh](#acosh) -- [asinh](#asinh) -- [atanh](#atanh) -- [cosh](#cosh) -- [sinh](#sinh) -- [tanh](#tanh) - -### Nearest -- [ceil](#ceil) -- [floor](#floor) -- [nearbyint](#nearbyint) -- [rint](#rint) -- [round](#round) -- [trunc](#trunc) -### Power -- [cbrt](#cbrt) -- [hypot](#hypot) -- [pow](#pow) -- [sqrt](#sqrt) - -### Special Functions -- [assoc_laguerre](#assoc_laguerre) -- [assoc_legendre](#assoc_legendre) -- [beta](#beta) -- [comp_ellint_1](#comp_ellint_1) -- [comp_ellint_2](#comp_ellint_2) -- [comp_ellint_3](#comp_ellint_3) -- [cyl_bessel_i](#cyl_bessel_i) -- [cyl_bessel_j](#cyl_bessel_j) -- [cyl_bessel_k](#cyl_bessel_k) -- [cyl_neumann](#cyl_neumann) -- [ellint_1](#ellint_1) -- [ellint_2](#ellint_2) -- [ellint_3](#ellint_3) -- [expint](#expint) -- [hermite](#hermite) -- [laguerre](#laguerre) -- [legendre](#legendre) -- [riemann_zeta](#riemann_zeta) -- [sph_bessel](#sph_bessel) -- [sph_legendre](#sph_legendre) -- [sph_neumann](#sph_neumann) +| Function | Class | Max ULP | Avg ULP | Notes | +|----------|-----------------|----------|----------|-------| +| acosh | Not implemented | not impl | not impl | | +| asinh | Not implemented | not impl | not impl | | +| atanh | Not implemented | not impl | not impl | | +| cosh | Not implemented | not impl | not impl | | +| sinh | Not implemented | not impl | not impl | | +| tanh | Not implemented | not impl | not impl | | + +### Nearest integer + +| Function | Class | Max ULP | Avg ULP | Notes | +|-----------|-------|---------|---------|-------| +| ceil | Exact | 0 | 0 | | +| floor | Exact | 0 | 0 | | +| nearbyint | Exact | 0 | 0 | | +| rint | Exact | 0 | 0 | | +| round | Exact | 0 | 0 | | +| trunc | Exact | 0 | 0 | | -### Trigonometric -- [acos](#acos) -- [asin](#asin) -- [atan](#atan) -- [atan2](#atan2) -- [cos](#cos) -- [sin](#sin) -- [tan](#tan) - -### Misc Functions -- [gamma](#gamma) -- [lerp](#lerp) -- [lgamma](#lgamma) - -## ULP Status - -## Basic - - -### fabs - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|---------------------------------------------------------------------------------------| -| float | N/a | N/a | N/a | See rigorous MPFR `fmaf` all-mode campaigns and simple constexpr residual regressions | -| double | N/a | N/a | N/a | See rigorous MPFR `fma` all-mode campaigns and simple constexpr residual regressions | -| long double | N/a | N/a | N/a | `fmal` remains a target-format audit path in this pass | - - -### fdim - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### fma - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### (f)max - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### (f)min - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### remainder - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### remquo - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### nan - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Compare - - -### fpclassify - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isfinite - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isgreater - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isgreaterequal - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isinf - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isless - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### islessequal - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### islessgreater - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isnan - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isnormal - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### isunordered - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### signbit - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Exponential - - -### exp - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### exp2 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### expm1 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### log - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### log1p - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### log2 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### log10 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Float Manipulation - - -### copysign - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### frexp - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### ilogb - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### ldexp - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### logb - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### modf - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### nextafter - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### nexttoward - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### scalbn - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Hyperbolic - - -### acosh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### asinh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### atanh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cosh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### sinh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### tanh - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Nearest - - -### ceil - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### floor - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### nearbyint - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### rint - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### round - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### trunc - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Power - - -### cbrt - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | +### Power - -### hypot - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|-------------------|---------|---------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| cbrtf (binary32) | Bounded | 1 | 0.296 | ccm::gen::cbrt_gen | | +| cbrt (binary64) | Bounded | pending | pending | ccm::gen::cbrt_gen | | +| cbrtl (long double) | Bounded | pending | pending | ccm::gen::cbrt_gen | | +| hypotf (binary32) | Bounded | pending | pending | | Binary argument. Measurement is pending a binary sweep harness. | +| hypot (binary64) | Bounded | pending | pending | | | +| hypotl (long double) | Bounded | pending | pending | | | +| powf (binary32) | Bounded | 0 | 0 | ccm::gen::pow_gen | Binary, so sampled over the finite-result band and broad bit patterns rather than exhaustive. See the pow campaign appendix below. | +| pow (binary64) | Bounded | pending | pending | ccm::gen::pow_gen | | +| powl (long double) | Bounded | pending | pending | ccm::gen::pow_gen | | +| sqrtf (binary32) | Correctly rounded | 0 | 0 | ccm::gen::sqrt_gen | Correctly rounded by IEEE 754. | +| sqrt (binary64) | Correctly rounded | pending | pending | ccm::gen::sqrt_gen | | +| sqrtl (long double) | Correctly rounded | pending | pending | ccm::gen::sqrt_gen | | + +### Special functions + +| Function | Class | Max ULP | Avg ULP | Notes | +|----------------|-----------------|----------|----------|-------| +| assoc_laguerre | Not implemented | not impl | not impl | | +| assoc_legendre | Not implemented | not impl | not impl | | +| beta | Not implemented | not impl | not impl | | +| comp_ellint_1 | Not implemented | not impl | not impl | | +| comp_ellint_2 | Not implemented | not impl | not impl | | +| comp_ellint_3 | Not implemented | not impl | not impl | | +| cyl_bessel_i | Not implemented | not impl | not impl | | +| cyl_bessel_j | Not implemented | not impl | not impl | | +| cyl_bessel_k | Not implemented | not impl | not impl | | +| cyl_neumann | Not implemented | not impl | not impl | | +| ellint_1 | Not implemented | not impl | not impl | | +| ellint_2 | Not implemented | not impl | not impl | | +| ellint_3 | Not implemented | not impl | not impl | | +| expint | Not implemented | not impl | not impl | | +| hermite | Not implemented | not impl | not impl | | +| laguerre | Not implemented | not impl | not impl | | +| legendre | Not implemented | not impl | not impl | | +| riemann_zeta | Not implemented | not impl | not impl | | +| sph_bessel | Not implemented | not impl | not impl | | +| sph_legendre | Not implemented | not impl | not impl | | +| sph_neumann | Not implemented | not impl | not impl | | - -### pow +### Trigonometric -Rigorous oracle quick campaigns (apple aarch64, clang 17, June 7 2026). Not proofs. Max ULP is the same in all four IEEE rounding modes on the corpora below. +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|---------|---------|----------|----------------------------------|-----------------------------------------------------------------| +| acosf (binary32) | Bounded | pending | pending | ccm::internal::impl::acos_float | | +| acos (binary64) | Bounded | pending | pending | ccm::internal::impl::acos_double | | +| acosl (long double) | Bounded | pending | pending | ccm::internal::impl::acos_double | | +| asinf (binary32) | Bounded | pending | pending | ccm::internal::impl::asin_float | | +| asin (binary64) | Bounded | pending | pending | ccm::internal::impl::asin_double | | +| asinl (long double) | Bounded | pending | pending | ccm::internal::impl::asin_double | | +| atanf (binary32) | Bounded | 1 | 4.67e-10 | ccm::internal::impl::atan_float | | +| atan (binary64) | Bounded | pending | pending | ccm::internal::impl::atan_double | | +| atanl (long double) | Bounded | pending | pending | ccm::internal::impl::atan_double | | +| atan2f (binary32) | Bounded | pending | pending | | Binary argument. Measurement is pending a binary sweep harness. | +| atan2 (binary64) | Bounded | pending | pending | | | +| atan2l (long double) | Bounded | pending | pending | | | +| cosf (binary32) | Bounded | pending | pending | ccm::gen::cos_gen | | +| cos (binary64) | Bounded | pending | pending | ccm::gen::cos_gen | | +| cosl (long double) | Bounded | pending | pending | ccm::gen::cos_gen | | +| sinf (binary32) | Bounded | pending | pending | ccm::gen::sin_gen | | +| sin (binary64) | Bounded | pending | pending | ccm::gen::sin_gen | | +| sinl (long double) | Bounded | pending | pending | ccm::gen::sin_gen | | +| tanf (binary32) | Bounded | pending | pending | ccm::gen::tan_gen | | +| tan (binary64) | Bounded | pending | pending | ccm::gen::tan_gen | | +| tanl (long double) | Bounded | pending | pending | ccm::gen::tan_gen | | + +### Miscellaneous + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|-----------------------|---------|---------|---------|------------------------------|---------------------------------------------------------------------------------------------------------------------| +| tgammaf (binary32) | Bounded | pending | pending | ccm::internal::gamma_float | Listed as gamma in earlier status docs. | +| tgamma (binary64) | Bounded | pending | pending | ccm::internal::gamma_double | | +| tgammal (long double) | Bounded | pending | pending | ccm::internal::gamma_double | | +| lerp (binary32) | Bounded | pending | pending | | Ternary argument, and a C++ template with no f or l suffix variant. Measurement is pending a ternary sweep harness. | +| lerp (binary64) | Bounded | pending | pending | | | +| lerp (long double) | Bounded | pending | pending | | | +| lgammaf (binary32) | Bounded | pending | pending | ccm::internal::lgamma_float | | +| lgamma (binary64) | Bounded | pending | pending | ccm::internal::lgamma_double | | +| lgammal (long double) | Bounded | pending | pending | ccm::internal::lgamma_double | | + + + +## Scope and caveats + +This pass measures binary32. binary64 is the next job, and long double after that, so both show up as +pending. The accuracy families list all three variants so the gaps are explicit rather than implied. + +The atan2, hypot, and lerp functions take more than one argument, so they wait on a binary and +ternary sweep and are marked pending. pow is measured separately, and its detailed campaign is in +the appendix below. + +Near a zero or a pole, ULP error is governed by conditioning rather than by rounding, because a +tiny absolute error sits across many representable values when the result itself is near zero or +very large. Functions with poles or zeros in range, such as tan near its poles and lgamma near its +zeros at 1 and 2, can show a large max and average that reflect how close the corpus landed to +those spots rather than the kernel's typical accuracy. + +## Appendix: pow campaign detail + +Rigorous oracle quick campaigns (apple aarch64, clang 17, June 7 2026). Not proofs. Max ULP is the +same in all four IEEE rounding modes on the corpora below. **Failure columns** @@ -581,312 +285,19 @@ Rigorous oracle quick campaigns (apple aarch64, clang 17, June 7 2026). Not proo | MPFR hard failures | Cases that exceed the campaign 4 ULP ceiling, or disagree with MPFR on NaN, infinity, or signed zero. Within-ceiling results that miss the 0.5 ULP correctly-rounded target are tracked separately and are not counted here. | | CORE-MATH bit mismatches | Cases where ccm bits differ from the CORE-MATH cr_* reference in the active rounding mode. This is not a ULP threshold. Cases where a higher-precision cross-check shows ccm is correct and the CORE-MATH oracle is wrong are excluded. | -Per-case logs with base, exponent, actual, and expected bit patterns live under [tests/rigorous/oracle_logs/README.md](../tests/rigorous/oracle_logs/README.md). Re-run the rigorous oracle ctest targets to refresh them. - -| Function | Path / configuration | MPFR cases | MPFR Max ULP | MPFR hard failures | CORE-MATH cases | CORE-MATH Max ULP | CORE-MATH bit mismatches | Notes | -|-------------|------------------------------------------------|------------|--------------|--------------------|-----------------|-------------------|--------------------------|-----------------------------------------------------------------------------------| -| `ccm::pow` | `public_default` | 3132 | 1 | 0 | 2068 | 1 | 59 | MPFR structured binary64 corpus. CORE-MATH finite regular corpus. | -| `ccm::pow` | `runtime_no_builtin`, `generic_modeled_domain` | N/a | N/a | 0 | N/a | N/a | N/a | Path-matrix quick campaign, no-builtin build only. | -| `ccm::powf` | `public_default` | 68036 | 1 | 2 | 62372 | 1 | 2050 | Structured binary32 corpus. Directional modes carry most CORE-MATH mismatches. | -| `ccm::powf` | Reduced domains | 6144 | 1 | 0 | 6144 | 1 | 396 | Mantissa/subnormal finite domains. Not evidence for NaN, infinity, or zero cases. | -| `ccm::powl` | ld64 alias | 144 | 0 | 0 | N/a | N/a | N/a | Double-shaped platform. ld64_conservative corpus. | -| `ccm::powl` | ld80 special + bounded int + general finite | N/a | N/a | N/a | N/a | N/a | N/a | Not exercised on double-shaped platforms. | -| `ccm::powl` | ld128 / unknown strict | N/a | N/a | N/a | N/a | N/a | N/a | Detection and NaN policy tests only. | -| `ccm::powl` | reduced-precision fallback | N/a | N/a | N/a | N/a | N/a | N/a | Only when CCMATH_ENABLE_REDUCED_PRECISION_POWL=ON. | - - - -### sqrt - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Special Functions - - -### assoc_laguerre - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### assoc_legendre - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### beta - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### comp_ellint_1 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### comp_ellint_2 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### comp_ellint_3 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cyl_bessel_i - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cyl_bessel_j - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cyl_bessel_k - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cyl_neumann - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### ellint_1 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### ellint_2 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### ellint_3 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### expint - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### hermite - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### laguerre - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### legendre - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### riemann_zeta - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### sph_bessel - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### sph_legendre - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### sph_neumann - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Trigonometric - - -### acos - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### asin - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### atan - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### atan2 - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### cos - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### sin - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### tan - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -## Misc Functions - - -### gamma - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### lerp - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - - -### lgamma - -| Precision | Min ULP | Max ULP | Avg ULP | Notes | -|-------------|---------|---------|---------|-------| -| float | N/a | N/a | N/a | | -| double | N/a | N/a | N/a | | -| long double | N/a | N/a | N/a | | - -> Last Updated: June 7, 2026 +Per-case logs with base, exponent, actual, and expected bit patterns live under +[tests/rigorous/oracle_logs/README.md](../tests/rigorous/oracle_logs/README.md). Re-run the +rigorous oracle ctest targets to refresh them. + +| Function | Configuration | MPFR cases | MPFR Max ULP | MPFR hard failures | CORE-MATH cases | CORE-MATH Max ULP | CORE-MATH bit mismatches | Notes | +|-----------|---------------------------------------------|------------|--------------|--------------------|-----------------|-------------------|--------------------------|-----------------------------------------------------------------------------------| +| ccm::pow | public_default | 3132 | 1 | 0 | 2068 | 1 | 59 | MPFR structured binary64 corpus. CORE-MATH finite regular corpus. | +| ccm::pow | runtime_no_builtin, generic_modeled_domain | N/a | N/a | 0 | N/a | N/a | N/a | Configuration-matrix quick campaign, no-builtin build only. | +| ccm::powf | public_default | 68036 | 1 | 2 | 62372 | 1 | 2050 | Structured binary32 corpus. Directional modes carry most CORE-MATH mismatches. | +| ccm::powf | Reduced domains | 6144 | 1 | 0 | 6144 | 1 | 396 | Mantissa/subnormal finite domains. Not evidence for NaN, infinity, or zero cases. | +| ccm::powl | ld64 alias | 144 | 0 | 0 | N/a | N/a | N/a | Double-shaped platform. ld64_conservative corpus. | +| ccm::powl | ld80 special + bounded int + general finite | N/a | N/a | N/a | N/a | N/a | N/a | Not exercised on double-shaped platforms. | +| ccm::powl | ld128 / unknown strict | N/a | N/a | N/a | N/a | N/a | N/a | Detection and NaN policy tests only. | +| ccm::powl | reduced-precision fallback | N/a | N/a | N/a | N/a | N/a | N/a | Only when CCMATH_ENABLE_REDUCED_PRECISION_POWL=ON. | + +> Last Updated: June 17, 2026 diff --git a/include/ccmath/internal/support/fenv/host_fenv.hpp b/include/ccmath/internal/support/fenv/host_fenv.hpp index ad16d3b9..f618ffab 100644 --- a/include/ccmath/internal/support/fenv/host_fenv.hpp +++ b/include/ccmath/internal/support/fenv/host_fenv.hpp @@ -26,6 +26,13 @@ // CCM_CONFIG_FORCE_FENV_H to exercise the tier on a host that also has (testing // only). // +// Freestanding builds are supported and resolved in config/freestanding.hpp. The host floating-point +// environment stays optional here, so a freestanding target needs no extra handling: this header +// uses or when the toolchain ships them and degrades to round-to-nearest when it +// does not. errno is not a freestanding obstacle either, since is a freestanding header in +// C++26 (Table 27 in [compliance]) and only supplies the EDOM and ERANGE macros plus the errno +// lvalue. The actual errno write stays behind CCM_CONFIG_DISABLE_ERRNO in fenv_support.hpp. +// // The detection rules out a missing __has_include first so that __has_include(...) is only used // once the compiler is known to provide it, which keeps every supported compiler (including MSVC) // happy. From 1d63baedaa3c670460fa517e9c1d5d3d2c68bbcd Mon Sep 17 00:00:00 2001 From: ianpike Date: Thu, 18 Jun 2026 17:32:45 -0400 Subject: [PATCH 38/52] Begin support for freestanding and also add in improvements to measuring ULPs --- cmake/config/BuildManifest.cmake | 9 + cmake/config/OracleCampaignRegistry.cmake | 33 + cmake/gen/meson.build | 3 + include/ccmath/internal/config/CMakeLists.txt | 1 + .../ccmath/internal/config/freestanding.hpp | 35 + .../math/runtime/func/detail/system_math.hpp | 9 +- .../internal/math/runtime/pp/simd_mask.hpp | 9 +- meson_options.txt | 1 + premake5.lua | 11 + tests/shared/oracle/mpfr_unary.cpp | 421 +++++ tests/shared/oracle/mpfr_unary_common.hpp | 1442 +++++++++++++++++ tools/measure_ulp.sh | 87 + tools/ulp_status.py | 229 +++ tools/ulp_status_registry.json | 452 ++++++ 14 files changed, 2739 insertions(+), 3 deletions(-) create mode 100644 include/ccmath/internal/config/freestanding.hpp create mode 100644 tests/shared/oracle/mpfr_unary.cpp create mode 100644 tests/shared/oracle/mpfr_unary_common.hpp create mode 100755 tools/measure_ulp.sh create mode 100755 tools/ulp_status.py create mode 100644 tools/ulp_status_registry.json diff --git a/cmake/config/BuildManifest.cmake b/cmake/config/BuildManifest.cmake index af89f100..874cf195 100644 --- a/cmake/config/BuildManifest.cmake +++ b/cmake/config/BuildManifest.cmake @@ -13,6 +13,7 @@ set(CCMATH_LIBRARY_MANIFEST_OPTION_KEYS runtime_simd disable_errno disable_fenv + freestanding reduced_precision_powl deterministic ) @@ -41,6 +42,14 @@ set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DEFINE CCM_CONFIG_DISABLE_FENV) set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DESCRIPTION "Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)") +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_VAR CCMATH_FREESTANDING) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_MESON_OPTION freestanding) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_DEFINE CCM_CONFIG_FREESTANDING) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_DESCRIPTION + "Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)") + set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_VAR CCMATH_DISABLE_REDUCED_PRECISION_POWL) set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_DEFAULT OFF) set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_INVERT FALSE) diff --git a/cmake/config/OracleCampaignRegistry.cmake b/cmake/config/OracleCampaignRegistry.cmake index 749f7495..be660ea9 100644 --- a/cmake/config/OracleCampaignRegistry.cmake +++ b/cmake/config/OracleCampaignRegistry.cmake @@ -11,6 +11,7 @@ set(CCMATH_ORACLE_MPFR_EXECUTABLE_IDS powl_characterization pow_search cross_libm_pow + mpfr_unary ) set(CCMATH_ORACLE_MPFR_mpfr_pow_double_TARGET ccmath-rigorous-mpfr-pow) @@ -124,6 +125,38 @@ set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_TIMEOUT 60) set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_LABELS rigorous mpfr) set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_ARGS --corpus=quick --format=json --output=cross-libm-pow-quick.json) +set(CCMATH_ORACLE_MPFR_mpfr_unary_TARGET ccmath-rigorous-mpfr-unary) +set(CCMATH_ORACLE_MPFR_mpfr_unary_SOURCE ../shared/oracle/mpfr_unary.cpp) + +# Sampled, CI-friendly smoke per unary function. The doc-quality numbers (large sampled binary64 +# and exhaustive binary32) come from tools/measure_ulp.sh, not from these ctests, so these stay +# bounded and do not write the canonical summary JSON the doc generator consumes. +set(CCMATH_ORACLE_MPFR_UNARY_DOUBLE_FUNCTIONS + exp exp2 expm1 log log1p log2 log10 sin cos tan asin acos atan cbrt sqrt tgamma lgamma) +set(CCMATH_ORACLE_MPFR_UNARY_FLOAT_FUNCTIONS exp log sin sqrt tgamma) + +set(CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS "") +foreach (_ccm_unary_fn IN LISTS CCMATH_ORACLE_MPFR_UNARY_DOUBLE_FUNCTIONS) + set(_ccm_unary_id unary_${_ccm_unary_fn}_double) + list(APPEND CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS ${_ccm_unary_id}) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_NAME ccmath-rigorous-mpfr-unary-${_ccm_unary_fn}-double) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TARGET ccmath-rigorous-mpfr-unary) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TIMEOUT 300) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_LABELS rigorous mpfr) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_ARGS + --function=${_ccm_unary_fn} --type=double --mode=quick --count=20000 --rounding-modes=all) +endforeach () +foreach (_ccm_unary_fn IN LISTS CCMATH_ORACLE_MPFR_UNARY_FLOAT_FUNCTIONS) + set(_ccm_unary_id unary_${_ccm_unary_fn}_float) + list(APPEND CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS ${_ccm_unary_id}) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_NAME ccmath-rigorous-mpfr-unary-${_ccm_unary_fn}-float) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TARGET ccmath-rigorous-mpfr-unary) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TIMEOUT 300) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_LABELS rigorous mpfr) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_ARGS + --function=${_ccm_unary_fn} --type=float --mode=quick --count=20000 --rounding-modes=all) +endforeach () + set(CCMATH_ORACLE_COREMATH_EXECUTABLE_IDS coremath_pow_double coremath_pow_float diff --git a/cmake/gen/meson.build b/cmake/gen/meson.build index ca8d7e38..30e907d9 100644 --- a/cmake/gen/meson.build +++ b/cmake/gen/meson.build @@ -27,6 +27,9 @@ endif if get_option('disable_fenv') _ccmath_defines += '-DCCM_CONFIG_DISABLE_FENV' endif +if get_option('freestanding') + _ccmath_defines += '-DCCM_CONFIG_FREESTANDING' +endif if get_option('disable_reduced_precision_powl') _ccmath_defines += '-DCCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL' endif diff --git a/include/ccmath/internal/config/CMakeLists.txt b/include/ccmath/internal/config/CMakeLists.txt index fddb30d4..50e44176 100644 --- a/include/ccmath/internal/config/CMakeLists.txt +++ b/include/ccmath/internal/config/CMakeLists.txt @@ -1,5 +1,6 @@ ccm_add_headers( compiler.hpp + freestanding.hpp type_support.hpp ) diff --git a/include/ccmath/internal/config/freestanding.hpp b/include/ccmath/internal/config/freestanding.hpp new file mode 100644 index 00000000..b6cc81ab --- /dev/null +++ b/include/ccmath/internal/config/freestanding.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +// Single point of truth for whether ccmath is building for a freestanding C++ environment. +// +// A freestanding environment (a console SDK, an embedded target, a kernel) provides only the +// freestanding subset of the standard library. ccmath stays inside that subset: every standard +// header it pulls into the library is a freestanding header in C++26 (Table 27 in [compliance]), +// including , and ccmath only ever touches the non-throwing members of the partially +// freestanding ones. The host floating-point environment is optional and resolved separately in +// host_fenv.hpp, which uses the host / when the toolchain ships them and degrades +// to round-to-nearest when it does not. The only headers a freestanding build deliberately drops +// are the test-only path in the runtime SIMD layer and the MSVC system-math path. +// +// CCM_CONFIG_FREESTANDING is auto-detected from __STDC_HOSTED__ but can be set either way by hand: +// define CCM_CONFIG_FREESTANDING to force it on (the build option does this), or define +// CCM_CONFIG_HOSTED to force it off on a toolchain that reports freestanding. Like every other +// CCM_CONFIG_ flag it is presence based, so define it, do not set it to a value. + +#if defined(CCM_CONFIG_FREESTANDING) +// Forced on: keep it defined and skip detection. +#elif defined(CCM_CONFIG_HOSTED) +// Forced off: leave CCM_CONFIG_FREESTANDING undefined so every consumer takes the hosted branch. +#elif defined(__STDC_HOSTED__) && (__STDC_HOSTED__ == 0) + #define CCM_CONFIG_FREESTANDING 1 +#endif diff --git a/include/ccmath/internal/math/runtime/func/detail/system_math.hpp b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp index fc1502bc..ebdee180 100644 --- a/include/ccmath/internal/math/runtime/func/detail/system_math.hpp +++ b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp @@ -10,6 +10,8 @@ #pragma once +#include "ccmath/internal/config/freestanding.hpp" + // On MSVC there is no foldable __builtin math and no SVML transcendental path, so the runtime scalar // fallback would otherwise use ccmath's generic kernels, which are not accurate enough to match the // system libm. Including the real and calling the global C runtime entry points gives UCRT @@ -19,8 +21,11 @@ // // CCM_CONFIG_SYSTEM_MATH is on by default for MSVC-proper. Define CCM_CONFIG_DISABLE_SYSTEM_MATH to // turn it off and keep the build free of (the generic kernels are used instead). Define -// CCM_CONFIG_FORCE_SYSTEM_MATH to exercise this path on other compilers for testing. -#if !defined(CCM_CONFIG_DISABLE_SYSTEM_MATH) && (defined(CCM_CONFIG_FORCE_SYSTEM_MATH) || (defined(_MSC_VER) && !defined(__clang__))) +// CCM_CONFIG_FORCE_SYSTEM_MATH to exercise this path on other compilers for testing. A freestanding +// build never takes this path: is not a freestanding header, so CCM_CONFIG_FREESTANDING +// wins over both the MSVC default and CCM_CONFIG_FORCE_SYSTEM_MATH. +#if !defined(CCM_CONFIG_DISABLE_SYSTEM_MATH) && !defined(CCM_CONFIG_FREESTANDING) && \ + (defined(CCM_CONFIG_FORCE_SYSTEM_MATH) || (defined(_MSC_VER) && !defined(__clang__))) #define CCM_CONFIG_SYSTEM_MATH 1 #endif diff --git a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp index 73a31f51..00f2c01c 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp @@ -10,15 +10,20 @@ #pragma once +#include "ccmath/internal/config/freestanding.hpp" #include "ccmath/internal/math/runtime/pp/declaration.hpp" #include "ccmath/internal/math/runtime/pp/scalar.hpp" #include "ccmath/internal/math/runtime/pp/utility.hpp" #include "ccmath/internal/math/runtime/pp/vec_ext.hpp" #include "ccmath/internal/predef/attributes/always_inline.hpp" -#include #include +// is not a freestanding header, so to_bitset() is only available in hosted builds. +#if !defined(CCM_CONFIG_FREESTANDING) + #include +#endif + namespace ccm::pp { // [simd.mask] A data-parallel boolean. Parametrized by lane byte width to @@ -62,8 +67,10 @@ namespace ccm::pp } return bits; } +#if !defined(CCM_CONFIG_FREESTANDING) [[nodiscard]] CCM_ALWAYS_INLINE std::bitset(SimdTraits, Abi>::size)> to_bitset() const { return std::bitset(size())>(to_ullong()); } +#endif CCM_ALWAYS_INLINE static basic_simd_mask from_bits(unsigned long long bits) { basic_simd_mask r; diff --git a/meson_options.txt b/meson_options.txt index 83578db3..518b27a0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,5 +4,6 @@ option('enable_runtime_simd', type: 'boolean', value: true, description: 'Enable SIMD optimization for runtime evaluation (does not affect compile-time)') option('disable_errno', type: 'boolean', value: false, description: 'Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)') option('disable_fenv', type: 'boolean', value: false, description: 'Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)') +option('freestanding', type : 'boolean', value : false, description : 'Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)') option('disable_reduced_precision_powl', type: 'boolean', value: false, description: 'Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback') option('deterministic', type: 'boolean', value: false, description: 'Produce bit-identical cross-hardware math: route transcendentals through the generic kernels (no libm), force the correctly-rounded FMA path, disable runtime SIMD, and evaluate long double in double precision (GCC/Clang)') diff --git a/premake5.lua b/premake5.lua index b0dcc190..19c0fe3d 100644 --- a/premake5.lua +++ b/premake5.lua @@ -35,6 +35,13 @@ newoption { default = "false", } +newoption { + trigger = "ccmath-freestanding", + description = "Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)", + allowed = { { "true", "Enable" }, { "false", "Disable" } }, + default = "false", +} + newoption { trigger = "ccmath-disable-reduced-precision-powl", description = "Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback", @@ -126,6 +133,10 @@ function ccmath.defines() table.insert(defs, "CCM_CONFIG_DISABLE_FENV") end + if _ccmath_option_enabled("ccmath-freestanding", false) then + table.insert(defs, "CCM_CONFIG_FREESTANDING") + end + if _ccmath_option_enabled("ccmath-disable-reduced-precision-powl", false) then table.insert(defs, "CCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL") end diff --git a/tests/shared/oracle/mpfr_unary.cpp b/tests/shared/oracle/mpfr_unary.cpp new file mode 100644 index 00000000..f9e85afb --- /dev/null +++ b/tests/shared/oracle/mpfr_unary.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "ccmath/ccmath.hpp" +#include "mpfr_unary_common.hpp" +#include "utils/test_runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// Measurement executable for the elementary functions, generic over arity. Adding a function is one +// case below: name it, point it at the ccm kernel, the MPFR reference, the libm exceptional +// reference, and a corpus. Invoke as: +// mpfr_unary --function=exp --type=float --exhaustive --rounding-modes=nearest +// mpfr_unary --function=pow --type=float --mode=extended --count=5000000 --rounding-modes=nearest +// +// --impl picks which implementation is measured: gen (the generic kernel, the default), public (the +// ccm:: public path, which lowers to libm under FE_TONEAREST so it matches libm in round-to-nearest), +// or libm (the system math library). The implementation is recorded in the output and the summary JSON. +// mpfr_unary --function=exp --type=double --impl=libm --rounding-modes=upward +// +// Region targeting. Clip any argument and the corpus switches to a confined region sample. The bounds +// are --lo/--hi (value range) and --scale=linear|log with --min-exp/--max-exp (sample across binades). +// An unsuffixed flag applies to every argument, a suffix picks one argument (--lo1 is pow's exponent): +// mpfr_unary --function=exp --type=float --lo=0 --hi=1 --count=20000000 +// mpfr_unary --function=pow --type=float --lo0=1 --hi0=2 --scale1=log --min-exp1=-10 --max-exp1=10 +// Add --confine to drop the structured edges and sample the region only. Add --progress for a live +// percent/throughput/ETA line on the long runs. A sampled sweep drops exact-bit repeat inputs by +// default so no value is measured twice; pass --allow-duplicates to keep them, which skips the dedup +// sort on a very large sample over a vast space (pow) where repeats are statistically absent anyway. +// +// Exhaustive binary32 can sweep a sub-range instead of all 2^32 patterns: pass a finite --lo/--hi +// (every float in that range is swept) or an explicit --exhaustive-range=START:END over bit patterns +// (decimal or 0x hex, inclusive): +// mpfr_unary --function=sin --type=float --exhaustive --lo=1 --hi=2 +// mpfr_unary --function=cbrt --type=float --exhaustive-range=0x3f800000:0x40000000 +// +// Every default sampled sweep also measures a deterministic seed of hard cases: the special-value +// matrix (pow corners such as pow(x, +/-0) and pow(negative, non-integer)), per-family branch points +// and cancellation regions (powers of two, the neighbourhood of 1, large multiples of pi/2, the gamma +// poles), and the exact over/underflow boundary inputs found by bisecting the reference. Replay an +// external worst-case table or a prior --record-output dump with --input-file (one case per line, N +// whitespace-separated values), and add --probe-neighbors to also test each seed's nextafter neighbours: +// mpfr_unary --function=sin --type=double --input-file=worst_cases.txt --probe-neighbors +// +// Pass --record-output= with --threshold=N to dump every input whose ULP distance is N or +// greater to a text file, one case per line, as they are found: +// mpfr_unary --function=tan --type=float --exhaustive --threshold=2 --record-output=tan_above2.txt +namespace +{ + namespace oracle = ccm::test::oracle::unary_oracle; + using oracle::domain; + + constexpr double kInf = std::numeric_limits::infinity(); + + // Convenience wrapper for the standard unary functions: a scalar ccm kernel, a standard MPFR + // unary op, a scalar libm reference, and a sampling domain. It adapts the scalar callables to the + // arity-1 array interface the generic campaign expects. + // Convenience wrapper for the standard unary functions. --impl picks which of the three callables is + // measured: the generic kernel (default), the public ccmath path, or the system libm. The MPFR + // reference and the exceptional libm cross-check are unaffected by the choice. + template + int run_standard(int argc, + char** argv, + std::string_view name, + GenFn gen_scalar, + PublicFn public_scalar, + oracle::mpfr_unary_op op, + StdFn std_scalar, + domain dom, + oracle::function_family family) + { + const oracle::impl_kind impl = oracle::parse_impl(argc, argv); + return oracle::run_campaign( + argc, + argv, + name, + [gen_scalar, public_scalar, std_scalar, impl](const std::array& inputs) -> T + { + switch (impl) + { + case oracle::impl_kind::libm: return std_scalar(inputs[0]); + case oracle::impl_kind::public_api: return public_scalar(inputs[0]); + case oracle::impl_kind::generic: break; + } + return gen_scalar(inputs[0]); + }, + oracle::make_unary_ref(op), + [std_scalar](const std::array& inputs) { return std_scalar(inputs[0]); }, + [dom](std::uint64_t seed, std::size_t count) { return oracle::build_unary_corpus(dom, seed, count); }, + std::array{ dom }, + oracle::unary_structured_seed(std_scalar, family), + oracle::impl_token(impl)); + } + + template + int dispatch(std::string_view function, int argc, char** argv) + { + if (function == "exp") + { + return run_standard( + argc, + argv, + "ccm::exp", + [](T x) { return ccm::gen::exp_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::exp(ccm::test::runtime_value(x)); }, + mpfr_exp, + [](T x) { return std::exp(x); }, + domain{ -745.0, 710.0 }, + oracle::function_family::exp); + } + if (function == "exp2") + { + return run_standard( + argc, + argv, + "ccm::exp2", + [](T x) { return ccm::gen::exp2_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::exp2(ccm::test::runtime_value(x)); }, + mpfr_exp2, + [](T x) { return std::exp2(x); }, + domain{ -1100.0, 1030.0 }, + oracle::function_family::exp); + } + if (function == "expm1") + { + return run_standard( + argc, + argv, + "ccm::expm1", + [](T x) { return ccm::gen::expm1_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::expm1(ccm::test::runtime_value(x)); }, + mpfr_expm1, + [](T x) { return std::expm1(x); }, + domain{ -50.0, 710.0 }, + oracle::function_family::exp); + } + if (function == "log") + { + return run_standard( + argc, + argv, + "ccm::log", + [](T x) { return ccm::gen::log_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::log(ccm::test::runtime_value(x)); }, + mpfr_log, + [](T x) { return std::log(x); }, + domain{ 0.0, kInf, true }, + oracle::function_family::logarithm); + } + if (function == "log1p") + { + return run_standard( + argc, + argv, + "ccm::log1p", + [](T x) { return ccm::gen::log1p_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::log1p(ccm::test::runtime_value(x)); }, + mpfr_log1p, + [](T x) { return std::log1p(x); }, + domain{ -1.0, kInf, true }, + oracle::function_family::logarithm); + } + if (function == "log2") + { + return run_standard( + argc, + argv, + "ccm::log2", + [](T x) { return ccm::gen::log2_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::log2(ccm::test::runtime_value(x)); }, + mpfr_log2, + [](T x) { return std::log2(x); }, + domain{ 0.0, kInf, true }, + oracle::function_family::logarithm); + } + if (function == "log10") + { + return run_standard( + argc, + argv, + "ccm::log10", + [](T x) { return ccm::gen::log10_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::log10(ccm::test::runtime_value(x)); }, + mpfr_log10, + [](T x) { return std::log10(x); }, + domain{ 0.0, kInf, true }, + oracle::function_family::logarithm); + } + if (function == "sin") + { + return run_standard( + argc, + argv, + "ccm::sin", + [](T x) { return ccm::gen::sin_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::sin(ccm::test::runtime_value(x)); }, + mpfr_sin, + [](T x) { return std::sin(x); }, + domain{ -kInf, kInf, true }, + oracle::function_family::trig); + } + if (function == "cos") + { + return run_standard( + argc, + argv, + "ccm::cos", + [](T x) { return ccm::gen::cos_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::cos(ccm::test::runtime_value(x)); }, + mpfr_cos, + [](T x) { return std::cos(x); }, + domain{ -kInf, kInf, true }, + oracle::function_family::trig); + } + if (function == "tan") + { + return run_standard( + argc, + argv, + "ccm::tan", + [](T x) { return ccm::gen::tan_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::tan(ccm::test::runtime_value(x)); }, + mpfr_tan, + [](T x) { return std::tan(x); }, + domain{ -kInf, kInf, true }, + oracle::function_family::trig); + } + if (function == "asin") + { + return run_standard( + argc, + argv, + "ccm::asin", + [](T x) -> T + { + if constexpr (std::is_same_v) { return ccm::internal::impl::asin_float(ccm::test::runtime_value(x)); } + else + { + return ccm::internal::impl::asin_double(ccm::test::runtime_value(x)); + } + }, + [](T x) { return ccm::asin(ccm::test::runtime_value(x)); }, + mpfr_asin, + [](T x) { return std::asin(x); }, + domain{ -1.0, 1.0 }, + oracle::function_family::inverse_trig); + } + if (function == "acos") + { + return run_standard( + argc, + argv, + "ccm::acos", + [](T x) -> T + { + if constexpr (std::is_same_v) { return ccm::internal::impl::acos_float(ccm::test::runtime_value(x)); } + else + { + return ccm::internal::impl::acos_double(ccm::test::runtime_value(x)); + } + }, + [](T x) { return ccm::acos(ccm::test::runtime_value(x)); }, + mpfr_acos, + [](T x) { return std::acos(x); }, + domain{ -1.0, 1.0 }, + oracle::function_family::inverse_trig); + } + if (function == "atan") + { + return run_standard( + argc, + argv, + "ccm::atan", + [](T x) -> T + { + if constexpr (std::is_same_v) { return ccm::internal::impl::atan_float(ccm::test::runtime_value(x)); } + else + { + return ccm::internal::impl::atan_double(ccm::test::runtime_value(x)); + } + }, + [](T x) { return ccm::atan(ccm::test::runtime_value(x)); }, + mpfr_atan, + [](T x) { return std::atan(x); }, + domain{ -kInf, kInf, true }, + oracle::function_family::inverse_trig); + } + if (function == "cbrt") + { + return run_standard( + argc, + argv, + "ccm::cbrt", + [](T x) { return ccm::gen::cbrt_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::cbrt(ccm::test::runtime_value(x)); }, + mpfr_cbrt, + [](T x) { return std::cbrt(x); }, + domain{ -kInf, kInf, true }, + oracle::function_family::power); + } + if (function == "sqrt") + { + return run_standard( + argc, + argv, + "ccm::sqrt", + [](T x) { return ccm::gen::sqrt_gen(ccm::test::runtime_value(x)); }, + [](T x) { return ccm::sqrt(ccm::test::runtime_value(x)); }, + mpfr_sqrt, + [](T x) { return std::sqrt(x); }, + domain{ 0.0, kInf, true }, + oracle::function_family::power); + } + if (function == "tgamma") + { + return run_standard( + argc, + argv, + "ccm::tgamma", + [](T x) -> T + { + if constexpr (std::is_same_v) { return ccm::internal::gamma_float(ccm::test::runtime_value(x)); } + else + { + return ccm::internal::gamma_double(ccm::test::runtime_value(x)); + } + }, + [](T x) { return ccm::tgamma(ccm::test::runtime_value(x)); }, + mpfr_gamma, + [](T x) { return std::tgamma(x); }, + domain{ -20.0, 171.0 }, + oracle::function_family::gamma); + } + if (function == "lgamma") + { + const oracle::impl_kind impl = oracle::parse_impl(argc, argv); + return oracle::run_campaign( + argc, + argv, + "ccm::lgamma", + [impl](const std::array& inputs) -> T + { + switch (impl) + { + case oracle::impl_kind::libm: return std::lgamma(inputs[0]); + case oracle::impl_kind::public_api: return ccm::lgamma(ccm::test::runtime_value(inputs[0])); + case oracle::impl_kind::generic: break; + } + if constexpr (std::is_same_v) { return ccm::internal::lgamma_float(ccm::test::runtime_value(inputs[0])); } + else + { + return ccm::internal::lgamma_double(ccm::test::runtime_value(inputs[0])); + } + }, + oracle::make_lgamma_ref(), + [](const std::array& inputs) { return std::lgamma(inputs[0]); }, + [](std::uint64_t seed, std::size_t count) { return oracle::build_unary_corpus(domain{ -50.0, kInf, true }, seed, count); }, + std::array{ domain{ -50.0, kInf, true } }, + oracle::unary_structured_seed([](T x) { return std::lgamma(x); }, oracle::function_family::gamma), + oracle::impl_token(impl)); + } + if (function == "pow") + { + const oracle::impl_kind impl = oracle::parse_impl(argc, argv); + return oracle::run_campaign( + argc, + argv, + "ccm::pow", + [impl](const std::array& inputs) -> T + { + switch (impl) + { + case oracle::impl_kind::libm: return std::pow(inputs[0], inputs[1]); + case oracle::impl_kind::public_api: return ccm::pow(ccm::test::runtime_value(inputs[0]), ccm::test::runtime_value(inputs[1])); + case oracle::impl_kind::generic: break; + } + return ccm::gen::pow_gen(ccm::test::runtime_value(inputs[0]), ccm::test::runtime_value(inputs[1])); + }, + oracle::make_pow_ref(), + [](const std::array& inputs) { return std::pow(inputs[0], inputs[1]); }, + [](std::uint64_t seed, std::size_t count) { return oracle::build_pow_corpus(seed, count); }, + std::array{ domain{ -kInf, kInf, true }, domain{ -kInf, kInf, true } }, + oracle::pow_special_matrix(), + oracle::impl_token(impl)); + } + + std::cerr << "unknown --function=" << function << '\n'; + return 2; + } +} // namespace + +int main(int argc, char** argv) +{ + std::string function = "exp"; + std::string type = "double"; + for (int i = 1; i < argc; ++i) + { + const std::string_view arg(argv[i]); + if (arg.rfind("--function=", 0) == 0) { function = std::string(arg.substr(std::string_view("--function=").size())); } + else if (arg.rfind("--type=", 0) == 0) { type = std::string(arg.substr(std::string_view("--type=").size())); } + } + + if (type == "float") { return dispatch(function, argc, argv); } + if (type == "double") { return dispatch(function, argc, argv); } + + std::cerr << "unknown --type=" << type << " (expected float or double)\n"; + return 2; +} diff --git a/tests/shared/oracle/mpfr_unary_common.hpp b/tests/shared/oracle/mpfr_unary_common.hpp new file mode 100644 index 00000000..492ba8e2 --- /dev/null +++ b/tests/shared/oracle/mpfr_unary_common.hpp @@ -0,0 +1,1442 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +#include "mpfr_oracle_harness.hpp" +#include "oracle_campaign_common.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Elementary-function campaign against a correctly-rounded MPFR reference, generic over arity. +// +// The inputs of a case are a std::array, so unary, binary, and ternary functions all run +// through one evaluation, summary, report, and JSON path. Only three things vary with arity: the +// ccm kernel call, the MPFR reference call (each MPFR function has a fixed signature, so a small +// make_*_ref helper adapts it), and how the corpus is built. Round-to-nearest binary32 with N == 1 +// can be swept exhaustively over every bit pattern; everything else is sampled. +// +// The namespace keeps its historical name. It is no longer unary-only. +namespace ccm::test::oracle::unary_oracle +{ + // Per-thread MPFR scratch with N operands plus an output, reused across evaluations so the + // exhaustive sweep does not allocate on every one of billions of references. Precision is fixed + // for a campaign, so the grow case only runs on the first call. + template + struct mpfr_scratch + { + mpfr_t in[N]; + mpfr_t out; + mpfr_prec_t precision = 0; + + mpfr_scratch() = default; + mpfr_scratch(const mpfr_scratch&) = delete; + mpfr_scratch& operator=(const mpfr_scratch&) = delete; + ~mpfr_scratch() + { + if (precision != 0) + { + for (std::size_t i = 0; i < N; ++i) { mpfr_clear(in[i]); } + mpfr_clear(out); + } + } + + void ensure(mpfr_prec_t wanted) + { + if (precision >= wanted) { return; } + if (precision != 0) + { + for (std::size_t i = 0; i < N; ++i) { mpfr_clear(in[i]); } + mpfr_clear(out); + } + for (std::size_t i = 0; i < N; ++i) { mpfr_init2(in[i], wanted); } + mpfr_init2(out, wanted); + precision = wanted; + } + }; + + template + inline mpfr_scratch& thread_scratch() + { + thread_local mpfr_scratch scratch; + return scratch; + } + + // Standard MPFR unary signature shared by exp, log, the trig family, cbrt, sqrt, and gamma. + using mpfr_unary_op = int (*)(mpfr_t, const mpfr_t, mpfr_rnd_t); + + // make_*_ref return a reference callable T(const std::array&, precision, rounding). They + // are the one place the differing MPFR signatures are handled. + template + inline auto make_unary_ref(mpfr_unary_op op) + { + return [op](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T + { + mpfr_scratch<1>& scratch = thread_scratch<1>(); + scratch.ensure(precision); + mpfr_set_scalar(scratch.in[0], inputs[0]); + op(scratch.out, scratch.in[0], rounding); + return mpfr_get_scalar(scratch.out, rounding); + }; + } + + template + inline auto make_lgamma_ref() + { + return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T + { + mpfr_scratch<1>& scratch = thread_scratch<1>(); + scratch.ensure(precision); + mpfr_set_scalar(scratch.in[0], inputs[0]); + int sign = 0; + mpfr_lgamma(scratch.out, &sign, scratch.in[0], rounding); + return mpfr_get_scalar(scratch.out, rounding); + }; + } + + template + inline auto make_pow_ref() + { + return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T + { + mpfr_scratch<2>& scratch = thread_scratch<2>(); + scratch.ensure(precision); + mpfr_set_scalar(scratch.in[0], inputs[0]); + mpfr_set_scalar(scratch.in[1], inputs[1]); + mpfr_pow(scratch.out, scratch.in[0], scratch.in[1], rounding); + return mpfr_get_scalar(scratch.out, rounding); + }; + } + + template + inline auto make_fma_ref() + { + return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T + { + mpfr_scratch<3>& scratch = thread_scratch<3>(); + scratch.ensure(precision); + mpfr_set_scalar(scratch.in[0], inputs[0]); + mpfr_set_scalar(scratch.in[1], inputs[1]); + mpfr_set_scalar(scratch.in[2], inputs[2]); + mpfr_fma(scratch.out, scratch.in[0], scratch.in[1], scratch.in[2], rounding); + return mpfr_get_scalar(scratch.out, rounding); + }; + } + + template + inline mpfr_prec_t default_precision() + { + // Well above the table-maker's-dilemma hardness for these functions at each width, so the + // MPFR value rounds to the correctly-rounded result of the target type. + if constexpr (std::is_same_v) { return 96; } + return 256; + } + + // ULP histogram buckets for finite scored cases: index 0..4 is an exact ULP distance, index 5 is + // every distance of 5 or more (out of the 4 ULP contract). The buckets sum to finite_count. + inline constexpr std::size_t ulp_bucket_count = 6; + + inline std::size_t ulp_bucket(std::uint64_t distance) + { return distance < ulp_bucket_count ? static_cast(distance) : ulp_bucket_count - 1; } + + template + struct campaign_summary + { + std::size_t case_count = 0; + std::size_t failure_count = 0; + std::size_t above_target_count = 0; + std::uint64_t finite_count = 0; + std::uint64_t max_observed_ulp = 0; + long double ulp_sum = 0.0L; + std::array ulp_histogram{}; + std::array worst_inputs{}; + T worst_actual{}; + T worst_expected{}; + }; + + template + inline void merge_summary(campaign_summary& into, const campaign_summary& from) + { + into.case_count += from.case_count; + into.failure_count += from.failure_count; + into.above_target_count += from.above_target_count; + into.finite_count += from.finite_count; + into.ulp_sum += from.ulp_sum; + for (std::size_t i = 0; i < ulp_bucket_count; ++i) { into.ulp_histogram[i] += from.ulp_histogram[i]; } + if (from.max_observed_ulp >= into.max_observed_ulp) + { + into.max_observed_ulp = from.max_observed_ulp; + into.worst_inputs = from.worst_inputs; + into.worst_actual = from.worst_actual; + into.worst_expected = from.worst_expected; + } + } + + // Optional sink that writes every input whose ULP distance reaches a threshold to a text file, + // one case per line in full round-trippable decimal, as the inputs are found. The file write is + // mutex-guarded so it is safe from the worker threads, and the line is formatted outside the lock. + // With more than one thread the lines land in the order the workers reach them; pass --threads=1 + // for a single deterministic stream. + class threshold_recorder + { + public: + threshold_recorder(const std::string& output_path, std::uint64_t threshold) : out_(output_path, std::ios::trunc), threshold_(threshold) {} + + bool active() const { return out_.is_open(); } + + template + void maybe_record(std::uint64_t distance, const std::array& inputs) + { + if (distance < threshold_) { return; } + std::ostringstream line; + line << std::setprecision(std::numeric_limits::max_digits10); + for (std::size_t i = 0; i < N; ++i) + { + if (i != 0) { line << ' '; } + line << inputs[i]; + } + const std::string text = line.str(); + const std::lock_guard lock(mutex_); + out_ << text << '\n'; + } + + private: + std::ofstream out_; + std::mutex mutex_; + std::uint64_t threshold_; + }; + + // One case: run the kernel and the references, classify, and fold the result into the summary. + // Exceptional and zero results are matched against libm or MPFR rather than scored as a ULP + // distance, exactly as the per-arity evaluators in the shared harness do. + template + inline void evaluate_case(const std::array& inputs, + KernelFn kernel, + RefFn reference, + StdFn std_reference, + mpfr_prec_t precision, + mpfr_rnd_t rounding, + std::uint64_t max_ulp, + campaign_summary& summary, + threshold_recorder* recorder) + { + ++summary.case_count; + const T actual = kernel(inputs); + + // Exceptional kernel result: cross-check libm first, which is far cheaper than MPFR, and only + // pay for the correctly-rounded MPFR value when libm disagrees. This skips the MPFR call over + // the large overflow, underflow, and out-of-domain regions where the result is inf, NaN, or + // zero and matches libm, which is most of the input space for exp, log, sqrt, and inverse trig. + if (is_exceptional_or_zero_result(actual)) + { + const T std_expected = std_reference(inputs); + if (exceptional_or_zero_match(actual, std_expected)) { return; } + const T mpfr_expected = reference(inputs, precision, rounding); + if (!exceptional_or_zero_match(actual, mpfr_expected)) { ++summary.failure_count; } + return; + } + + // Finite kernel result: the correctly-rounded MPFR value is needed for the ULP distance. + const T mpfr_expected = reference(inputs, precision, rounding); + if (is_exceptional_or_zero_result(mpfr_expected)) + { + const T std_expected = std_reference(inputs); + if (!exceptional_or_zero_match(actual, std_expected) && !exceptional_or_zero_match(actual, mpfr_expected)) { ++summary.failure_count; } + return; + } + + std::uint64_t distance = 0; + std::string notes; + const bool pass = oracle_match(actual, mpfr_expected, max_ulp, distance, notes); + if (recorder != nullptr) { recorder->maybe_record(distance, inputs); } + summary.ulp_sum += static_cast(distance); + ++summary.ulp_histogram[ulp_bucket(distance)]; + ++summary.finite_count; + if (distance >= summary.max_observed_ulp) + { + summary.max_observed_ulp = distance; + summary.worst_inputs = inputs; + summary.worst_actual = actual; + summary.worst_expected = mpfr_expected; + } + if (!pass) { ++summary.failure_count; } + else if (distance > 0) { ++summary.above_target_count; } + } + + template + struct campaign_report + { + std::string function_name; + std::string input_type; + std::string implementation; + std::string oracle; + std::string corpus; + std::string rounding_modes; + std::size_t case_count = 0; + std::size_t hard_failure_count = 0; + std::size_t above_target_count = 0; + std::uint64_t finite_count = 0; + std::uint64_t max_observed_ulp = 0; + double average_ulp = 0.0; + std::array ulp_histogram{}; + std::string worst_inputs_bits; + std::string worst_actual_bits; + std::string worst_expected_bits; + unsigned long oracle_precision = 0; + std::string compiler; + std::string platform; + std::string optimization_mode; + std::uint64_t seed = 0; + std::uint64_t elapsed_ms = 0; + std::string timestamp; + }; + + template + inline std::string join_input_bits(const std::array& inputs) + { + std::string joined; + for (std::size_t i = 0; i < N; ++i) + { + if (i != 0) { joined += ","; } + joined += bits_hex(inputs[i]); + } + return joined; + } + + template + inline void write_campaign_summary_json(const std::string& output_path, const campaign_report& report) + { + std::ofstream out(output_path, std::ios::trunc); + out << "{\n"; + out << " \"function_name\": \"" << json_escape(report.function_name) << "\",\n"; + out << " \"input_type\": \"" << json_escape(report.input_type) << "\",\n"; + out << " \"implementation\": \"" << json_escape(report.implementation) << "\",\n"; + out << " \"arity\": " << N << ",\n"; + out << " \"oracle\": \"" << json_escape(report.oracle) << "\",\n"; + out << " \"corpus\": \"" << json_escape(report.corpus) << "\",\n"; + out << " \"rounding_modes\": \"" << json_escape(report.rounding_modes) << "\",\n"; + out << " \"case_count\": " << report.case_count << ",\n"; + out << " \"hard_failure_count\": " << report.hard_failure_count << ",\n"; + out << " \"above_target_count\": " << report.above_target_count << ",\n"; + out << " \"finite_count\": " << report.finite_count << ",\n"; + out << " \"max_observed_ulp\": " << report.max_observed_ulp << ",\n"; + out << " \"average_ulp\": " << report.average_ulp << ",\n"; + out << " \"ulp_histogram\": ["; + for (std::size_t i = 0; i < ulp_bucket_count; ++i) + { + if (i != 0) { out << ", "; } + out << report.ulp_histogram[i]; + } + out << "],\n"; + out << " \"worst_input_bits\": \"" << report.worst_inputs_bits << "\",\n"; + out << " \"worst_actual_bits\": \"" << report.worst_actual_bits << "\",\n"; + out << " \"worst_expected_bits\": \"" << report.worst_expected_bits << "\",\n"; + out << " \"oracle_precision\": " << report.oracle_precision << ",\n"; + out << " \"compiler\": \"" << json_escape(report.compiler) << "\",\n"; + out << " \"platform\": \"" << json_escape(report.platform) << "\",\n"; + out << " \"optimization_mode\": \"" << json_escape(report.optimization_mode) << "\",\n"; + out << " \"seed\": " << report.seed << ",\n"; + out << " \"elapsed_ms\": " << report.elapsed_ms << ",\n"; + out << " \"timestamp\": \"" << json_escape(report.timestamp) << "\"\n"; + out << "}\n"; + } + + inline const char* current_mode_token(int mode) + { + switch (mode) + { + case FE_TONEAREST: return "nearest"; + case FE_UPWARD: return "upward"; + case FE_DOWNWARD: return "downward"; + case FE_TOWARDZERO: return "towardzero"; + default: return "unknown"; + } + } + + inline std::string rounding_modes_label(const std::vector& modes) + { + std::string label; + for (std::size_t i = 0; i < modes.size(); ++i) + { + if (i != 0) { label += ","; } + label += current_mode_token(modes[i]); + } + return label; + } + + // Per-function domain used when sampling a coordinate. The whole-range bit-pattern draws always + // run too, so this only steers where the targeted sampling spends its budget. + struct domain + { + double lo = -std::numeric_limits::infinity(); + double hi = std::numeric_limits::infinity(); + bool log_magnitude = false; // sample across exponents rather than uniformly over [lo, hi] + int min_exp = -1074; + int max_exp = 1023; + }; + + template + inline std::vector structured_inputs() + { + const T inf = std::numeric_limits::infinity(); + const T nan = std::numeric_limits::quiet_NaN(); + const T max = std::numeric_limits::max(); + const T min = std::numeric_limits::min(); + const T denorm = std::numeric_limits::denorm_min(); + const T pi = static_cast(3.141592653589793238462643383279502884L); + + return { + static_cast(0), + -static_cast(0), + static_cast(1), + -static_cast(1), + static_cast(0.5), + -static_cast(0.5), + static_cast(2), + -static_cast(2), + static_cast(0.25), + -static_cast(0.25), + static_cast(10), + -static_cast(10), + pi, + -pi, + pi / static_cast(2), + pi / static_cast(4), + pi / static_cast(6), + max, + -max, + min, + -min, + denorm, + -denorm, + inf, + -inf, + nan, + }; + } + + // Function shape. The dispatch tags each function with one, and unary_structured_seed reads it to + // choose the hard-case inputs and the over/underflow boundary search. A new function takes the + // nearest family. + enum class function_family + { + generic, + exp, + logarithm, + trig, + inverse_trig, + power, + gamma, + }; + + // Which implementation of a function to measure, chosen with --impl. The generic kernel is the + // default and the thing ccmath owns. The public path lowers to libm under FE_TONEAREST on clang and + // gcc, so it matches libm in round-to-nearest and shows its own behaviour only in the directed modes + // and at compile time. libm is the system math library reached through the standard headers. + enum class impl_kind + { + generic, + public_api, + libm, + }; + + inline impl_kind parse_impl(int argc, char** argv) + { + const auto raw = option_value(argc, argv, "--impl="); + if (!raw.has_value() || *raw == "gen" || *raw == "generic") { return impl_kind::generic; } + if (*raw == "public") { return impl_kind::public_api; } + if (*raw == "libm" || *raw == "std") { return impl_kind::libm; } + std::cerr << "unknown --impl=" << *raw << " (expected gen, public, or libm), using gen\n"; + return impl_kind::generic; + } + + inline std::string_view impl_token(impl_kind impl) + { + switch (impl) + { + case impl_kind::generic: return "gen"; + case impl_kind::public_api: return "public"; + case impl_kind::libm: return "libm"; + } + return "gen"; + } + + // Deterministic hard-case inputs beyond the common structured edges, chosen per family: the branch + // points and cancellation regions where faithful-but-not-correctly-rounded results tend to hide. + // These are seeded into the default corpus so every sweep measures them regardless of the random + // draw. The boundary inputs (overflow and underflow) are found separately by unary_boundary_cases. + template + inline std::vector unary_extra_inputs(function_family family) + { + std::vector values; + const auto add = [&](double value) { values.push_back(static_cast(value)); }; + const auto add_pow2 = [&](int lo, int hi) + { + for (int k = lo; k <= hi; ++k) + { + add(std::ldexp(1.0, k)); + add(-std::ldexp(1.0, k)); + } + }; + const T one = static_cast(1); + + switch (family) + { + case function_family::exp: + for (int n = -16; n <= 16; ++n) { add(static_cast(n)); } // integers, exp2 lands on a power of two + add_pow2(-28, 28); + add(std::log(2.0)); + add(std::log(10.0)); + break; + case function_family::logarithm: + { + // The dyadic neighbourhood of 1, where log and log1p cancel hardest. + T up = one; + T down = one; + for (int i = 0; i < 8; ++i) + { + up = std::nextafter(up, static_cast(2)); + down = std::nextafter(down, static_cast(0)); + values.push_back(up); + values.push_back(down); + } + add_pow2(-28, 28); // exact integer results for log2 + add(std::exp(1.0)); + break; + } + case function_family::trig: + // Multiples of pi/2 from small to large, the worst case for argument reduction, up through + // the Payne-Hanek cancellation region at large magnitudes. + for (int k = 1; k <= 64; ++k) + { + add(static_cast(k) * 1.57079632679489661923); + add(-static_cast(k) * 1.57079632679489661923); + } + for (int e : { 10, 16, 20, 23, 30, 40, 50, 60, 80, 100 }) + { + add(std::ldexp(1.57079632679489661923, e)); + add(std::ldexp(1.0, e)); + } + break; + case function_family::inverse_trig: + { + const T inside = std::nextafter(one, static_cast(0)); // just inside the +/-1 domain edge + const T outside = std::nextafter(one, static_cast(2)); // just outside, asin and acos go NaN + values.push_back(inside); + values.push_back(-inside); + values.push_back(outside); + values.push_back(-outside); + add(0.5); + add(-0.5); + add(0.70710678118654752440); + add(-0.70710678118654752440); + add(0.86602540378443864676); + break; + } + case function_family::power: + for (int n = 1; n <= 16; ++n) { add(static_cast(n) * static_cast(n)); } // perfect squares + for (int n = -5; n <= 5; ++n) { add(static_cast(n) * static_cast(n) * static_cast(n)); } // perfect cubes, cbrt of negatives + add_pow2(-28, 28); + break; + case function_family::gamma: + for (int n = 0; n <= 20; ++n) { add(-static_cast(n)); } // poles at zero and the negative integers + for (int n = 0; n <= 10; ++n) + { + const T pole = static_cast(-n); // step a few ULP off each pole, where the dynamic range is largest + values.push_back(std::nextafter(pole, static_cast(1))); + values.push_back(std::nextafter(pole, static_cast(-100))); + add(-static_cast(n) + 1.0e-3); + add(-static_cast(n) - 1.0e-3); + } + for (int n = 1; n <= 15; ++n) { add(static_cast(n)); } // tgamma(n) is (n-1)! + for (int n = 0; n <= 10; ++n) { add(static_cast(n) + 0.5); } // half-integers + break; + case function_family::generic: add_pow2(-16, 16); break; + } + return values; + } + + // The IEEE/C special-value matrix for a two-argument function such as pow: the cross product of the + // special and boundary values for each argument. A random bit-pattern draw almost never makes both + // arguments special at once, so these enumerated corners (pow(x, +/-0) is 1, pow(+/-1, inf) is 1, + // pow(negative, non-integer) is NaN, pow(+/-0, negative odd) is +/-inf, and the rest) are measured + // only because they are seeded here. + template + inline std::vector> pow_special_matrix() + { + const T inf = std::numeric_limits::infinity(); + const T nan = std::numeric_limits::quiet_NaN(); + const T one = static_cast(1); + + const std::vector bases = { static_cast(0), + -static_cast(0), + one, + -one, + static_cast(2), + -static_cast(2), + static_cast(0.5), + -static_cast(0.5), + static_cast(3), + -static_cast(3), + std::nextafter(one, static_cast(2)), + std::nextafter(one, static_cast(0)), + static_cast(1.0e30), + static_cast(-1.0e30), + static_cast(1.0e-30), + inf, + -inf, + nan }; + const std::vector exponents = { static_cast(0), + -static_cast(0), + one, + -one, + static_cast(2), + -static_cast(2), + static_cast(3), + -static_cast(3), + static_cast(0.5), + -static_cast(0.5), + static_cast(2.5), + -static_cast(2.5), + static_cast(100), + -static_cast(100), + static_cast(1.0e30), + -static_cast(1.0e30), + inf, + -inf, + nan }; + + std::vector> cases; + cases.reserve(bases.size() * exponents.size()); + for (const T base : bases) + { + for (const T exponent : exponents) { cases.push_back({ base, exponent }); } + } + return cases; + } + + // Pin the exact over/underflow boundary inputs by bisecting the standard reference for the + // finite-to-special transition: the last input with a finite result and the first that overflows to + // infinity or underflows to zero. These adjacent floats are where directed-rounding overflow handling + // is decided, so they belong in every corpus. Only the families with a clean monotone boundary run a + // search; the gate on the endpoint result keeps it from firing where there is no transition. + template + inline std::vector> unary_boundary_cases(StdScalar std_fn, function_family family) + { + std::vector> out; + const T anchor = static_cast(1); // finite for every function here + + const auto bisect = [&](T special_pt, auto is_past) + { + T finite_pt = anchor; + for (int i = 0; i < 5000; ++i) + { + const T mid = finite_pt + (special_pt - finite_pt) / static_cast(2); + if (mid == finite_pt || mid == special_pt) { break; } + if (is_past(std_fn(mid))) { special_pt = mid; } + else + { + finite_pt = mid; + } + } + out.push_back({ finite_pt }); + out.push_back({ special_pt }); + }; + + const bool has_overflow = family == function_family::exp || family == function_family::gamma; + const bool has_underflow = family == function_family::exp; + const auto is_inf = [](T value) { return std::isinf(value); }; + const auto is_zero = [](T value) { return value == static_cast(0); }; + + if (has_overflow && is_inf(std_fn(std::numeric_limits::max()))) { bisect(std::numeric_limits::max(), is_inf); } + if (has_underflow && is_zero(std_fn(std::numeric_limits::lowest()))) { bisect(std::numeric_limits::lowest(), is_zero); } + return out; + } + + // The full per-function deterministic seed for the unary case: family hard cases plus the boundary + // inputs, returned as arity-1 cases for the corpus. + template + inline std::vector> unary_structured_seed(StdScalar std_fn, function_family family) + { + std::vector> out; + for (const T value : unary_extra_inputs(family)) { out.push_back({ value }); } + const auto boundary = unary_boundary_cases(std_fn, family); + out.insert(out.end(), boundary.begin(), boundary.end()); + return out; + } + + inline std::size_t sampled_count(campaign_mode mode) + { + switch (mode) + { + case campaign_mode::quick: return 500000; + case campaign_mode::extended: return 5000000; + case campaign_mode::full: + case campaign_mode::release: return 50000000; + } + return 500000; + } + + // Unary corpus: structured edges, a per-function domain draw, and broad bit patterns. + template + inline std::vector> build_unary_corpus(const domain& dom, std::uint64_t seed, std::size_t count) + { + std::vector> cases; + for (const T value : structured_inputs()) { cases.push_back({ value }); } + + std::mt19937_64 rng(seed); + if (dom.log_magnitude) + { + std::uniform_int_distribution exp_dist(dom.min_exp, dom.max_exp); + std::uniform_real_distribution mant(1.0, 2.0); + const bool both_signs = dom.lo < 0.0; + for (std::size_t i = 0; i < count; ++i) + { + double value = std::ldexp(mant(rng), exp_dist(rng)); + if (both_signs && (rng() & 1U) != 0U) { value = -value; } + if (value >= dom.lo && value <= dom.hi) { cases.push_back({ static_cast(value) }); } + } + } + else + { + const double lo = std::isfinite(dom.lo) ? dom.lo : -1.0e6; + const double hi = std::isfinite(dom.hi) ? dom.hi : 1.0e6; + std::uniform_real_distribution uniform(lo, hi); + for (std::size_t i = 0; i < count; ++i) { cases.push_back({ static_cast(uniform(rng)) }); } + } + + std::mt19937_64 bits(seed ^ 0x9E3779B97F4A7C15ULL); + const std::size_t bit_pattern_count = count / 4 + 1; + for (std::size_t i = 0; i < bit_pattern_count; ++i) + { + if constexpr (std::is_same_v) { cases.push_back({ ccm::support::bit_cast(static_cast(bits())) }); } + else + { + cases.push_back({ ccm::support::bit_cast(bits()) }); + } + } + return cases; + } + + // pow corpus: the targeted finite-result band and hard-case families plus broad bit patterns, + // reusing the pow case generators from oracle_campaign_common.hpp. + template + inline std::vector> build_pow_corpus(std::uint64_t seed, std::size_t count) + { + // Generate in chunks so the heavier pow_case intermediate (it carries a provenance string) is + // never fully materialized at once. Only the compact array corpus is kept, which keeps + // the per-case cost near 8 bytes and lets a very large sample fit in memory. + const std::size_t random_count = count / 4 + 1; + constexpr std::size_t chunk_size = std::size_t{ 1 } << 22; + + std::vector> cases; + cases.reserve(count + random_count); + std::vector> chunk; + + for (std::size_t done = 0; done < count; done += chunk_size) + { + const std::size_t n = std::min(chunk_size, count - done); + chunk.clear(); + add_targeted_random_cases(chunk, seed + done, n, "pow targeted finite band"); + for (const auto& test_case : chunk) { cases.push_back({ test_case.base, test_case.exponent }); } + } + for (std::size_t done = 0; done < random_count; done += chunk_size) + { + const std::size_t n = std::min(chunk_size, random_count - done); + chunk.clear(); + add_random_cases(chunk, (seed ^ 0x9E3779B97F4A7C15ULL) + done, n, "pow whole-range bit pattern"); + for (const auto& test_case : chunk) { cases.push_back({ test_case.base, test_case.exponent }); } + } + return cases; + } + + // True when value sits inside the closed domain [lo, hi]. NaN is never in a numeric region, and an + // infinity counts only when the matching bound is infinite, which keeps structured edges sensible. + template + inline bool in_domain(const domain& dom, T value) + { + if (std::isnan(value)) { return false; } + const double as_double = static_cast(value); + return as_double >= dom.lo && as_double <= dom.hi; + } + + // Draw one value from a single axis. log_magnitude spreads draws evenly across exponents (the right + // shape for functions whose error is per-binade), otherwise the draw is uniform over [lo, hi]. The + // log draw retries a few times to land inside [lo, hi] before clamping, so a tight clip stays honest + // without risking an unbounded loop. + template + inline T draw_in_domain(const domain& dom, std::mt19937_64& rng) + { + if (dom.log_magnitude) + { + std::uniform_int_distribution exp_dist(dom.min_exp, dom.max_exp); + std::uniform_real_distribution mant(1.0, 2.0); + const bool both_signs = dom.lo < 0.0; + for (int attempt = 0; attempt < 8; ++attempt) + { + double value = std::ldexp(mant(rng), exp_dist(rng)); + if (both_signs && (rng() & 1U) != 0U) { value = -value; } + if (value >= dom.lo && value <= dom.hi) { return static_cast(value); } + } + return static_cast(std::min(std::max(0.0, dom.lo), dom.hi)); + } + const double lo = std::isfinite(dom.lo) ? dom.lo : -1.0e6; + const double hi = std::isfinite(dom.hi) ? dom.hi : 1.0e6; + std::uniform_real_distribution uniform(lo, hi); + return static_cast(uniform(rng)); + } + + // Generic arity-N corpus confined to per-argument domains. Each coordinate draws independently from + // its own domain with its own stream, so a region of the joint space is sampled directly. When not + // confined and N == 1 the in-region structured edges are prepended; a confined sweep is region draws + // only, which is what you want when aiming a deep sample at one band. + template + inline std::vector> build_region_corpus(const std::array& domains, std::uint64_t seed, std::size_t count, bool confine) + { + std::vector> cases; + cases.reserve(count + 32); + if constexpr (N == 1) + { + if (!confine) + { + for (const T value : structured_inputs()) + { + if (in_domain(domains[0], value)) { cases.push_back({ value }); } + } + } + } + + std::array rng; + for (std::size_t axis = 0; axis < N; ++axis) { rng[axis] = std::mt19937_64(seed + 0x9E3779B97F4A7C15ULL * (axis + 1)); } + for (std::size_t i = 0; i < count; ++i) + { + std::array input{}; + for (std::size_t axis = 0; axis < N; ++axis) { input[axis] = draw_in_domain(domains[axis], rng[axis]); } + cases.push_back(input); + } + return cases; + } + + // Bit pattern of a scalar as an unsigned integer, used as a total-order key for deduplication. + // Keying on bits keeps -0.0 distinct from +0.0 and keeps distinct NaN payloads distinct. + template + inline auto canonical_bits(T value) + { + if constexpr (sizeof(T) == 4) { return ccm::support::bit_cast(value); } + else + { + return ccm::support::bit_cast(value); + } + } + + // Drop exact-bit duplicate cases from a sampled corpus so a random sweep never measures the same + // input twice. Sorting by bit pattern and removing adjacent equals happens in place, so it adds no + // memory beyond the corpus itself and scales to the largest samples. Corpus order does not affect + // the measurement, so the reordering is harmless. Returns how many repeats were removed. + template + inline std::size_t deduplicate_cases(std::vector>& cases) + { + const auto less = [](const std::array& a, const std::array& b) + { + for (std::size_t i = 0; i < N; ++i) + { + const auto ka = canonical_bits(a[i]); + const auto kb = canonical_bits(b[i]); + if (ka != kb) { return ka < kb; } + } + return false; + }; + const auto equal = [](const std::array& a, const std::array& b) + { + for (std::size_t i = 0; i < N; ++i) + { + if (canonical_bits(a[i]) != canonical_bits(b[i])) { return false; } + } + return true; + }; + const std::size_t before = cases.size(); + std::sort(cases.begin(), cases.end(), less); + cases.erase(std::unique(cases.begin(), cases.end(), equal), cases.end()); + return before - cases.size(); + } + + // Read a corpus of explicit cases from a text file, one case per line, N whitespace-separated values + // in decimal or 0x hex. This is the same shape --record-output writes, so a dump of hard cases from + // one run replays directly, and external worst-case tables (Lefevre, CORE-MATH, RLIBM) drop straight + // in. Malformed lines are skipped with a count on stderr. + template + inline std::vector> read_input_file(const std::string& path) + { + std::vector> cases; + std::ifstream in(path); + if (!in) + { + std::cerr << "input file not found: " << path << '\n'; + return cases; + } + std::string line; + std::size_t skipped = 0; + while (std::getline(in, line)) + { + std::istringstream stream(line); + std::array values{}; + std::size_t parsed = 0; + double token = 0.0; + while (parsed < N && (stream >> token)) { values[parsed++] = static_cast(token); } + if (parsed == N) { cases.push_back(values); } + else if (!line.empty()) { ++skipped; } + } + if (skipped != 0) { std::cerr << "skipped " << skipped << " malformed line(s) in " << path << '\n'; } + return cases; + } + + // Widen coverage around a set of seeded cases by adding each coordinate's two nextafter neighbours. + // The correctly-rounded result of a function near a seed is most likely to sit just over a rounding + // boundary at an adjacent float, so probing neighbours of the known-interesting inputs is a cheap way + // to catch hard-to-round cases the random draw misses. + template + inline void add_neighbor_probes(std::vector>& cases) + { + const std::size_t original = cases.size(); + cases.reserve(original * (2 * N + 1)); + const T up = std::numeric_limits::infinity(); + const T down = -std::numeric_limits::infinity(); + for (std::size_t i = 0; i < original; ++i) + { + const std::array base = cases[i]; + for (std::size_t axis = 0; axis < N; ++axis) + { + std::array above = base; + std::array below = base; + above[axis] = std::nextafter(base[axis], up); + below[axis] = std::nextafter(base[axis], down); + cases.push_back(above); + cases.push_back(below); + } + } + } + + // Read one axis domain from the command line, falling back to the per-function default. Each field + // has an unindexed flag that applies to every axis and an indexed flag (--lo0, --hi1, ...) that wins + // for that axis, so a unary sweep stays simple while pow can clip each argument independently. + inline domain parse_axis_domain(int argc, char** argv, std::size_t index, const domain& fallback) + { + domain dom = fallback; + const std::string idx = std::to_string(index); + const auto field = [&](const std::string& base) -> std::optional + { + if (auto indexed = option_value(argc, argv, base + idx + "=")) { return indexed; } + return option_value(argc, argv, base + "="); + }; + if (const auto value = field("--lo")) { dom.lo = std::stod(*value); } + if (const auto value = field("--hi")) { dom.hi = std::stod(*value); } + if (const auto value = field("--min-exp")) { dom.min_exp = std::stoi(*value); } + if (const auto value = field("--max-exp")) { dom.max_exp = std::stoi(*value); } + if (const auto value = field("--scale")) { dom.log_magnitude = (*value == "log"); } + return dom; + } + + // True when any axis-domain flag is present, which switches the corpus from the per-function default + // builder over to the confined region builder. + inline bool domain_flags_present(int argc, char** argv, std::size_t arity) + { + static const char* const bases[] = { "--lo", "--hi", "--min-exp", "--max-exp", "--scale" }; + for (const char* base : bases) + { + if (option_value(argc, argv, std::string(base) + "=")) { return true; } + for (std::size_t index = 0; index < arity; ++index) + { + if (option_value(argc, argv, std::string(base) + std::to_string(index) + "=")) { return true; } + } + } + return false; + } + + // Half-open bit-pattern interval [begin, end) over the 2^32 binary32 patterns, held in 64-bit so the + // top end (2^32) is representable. + struct bit_interval + { + std::uint64_t begin = 0; + std::uint64_t end = 0; + }; + + // The bit-pattern intervals an exhaustive binary32 sweep should cover. An explicit --exhaustive-range + // wins (decimal or 0x hex, inclusive). Otherwise a finite [lo, hi] on axis 0 is mapped to its + // covering interval(s): non-negative floats are monotone in their bit pattern, negatives are monotone + // in reverse, and a range straddling zero becomes one interval per side. With no clip it is the whole + // space. + inline std::vector float_exhaustive_intervals(int argc, char** argv, const domain& axis0) + { + if (const auto raw = option_value(argc, argv, "--exhaustive-range=")) + { + const std::size_t colon = raw->find(':'); + const std::uint64_t lo = std::stoull(raw->substr(0, colon), nullptr, 0); + const std::uint64_t hi = colon == std::string::npos ? lo : std::stoull(raw->substr(colon + 1), nullptr, 0); + return { bit_interval{ lo, hi + 1 } }; + } + + const auto to_bits = [](float value) { return static_cast(ccm::support::bit_cast(value)); }; + if (std::isfinite(axis0.lo) && std::isfinite(axis0.hi)) + { + const float lo = static_cast(axis0.lo); + const float hi = static_cast(axis0.hi); + if (lo >= 0.0F) { return { bit_interval{ to_bits(lo), to_bits(hi) + 1 } }; } + if (hi <= 0.0F) { return { bit_interval{ to_bits(hi), to_bits(lo) + 1 } }; } + return { bit_interval{ to_bits(-0.0F), to_bits(lo) + 1 }, bit_interval{ to_bits(0.0F), to_bits(hi) + 1 } }; + } + + return { bit_interval{ 0, std::uint64_t{ 1 } << 32 } }; + } + + inline std::string format_magnitude(std::uint64_t value) + { + if (value < 1000) { return std::to_string(value); } + static const char* const suffix[] = { "", "K", "M", "B", "T" }; + double scaled = static_cast(value); + std::size_t step = 0; + while (scaled >= 1000.0 && step + 1 < std::size(suffix)) + { + scaled /= 1000.0; + ++step; + } + std::ostringstream out; + out << std::fixed << std::setprecision(scaled < 10.0 ? 2 : (scaled < 100.0 ? 1 : 0)) << scaled << suffix[step]; + return out.str(); + } + + inline std::string format_duration(double seconds) + { + std::ostringstream out; + if (seconds < 90.0) { out << static_cast(seconds) << 's'; } + else if (seconds < 5400.0) { out << std::fixed << std::setprecision(1) << seconds / 60.0 << 'm'; } + else if (seconds < 172800.0) { out << std::fixed << std::setprecision(1) << seconds / 3600.0 << 'h'; } + else + { + out << std::fixed << std::setprecision(1) << seconds / 86400.0 << 'd'; + } + return out.str(); + } + + // Optional live progress to stderr for the long sweeps. It reads the shared chunk counter (chunks + // dispatched, so it runs a touch ahead of cases finished) and reports percent, throughput, and an + // ETA every couple of seconds. Construction starts the thread, destruction stops and joins it, so a + // pass scopes the ticker around its worker block. + template + class progress_ticker + { + public: + progress_ticker(const std::atomic& counter, std::uint64_t chunk, std::uint64_t total, std::string label) + : counter_(counter), chunk_(chunk), total_(total), label_(std::move(label)) + { + worker_ = std::thread([this]() { run(); }); + } + progress_ticker(const progress_ticker&) = delete; + progress_ticker& operator=(const progress_ticker&) = delete; + ~progress_ticker() + { + stop_.store(true, std::memory_order_relaxed); + if (worker_.joinable()) { worker_.join(); } + } + + private: + void run() + { + const auto start = std::chrono::steady_clock::now(); + bool printed = false; + while (!stop_.load(std::memory_order_relaxed)) + { + for (int i = 0; i < 20 && !stop_.load(std::memory_order_relaxed); ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + if (stop_.load(std::memory_order_relaxed)) { break; } + const std::uint64_t done = std::min(static_cast(counter_.load(std::memory_order_relaxed)) * chunk_, total_); + const double seconds = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + const double rate = seconds > 0.0 ? static_cast(done) / seconds : 0.0; + const double percent = total_ != 0 ? 100.0 * static_cast(done) / static_cast(total_) : 0.0; + const double eta = rate > 0.0 ? static_cast(total_ - done) / rate : 0.0; + std::cerr << "\rprogress " << label_ << ' ' << std::fixed << std::setprecision(1) << percent << "% " << format_magnitude(done) << '/' + << format_magnitude(total_) << ' ' << format_magnitude(static_cast(rate)) << "/s eta " << format_duration(eta) + << " " << std::flush; + printed = true; + } + if (printed) { std::cerr << '\n'; } + } + + const std::atomic& counter_; + std::uint64_t chunk_; + std::uint64_t total_; + std::string label_; + std::atomic stop_{ false }; + std::thread worker_; + }; + + // Sampled corpus, evaluated in parallel. The case vector is read-only and shared, and each thread + // pulls chunks from a shared counter into its own partial summary, the same load-balanced scheme + // the exhaustive sweep uses. The MPFR default rounding mode is set once per pass and only read by + // the workers, and each worker sets its own hardware rounding mode. + template + inline campaign_summary run_sampled(const std::vector>& cases, + const std::vector& rounding_modes, + mpfr_prec_t precision, + std::uint64_t max_ulp, + unsigned int thread_count, + KernelFn kernel, + RefFn reference, + StdFn std_reference, + threshold_recorder* recorder, + bool show_progress) + { + if (thread_count == 0U) { thread_count = 1U; } + constexpr std::size_t chunk_size = std::size_t{ 1 } << 14; + const std::size_t total_cases = cases.size(); + + const mpfr_rnd_t saved_default = mpfr_get_default_rounding_mode(); + campaign_summary total; + + for (const int mode : rounding_modes) + { + mpfr_set_default_rounding_mode(fe_to_mpfr_rounding(mode)); + + std::vector> partials(thread_count); + std::vector workers; + workers.reserve(thread_count); + std::atomic next_chunk{ 0 }; + + std::unique_ptr> ticker; + if (show_progress) + { + ticker = + std::make_unique>(next_chunk, chunk_size, total_cases, std::string("sampled ") + current_mode_token(mode)); + } + + for (unsigned int t = 0; t < thread_count; ++t) + { + workers.emplace_back( + [&, t]() + { + ccm::test::ScopedRoundingMode hardware(mode); + if (!hardware.active()) { return; } + const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); + campaign_summary& summary = partials[t]; + for (;;) + { + const std::size_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; + if (begin >= total_cases) { break; } + const std::size_t end = std::min(begin + chunk_size, total_cases); + for (std::size_t i = begin; i < end; ++i) + { + evaluate_case(cases[i], kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); + } + } + }); + } + for (auto& worker : workers) { worker.join(); } + ticker.reset(); + for (const auto& partial : partials) { merge_summary(total, partial); } + } + + mpfr_set_default_rounding_mode(saved_default); + return total; + } + + // Exhaustive binary32 sweep over one or more bit-pattern intervals, only meaningful for a single + // input. The intervals are flattened into one virtual index space handed out in small chunks from a + // shared counter, so every core stays busy on the expensive band whether the sweep is the whole + // 2^32 or a single targeted sub-range. + template + inline campaign_summary run_exhaustive_float(const std::vector& intervals, + const std::vector& rounding_modes, + mpfr_prec_t precision, + std::uint64_t max_ulp, + unsigned int thread_count, + KernelFn kernel, + RefFn reference, + StdFn std_reference, + threshold_recorder* recorder, + bool show_progress) + { + if (thread_count == 0U) { thread_count = 1U; } + constexpr std::uint64_t chunk_size = std::uint64_t{ 1 } << 20; + + std::uint64_t total_patterns = 0; + for (const bit_interval& interval : intervals) { total_patterns += interval.end - interval.begin; } + + // Map a flat index across all intervals back to its bit pattern. The interval count is one or two + // in practice, so the linear walk is negligible next to an MPFR reference call. + const auto pattern_at = [&intervals](std::uint64_t index) -> std::uint32_t + { + for (const bit_interval& interval : intervals) + { + const std::uint64_t span = interval.end - interval.begin; + if (index < span) { return static_cast(interval.begin + index); } + index -= span; + } + return 0; + }; + + const mpfr_rnd_t saved_default = mpfr_get_default_rounding_mode(); + campaign_summary total; + + for (const int mode : rounding_modes) + { + mpfr_set_default_rounding_mode(fe_to_mpfr_rounding(mode)); + + std::vector> partials(thread_count); + std::vector workers; + workers.reserve(thread_count); + std::atomic next_chunk{ 0 }; + + std::unique_ptr> ticker; + if (show_progress) + { + ticker = std::make_unique>( + next_chunk, chunk_size, total_patterns, std::string("exhaustive ") + current_mode_token(mode)); + } + + for (unsigned int t = 0; t < thread_count; ++t) + { + workers.emplace_back( + [&, t]() + { + ccm::test::ScopedRoundingMode hardware(mode); + if (!hardware.active()) { return; } + const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); + campaign_summary& summary = partials[t]; + for (;;) + { + const std::uint64_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; + if (begin >= total_patterns) { break; } + const std::uint64_t end = std::min(begin + chunk_size, total_patterns); + for (std::uint64_t index = begin; index < end; ++index) + { + const float input = ccm::support::bit_cast(pattern_at(index)); + if (std::isnan(input)) { continue; } + evaluate_case( + std::array{ input }, kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); + } + } + }); + } + for (auto& worker : workers) { worker.join(); } + ticker.reset(); + for (const auto& partial : partials) { merge_summary(total, partial); } + } + + mpfr_set_default_rounding_mode(saved_default); + return total; + } + + template + inline int run_campaign(int argc, + char** argv, + std::string_view function_name, + KernelFn kernel, + RefFn reference, + StdFn std_reference, + CorpusFn build_corpus, + const std::array& default_domains, + std::vector> structured_seed, + std::string_view implementation = "gen") + { + const auto mode = parse_mode(option_value(argc, argv, "--mode=")); + const auto rounding_modes = parse_rounding_modes(argc, argv); + const auto output_path = resolve_event_log_path(argc, argv); + const bool exhaustive_flag = has_flag(argc, argv, "--exhaustive") || option_value(argc, argv, "--exhaustive-range=").has_value(); + const bool confine = has_flag(argc, argv, "--confine"); + const bool show_progress = has_flag(argc, argv, "--progress"); + const bool allow_duplicates = has_flag(argc, argv, "--allow-duplicates"); + const bool probe_neighbors = has_flag(argc, argv, "--probe-neighbors"); + const auto input_file = option_value(argc, argv, "--input-file="); + const std::uint64_t seed = + parse_option_or(option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xCCA77811ULL); + const std::uint64_t max_ulp = parse_option_or( + option_value(argc, argv, "--max-ulp="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 4); + const mpfr_prec_t precision = parse_option_or( + option_value(argc, argv, "--oracle-precision="), + [](const std::string& value) { return static_cast(std::stoul(value)); }, + default_precision()); + const std::size_t count = parse_option_or( + option_value(argc, argv, "--count="), [](const std::string& value) { return static_cast(std::stoull(value)); }, sampled_count(mode)); + const unsigned int thread_count = parse_option_or( + option_value(argc, argv, "--threads="), + [](const std::string& value) { return static_cast(std::stoul(value)); }, + std::max(1U, std::thread::hardware_concurrency())); + const std::uint64_t record_threshold = parse_option_or( + option_value(argc, argv, "--threshold="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 1); + const auto record_output = option_value(argc, argv, "--record-output="); + + // Per-argument domains: start from the function defaults, then let the command line clip any axis + // (--lo/--hi/--scale/--min-exp/--max-exp, optionally suffixed with the argument index). Any such + // flag, or --confine, switches the corpus to the confined region builder for every arity. + std::array domains; + for (std::size_t axis = 0; axis < N; ++axis) { domains[axis] = parse_axis_domain(argc, argv, axis, default_domains[axis]); } + const bool region_mode = confine || domain_flags_present(argc, argv, N); + + // Optional sink for the inputs whose ULP distance reaches --threshold (default 1), written to + // the --record-output text file one case per line as they are found. Omit --record-output and + // no record file is written, the same way omitting --log-output writes no summary JSON. + std::unique_ptr recorder; + if (record_output.has_value()) { recorder = std::make_unique(*record_output, record_threshold); } + threshold_recorder* recorder_ptr = recorder.get(); + + const bool run_exhaustive = exhaustive_flag && N == 1 && std::is_same_v; + + // Deterministic seed measured alongside the random draws in the default sampled sweep: the + // function hard cases and boundary inputs, any --input-file cases, and their nextafter neighbours + // when --probe-neighbors is set. A targeted region sweep skips the global seed but still honours + // an explicit --input-file. The exhaustive sweep already covers these inputs and uses none. + std::vector> seeded; + if (!region_mode) { seeded = std::move(structured_seed); } + if (input_file.has_value()) + { + auto file_cases = read_input_file(*input_file); + seeded.insert(seeded.end(), file_cases.begin(), file_cases.end()); + } + if (probe_neighbors && !seeded.empty()) { add_neighbor_probes(seeded); } + + std::vector intervals; + std::string corpus = region_mode ? (confine ? "confined region" : "region") : "sampled"; + if (run_exhaustive) + { + intervals = float_exhaustive_intervals(argc, argv, domains[0]); + const bool full_space = intervals.size() == 1 && intervals[0].begin == 0 && intervals[0].end == (std::uint64_t{ 1 } << 32); + corpus = full_space ? "exhaustive binary32" : "exhaustive binary32 sub-range"; + } + + std::cout << "mpfr " << function_name << " impl=" << implementation << " type=" << scalar_type_name() << " arity=" << N << " corpus=" << corpus + << " modes=" << rounding_modes.size() << " precision=" << precision << " threads=" << thread_count << '\n'; + if (run_exhaustive) + { + for (const bit_interval& interval : intervals) + { + std::cout << " sweep [0x" << std::hex << interval.begin << ", 0x" << (interval.end - 1) << std::dec << "] " << (interval.end - interval.begin) + << " patterns\n"; + } + } + else if (region_mode) + { + for (std::size_t axis = 0; axis < N; ++axis) + { + std::cout << " arg" << axis << " lo=" << domains[axis].lo << " hi=" << domains[axis].hi + << " scale=" << (domains[axis].log_magnitude ? "log" : "linear"); + if (domains[axis].log_magnitude) { std::cout << " exp=[" << domains[axis].min_exp << ", " << domains[axis].max_exp << "]"; } + std::cout << '\n'; + } + } + if (recorder_ptr != nullptr) { std::cout << "recording inputs with ulp >= " << record_threshold << " to " << *record_output << '\n'; } + if (!seeded.empty() && !run_exhaustive) + { + std::cout << "seeded cases: " << seeded.size() << (probe_neighbors ? " (with neighbour probes)" : "") << '\n'; + } + + const auto started = std::chrono::steady_clock::now(); + campaign_summary summary; + + const auto sample = [&]() + { + auto cases = region_mode ? build_region_corpus(domains, seed, count, confine) : build_corpus(seed, count); + if (!seeded.empty()) { cases.insert(cases.end(), seeded.begin(), seeded.end()); } + // A random sweep can draw the same input more than once and across the structured edges, so + // drop exact-bit repeats unless --allow-duplicates is given. The exhaustive sweep is unique + // already and never reaches here. + if (!allow_duplicates) + { + const std::size_t removed = deduplicate_cases(cases); + if (removed != 0) { std::cout << "deduplicated: removed " << removed << " repeat input" << (removed == 1 ? "" : "s") << '\n'; } + } + return run_sampled(cases, rounding_modes, precision, max_ulp, thread_count, kernel, reference, std_reference, recorder_ptr, show_progress); + }; + + if constexpr (N == 1 && std::is_same_v) + { + if (run_exhaustive) + { + summary = run_exhaustive_float( + intervals, rounding_modes, precision, max_ulp, thread_count, kernel, reference, std_reference, recorder_ptr, show_progress); + } + else + { + summary = sample(); + } + } + else + { + summary = sample(); + } + + const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - started); + const double average_ulp = summary.finite_count != 0 ? static_cast(summary.ulp_sum / static_cast(summary.finite_count)) : 0.0; + + std::cout << "function=" << function_name << " impl=" << implementation << " type=" << scalar_type_name() << " corpus=" << corpus + << " cases=" << summary.case_count << " max_ulp=" << summary.max_observed_ulp << " avg_ulp=" << average_ulp + << " above_target=" << summary.above_target_count << " hard_failures=" << summary.failure_count << " elapsed_ms=" << elapsed.count() << '\n'; + + // Distribution of finite scored cases by ULP distance, the last bucket being everything past the + // 4 ULP contract. It shows how close to correctly rounded the function is, not just its worst case. + std::cout << "ulp_histogram finite=" << summary.finite_count; + for (std::size_t i = 0; i < ulp_bucket_count; ++i) + { + std::cout << ' ' << (i + 1 == ulp_bucket_count ? std::to_string(i) + "+" : std::to_string(i)) << '=' << summary.ulp_histogram[i]; + } + std::cout << '\n'; + + if (output_path.has_value()) + { + campaign_report report; + report.function_name = std::string(function_name); + report.input_type = scalar_type_name(); + report.implementation = std::string(implementation); + report.oracle = "mpfr"; + report.corpus = corpus; + report.rounding_modes = rounding_modes_label(rounding_modes); + report.case_count = summary.case_count; + report.hard_failure_count = summary.failure_count; + report.above_target_count = summary.above_target_count; + report.finite_count = summary.finite_count; + report.max_observed_ulp = summary.max_observed_ulp; + report.average_ulp = average_ulp; + report.ulp_histogram = summary.ulp_histogram; + report.worst_inputs_bits = join_input_bits(summary.worst_inputs); + report.worst_actual_bits = bits_hex(summary.worst_actual); + report.worst_expected_bits = bits_hex(summary.worst_expected); + report.oracle_precision = static_cast(precision); + report.compiler = compiler_id(); + report.platform = platform_id(); + report.optimization_mode = optimization_mode(); + report.seed = seed; + report.elapsed_ms = static_cast(elapsed.count()); + report.timestamp = utc_timestamp(); + write_campaign_summary_json(*output_path, report); + std::cout << "summary: " << *output_path << '\n'; + } + + if (summary.failure_count != 0) { std::cout << "reference disagreements (inf/zero/overflow/ulp ceiling): " << summary.failure_count << '\n'; } + return summary.failure_count == 0 ? 0 : 1; + } +} // namespace ccm::test::oracle::unary_oracle diff --git a/tools/measure_ulp.sh b/tools/measure_ulp.sh new file mode 100755 index 00000000..0e4ed5e6 --- /dev/null +++ b/tools/measure_ulp.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# Copyright (c) Ian Pike +# Copyright (c) CCMath contributors +# +# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. +# See LICENSE for more information. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Sweep the unary elementary functions against a correctly-rounded MPFR reference and write the +# per-function campaign summaries that tools/ulp_status.py folds into docs/ULP_STATUS.md. +# +# By default binary64 is sampled across the per-function domain in all four rounding modes. Pass +# --exhaustive-float to sweep every binary32 bit pattern (around three minutes per function per +# mode); without it binary32 is sampled like binary64. +# +# tools/measure_ulp.sh # sampled binary64 and binary32, all functions +# tools/measure_ulp.sh --exhaustive-float # exhaustive binary32 (nearest), sampled binary64 +# tools/measure_ulp.sh --functions "exp log" # restrict to a subset + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +build_dir="${CCM_ULP_BUILD_DIR:-${repo_root}/out/build/ulp-measure}" +log_dir="${repo_root}/tests/rigorous/oracle_logs" +target="ccmath-rigorous-mpfr-unary" +exhaustive_float=0 +double_count=2000000 +float_count=2000000 +double_modes="nearest" +float_modes="nearest" +functions="exp exp2 expm1 log log1p log2 log10 sin cos tan asin acos atan cbrt sqrt tgamma lgamma" + +while [ "$#" -gt 0 ]; do + case "$1" in + --build-dir) build_dir="$2"; shift 2 ;; + --exhaustive-float) exhaustive_float=1; shift ;; + --functions) functions="$2"; shift 2 ;; + --double-count) double_count="$2"; shift 2 ;; + --float-count) float_count="$2"; shift 2 ;; + -h|--help) sed -n '11,24p' "${BASH_SOURCE[0]}"; exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done + +bin="${build_dir}/tests/rigorous/${target}" +if [ ! -x "${bin}" ]; then + echo "configuring ${build_dir}" + cmake -S "${repo_root}" -B "${build_dir}" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DCCMATH_BUILD_TESTS=ON -DCCMATH_BUILD_SIMPLE_TESTS=OFF \ + -DCCMATH_BUILD_RIGOROUS_TESTS=ON -DCCMATH_ENABLE_MPFR_TESTS=ON \ + -DCCMATH_ENABLE_COREMATH_TESTS=OFF +fi +cmake --build "${build_dir}" --target "${target}" + +mkdir -p "${log_dir}" + +# A non-zero exit only means the campaign saw cases outside the 4 ULP ceiling or a directed-mode +# special-value disagreement. That is measurement data, recorded in the summary, not a script error, +# so the sweep keeps going. +run_campaign() { + "$@" || echo " (campaign reported out-of-contract cases, recorded in the summary)" +} + +for fn in ${functions}; do + echo "=== ${fn} binary64 (sampled, ${double_modes}) ===" + run_campaign "${bin}" --function="${fn}" --type=double --mode=extended --count="${double_count}" \ + --rounding-modes="${double_modes}" \ + --log-output="${log_dir}/mpfr-unary-${fn}-double-summary.json" + + if [ "${exhaustive_float}" -eq 1 ]; then + echo "=== ${fn} binary32 (exhaustive, nearest) ===" + run_campaign "${bin}" --function="${fn}" --type=float --exhaustive --rounding-modes=nearest \ + --log-output="${log_dir}/mpfr-unary-${fn}-float-summary.json" + else + echo "=== ${fn} binary32 (sampled, ${float_modes}) ===" + run_campaign "${bin}" --function="${fn}" --type=float --mode=extended --count="${float_count}" \ + --rounding-modes="${float_modes}" \ + --log-output="${log_dir}/mpfr-unary-${fn}-float-summary.json" + fi +done + +echo "summaries written to ${log_dir}" +echo "refresh the doc with: python3 ${repo_root}/tools/ulp_status.py" diff --git a/tools/ulp_status.py b/tools/ulp_status.py new file mode 100755 index 00000000..6aa57d8d --- /dev/null +++ b/tools/ulp_status.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +# Copyright (c) Ian Pike +# Copyright (c) CCMath contributors +# +# CCMath is provided under the Apache-2.0 License WITH LLVM-exception. +# See LICENSE for more information. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Regenerate the measured tables in docs/ULP_STATUS.md. + +Reads the function registry (tools/ulp_status_registry.json) and the per-function MPFR campaign +summaries written under tests/rigorous/oracle_logs/ by the mpfr_unary measurement target, then +rewrites the block between the BEGIN GENERATED and END GENERATED markers in the doc. Functions +with no completed summary render as measurement pending, so the doc never claims a number that was +not measured. Run after a sweep (tools/measure_ulp.sh). Running it twice is idempotent. +""" +import argparse +import json +import os +import sys + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DEFAULT_REGISTRY = os.path.join(REPO_ROOT, "tools", "ulp_status_registry.json") +DEFAULT_LOGS = os.path.join(REPO_ROOT, "tests", "rigorous", "oracle_logs") +DEFAULT_DOC = os.path.join(REPO_ROOT, "docs", "ULP_STATUS.md") + +BEGIN_MARKER = "" +END_MARKER = "" + +CLASS_LABEL = { + "exact": "Exact", + "correctly_rounded": "Correctly rounded", + "bounded": "Bounded", + "not_rounding": "Not a rounding function", + "not_implemented": "Not implemented", +} + + +def load_summary(logs_dir, glob_pattern, fn_id, fp_type): + name = glob_pattern.format(id=fn_id, type=fp_type) + path = os.path.join(logs_dir, name) + if not os.path.isfile(path): + return None + with open(path, encoding="utf-8") as handle: + return json.load(handle) + + +def format_ulp(value): + # Trig functions near a pole or zero produce enormous ULP distances that are dominated by + # argument reduction rather than ordinary rounding. Keep small numbers exact and render the + # outliers compactly so the tables stay readable. + if value >= 1000000: + return "{:.1e}".format(value) + return str(value) + + +def format_avg(value): + if value == 0: + return "0" + return "{:.3g}".format(value) + + +# The three type variants. summary_type is the token in the summary filename; long double has no +# summary because the harness measures binary32 and binary64 only, so it always renders pending. +TYPES = [ + {"label": "binary32", "summary_type": "float", "suffix": "f", "cpp": "float"}, + {"label": "binary64", "summary_type": "double", "suffix": "", "cpp": "double"}, + {"label": "long double", "summary_type": None, "suffix": "l", "cpp": "long double"}, +] + + +def kernel_for_type(symbol, cpp): + # The internal-impl kernels are named with a _float or _double suffix and have no dedicated long + # double kernel, so long double uses the double one. The gen kernels are T templates, so the same + # symbol instantiates at each type. + if not symbol: + return "" + if symbol.endswith("_float"): + base = symbol[: -len("_float")] + return symbol if cpp == "float" else base + "_double" + return "{}<{}>".format(symbol, cpp) + + +def measured_cells(fn, summary_type, logs_dir, glob_pattern): + fn_id = fn.get("id") + if summary_type is None or not fn_id: + return ("pending", "pending") + summary = load_summary(logs_dir, glob_pattern, fn_id, summary_type) + if summary is None: + return ("pending", "pending") + return (format_ulp(summary.get("max_observed_ulp", 0)), format_avg(summary.get("average_ulp", 0.0))) + + +def build_row(fn, logs_dir, glob_pattern): + name = fn["name"] + klass = fn["class"] + label = CLASS_LABEL.get(klass, klass) + note = fn.get("note", "") + + if klass == "exact": + return [name, label, "0", "0", note] + if klass == "not_rounding": + return [name, label, "n/a", "n/a", note] + if klass == "not_implemented": + return [name, label, "not impl", "not impl", note] + + fn_id = fn.get("id") + summary = load_summary(logs_dir, glob_pattern, fn_id, "float") if fn_id else None + if summary is None: + # fma is correctly rounded by its own campaign even without a unary summary. + if klass == "correctly_rounded" and fn_id is None: + return [name, label, "0", "0", note] + return [name, label, "pending", "pending", note] + + return [name, label, format_ulp(summary.get("max_observed_ulp", 0)), format_avg(summary.get("average_ulp", 0.0)), + note] + + +def render_by_type_family(family, logs_dir, glob_pattern): + # Accuracy-bearing families break each function into its float, double, and long double variant + # rows (expf, exp, expl), each with its own measured numbers and the exact kernel that runs. + lines = [f"### {family['name']}", ""] + lines.append("| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes |") + lines.append("|---------|-------|---------|---------|---------------|-------|") + for fn in family["functions"]: + name = fn["name"] + label = CLASS_LABEL.get(fn["class"], fn["class"]) + symbol = fn.get("symbol", "") + note = fn.get("note", "") + no_suffix = fn.get("no_suffix", False) + for index, variant in enumerate(TYPES): + display = name if no_suffix else name + variant["suffix"] + cell_name = "{} ({})".format(display, variant["label"]) + max_ulp, avg_ulp = measured_cells(fn, variant["summary_type"], logs_dir, glob_pattern) + kernel = kernel_for_type(symbol, variant["cpp"]) + row = [cell_name, label, max_ulp, avg_ulp, kernel, note if index == 0 else ""] + lines.append("| " + " | ".join(cell if cell else "" for cell in row) + " |") + lines.append("") + return lines + + +def render_family(family, logs_dir, glob_pattern): + if family.get("by_type"): + return render_by_type_family(family, logs_dir, glob_pattern) + lines = [f"### {family['name']}", ""] + lines.append("| Function | Class | Max ULP | Avg ULP | Notes |") + lines.append("|----------|-------|---------|---------|-------|") + for fn in family["functions"]: + row = build_row(fn, logs_dir, glob_pattern) + lines.append("| " + " | ".join(cell if cell else "" for cell in row) + " |") + lines.append("") + return lines + + +def first_available_summary(registry, logs_dir, glob_pattern): + for family in registry["families"]: + for fn in family["functions"]: + fn_id = fn.get("id") + if not fn_id: + continue + summary = load_summary(logs_dir, glob_pattern, fn_id, "float") + if summary is not None: + return summary + return None + + +def render_provenance(registry, logs_dir, glob_pattern): + summary = first_available_summary(registry, logs_dir, glob_pattern) + if summary is None: + return ["> No campaign summaries found yet. Run `tools/measure_ulp.sh` then `tools/ulp_status.py`.", ""] + return [ + "> ccmath generic kernels (--impl=gen), round-to-nearest. MPFR correctly-rounded reference. The " + "accuracy families break each function into its float, double, and long double variant (expf, exp, " + "expl). binary32 is measured by an exhaustive sweep over all bit patterns, so its max is a proven " + "worst case. binary64 and long double are pending. The Tested kernel column names the exact symbol " + "that runs. Measured on {} with {}. Last refreshed {}.".format( + summary.get("platform", "?"), summary.get("compiler", "?"), summary.get("timestamp", "?")), + "", + ] + + +def generate_block(registry, logs_dir, glob_pattern): + lines = [] + lines.extend(render_provenance(registry, logs_dir, glob_pattern)) + for family in registry["families"]: + lines.extend(render_family(family, logs_dir, glob_pattern)) + return "\n".join(lines).rstrip() + "\n" + + +def main(): + parser = argparse.ArgumentParser(description="Regenerate the measured tables in docs/ULP_STATUS.md.") + parser.add_argument("--registry", default=DEFAULT_REGISTRY) + parser.add_argument("--logs", default=DEFAULT_LOGS) + parser.add_argument("--doc", default=DEFAULT_DOC) + parser.add_argument("--check", action="store_true", help="Fail if the doc would change instead of writing it.") + args = parser.parse_args() + + with open(args.registry, encoding="utf-8") as handle: + registry = json.load(handle) + glob_pattern = registry.get("summary_glob", "mpfr-unary-{id}-{type}-summary.json") + + block = generate_block(registry, args.logs, glob_pattern) + + with open(args.doc, encoding="utf-8") as handle: + doc = handle.read() + + if BEGIN_MARKER not in doc or END_MARKER not in doc: + sys.stderr.write("error: doc is missing the generated markers\n") + return 2 + + head, rest = doc.split(BEGIN_MARKER, 1) + _, tail = rest.split(END_MARKER, 1) + new_doc = head + BEGIN_MARKER + "\n\n" + block + "\n" + END_MARKER + tail + + if args.check: + if new_doc != doc: + sys.stderr.write("error: docs/ULP_STATUS.md is stale, run tools/ulp_status.py\n") + return 1 + return 0 + + if new_doc != doc: + with open(args.doc, "w", encoding="utf-8") as handle: + handle.write(new_doc) + sys.stderr.write("updated {}\n".format(args.doc)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ulp_status_registry.json b/tools/ulp_status_registry.json new file mode 100644 index 00000000..6ae7790e --- /dev/null +++ b/tools/ulp_status_registry.json @@ -0,0 +1,452 @@ +{ + "_comment": "Single source of truth for docs/ULP_STATUS.md. Each function has a status class and, when measured, an id the generator uses to find tests/rigorous/oracle_logs/mpfr-unary---summary.json, plus a symbol naming the exact ccmath kernel measured (the float symbol; the generator derives the double and long double symbols from it). A family with by_type true breaks each function out into its float, double, and long double variant rows (expf, exp, expl). no_suffix true marks a function with no C f or l variant, such as lerp. Classes: exact, correctly_rounded, bounded, not_rounding, not_implemented. Regenerate the doc with tools/ulp_status.py after a sweep (tools/measure_ulp.sh).", + "summary_glob": "mpfr-unary-{id}-{type}-summary.json", + "families": [ + { + "name": "Basic", + "functions": [ + { + "name": "fabs", + "class": "exact" + }, + { + "name": "fdim", + "class": "exact" + }, + { + "name": "fma", + "class": "correctly_rounded", + "note": "Native FMA where trusted, exact software fallback otherwise. Validated by the MPFR fma all-mode campaign." + }, + { + "name": "fmax", + "class": "exact" + }, + { + "name": "fmin", + "class": "exact" + }, + { + "name": "fmod", + "class": "exact", + "note": "Exact remainder by construction." + }, + { + "name": "remainder", + "class": "exact" + }, + { + "name": "remquo", + "class": "exact" + }, + { + "name": "nan", + "class": "not_rounding", + "note": "Constructs a NaN payload. No rounded result." + } + ] + }, + { + "name": "Comparison", + "functions": [ + { + "name": "fpclassify", + "class": "not_rounding" + }, + { + "name": "isfinite", + "class": "not_rounding" + }, + { + "name": "isgreater", + "class": "not_rounding" + }, + { + "name": "isgreaterequal", + "class": "not_rounding" + }, + { + "name": "isinf", + "class": "not_rounding" + }, + { + "name": "isless", + "class": "not_rounding" + }, + { + "name": "islessequal", + "class": "not_rounding" + }, + { + "name": "islessgreater", + "class": "not_rounding" + }, + { + "name": "isnan", + "class": "not_rounding" + }, + { + "name": "isnormal", + "class": "not_rounding" + }, + { + "name": "isunordered", + "class": "not_rounding" + }, + { + "name": "signbit", + "class": "not_rounding" + } + ] + }, + { + "name": "Exponential", + "by_type": true, + "functions": [ + { + "name": "exp", + "class": "bounded", + "id": "exp", + "symbol": "ccm::gen::exp_gen" + }, + { + "name": "exp2", + "class": "bounded", + "id": "exp2", + "symbol": "ccm::gen::exp2_gen" + }, + { + "name": "expm1", + "class": "bounded", + "id": "expm1", + "symbol": "ccm::gen::expm1_gen" + }, + { + "name": "log", + "class": "bounded", + "id": "log", + "symbol": "ccm::gen::log_gen" + }, + { + "name": "log1p", + "class": "bounded", + "id": "log1p", + "symbol": "ccm::gen::log1p_gen" + }, + { + "name": "log2", + "class": "bounded", + "id": "log2", + "symbol": "ccm::gen::log2_gen" + }, + { + "name": "log10", + "class": "bounded", + "id": "log10", + "symbol": "ccm::gen::log10_gen" + } + ] + }, + { + "name": "Float manipulation", + "functions": [ + { + "name": "copysign", + "class": "exact" + }, + { + "name": "frexp", + "class": "exact" + }, + { + "name": "ilogb", + "class": "not_rounding", + "note": "Returns an integer exponent." + }, + { + "name": "ldexp", + "class": "exact" + }, + { + "name": "logb", + "class": "exact" + }, + { + "name": "modf", + "class": "exact" + }, + { + "name": "nextafter", + "class": "exact" + }, + { + "name": "nexttoward", + "class": "exact" + }, + { + "name": "nextup", + "class": "exact" + }, + { + "name": "nextdown", + "class": "exact" + }, + { + "name": "scalbn", + "class": "exact", + "note": "long double is slow but exact." + } + ] + }, + { + "name": "Hyperbolic", + "functions": [ + { + "name": "acosh", + "class": "not_implemented" + }, + { + "name": "asinh", + "class": "not_implemented" + }, + { + "name": "atanh", + "class": "not_implemented" + }, + { + "name": "cosh", + "class": "not_implemented" + }, + { + "name": "sinh", + "class": "not_implemented" + }, + { + "name": "tanh", + "class": "not_implemented" + } + ] + }, + { + "name": "Nearest integer", + "functions": [ + { + "name": "ceil", + "class": "exact" + }, + { + "name": "floor", + "class": "exact" + }, + { + "name": "nearbyint", + "class": "exact" + }, + { + "name": "rint", + "class": "exact" + }, + { + "name": "round", + "class": "exact" + }, + { + "name": "trunc", + "class": "exact" + } + ] + }, + { + "name": "Power", + "by_type": true, + "functions": [ + { + "name": "cbrt", + "class": "bounded", + "id": "cbrt", + "symbol": "ccm::gen::cbrt_gen" + }, + { + "name": "hypot", + "class": "bounded", + "note": "Binary argument. Measurement is pending a binary sweep harness." + }, + { + "name": "pow", + "class": "bounded", + "id": "pow", + "symbol": "ccm::gen::pow_gen", + "note": "Binary, so sampled over the finite-result band and broad bit patterns rather than exhaustive. See the pow campaign appendix below." + }, + { + "name": "sqrt", + "class": "correctly_rounded", + "id": "sqrt", + "symbol": "ccm::gen::sqrt_gen", + "note": "Correctly rounded by IEEE 754." + } + ] + }, + { + "name": "Special functions", + "functions": [ + { + "name": "assoc_laguerre", + "class": "not_implemented" + }, + { + "name": "assoc_legendre", + "class": "not_implemented" + }, + { + "name": "beta", + "class": "not_implemented" + }, + { + "name": "comp_ellint_1", + "class": "not_implemented" + }, + { + "name": "comp_ellint_2", + "class": "not_implemented" + }, + { + "name": "comp_ellint_3", + "class": "not_implemented" + }, + { + "name": "cyl_bessel_i", + "class": "not_implemented" + }, + { + "name": "cyl_bessel_j", + "class": "not_implemented" + }, + { + "name": "cyl_bessel_k", + "class": "not_implemented" + }, + { + "name": "cyl_neumann", + "class": "not_implemented" + }, + { + "name": "ellint_1", + "class": "not_implemented" + }, + { + "name": "ellint_2", + "class": "not_implemented" + }, + { + "name": "ellint_3", + "class": "not_implemented" + }, + { + "name": "expint", + "class": "not_implemented" + }, + { + "name": "hermite", + "class": "not_implemented" + }, + { + "name": "laguerre", + "class": "not_implemented" + }, + { + "name": "legendre", + "class": "not_implemented" + }, + { + "name": "riemann_zeta", + "class": "not_implemented" + }, + { + "name": "sph_bessel", + "class": "not_implemented" + }, + { + "name": "sph_legendre", + "class": "not_implemented" + }, + { + "name": "sph_neumann", + "class": "not_implemented" + } + ] + }, + { + "name": "Trigonometric", + "by_type": true, + "functions": [ + { + "name": "acos", + "class": "bounded", + "id": "acos", + "symbol": "ccm::internal::impl::acos_float" + }, + { + "name": "asin", + "class": "bounded", + "id": "asin", + "symbol": "ccm::internal::impl::asin_float" + }, + { + "name": "atan", + "class": "bounded", + "id": "atan", + "symbol": "ccm::internal::impl::atan_float" + }, + { + "name": "atan2", + "class": "bounded", + "note": "Binary argument. Measurement is pending a binary sweep harness." + }, + { + "name": "cos", + "class": "bounded", + "id": "cos", + "symbol": "ccm::gen::cos_gen" + }, + { + "name": "sin", + "class": "bounded", + "id": "sin", + "symbol": "ccm::gen::sin_gen" + }, + { + "name": "tan", + "class": "bounded", + "id": "tan", + "symbol": "ccm::gen::tan_gen" + } + ] + }, + { + "name": "Miscellaneous", + "by_type": true, + "functions": [ + { + "name": "tgamma", + "class": "bounded", + "id": "tgamma", + "symbol": "ccm::internal::gamma_float", + "note": "Listed as gamma in earlier status docs." + }, + { + "name": "lerp", + "class": "bounded", + "no_suffix": true, + "note": "Ternary argument, and a C++ template with no f or l suffix variant. Measurement is pending a ternary sweep harness." + }, + { + "name": "lgamma", + "class": "bounded", + "id": "lgamma", + "symbol": "ccm::internal::lgamma_float" + } + ] + } + ] +} From 66a1eb09a587069616705fc372fddd38a15c0b65 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 13:11:28 -0400 Subject: [PATCH 39/52] Remove typo in _MSC_VER --- include/ccmath/internal/math/generic/builtins/basic/fabs.hpp | 2 +- include/ccmath/internal/math/generic/builtins/basic/fmax.hpp | 3 +-- include/ccmath/internal/math/generic/builtins/basic/fmin.hpp | 3 +-- .../ccmath/internal/math/generic/builtins/compare/isfinite.hpp | 2 +- .../ccmath/internal/math/generic/builtins/compare/isinf.hpp | 2 +- .../ccmath/internal/math/generic/builtins/compare/isnormal.hpp | 2 +- .../ccmath/internal/math/generic/builtins/fmanip/copysign.hpp | 2 +- 7 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp index 4cf9bdad..09f64770 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp @@ -38,7 +38,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_ABS - #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_ABS #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp b/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp index 6182a29f..9b283aec 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp @@ -31,8 +31,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_FMAX - #if defined(__clang__) && (__clang_major__ > 16 || (__clang_major__ == 16 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && \ - !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 16 || (__clang_major__ == 16 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_FMAX #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp b/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp index 1742136b..0f8933e0 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp @@ -29,8 +29,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_FMIN - #if defined(__clang__) && (__clang_major__ > 16 || (__clang_major__ == 16 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && \ - !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 16 || (__clang_major__ == 16 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_FMIN #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp b/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp index 4a6f4e63..9ce91303 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp @@ -30,7 +30,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_ISFINITE - #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_ISFINITE #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp b/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp index f69cc7b9..0183d8e8 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp @@ -31,7 +31,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_ISINF - #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_ISINF #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp b/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp index 068fda38..7dadcb1f 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp @@ -32,7 +32,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_ISNORMAL - #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_ISNORMAL #endif #endif diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp index 6d6908ff..da8af3ed 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp @@ -32,7 +32,7 @@ #endif #ifndef CCMATH_HAS_CONSTEXPR_BUILTIN_COPYSIGN - #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(__MSC_VER) && !defined(__INTEL_LLVM_COMPILER) + #if defined(__clang__) && (__clang_major__ > 5 || (__clang_major__ == 5 && __clang_minor__ >= 0)) && !defined(_MSC_VER) && !defined(__INTEL_LLVM_COMPILER) #define CCMATH_HAS_CONSTEXPR_BUILTIN_COPYSIGN #endif #endif From 678ac7cc0d40d893b1d9d5809c52a17fc059a632 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 15:16:32 -0400 Subject: [PATCH 40/52] Make ext helpers guarded by default and switch angle helpers to radians --- include/ccmath/ext/ceil_div.hpp | 31 ++--------- include/ccmath/ext/delta_angle.hpp | 35 +++--------- include/ccmath/ext/inverse_lerp.hpp | 25 ++------- include/ccmath/ext/is_power_of_two.hpp | 53 +++--------------- include/ccmath/ext/lerp_angle.hpp | 26 ++------- include/ccmath/ext/move_towards_angle.hpp | 26 ++------- include/ccmath/ext/normalize.hpp | 54 ++++++++++-------- include/ccmath/ext/remap.hpp | 68 +++++++++++------------ include/ccmath/ext/repeat.hpp | 44 ++++++++------- include/ccmath/ext/unlerp.hpp | 56 ++++++++++--------- tests/src/ext/ceil_div_test.cpp | 6 +- tests/src/ext/constexpr_smoke_test.cpp | 30 +++++----- tests/src/ext/delta_angle_test.cpp | 12 ++-- tests/src/ext/inverse_lerp_test.cpp | 4 +- tests/src/ext/is_power_of_two_test.cpp | 10 +++- tests/src/ext/lerp_angle_test.cpp | 10 +++- tests/src/ext/move_towards_angle_test.cpp | 14 +++-- tests/src/ext/normalize_test.cpp | 5 +- tests/src/ext/remap_test.cpp | 5 +- tests/src/ext/repeat_test.cpp | 6 +- tests/src/ext/unlerp_test.cpp | 5 +- 21 files changed, 218 insertions(+), 307 deletions(-) diff --git a/include/ccmath/ext/ceil_div.hpp b/include/ccmath/ext/ceil_div.hpp index bafebad8..e1b770fb 100644 --- a/include/ccmath/ext/ceil_div.hpp +++ b/include/ccmath/ext/ceil_div.hpp @@ -17,9 +17,10 @@ namespace ccm::ext /** * @brief Divide two integral values and round the quotient toward positive infinity. * - * Works for signed and unsigned types and rounds correctly for negative operands. - * The behavior is undefined if divisor is zero or if the exact quotient is not - * representable in T, for example the most negative value divided by minus one. + * If divisor is zero, this function returns 0. Otherwise it works for signed + * and unsigned types and rounds correctly for negative operands. The behavior + * is still undefined if the exact quotient is not representable in T, for + * example the most negative value divided by minus one. * * @tparam T Integral type of the inputs and output. * @param value The dividend. @@ -29,6 +30,8 @@ namespace ccm::ext template && !std::is_same_v, bool>, bool> = true> constexpr T ceil_div(T value, T divisor) noexcept { + if (divisor == T(0)) { return T(0); } + // Divide first, then add the correction, so there is no pre-division addition that can overflow. if constexpr (std::is_signed_v) { @@ -40,26 +43,4 @@ namespace ccm::ext return static_cast(value / divisor + (value % divisor != 0)); } } - - namespace safe - { - /** - * @brief Safely divide two integral values and round the quotient toward positive infinity. - * - * If divisor is zero, this function returns 0. Otherwise it matches ext::ceil_div, - * which works for signed and unsigned types and rounds correctly for negative operands. - * - * @tparam T Integral type of the inputs and output. - * @param value The dividend. - * @param divisor The divisor. - * @return The ceiling of value divided by divisor. - */ - template && !std::is_same_v, bool>, bool> = true> - constexpr T ceil_div(T value, T divisor) noexcept - { - if (divisor == T(0)) { return T(0); } - - return ext::ceil_div(value, divisor); - } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/delta_angle.hpp b/include/ccmath/ext/delta_angle.hpp index d896e106..603dc486 100644 --- a/include/ccmath/ext/delta_angle.hpp +++ b/include/ccmath/ext/delta_angle.hpp @@ -11,45 +11,28 @@ #pragma once #include "ccmath/ext/repeat.hpp" +#include "ccmath/math/numbers.hpp" #include namespace ccm::ext { /** - * @brief Compute the shortest signed difference between two angles in degrees. + * @brief Compute the shortest signed difference between two angles in radians. * @tparam T Type of the input and output. - * @param current The current angle, in degrees. - * @param target The target angle, in degrees. - * @return The shortest signed angular difference in the range [-180, 180]. + * @param current The current angle, in radians. + * @param target The target angle, in radians. + * @return The shortest signed angular difference in the range [-pi, pi]. */ template , bool> = true> constexpr T delta_angle(T current, T target) noexcept { - T delta = ext::repeat(target - current, T(360)); + const T two_pi = T(2) * ccm::numbers::pi_v; - if (delta > T(180)) { delta -= T(360); } + T delta = ext::repeat(target - current, two_pi); + + if (delta > ccm::numbers::pi_v) { delta -= two_pi; } return delta; } - - namespace safe - { - /** - * @brief Safely compute the shortest signed difference between two angles in degrees. - * @tparam T Type of the input and output. - * @param current The current angle, in degrees. - * @param target The target angle, in degrees. - * @return The shortest signed angular difference in the range [-180, 180]. - */ - template , bool> = true> - constexpr T delta_angle(T current, T target) noexcept - { - T delta = ext::safe::repeat(target - current, T(360)); - - if (delta > T(180)) { delta -= T(360); } - - return delta; - } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/inverse_lerp.hpp b/include/ccmath/ext/inverse_lerp.hpp index de515258..05023d1d 100644 --- a/include/ccmath/ext/inverse_lerp.hpp +++ b/include/ccmath/ext/inverse_lerp.hpp @@ -21,6 +21,10 @@ namespace ccm::ext * * This is an alias-style wrapper around ext::unlerp. * + * If start == end, the source range has zero length. In that case this + * function returns 0 to avoid division by zero and NaN propagation. For the + * unguarded form see ccm::ext::unsafe::unlerp. + * * @tparam T Type of the input and output. * @param start The start of the source range. * @param end The end of the source range. @@ -30,25 +34,4 @@ namespace ccm::ext template , bool> = true> constexpr T inverse_lerp(T start, T end, T value) noexcept { return ext::unlerp(start, end, value); } - - namespace safe - { - /** - * @brief Safely compute the inverse linear interpolation parameter of a value. - * - * This is an alias-style wrapper around ext::safe::unlerp. - * - * If start == end, the source range has zero length. In that case this - * function returns 0 to avoid division by zero and NaN propagation. - * - * @tparam T Type of the input and output. - * @param start The start of the source range. - * @param end The end of the source range. - * @param value The value to evaluate. - * @return The interpolation parameter of value in the range [start, end]. - */ - template , bool> = true> - constexpr T inverse_lerp(T start, T end, T value) noexcept - { return ext::safe::unlerp(start, end, value); } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/is_power_of_two.hpp b/include/ccmath/ext/is_power_of_two.hpp index 6eac2efc..3e5aeac0 100644 --- a/include/ccmath/ext/is_power_of_two.hpp +++ b/include/ccmath/ext/is_power_of_two.hpp @@ -30,16 +30,21 @@ namespace ccm::ext /** * @brief Determines if a signed integer value is a power of two. * + * Negative values and zero are never a power of two. The positive check is + * done on the unsigned value, so the minimum representable value does not + * trigger signed overflow. + * * @tparam T Type of the input. * @param n Value to check. * @return True if the input is a power of two, false otherwise. - * - * @note Prefer ccm::ext::safe::is_power_of_two for signed inputs to avoid signed - * overflow on the minimum representable value. */ template && !std::is_same_v, bool> = true> constexpr bool is_power_of_two(T n) noexcept - { return n && !(n & (n - T(1))); } + { + using unsigned_type = std::make_unsigned_t; + + return n > T(0) && support::has_single_bit(static_cast(n)); + } /** * @brief Alias for is_power_of_two. @@ -51,44 +56,4 @@ namespace ccm::ext template && !std::is_same_v, bool> = true> constexpr bool ispow2(T n) noexcept { return is_power_of_two(n); } - - namespace safe - { - /** - * @brief Safely determines if an unsigned integer value is a power of two. - * - * @tparam T Type of the input. - * @param n Value to check. - * @return True if the input is a power of two, false otherwise. - */ - template && !std::is_same_v, bool> = true> - constexpr bool is_power_of_two(T n) noexcept - { return support::has_single_bit(n); } - - /** - * @brief Safely determines if a signed integer value is a positive power of two. - * - * @tparam T Type of the input. - * @param n Value to check. - * @return True if the input is a positive power of two, false otherwise. - */ - template && !std::is_same_v, bool> = true> - constexpr bool is_power_of_two(T n) noexcept - { - using unsigned_type = std::make_unsigned_t; - - return n > T(0) && support::has_single_bit(static_cast(n)); - } - - /** - * @brief Alias for safe::is_power_of_two. - * - * @tparam T Type of the input. - * @param n Value to check. - * @return True if the input is a power of two, false otherwise. - */ - template && !std::is_same_v, bool> = true> - constexpr bool ispow2(T n) noexcept - { return safe::is_power_of_two(n); } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/lerp_angle.hpp b/include/ccmath/ext/lerp_angle.hpp index e5875295..2bf24dfc 100644 --- a/include/ccmath/ext/lerp_angle.hpp +++ b/include/ccmath/ext/lerp_angle.hpp @@ -17,35 +17,17 @@ namespace ccm::ext { /** - * @brief Linearly interpolate between two angles in degrees along the shortest path. + * @brief Linearly interpolate between two angles in radians along the shortest arc. * * This function does not clamp t. Values of t outside [0, 1] extrapolate. * * @tparam T Type of the input and output. - * @param start The start angle, in degrees. - * @param end The end angle, in degrees. + * @param start The start angle, in radians. + * @param end The end angle, in radians. * @param t The interpolation parameter. - * @return The interpolated angle, in degrees. + * @return The interpolated angle, in radians. */ template , bool> = true> constexpr T lerp_angle(T start, T end, T t) noexcept { return start + (ext::delta_angle(start, end) * t); } - - namespace safe - { - /** - * @brief Safely linearly interpolate between two angles in degrees along the shortest path. - * - * This function does not clamp t. Values of t outside [0, 1] extrapolate. - * - * @tparam T Type of the input and output. - * @param start The start angle, in degrees. - * @param end The end angle, in degrees. - * @param t The interpolation parameter. - * @return The interpolated angle, in degrees. - */ - template , bool> = true> - constexpr T lerp_angle(T start, T end, T t) noexcept - { return start + (ext::safe::delta_angle(start, end) * t); } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/move_towards_angle.hpp b/include/ccmath/ext/move_towards_angle.hpp index f0596ec2..0f1d25d8 100644 --- a/include/ccmath/ext/move_towards_angle.hpp +++ b/include/ccmath/ext/move_towards_angle.hpp @@ -20,10 +20,10 @@ namespace ccm::ext /** * @brief Move an angle toward a target angle without overshooting. * @tparam T Type of the input and output. - * @param current The current angle, in degrees. - * @param target The target angle, in degrees. - * @param max_delta The maximum angular change to apply. - * @return The moved angle, in degrees. + * @param current The current angle, in radians. + * @param target The target angle, in radians. + * @param max_delta The maximum angular change to apply, in radians. + * @return The moved angle, in radians. */ template , bool> = true> constexpr T move_towards_angle(T current, T target, T max_delta) noexcept @@ -31,22 +31,4 @@ namespace ccm::ext const T delta = ext::delta_angle(current, target); return ext::move_towards(current, current + delta, max_delta); } - - namespace safe - { - /** - * @brief Safely move an angle toward a target angle without overshooting. - * @tparam T Type of the input and output. - * @param current The current angle, in degrees. - * @param target The target angle, in degrees. - * @param max_delta The maximum angular change to apply. - * @return The moved angle, in degrees. - */ - template , bool> = true> - constexpr T move_towards_angle(T current, T target, T max_delta) noexcept - { - const T delta = ext::safe::delta_angle(current, target); - return ext::move_towards(current, current + delta, max_delta); - } - } // namespace safe } // namespace ccm::ext diff --git a/include/ccmath/ext/normalize.hpp b/include/ccmath/ext/normalize.hpp index ad320eeb..cbb80933 100644 --- a/include/ccmath/ext/normalize.hpp +++ b/include/ccmath/ext/normalize.hpp @@ -16,27 +16,14 @@ namespace ccm::ext { - /** - * @brief Normalize a value to a range. - * @tparam T Type of the input and output. - * @param value The value to normalize. - * @param min The minimum value of the range. - * @param max The maximum value of the range. - * @return The normalized value. - */ - template , bool> = true> - constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept - { return ext::clamp((value - min) / (max - min), T(0), T(1)); } - - namespace safe + namespace unsafe { /** - * @brief Safely normalize a value to a range. - * - * Maps value from the range [min, max] to [0, 1]. + * @brief Normalize a value to a range without guarding the range length. * - * If min == max, the source range has zero length. In that case this - * function returns 0 to avoid division by zero and NaN propagation. + * Maps value from the range [min, max] to [0, 1]. This is the raw form + * with no zero check. If min == max the result follows normal + * floating-point division semantics and may be NaN. * * @tparam T Type of the input and output. * @param value The value to normalize. @@ -46,12 +33,31 @@ namespace ccm::ext */ template , bool> = true> constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept - { - const T length = max - min; + { return ext::clamp((value - min) / (max - min), T(0), T(1)); } + } // namespace unsafe + + /** + * @brief Normalize a value to a range. + * + * Maps value from the range [min, max] to [0, 1]. + * + * If min == max, the source range has zero length. In that case this + * function returns 0 to avoid division by zero and NaN propagation. For the + * unguarded form see ccm::ext::unsafe::normalize. + * + * @tparam T Type of the input and output. + * @param value The value to normalize. + * @param min The minimum value of the range. + * @param max The maximum value of the range. + * @return The normalized value. + */ + template , bool> = true> + constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept + { + const T length = max - min; - if (length == T(0)) { return T(0); } + if (length == T(0)) { return T(0); } - return ext::clamp((value - min) / length, T(0), T(1)); - } - } // namespace safe + return ext::unsafe::normalize(value, min, max); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/remap.hpp b/include/ccmath/ext/remap.hpp index ccd336cf..e3fa25c0 100644 --- a/include/ccmath/ext/remap.hpp +++ b/include/ccmath/ext/remap.hpp @@ -14,6 +14,33 @@ namespace ccm::ext { + namespace unsafe + { + /** + * @brief Remap a value from one range to another without guarding the source length. + * + * Maps x from the source range [src_start, src_end] to the destination + * range [dst_start, dst_end]. + * + * This is a non-clamping linear remap. Values outside the source range + * are mapped outside the destination range. + * + * This is the raw form with no zero check. If src_start == src_end the + * result follows normal floating-point division semantics and may be NaN. + * + * @tparam T Built-in floating-point type of the input and output. + * @param src_start Start of the source range. + * @param src_end End of the source range. + * @param dst_start Start of the destination range. + * @param dst_end End of the destination range. + * @param x Value to remap. + * @return The remapped value. + */ + template , bool> = true> + constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept + { return dst_start + ((x - src_start) * (dst_end - dst_start)) / (src_end - src_start); } + } // namespace unsafe + /** * @brief Remap a value from one range to another. * @@ -23,8 +50,9 @@ namespace ccm::ext * This is a non-clamping linear remap. Values outside the source range * are mapped outside the destination range. * - * This is the raw formula. If src_start == src_end, the result follows - * normal floating-point division semantics. + * If src_start == src_end, the source range has zero length. In that case + * this function returns dst_start to avoid division by zero and NaN + * propagation. For the unguarded form see ccm::ext::unsafe::remap. * * @tparam T Built-in floating-point type of the input and output. * @param src_start Start of the source range. @@ -36,39 +64,11 @@ namespace ccm::ext */ template , bool> = true> constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept - { return dst_start + ((x - src_start) * (dst_end - dst_start)) / (src_end - src_start); } - - namespace safe { - /** - * @brief Safely remap a value from one range to another. - * - * Maps x from the source range [src_start, src_end] to the destination - * range [dst_start, dst_end]. - * - * This is a non-clamping linear remap. Values outside the source range - * are mapped outside the destination range. - * - * If src_start == src_end, the source range has zero length. In that - * case this function returns dst_start to avoid division by zero and - * NaN propagation. - * - * @tparam T Built-in floating-point type of the input and output. - * @param src_start Start of the source range. - * @param src_end End of the source range. - * @param dst_start Start of the destination range. - * @param dst_end End of the destination range. - * @param x Value to remap. - * @return The remapped value. - */ - template , bool> = true> - constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept - { - const T length = src_end - src_start; + const T length = src_end - src_start; - if (length == T(0)) { return dst_start; } + if (length == T(0)) { return dst_start; } - return dst_start + ((x - src_start) * (dst_end - dst_start)) / length; - } - } // namespace safe + return ext::unsafe::remap(src_start, src_end, dst_start, dst_end, x); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/repeat.hpp b/include/ccmath/ext/repeat.hpp index 5d9e5648..ed9f5315 100644 --- a/include/ccmath/ext/repeat.hpp +++ b/include/ccmath/ext/repeat.hpp @@ -16,24 +16,13 @@ namespace ccm::ext { - /** - * @brief Repeat a value within the range [0, length). - * @tparam T Type of the input and output. - * @param value The value to repeat. - * @param length The length of the repeating interval. - * @return The repeated value in the range [0, length). - */ - template , bool> = true> - constexpr T repeat(T value, T length) noexcept - { return value - (ccm::floor(value / length) * length); } - - namespace safe + namespace unsafe { /** - * @brief Safely repeat a value within the range [0, length). + * @brief Repeat a value within the range [0, length) without guarding the length. * - * If length is zero, this function returns 0 to avoid division by zero - * and NaN propagation. + * This is the raw form with no zero check. If length is zero the result + * follows normal floating-point division semantics and may be NaN. * * @tparam T Type of the input and output. * @param value The value to repeat. @@ -42,10 +31,25 @@ namespace ccm::ext */ template , bool> = true> constexpr T repeat(T value, T length) noexcept - { - if (length == T(0)) { return T(0); } + { return value - (ccm::floor(value / length) * length); } + } // namespace unsafe + + /** + * @brief Repeat a value within the range [0, length). + * + * If length is zero, this function returns 0 to avoid division by zero and + * NaN propagation. For the unguarded form see ccm::ext::unsafe::repeat. + * + * @tparam T Type of the input and output. + * @param value The value to repeat. + * @param length The length of the repeating interval. + * @return The repeated value in the range [0, length). + */ + template , bool> = true> + constexpr T repeat(T value, T length) noexcept + { + if (length == T(0)) { return T(0); } - return ext::repeat(value, length); - } - } // namespace safe + return ext::unsafe::repeat(value, length); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/unlerp.hpp b/include/ccmath/ext/unlerp.hpp index 27618419..efeacaee 100644 --- a/include/ccmath/ext/unlerp.hpp +++ b/include/ccmath/ext/unlerp.hpp @@ -14,32 +14,16 @@ namespace ccm::ext { - /** - * @brief Compute the inverse linear interpolation parameter of a value. - * - * This function does not clamp the result. Values outside [start, end] - * return values outside [0, 1]. - * - * @tparam T Type of the input and output. - * @param start The start of the source range. - * @param end The end of the source range. - * @param value The value to evaluate. - * @return The interpolation parameter of value in the range [start, end]. - */ - template , bool> = true> - constexpr T unlerp(T start, T end, T value) noexcept - { return (value - start) / (end - start); } - - namespace safe + namespace unsafe { /** - * @brief Safely compute the inverse linear interpolation parameter of a value. + * @brief Compute the inverse linear interpolation parameter of a value without guarding the range length. * * This function does not clamp the result. Values outside [start, end] * return values outside [0, 1]. * - * If start == end, the source range has zero length. In that case this - * function returns 0 to avoid division by zero and NaN propagation. + * This is the raw form with no zero check. If start == end the result + * follows normal floating-point division semantics and may be NaN. * * @tparam T Type of the input and output. * @param start The start of the source range. @@ -49,12 +33,32 @@ namespace ccm::ext */ template , bool> = true> constexpr T unlerp(T start, T end, T value) noexcept - { - const T length = end - start; + { return (value - start) / (end - start); } + } // namespace unsafe + + /** + * @brief Compute the inverse linear interpolation parameter of a value. + * + * This function does not clamp the result. Values outside [start, end] + * return values outside [0, 1]. + * + * If start == end, the source range has zero length. In that case this + * function returns 0 to avoid division by zero and NaN propagation. For the + * unguarded form see ccm::ext::unsafe::unlerp. + * + * @tparam T Type of the input and output. + * @param start The start of the source range. + * @param end The end of the source range. + * @param value The value to evaluate. + * @return The interpolation parameter of value in the range [start, end]. + */ + template , bool> = true> + constexpr T unlerp(T start, T end, T value) noexcept + { + const T length = end - start; - if (length == T(0)) { return T(0); } + if (length == T(0)) { return T(0); } - return (value - start) / length; - } - } // namespace safe + return ext::unsafe::unlerp(start, end, value); + } } // namespace ccm::ext diff --git a/tests/src/ext/ceil_div_test.cpp b/tests/src/ext/ceil_div_test.cpp index 6a953cf6..7cfa5aa1 100644 --- a/tests/src/ext/ceil_div_test.cpp +++ b/tests/src/ext/ceil_div_test.cpp @@ -19,10 +19,10 @@ TEST(CcmathExtCeilDivTest, Smoke) static_assert(ccm::ext::ceil_div(7, 3) == 3); static_assert(ccm::ext::ceil_div(6, 3) == 2); static_assert(ccm::ext::ceil_div(1, 4) == 1); - static_assert(ccm::ext::safe::ceil_div(7, 0) == 0); + static_assert(ccm::ext::ceil_div(7, 0) == 0); EXPECT_EQ(ccm::ext::ceil_div(10, 4), 3); - EXPECT_EQ(ccm::ext::safe::ceil_div(10, 0), 0); + EXPECT_EQ(ccm::ext::ceil_div(10, 0), 0); } TEST(CcmathExtCeilDivTest, ExactDivisionDoesNotRoundUp) @@ -43,7 +43,7 @@ TEST(CcmathExtCeilDivTest, NegativeOperands) EXPECT_EQ(ccm::ext::ceil_div(-10, 4), -2); // ceil(-2.5) EXPECT_EQ(ccm::ext::ceil_div(10, -4), -2); - EXPECT_EQ(ccm::ext::safe::ceil_div(-7, 2), -3); + EXPECT_EQ(ccm::ext::ceil_div(-7, 2), -3); } TEST(CcmathExtCeilDivTest, NoOverflowAtExtremes) diff --git a/tests/src/ext/constexpr_smoke_test.cpp b/tests/src/ext/constexpr_smoke_test.cpp index 103e026a..b9e3d16f 100644 --- a/tests/src/ext/constexpr_smoke_test.cpp +++ b/tests/src/ext/constexpr_smoke_test.cpp @@ -51,15 +51,15 @@ TEST(CcmathExtConstexprSmokeTest, AllFunctions) static_assert(ccm::ext::clamp(5.0, 0.0, 10.0) == 5.0); static_assert(ccm::ext::cubic(0.0, 1.0, 2.0, 3.0, 0.5) == 1.5); static_assert(ccm::ext::degrees(ccm::numbers::pi_v) == 180.0); - static_assert(ccm::ext::delta_angle(0.0, 90.0) == 90.0); + static_assert(ccm::ext::delta_angle(0.0, ccm::numbers::pi_v) == ccm::numbers::pi_v); static_assert(ccm::ext::fract(3.25) == 0.25); static_assert(ccm::ext::inverse_lerp(0.0, 10.0, 5.0) == 0.5); static_assert(ccm::ext::is_power_of_two(8U)); static_assert(ccm::ext::ispow2(16)); - static_assert(ccm::ext::lerp_angle(0.0, 90.0, 0.5) == 45.0); + static_assert(ccm::ext::lerp_angle(0.0, ccm::numbers::pi_v / 2, 0.5) == ccm::numbers::pi_v / 4); static_assert(ccm::ext::lerp_smooth(0.0, 10.0, 0.0, 1.0) == 0.0); static_assert(ccm::ext::move_towards(0.0, 10.0, 5.0) == 5.0); - static_assert(ccm::ext::move_towards_angle(0.0, 90.0, 45.0) == 45.0); + static_assert(ccm::ext::move_towards_angle(0.0, ccm::numbers::pi_v / 2, ccm::numbers::pi_v / 4) == ccm::numbers::pi_v / 4); static_assert(ccm::ext::normalize(5.0, 0.0, 10.0) == 0.5); static_assert(ccm::ext::ping_pong(3.0, 4.0) == 3.0); static_assert(ccm::ext::radians(180.0) > 3.0); @@ -72,15 +72,17 @@ TEST(CcmathExtConstexprSmokeTest, AllFunctions) static_assert(ccm::ext::step(1.0, 0.5) == 0.0); static_assert(ccm::ext::unlerp(0.0, 10.0, 5.0) == 0.5); - static_assert(ccm::ext::safe::ceil_div(7, 0) == 0); - static_assert(ccm::ext::safe::delta_angle(350.0, 10.0) == 20.0); - static_assert(ccm::ext::safe::inverse_lerp(5.0, 5.0, 7.0) == 0.0); - static_assert(ccm::ext::safe::is_power_of_two(4)); - static_assert(ccm::ext::safe::ispow2(32)); - static_assert(ccm::ext::safe::lerp_angle(0.0, 90.0, 0.5) == 45.0); - static_assert(ccm::ext::safe::move_towards_angle(0.0, 90.0, 45.0) == 45.0); - static_assert(ccm::ext::safe::normalize(5.0, 5.0, 5.0) == 0.0); - static_assert(ccm::ext::safe::remap(5.0, 5.0, 1.0, 2.0, 7.0) == 1.0); - static_assert(ccm::ext::safe::repeat(5.0, 0.0) == 0.0); - static_assert(ccm::ext::safe::unlerp(5.0, 5.0, 7.0) == 0.0); + // Default helpers guard the degenerate input. + static_assert(ccm::ext::ceil_div(7, 0) == 0); + static_assert(ccm::ext::inverse_lerp(5.0, 5.0, 7.0) == 0.0); + static_assert(ccm::ext::normalize(5.0, 5.0, 5.0) == 0.0); + static_assert(ccm::ext::remap(5.0, 5.0, 1.0, 2.0, 7.0) == 1.0); + static_assert(ccm::ext::repeat(5.0, 0.0) == 0.0); + static_assert(ccm::ext::unlerp(5.0, 5.0, 7.0) == 0.0); + + // The unsafe variants skip the guard. + static_assert(ccm::ext::unsafe::normalize(5.0, 0.0, 10.0) == 0.5); + static_assert(ccm::ext::unsafe::remap(0.0, 10.0, 0.0, 100.0, 5.0) == 50.0); + static_assert(ccm::ext::unsafe::repeat(5.0, 4.0) == 1.0); + static_assert(ccm::ext::unsafe::unlerp(0.0, 10.0, 5.0) == 0.5); } diff --git a/tests/src/ext/delta_angle_test.cpp b/tests/src/ext/delta_angle_test.cpp index b415c99e..94dd45df 100644 --- a/tests/src/ext/delta_angle_test.cpp +++ b/tests/src/ext/delta_angle_test.cpp @@ -11,12 +11,16 @@ #include #include +#include TEST(CcmathExtDeltaAngleTest, Smoke) { - static_assert(ccm::ext::delta_angle(0.0, 90.0) == 90.0); - static_assert(ccm::ext::delta_angle(350.0, 10.0) == 20.0); - static_assert(ccm::ext::safe::delta_angle(350.0, 10.0) == 20.0); + constexpr double pi = ccm::numbers::pi_v; - EXPECT_DOUBLE_EQ(ccm::ext::delta_angle(0.0, 180.0), 180.0); + static_assert(ccm::ext::delta_angle(0.0, pi / 2) == pi / 2); + static_assert(ccm::ext::delta_angle(0.0, 2.0 * pi) == 0.0); + static_assert(ccm::ext::delta_angle(0.0, pi) == pi); + + EXPECT_DOUBLE_EQ(ccm::ext::delta_angle(0.0, pi), pi); + EXPECT_DOUBLE_EQ(ccm::ext::delta_angle(0.0, 1.5 * pi), -0.5 * pi); } diff --git a/tests/src/ext/inverse_lerp_test.cpp b/tests/src/ext/inverse_lerp_test.cpp index cadb1ea4..fb630799 100644 --- a/tests/src/ext/inverse_lerp_test.cpp +++ b/tests/src/ext/inverse_lerp_test.cpp @@ -15,8 +15,8 @@ TEST(CcmathExtInverseLerpTest, Smoke) { static_assert(ccm::ext::inverse_lerp(0.0, 10.0, 5.0) == 0.5); - static_assert(ccm::ext::safe::inverse_lerp(5.0, 5.0, 7.0) == 0.0); + static_assert(ccm::ext::inverse_lerp(5.0, 5.0, 7.0) == 0.0); EXPECT_DOUBLE_EQ(ccm::ext::inverse_lerp(2.0, 6.0, 4.0), 0.5); - EXPECT_DOUBLE_EQ(ccm::ext::safe::inverse_lerp(1.0, 1.0, 3.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::inverse_lerp(1.0, 1.0, 3.0), 0.0); } diff --git a/tests/src/ext/is_power_of_two_test.cpp b/tests/src/ext/is_power_of_two_test.cpp index 0c0fe8a0..e508827a 100644 --- a/tests/src/ext/is_power_of_two_test.cpp +++ b/tests/src/ext/is_power_of_two_test.cpp @@ -12,18 +12,22 @@ #include +#include + TEST(CcmathExtIsPowerOfTwoTest, Smoke) { static_assert(ccm::ext::is_power_of_two(1U)); static_assert(ccm::ext::is_power_of_two(8U)); static_assert(!ccm::ext::is_power_of_two(0U)); static_assert(!ccm::ext::is_power_of_two(6U)); - static_assert(ccm::ext::safe::is_power_of_two(4)); - static_assert(!ccm::ext::safe::is_power_of_two(-4)); + static_assert(ccm::ext::is_power_of_two(4)); + static_assert(!ccm::ext::is_power_of_two(-4)); + static_assert(!ccm::ext::is_power_of_two(std::numeric_limits::min())); static_assert(ccm::ext::ispow2(16)); - static_assert(ccm::ext::safe::ispow2(32)); + static_assert(ccm::ext::ispow2(32)); EXPECT_TRUE(ccm::ext::is_power_of_two(16U)); EXPECT_FALSE(ccm::ext::is_power_of_two(15U)); EXPECT_TRUE(ccm::ext::ispow2(64U)); + EXPECT_FALSE(ccm::ext::is_power_of_two(std::numeric_limits::min())); } diff --git a/tests/src/ext/lerp_angle_test.cpp b/tests/src/ext/lerp_angle_test.cpp index 6c2a2c5e..4d11b807 100644 --- a/tests/src/ext/lerp_angle_test.cpp +++ b/tests/src/ext/lerp_angle_test.cpp @@ -11,11 +11,15 @@ #include #include +#include TEST(CcmathExtLerpAngleTest, Smoke) { - static_assert(ccm::ext::lerp_angle(0.0, 90.0, 0.5) == 45.0); - static_assert(ccm::ext::safe::lerp_angle(0.0, 90.0, 0.5) == 45.0); + constexpr double pi = ccm::numbers::pi_v; - EXPECT_DOUBLE_EQ(ccm::ext::lerp_angle(0.0, 90.0, 0.5), 45.0); + static_assert(ccm::ext::lerp_angle(0.0, pi / 2, 0.5) == pi / 4); + static_assert(ccm::ext::lerp_angle(0.0, pi / 2, 0.0) == 0.0); + + EXPECT_DOUBLE_EQ(ccm::ext::lerp_angle(0.0, pi / 2, 0.5), pi / 4); + EXPECT_DOUBLE_EQ(ccm::ext::lerp_angle(0.0, pi / 2, 1.0), pi / 2); } diff --git a/tests/src/ext/move_towards_angle_test.cpp b/tests/src/ext/move_towards_angle_test.cpp index 8898de83..ddd4f1c2 100644 --- a/tests/src/ext/move_towards_angle_test.cpp +++ b/tests/src/ext/move_towards_angle_test.cpp @@ -11,14 +11,16 @@ #include #include +#include TEST(CcmathExtMoveTowardsAngleTest, Smoke) { - static_assert(ccm::ext::move_towards_angle(0.0, 90.0, 45.0) == 45.0); - static_assert(ccm::ext::safe::move_towards_angle(0.0, 90.0, 45.0) == 45.0); + constexpr double pi = ccm::numbers::pi_v; - EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(0.0, 90.0, 45.0), 45.0); - EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(0.0, 90.0, 200.0), 90.0); - EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(350.0, 10.0, 15.0), 365.0); - EXPECT_DOUBLE_EQ(ccm::ext::safe::move_towards_angle(350.0, 10.0, 15.0), 365.0); + static_assert(ccm::ext::move_towards_angle(0.0, pi / 2, pi / 4) == pi / 4); + static_assert(ccm::ext::move_towards_angle(0.0, pi / 2, pi) == pi / 2); + + EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(0.0, pi / 2, pi / 4), pi / 4); + EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(0.0, pi / 2, pi), pi / 2); + EXPECT_DOUBLE_EQ(ccm::ext::move_towards_angle(1.5 * pi, 0.0, 0.25 * pi), 1.75 * pi); } diff --git a/tests/src/ext/normalize_test.cpp b/tests/src/ext/normalize_test.cpp index 86c4cde3..ceba69b0 100644 --- a/tests/src/ext/normalize_test.cpp +++ b/tests/src/ext/normalize_test.cpp @@ -17,8 +17,9 @@ TEST(CcmathExtNormalizeTest, Smoke) static_assert(ccm::ext::normalize(5.0, 0.0, 10.0) == 0.5); static_assert(ccm::ext::normalize(-5.0, 0.0, 10.0) == 0.0); static_assert(ccm::ext::normalize(15.0, 0.0, 10.0) == 1.0); - static_assert(ccm::ext::safe::normalize(5.0, 5.0, 5.0) == 0.0); + static_assert(ccm::ext::normalize(5.0, 5.0, 5.0) == 0.0); + static_assert(ccm::ext::unsafe::normalize(5.0, 0.0, 10.0) == 0.5); EXPECT_DOUBLE_EQ(ccm::ext::normalize(2.5, 0.0, 10.0), 0.25); - EXPECT_DOUBLE_EQ(ccm::ext::safe::normalize(7.0, 3.0, 3.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::normalize(7.0, 3.0, 3.0), 0.0); } diff --git a/tests/src/ext/remap_test.cpp b/tests/src/ext/remap_test.cpp index dadb216d..744d3492 100644 --- a/tests/src/ext/remap_test.cpp +++ b/tests/src/ext/remap_test.cpp @@ -15,8 +15,9 @@ TEST(CcmathExtRemapTest, Smoke) { static_assert(ccm::ext::remap(0.0, 10.0, 100.0, 200.0, 5.0) == 150.0); - static_assert(ccm::ext::safe::remap(5.0, 5.0, 1.0, 2.0, 7.0) == 1.0); + static_assert(ccm::ext::remap(5.0, 5.0, 1.0, 2.0, 7.0) == 1.0); + static_assert(ccm::ext::unsafe::remap(0.0, 10.0, 100.0, 200.0, 5.0) == 150.0); EXPECT_DOUBLE_EQ(ccm::ext::remap(0.0, 1.0, 10.0, 20.0, 0.5), 15.0); - EXPECT_DOUBLE_EQ(ccm::ext::safe::remap(2.0, 2.0, 0.0, 1.0, 9.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::remap(2.0, 2.0, 0.0, 1.0, 9.0), 0.0); } diff --git a/tests/src/ext/repeat_test.cpp b/tests/src/ext/repeat_test.cpp index 4cb61e4b..bee5deb8 100644 --- a/tests/src/ext/repeat_test.cpp +++ b/tests/src/ext/repeat_test.cpp @@ -16,8 +16,10 @@ TEST(CcmathExtRepeatTest, Smoke) { static_assert(ccm::ext::repeat(370.0, 360.0) == 10.0); static_assert(ccm::ext::repeat(-10.0, 360.0) == 350.0); - static_assert(ccm::ext::safe::repeat(5.0, 0.0) == 0.0); + static_assert(ccm::ext::repeat(5.0, 0.0) == 0.0); + static_assert(ccm::ext::unsafe::repeat(370.0, 360.0) == 10.0); EXPECT_DOUBLE_EQ(ccm::ext::repeat(7.5, 3.0), 1.5); - EXPECT_DOUBLE_EQ(ccm::ext::safe::repeat(7.5, 0.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::repeat(7.5, 0.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::unsafe::repeat(7.5, 3.0), 1.5); } diff --git a/tests/src/ext/unlerp_test.cpp b/tests/src/ext/unlerp_test.cpp index 148a65ea..76cf855b 100644 --- a/tests/src/ext/unlerp_test.cpp +++ b/tests/src/ext/unlerp_test.cpp @@ -15,8 +15,9 @@ TEST(CcmathExtUnlerpTest, Smoke) { static_assert(ccm::ext::unlerp(0.0, 10.0, 5.0) == 0.5); - static_assert(ccm::ext::safe::unlerp(5.0, 5.0, 7.0) == 0.0); + static_assert(ccm::ext::unlerp(5.0, 5.0, 7.0) == 0.0); + static_assert(ccm::ext::unsafe::unlerp(0.0, 10.0, 5.0) == 0.5); EXPECT_DOUBLE_EQ(ccm::ext::unlerp(2.0, 6.0, 4.0), 0.5); - EXPECT_DOUBLE_EQ(ccm::ext::safe::unlerp(1.0, 1.0, 3.0), 0.0); + EXPECT_DOUBLE_EQ(ccm::ext::unlerp(1.0, 1.0, 3.0), 0.0); } From 5045886a2f1abdb4afef1795d2dea2c6ddccada9 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 18:10:58 -0400 Subject: [PATCH 41/52] Expand difficulty labels to six tiers and add a meta-issue label --- .github/labels.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/labels.yml b/.github/labels.yml index 72c25f15..0b095c6d 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -109,21 +109,30 @@ - name: "D0: Irrational" color: "c603fc" description: "A cursed correctly-rounded or hard-to-round problem that may take unreasonable effort." -- name: "D1: High" +- name: "D1: Very High" color: "b60205" + description: "Requires deep floating-point expertise and a complex or specialised algorithm." +- name: "D2: High" + color: "d93f0b" description: "Requires deep floating-point and codebase knowledge." -- name: "D2: Medium" +- name: "D3: Medium" color: "fbca04" description: "Requires a fair amount of numeric and codebase knowledge." -- name: "D3: Low" +- name: "D4: Low" color: "84dc3e" description: "Reasonably approachable for newer contributors." +- name: "D5: Trivial" + color: "c2e0c6" + description: "Mostly bit or exponent manipulation, approachable with little numeric background." - name: "Feature: Approved" color: "0e8a16" description: "Feature request approved by an admin. Work may proceed." - name: "Feature: Unapproved" color: "fbca04" description: "Feature request awaiting admin approval before work begins." +- name: "meta-issue" + color: "d4c5f9" + description: "Umbrella issue tracking work across multiple sub-issues or pull requests." - name: "P1: Critical" color: "b60205" description: "Needs immediate attention." From 18b60cc288c853f36d77211049f79af75c9ad9f5 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 15:32:55 -0400 Subject: [PATCH 42/52] Fix constexpr remainder and make fmod exact for large quotients --- .../math/runtime/func/basic/fmod_rt.hpp | 11 +- .../math/runtime/func/basic/remainder_rt.hpp | 20 +-- include/ccmath/math/basic/fmod.hpp | 13 +- include/ccmath/math/basic/impl/CMakeLists.txt | 2 + .../math/basic/impl/fmod_double_impl.hpp | 121 ++++++++++++++++++ .../math/basic/impl/fmod_float_impl.hpp | 121 ++++++++++++++++++ include/ccmath/math/basic/remainder.hpp | 30 ++--- tests/src/math/basic/fmod_test.cpp | 15 +++ tests/src/math/basic/remainder_test.cpp | 120 +++++++++++++++-- tests/src/math/basic/remquo_test.cpp | 5 + 10 files changed, 410 insertions(+), 48 deletions(-) create mode 100644 include/ccmath/math/basic/impl/fmod_double_impl.hpp create mode 100644 include/ccmath/math/basic/impl/fmod_float_impl.hpp diff --git a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp index 2b68f332..1e3d6ff5 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp @@ -12,8 +12,9 @@ #include "ccmath/internal/math/generic/builtins/basic/fmod.hpp" #include "ccmath/internal/math/runtime/func/detail/system_math.hpp" -#include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" +#include "ccmath/math/basic/impl/fmod_double_impl.hpp" +#include "ccmath/math/basic/impl/fmod_float_impl.hpp" #include @@ -28,7 +29,13 @@ namespace ccm::rt if constexpr (ccm::builtin::has_runtime_fmod) { return ccm::builtin::fmod_rt(x, y); } else { - return static_cast(x - (detail::trunc_scalar(x / y) * y)); + // No builtin and no system math, so reuse the exact fdlibm bit-reduction. It is exact for + // every magnitude, unlike the trunc formula the other no-builtin basic fallbacks share. + if constexpr (std::is_same_v) { return ccm::internal::fmod_float(x, y); } + else + { + return static_cast(ccm::internal::fmod_double(static_cast(x), static_cast(y))); + } } #endif } diff --git a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp index 786e52e8..c9747052 100644 --- a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp @@ -12,12 +12,9 @@ #include "ccmath/internal/math/generic/builtins/basic/remainder.hpp" #include "ccmath/internal/math/runtime/func/detail/system_math.hpp" -#include "ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" -#include "ccmath/internal/predef/unlikely.hpp" -#include "ccmath/internal/support/fp/fp_bits.hpp" +#include "ccmath/math/basic/remquo.hpp" -#include #include namespace ccm::rt @@ -31,16 +28,11 @@ namespace ccm::rt if constexpr (ccm::builtin::has_runtime_remainder) { return ccm::builtin::remainder_rt(x, y); } else { - using FPBits_t = typename ccm::support::fp::FPBits; - const FPBits_t x_bits(x); - const FPBits_t y_bits(y); - const bool x_is_nan = x_bits.is_nan(); - const bool y_is_nan = y_bits.is_nan(); - if (CCM_UNLIKELY((x_bits.is_inf() && !y_is_nan) || (y_bits.is_zero() && !x_is_nan) || (x_is_nan || y_is_nan))) - { - return -std::numeric_limits::quiet_NaN(); - } - return static_cast(x - (detail::trunc_scalar(x / y) * y)); + // No builtin and no system math, so reuse the exact remquo reduction. It rounds the + // quotient to nearest, ties-to-even, and covers the special cases, unlike the trunc + // formula that the other no-builtin basic fallbacks share. + int quotient = 0; + return ccm::remquo(x, y, "ient); } #endif } diff --git a/include/ccmath/math/basic/fmod.hpp b/include/ccmath/math/basic/fmod.hpp index ba9f8ba7..85e68503 100644 --- a/include/ccmath/math/basic/fmod.hpp +++ b/include/ccmath/math/basic/fmod.hpp @@ -15,7 +15,8 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" -#include "ccmath/math/nearest/trunc.hpp" +#include "ccmath/math/basic/impl/fmod_double_impl.hpp" +#include "ccmath/math/basic/impl/fmod_float_impl.hpp" #include @@ -68,7 +69,15 @@ namespace ccm } } - return static_cast(x - (ccm::trunc(x / y) * y)); + // Exact, magnitude-independent reduction via the fdlibm integer bit-reduction. The old + // x - trunc(x / y) * y formula was only exact while x / y stayed representable, so it lost + // low bits once abs(x / y) reached 2^53. long double delegates to the double kernel, matching + // the remquol convention. + if constexpr (std::is_same_v) { return internal::fmod_float(x, y); } + else + { + return static_cast(internal::fmod_double(static_cast(x), static_cast(y))); + } } template > diff --git a/include/ccmath/math/basic/impl/CMakeLists.txt b/include/ccmath/math/basic/impl/CMakeLists.txt index 44fe9fa9..77099abe 100644 --- a/include/ccmath/math/basic/impl/CMakeLists.txt +++ b/include/ccmath/math/basic/impl/CMakeLists.txt @@ -1,4 +1,6 @@ ccm_add_headers( + fmod_double_impl.hpp + fmod_float_impl.hpp nan_double_impl.hpp nan_float_impl.hpp nan_ldouble_impl.hpp diff --git a/include/ccmath/math/basic/impl/fmod_double_impl.hpp b/include/ccmath/math/basic/impl/fmod_double_impl.hpp new file mode 100644 index 00000000..9e6b98ed --- /dev/null +++ b/include/ccmath/math/basic/impl/fmod_double_impl.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +#include "ccmath/internal/predef/unlikely.hpp" +#include "ccmath/internal/support/bits.hpp" + +#include + +namespace ccm::internal +{ + namespace impl + { + // Exact floating-point remainder for double using the fdlibm integer bit-reduction. + // The quotient is reduced one binary digit at a time on the raw significand integers, so + // the result is exact for every magnitude and is independent of the active rounding mode, + // unlike the x - trunc(x / y) * y formula which loses bits once x / y is not representable. + // NOLINTNEXTLINE(readability-function-cognitive-complexity) + constexpr double fmod_double_impl(double x, double y) noexcept + { + std::int64_t hx = support::double_to_int64(x); + std::int64_t hy = support::double_to_int64(y); + + // Sign of the result follows x. + const std::int64_t sx = hx & static_cast(0x8000000000000000ULL); + + hx &= 0x7fffffffffffffffLL; // |x| + hy &= 0x7fffffffffffffffLL; // |y| + + // Purge exceptional inputs: y is zero, x is inf or NaN, or y is NaN. The (x * y) / (x * y) + // form yields the IEEE-mandated NaN for these cases. + if (CCM_UNLIKELY(hy == 0 || hx >= 0x7ff0000000000000LL || hy > 0x7ff0000000000000LL)) { return (x * y) / (x * y); } + + // |x| < |y| leaves x unchanged (this also covers x == 0). |x| == |y| gives signed zero. + if (hx < hy) { return x; } + if (hx == hy) { return support::int64_to_double(sx); } + + // Unbiased exponent of x, handling subnormals by normalizing the leading significand bit. + int ix = 0; + if (hx < 0x0010000000000000LL) + { + ix = -1022; + for (std::int64_t i = hx << 11; i > 0; i <<= 1) { ix -= 1; } + } + else + { + ix = static_cast(hx >> 52) - 1023; + } + + // Unbiased exponent of y. + int iy = 0; + if (hy < 0x0010000000000000LL) + { + iy = -1022; + for (std::int64_t i = hy << 11; i > 0; i <<= 1) { iy -= 1; } + } + else + { + iy = static_cast(hy >> 52) - 1023; + } + + // Promote both significands to integers with the implicit bit made explicit (subnormals are + // shifted up so their leading set bit sits in the same position a normal significand would). + if (ix >= -1022) { hx = 0x0010000000000000LL | (0x000fffffffffffffLL & hx); } + else + { + hx <<= (-1022 - ix); + } + if (iy >= -1022) { hy = 0x0010000000000000LL | (0x000fffffffffffffLL & hy); } + else + { + hy <<= (-1022 - iy); + } + + // Fixed-point remainder: shift-and-subtract for each binary digit of the quotient. + int n = ix - iy; + while (n--) + { + const std::int64_t hz = hx - hy; + if (hz < 0) { hx = hx + hx; } + else + { + if (hz == 0) { return support::int64_to_double(sx); } + hx = hz + hz; + } + } + const std::int64_t hz = hx - hy; + if (hz >= 0) { hx = hz; } + + // An exact zero remainder keeps the sign of x. + if (hx == 0) { return support::int64_to_double(sx); } + + // Renormalize the remainder back into a floating-point significand. + while (hx < 0x0010000000000000LL) + { + hx = hx + hx; + iy -= 1; + } + if (iy >= -1022) // normal result + { + hx = (hx - 0x0010000000000000LL) | (static_cast(iy + 1023) << 52); + } + else // subnormal result + { + hx >>= (-1022 - iy); + } + return support::int64_to_double(hx | sx); + } + } // namespace impl + + constexpr double fmod_double(double x, double y) noexcept + { return impl::fmod_double_impl(x, y); } +} // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/fmod_float_impl.hpp b/include/ccmath/math/basic/impl/fmod_float_impl.hpp new file mode 100644 index 00000000..314e2630 --- /dev/null +++ b/include/ccmath/math/basic/impl/fmod_float_impl.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +#include "ccmath/internal/predef/unlikely.hpp" +#include "ccmath/internal/support/bits.hpp" + +#include + +namespace ccm::internal +{ + namespace impl + { + // Exact floating-point remainder for float using the fdlibm integer bit-reduction. + // The quotient is reduced one binary digit at a time on the raw significand integers, so + // the result is exact for every magnitude and is independent of the active rounding mode, + // unlike the x - trunc(x / y) * y formula which loses bits once x / y is not representable. + // NOLINTNEXTLINE(readability-function-cognitive-complexity) + constexpr float fmod_float_impl(float x, float y) noexcept + { + std::int32_t hx = support::float_to_int32(x); + std::int32_t hy = support::float_to_int32(y); + + // Sign of the result follows x. + const std::int32_t sx = hx & static_cast(0x80000000U); + + hx &= 0x7fffffff; // |x| + hy &= 0x7fffffff; // |y| + + // Purge exceptional inputs: y is zero, x is inf or NaN, or y is NaN. The (x * y) / (x * y) + // form yields the IEEE-mandated NaN for these cases. + if (CCM_UNLIKELY(hy == 0 || hx >= 0x7f800000 || hy > 0x7f800000)) { return (x * y) / (x * y); } + + // |x| < |y| leaves x unchanged (this also covers x == 0). |x| == |y| gives signed zero. + if (hx < hy) { return x; } + if (hx == hy) { return support::int32_to_float(sx); } + + // Unbiased exponent of x, handling subnormals by normalizing the leading significand bit. + int ix = 0; + if (hx < 0x00800000) + { + ix = -126; + for (std::int32_t i = hx << 8; i > 0; i <<= 1) { ix -= 1; } + } + else + { + ix = (hx >> 23) - 127; + } + + // Unbiased exponent of y. + int iy = 0; + if (hy < 0x00800000) + { + iy = -126; + for (std::int32_t i = hy << 8; i > 0; i <<= 1) { iy -= 1; } + } + else + { + iy = (hy >> 23) - 127; + } + + // Promote both significands to integers with the implicit bit made explicit (subnormals are + // shifted up so their leading set bit sits in the same position a normal significand would). + if (ix >= -126) { hx = 0x00800000 | (0x007fffff & hx); } + else + { + hx <<= (-126 - ix); + } + if (iy >= -126) { hy = 0x00800000 | (0x007fffff & hy); } + else + { + hy <<= (-126 - iy); + } + + // Fixed-point remainder: shift-and-subtract for each binary digit of the quotient. + int n = ix - iy; + while (n--) + { + const std::int32_t hz = hx - hy; + if (hz < 0) { hx = hx + hx; } + else + { + if (hz == 0) { return support::int32_to_float(sx); } + hx = hz + hz; + } + } + const std::int32_t hz = hx - hy; + if (hz >= 0) { hx = hz; } + + // An exact zero remainder keeps the sign of x. + if (hx == 0) { return support::int32_to_float(sx); } + + // Renormalize the remainder back into a floating-point significand. + while (hx < 0x00800000) + { + hx = hx + hx; + iy -= 1; + } + if (iy >= -126) // normal result + { + hx = (hx - 0x00800000) | ((iy + 127) << 23); + } + else // subnormal result + { + hx >>= (-126 - iy); + } + return support::int32_to_float(hx | sx); + } + } // namespace impl + + constexpr float fmod_float(float x, float y) noexcept + { return impl::fmod_float_impl(x, y); } +} // namespace ccm::internal diff --git a/include/ccmath/math/basic/remainder.hpp b/include/ccmath/math/basic/remainder.hpp index 44a35170..8147d740 100644 --- a/include/ccmath/math/basic/remainder.hpp +++ b/include/ccmath/math/basic/remainder.hpp @@ -10,12 +10,9 @@ #pragma once -#include "ccmath/internal/math/generic/builtins/basic/remainder.hpp" #include "ccmath/internal/math/runtime/func/basic/remainder_rt.hpp" -#include "ccmath/internal/predef/unlikely.hpp" -#include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" -#include "ccmath/math/nearest/trunc.hpp" +#include "ccmath/math/basic/remquo.hpp" namespace ccm { @@ -31,23 +28,14 @@ namespace ccm { if (!ccm::support::is_constant_evaluated()) { return ccm::rt::remainder_rt(x, y); } - using FPBits_t = typename ccm::support::fp::FPBits; - const FPBits_t x_bits(x); - const FPBits_t y_bits(y); - - const bool x_is_nan = x_bits.is_nan(); - const bool y_is_nan = y_bits.is_nan(); - - // If x is ±∞ and y is not NaN, NaN is returned. - // If y is ±0 and x is not NaN, NaN is returned. - // If either argument is NaN, NaN is returned. - if (CCM_UNLIKELY((x_bits.is_inf() && !y_is_nan) || (y_bits.is_zero() && !x_is_nan) || (x_is_nan || y_is_nan))) - { - // All major compilers return -NaN. - return -std::numeric_limits::quiet_NaN(); - } - - return static_cast(x - (ccm::trunc(x / y) * y)); + // remainder is the remainder component of remquo, so reuse the exact iterative reduction + // already implemented there. The quotient rounds to nearest, ties-to-even, which keeps the + // result in the closed interval from -abs(y) / 2 to +abs(y) / 2. The result is an exact + // subtraction, so it does not depend on the active rounding mode, and the remquo kernel also + // covers the special cases (remainder of a finite value by an infinity is that finite value, + // and NaN is returned for an infinite dividend, a zero divisor, or a NaN argument). + int quotient = 0; + return ccm::remquo(x, y, "ient); } /** diff --git a/tests/src/math/basic/fmod_test.cpp b/tests/src/math/basic/fmod_test.cpp index 6297e937..78e03aa2 100644 --- a/tests/src/math/basic/fmod_test.cpp +++ b/tests/src/math/basic/fmod_test.cpp @@ -17,6 +17,21 @@ #include #include +TEST(CcmathBasicTests, FmodLargeQuotientCompileTime) +{ + // The exact fdlibm bit-reduction must reduce these at compile time even though abs(x / y) is far + // above 2^53. The old constexpr x - trunc(x / y) * y formula collapsed to 0 here. + static_assert(ccm::fmod(1e30, 3.0) == 1.0, "fmod(1e30, 3) must be 1"); + static_assert(ccm::fmod(-1e30, 3.0) == -1.0, "fmod(-1e30, 3) must be -1"); + static_assert(ccm::fmod(1e300, 7.0) == 1.0, "fmod(1e300, 7) must reduce exactly to 1"); + static_assert(ccm::fmod(1e30F, 3.0F) == 0.0F, "fmodf(1e30, 3) must reduce exactly to 0"); + + // Small / normal cases stay exact. + static_assert(ccm::fmod(10.0, 3.0) == 1.0, "fmod(10, 3) must be 1"); + static_assert(ccm::fmod(-10.0, 3.0) == -1.0, "fmod(-10, 3) must be -1"); + static_assert(ccm::fmod(7.5, 2.0) == 1.5, "fmod(7.5, 2) must be 1.5"); +} + TEST(CcmathBasicTests, Fmod) { diff --git a/tests/src/math/basic/remainder_test.cpp b/tests/src/math/basic/remainder_test.cpp index 4cb7d91b..69da67f4 100644 --- a/tests/src/math/basic/remainder_test.cpp +++ b/tests/src/math/basic/remainder_test.cpp @@ -17,20 +17,122 @@ #include #include +namespace +{ + constexpr double make_remainder(double x, double y) + { return ccm::remainder(x, y); } + + // Pulls the result of remainder(finite, inf) into a constant context. + constexpr double remainder_by_inf(double x) + { return ccm::remainder(x, std::numeric_limits::infinity()); } +} // namespace + +TEST(CcmathBasicTests, RemainderCompileTime) +{ + // remainder rounds the quotient to nearest, ties-to-even, so these are the values the standard + // requires. The old trunc/fmod formula returned 2.0 and 1.5 here. + static_assert(make_remainder(5.0, 3.0) == -1.0, "remainder(5, 3) must be -1"); + static_assert(make_remainder(7.5, 2.0) == -0.5, "remainder(7.5, 2) must be -0.5"); + + // A half-way quotient rounds to even. + static_assert(make_remainder(2.0, 1.0) == 0.0, "remainder(2, 1) must round the tie to 0"); + static_assert(make_remainder(3.0, 2.0) == -1.0, "remainder(3, 2) must be -1"); + + // remainder of a finite value by an infinity is that finite value. + static_assert(remainder_by_inf(3.0) == 3.0, "remainder(3, inf) must be 3"); + + // abs(x / y) well above 2^53 must still reduce exactly at compile time. This is the residual that + // slipped through before fmod's generic kernel became the exact fdlibm reduction (remainder routes + // through remquo, which reduces with ccm::fmod). + static_assert(make_remainder(1e30, 3.0) == 1.0, "remainder(1e30, 3) must be 1"); + static_assert(make_remainder(-1e30, 3.0) == -1.0, "remainder(-1e30, 3) must be -1"); + + static_assert(ccm::remainder(1.0, 1.0) == 0.0, "remainder(1, 1) must be 0"); + static_assert(ccm::remainderf(5.0F, 3.0F) == -1.0F, "remainderf(5, 3) must be -1"); +} + +TEST(CcmathBasicTests, RemainderConfirmedCases) +{ + // The three confirmed-wrong cases from the H1 bug report, checked at runtime. + EXPECT_EQ(ccm::remainder(5.0, 3.0), -1.0); + EXPECT_EQ(ccm::remainder(7.5, 2.0), -0.5); + + const double inf = std::numeric_limits::infinity(); + EXPECT_EQ(ccm::remainder(3.0, inf), 3.0); + EXPECT_EQ(ccm::remainder(-3.0, inf), -3.0); + EXPECT_EQ(ccm::remainder(3.0, -inf), 3.0); +} + +TEST(CcmathBasicTests, RemainderSignOfZero) +{ + // remainder(+/-0, y) is +/-0 for finite nonzero y, sign of x preserved. + const double pos = ccm::remainder(0.0, 3.0); + const double neg = ccm::remainder(-0.0, 3.0); + EXPECT_EQ(pos, 0.0); + EXPECT_EQ(neg, 0.0); + EXPECT_FALSE(std::signbit(pos)); + EXPECT_TRUE(std::signbit(neg)); + + const float posf = ccm::remainderf(0.0F, 3.0F); + const float negf = ccm::remainderf(-0.0F, 3.0F); + EXPECT_EQ(posf, 0.0F); + EXPECT_EQ(negf, 0.0F); + EXPECT_FALSE(std::signbit(posf)); + EXPECT_TRUE(std::signbit(negf)); +} + +TEST(CcmathBasicTests, RemainderSpecialPropagation) +{ + const double inf = std::numeric_limits::infinity(); + const double nan = std::numeric_limits::quiet_NaN(); + + // NaN is returned for an infinite dividend, a zero divisor, or a NaN argument. + EXPECT_TRUE(std::isnan(ccm::remainder(inf, 3.0))); + EXPECT_TRUE(std::isnan(ccm::remainder(-inf, 3.0))); + EXPECT_TRUE(std::isnan(ccm::remainder(3.0, 0.0))); + EXPECT_TRUE(std::isnan(ccm::remainder(3.0, -0.0))); + EXPECT_TRUE(std::isnan(ccm::remainder(nan, 3.0))); + EXPECT_TRUE(std::isnan(ccm::remainder(3.0, nan))); + EXPECT_TRUE(std::isnan(ccm::remainder(nan, nan))); +} + +TEST(CcmathBasicTests, RemainderLargeQuotient) +{ + // |x / y| well above 2^53 must still reduce exactly. The trunc formula collapsed here. + ccm::test::ExpectBinaryMatchesStd(1e30, 3.0, ccm::remainder, static_cast(std::remainder)); + ccm::test::ExpectBinaryMatchesStd(-1e30, 3.0, ccm::remainder, static_cast(std::remainder)); + ccm::test::ExpectBinaryMatchesStd(1e300, 7.0, ccm::remainder, static_cast(std::remainder)); + ccm::test::ExpectBinaryMatchesStd(1e30F, 3.0F, ccm::remainder, static_cast(std::remainder)); +} + TEST(CcmathBasicTests, Remainder) { static_assert(ccm::remainder(1.0, 1.0) == 0.0, "remainder has failed testing that it is static_assert-able!"); - ccm::test::ExpectBinaryMatchesStd(1.0, 1.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(1.0, 0.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(0.0, 1.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(0.0, 0.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(-1.0, 1.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(1.0, -1.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(-1.0, -1.0, ccm::remainder, static_cast(std::remainder)); - ccm::test::ExpectBinaryMatchesStd(-1.0, 0.0, ccm::remainder, static_cast(std::remainder)); + using double_fn = double (*)(double, double); + using float_fn = float (*)(float, float); + // A spread of normal values, both signs, for double. + const double doubles[][2] = { + { 1.0, 1.0 }, { 1.0, 0.0 }, { 0.0, 1.0 }, { 0.0, 0.0 }, { -1.0, 1.0 }, { 1.0, -1.0 }, { -1.0, -1.0 }, + { -1.0, 0.0 }, { 5.0, 3.0 }, { 7.5, 2.0 }, { -5.0, 3.0 }, { 5.0, -3.0 }, { -7.5, 2.0 }, { 9.0, 4.0 }, + { 2.0, 1.0 }, { 3.0, 2.0 }, { 10.5, 3.25 }, { -10.5, 3.25 }, { 123.456, 7.0 }, { 0.1, 0.03 }, { -0.1, 0.03 }, + }; + for (const auto & c : doubles) { ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); } + + // A spread of normal values, both signs, for float. + const float floats[][2] = { + { 1.0F, 1.0F }, { 5.0F, 3.0F }, { 7.5F, 2.0F }, { -5.0F, 3.0F }, { 5.0F, -3.0F }, + { 9.0F, 4.0F }, { 2.0F, 1.0F }, { 10.5F, 3.25F }, { 123.456F, 7.0F }, { 0.1F, 0.03F }, + }; + for (const auto & c : floats) { ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); } + + // Subnormal operands. constexpr double subnormal_dividend = -5.0166534782602e-198; constexpr double subnormal_divisor = 5.27085811e-315; - ccm::test::ExpectBinaryMatchesStd(subnormal_dividend, subnormal_divisor, ccm::remainder, static_cast(std::remainder)); + ccm::test::ExpectBinaryMatchesStd(subnormal_dividend, subnormal_divisor, ccm::remainder, static_cast(std::remainder)); + + // Long double goes through the same public entry point. + ccm::test::ExpectBinaryMatchesStd(5.0L, 3.0L, ccm::remainderl, static_cast(std::remainder)); + ccm::test::ExpectBinaryMatchesStd(7.5L, 2.0L, ccm::remainderl, static_cast(std::remainder)); } diff --git a/tests/src/math/basic/remquo_test.cpp b/tests/src/math/basic/remquo_test.cpp index 60a3c9ca..cddf8659 100644 --- a/tests/src/math/basic/remquo_test.cpp +++ b/tests/src/math/basic/remquo_test.cpp @@ -55,6 +55,11 @@ TEST(CcmathBasicTests, Remquo) static_assert(sa_quotient == -4, "sa_quotient == -4"); static_assert(sa_remainder == 1, "sa_quotient == 1"); + // abs(x / y) well above 2^53 must reduce exactly at compile time. remquo coarse-reduces with + // ccm::fmod, so this locks in that the exact fdlibm fmod reduction propagates through remquo. + static_assert(get_ccm_rem(1e30, 3.0) == 1.0, "remquo(1e30, 3) remainder must be 1"); + static_assert(get_ccm_rem(-1e30, 3.0) == -1.0, "remquo(-1e30, 3) remainder must be -1"); + // Test with positive values ccm::test::ExpectRemquoMatchesStd(7.0, 2.0); From 327035189775c9ccee7c3a5f75ceb7518f0cb85f Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 16:03:30 -0400 Subject: [PATCH 43/52] Set a read-only default token permission for the lint workflow --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ef24bd6e..899f67fc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,6 +24,9 @@ on: branches: - '**' +permissions: + contents: read + concurrency: group: lint-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true From 36002ba31c7a2ffa4c0661cd85f49517780905e4 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 16:03:43 -0400 Subject: [PATCH 44/52] Normalize spacing in the freestanding meson option --- meson_options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index 518b27a0..82eda77f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,6 @@ option('enable_runtime_simd', type: 'boolean', value: true, description: 'Enable SIMD optimization for runtime evaluation (does not affect compile-time)') option('disable_errno', type: 'boolean', value: false, description: 'Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)') option('disable_fenv', type: 'boolean', value: false, description: 'Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)') -option('freestanding', type : 'boolean', value : false, description : 'Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)') +option('freestanding', type: 'boolean', value: false, description: 'Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)') option('disable_reduced_precision_powl', type: 'boolean', value: false, description: 'Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback') option('deterministic', type: 'boolean', value: false, description: 'Produce bit-identical cross-hardware math: route transcendentals through the generic kernels (no libm), force the correctly-rounded FMA path, disable runtime SIMD, and evaluate long double in double precision (GCC/Clang)') From e5a117f8255343149ca534d68de89764c47a9020 Mon Sep 17 00:00:00 2001 From: ianpike Date: Fri, 19 Jun 2026 19:57:59 -0400 Subject: [PATCH 45/52] Fix long double narrowing in the remquo dispatch --- include/ccmath/math/basic/remquo.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/ccmath/math/basic/remquo.hpp b/include/ccmath/math/basic/remquo.hpp index d0eb7c14..e7636696 100644 --- a/include/ccmath/math/basic/remquo.hpp +++ b/include/ccmath/math/basic/remquo.hpp @@ -49,7 +49,9 @@ namespace ccm if constexpr (std::is_same_v) { return internal::remquo_float(x, y, quo); } else { - return internal::remquo_double(x, y, quo); + // double runs natively. long double delegates to the double kernel, so the narrowing + // is made explicit to stay clean under -Wconversion. + return static_cast(internal::remquo_double(static_cast(x), static_cast(y), quo)); } } From 707f775aef52fa0cb325510f9d64f1e475098140 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sat, 20 Jun 2026 01:43:16 -0400 Subject: [PATCH 46/52] Rework fmod --- .../math/runtime/func/basic/fmod_rt.hpp | 10 +- include/ccmath/math/basic/fmod.hpp | 14 +- include/ccmath/math/basic/impl/CMakeLists.txt | 3 +- .../math/basic/impl/fmod_double_impl.hpp | 121 ------------------ .../math/basic/impl/fmod_float_impl.hpp | 121 ------------------ include/ccmath/math/basic/impl/fmod_impl.hpp | 83 ++++++++++++ tests/src/math/basic/fmod_test.cpp | 3 +- 7 files changed, 95 insertions(+), 260 deletions(-) delete mode 100644 include/ccmath/math/basic/impl/fmod_double_impl.hpp delete mode 100644 include/ccmath/math/basic/impl/fmod_float_impl.hpp create mode 100644 include/ccmath/math/basic/impl/fmod_impl.hpp diff --git a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp index 1e3d6ff5..ebbb7f3a 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp @@ -13,8 +13,7 @@ #include "ccmath/internal/math/generic/builtins/basic/fmod.hpp" #include "ccmath/internal/math/runtime/func/detail/system_math.hpp" #include "ccmath/internal/math/runtime/func/rt_dispatch.hpp" -#include "ccmath/math/basic/impl/fmod_double_impl.hpp" -#include "ccmath/math/basic/impl/fmod_float_impl.hpp" +#include "ccmath/math/basic/impl/fmod_impl.hpp" #include @@ -29,12 +28,11 @@ namespace ccm::rt if constexpr (ccm::builtin::has_runtime_fmod) { return ccm::builtin::fmod_rt(x, y); } else { - // No builtin and no system math, so reuse the exact fdlibm bit-reduction. It is exact for - // every magnitude, unlike the trunc formula the other no-builtin basic fallbacks share. - if constexpr (std::is_same_v) { return ccm::internal::fmod_float(x, y); } + // No builtin and no system math, so use the exact FPBits reduction. It is exact for every magnitude. + if constexpr (std::is_same_v) { return static_cast(ccm::internal::fmod(static_cast(x), static_cast(y))); } else { - return static_cast(ccm::internal::fmod_double(static_cast(x), static_cast(y))); + return ccm::internal::fmod(x, y); } } #endif diff --git a/include/ccmath/math/basic/fmod.hpp b/include/ccmath/math/basic/fmod.hpp index 85e68503..3dc9615f 100644 --- a/include/ccmath/math/basic/fmod.hpp +++ b/include/ccmath/math/basic/fmod.hpp @@ -15,8 +15,7 @@ #include "ccmath/internal/predef/unlikely.hpp" #include "ccmath/internal/support/fp/fp_bits.hpp" #include "ccmath/internal/support/is_constant_evaluated.hpp" -#include "ccmath/math/basic/impl/fmod_double_impl.hpp" -#include "ccmath/math/basic/impl/fmod_float_impl.hpp" +#include "ccmath/math/basic/impl/fmod_impl.hpp" #include @@ -69,14 +68,13 @@ namespace ccm } } - // Exact, magnitude-independent reduction via the fdlibm integer bit-reduction. The old - // x - trunc(x / y) * y formula was only exact while x / y stayed representable, so it lost - // low bits once abs(x / y) reached 2^53. long double delegates to the double kernel, matching - // the remquol convention. - if constexpr (std::is_same_v) { return internal::fmod_float(x, y); } + // Exact, magnitude-independent reduction over FPBits that gives the same result in every + // rounding mode. long double reduces through the double kernel, matching the fmodl and + // remquol convention. + if constexpr (std::is_same_v) { return internal::fmod(x, y); } else { - return static_cast(internal::fmod_double(static_cast(x), static_cast(y))); + return static_cast(internal::fmod(static_cast(x), static_cast(y))); } } diff --git a/include/ccmath/math/basic/impl/CMakeLists.txt b/include/ccmath/math/basic/impl/CMakeLists.txt index 77099abe..80969919 100644 --- a/include/ccmath/math/basic/impl/CMakeLists.txt +++ b/include/ccmath/math/basic/impl/CMakeLists.txt @@ -1,6 +1,5 @@ ccm_add_headers( - fmod_double_impl.hpp - fmod_float_impl.hpp + fmod_impl.hpp nan_double_impl.hpp nan_float_impl.hpp nan_ldouble_impl.hpp diff --git a/include/ccmath/math/basic/impl/fmod_double_impl.hpp b/include/ccmath/math/basic/impl/fmod_double_impl.hpp deleted file mode 100644 index 9e6b98ed..00000000 --- a/include/ccmath/math/basic/impl/fmod_double_impl.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#pragma once - -#include "ccmath/internal/predef/unlikely.hpp" -#include "ccmath/internal/support/bits.hpp" - -#include - -namespace ccm::internal -{ - namespace impl - { - // Exact floating-point remainder for double using the fdlibm integer bit-reduction. - // The quotient is reduced one binary digit at a time on the raw significand integers, so - // the result is exact for every magnitude and is independent of the active rounding mode, - // unlike the x - trunc(x / y) * y formula which loses bits once x / y is not representable. - // NOLINTNEXTLINE(readability-function-cognitive-complexity) - constexpr double fmod_double_impl(double x, double y) noexcept - { - std::int64_t hx = support::double_to_int64(x); - std::int64_t hy = support::double_to_int64(y); - - // Sign of the result follows x. - const std::int64_t sx = hx & static_cast(0x8000000000000000ULL); - - hx &= 0x7fffffffffffffffLL; // |x| - hy &= 0x7fffffffffffffffLL; // |y| - - // Purge exceptional inputs: y is zero, x is inf or NaN, or y is NaN. The (x * y) / (x * y) - // form yields the IEEE-mandated NaN for these cases. - if (CCM_UNLIKELY(hy == 0 || hx >= 0x7ff0000000000000LL || hy > 0x7ff0000000000000LL)) { return (x * y) / (x * y); } - - // |x| < |y| leaves x unchanged (this also covers x == 0). |x| == |y| gives signed zero. - if (hx < hy) { return x; } - if (hx == hy) { return support::int64_to_double(sx); } - - // Unbiased exponent of x, handling subnormals by normalizing the leading significand bit. - int ix = 0; - if (hx < 0x0010000000000000LL) - { - ix = -1022; - for (std::int64_t i = hx << 11; i > 0; i <<= 1) { ix -= 1; } - } - else - { - ix = static_cast(hx >> 52) - 1023; - } - - // Unbiased exponent of y. - int iy = 0; - if (hy < 0x0010000000000000LL) - { - iy = -1022; - for (std::int64_t i = hy << 11; i > 0; i <<= 1) { iy -= 1; } - } - else - { - iy = static_cast(hy >> 52) - 1023; - } - - // Promote both significands to integers with the implicit bit made explicit (subnormals are - // shifted up so their leading set bit sits in the same position a normal significand would). - if (ix >= -1022) { hx = 0x0010000000000000LL | (0x000fffffffffffffLL & hx); } - else - { - hx <<= (-1022 - ix); - } - if (iy >= -1022) { hy = 0x0010000000000000LL | (0x000fffffffffffffLL & hy); } - else - { - hy <<= (-1022 - iy); - } - - // Fixed-point remainder: shift-and-subtract for each binary digit of the quotient. - int n = ix - iy; - while (n--) - { - const std::int64_t hz = hx - hy; - if (hz < 0) { hx = hx + hx; } - else - { - if (hz == 0) { return support::int64_to_double(sx); } - hx = hz + hz; - } - } - const std::int64_t hz = hx - hy; - if (hz >= 0) { hx = hz; } - - // An exact zero remainder keeps the sign of x. - if (hx == 0) { return support::int64_to_double(sx); } - - // Renormalize the remainder back into a floating-point significand. - while (hx < 0x0010000000000000LL) - { - hx = hx + hx; - iy -= 1; - } - if (iy >= -1022) // normal result - { - hx = (hx - 0x0010000000000000LL) | (static_cast(iy + 1023) << 52); - } - else // subnormal result - { - hx >>= (-1022 - iy); - } - return support::int64_to_double(hx | sx); - } - } // namespace impl - - constexpr double fmod_double(double x, double y) noexcept - { return impl::fmod_double_impl(x, y); } -} // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/fmod_float_impl.hpp b/include/ccmath/math/basic/impl/fmod_float_impl.hpp deleted file mode 100644 index 314e2630..00000000 --- a/include/ccmath/math/basic/impl/fmod_float_impl.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Ian Pike - * Copyright (c) CCMath contributors - * - * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. - * See LICENSE for more information. - * - * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - */ - -#pragma once - -#include "ccmath/internal/predef/unlikely.hpp" -#include "ccmath/internal/support/bits.hpp" - -#include - -namespace ccm::internal -{ - namespace impl - { - // Exact floating-point remainder for float using the fdlibm integer bit-reduction. - // The quotient is reduced one binary digit at a time on the raw significand integers, so - // the result is exact for every magnitude and is independent of the active rounding mode, - // unlike the x - trunc(x / y) * y formula which loses bits once x / y is not representable. - // NOLINTNEXTLINE(readability-function-cognitive-complexity) - constexpr float fmod_float_impl(float x, float y) noexcept - { - std::int32_t hx = support::float_to_int32(x); - std::int32_t hy = support::float_to_int32(y); - - // Sign of the result follows x. - const std::int32_t sx = hx & static_cast(0x80000000U); - - hx &= 0x7fffffff; // |x| - hy &= 0x7fffffff; // |y| - - // Purge exceptional inputs: y is zero, x is inf or NaN, or y is NaN. The (x * y) / (x * y) - // form yields the IEEE-mandated NaN for these cases. - if (CCM_UNLIKELY(hy == 0 || hx >= 0x7f800000 || hy > 0x7f800000)) { return (x * y) / (x * y); } - - // |x| < |y| leaves x unchanged (this also covers x == 0). |x| == |y| gives signed zero. - if (hx < hy) { return x; } - if (hx == hy) { return support::int32_to_float(sx); } - - // Unbiased exponent of x, handling subnormals by normalizing the leading significand bit. - int ix = 0; - if (hx < 0x00800000) - { - ix = -126; - for (std::int32_t i = hx << 8; i > 0; i <<= 1) { ix -= 1; } - } - else - { - ix = (hx >> 23) - 127; - } - - // Unbiased exponent of y. - int iy = 0; - if (hy < 0x00800000) - { - iy = -126; - for (std::int32_t i = hy << 8; i > 0; i <<= 1) { iy -= 1; } - } - else - { - iy = (hy >> 23) - 127; - } - - // Promote both significands to integers with the implicit bit made explicit (subnormals are - // shifted up so their leading set bit sits in the same position a normal significand would). - if (ix >= -126) { hx = 0x00800000 | (0x007fffff & hx); } - else - { - hx <<= (-126 - ix); - } - if (iy >= -126) { hy = 0x00800000 | (0x007fffff & hy); } - else - { - hy <<= (-126 - iy); - } - - // Fixed-point remainder: shift-and-subtract for each binary digit of the quotient. - int n = ix - iy; - while (n--) - { - const std::int32_t hz = hx - hy; - if (hz < 0) { hx = hx + hx; } - else - { - if (hz == 0) { return support::int32_to_float(sx); } - hx = hz + hz; - } - } - const std::int32_t hz = hx - hy; - if (hz >= 0) { hx = hz; } - - // An exact zero remainder keeps the sign of x. - if (hx == 0) { return support::int32_to_float(sx); } - - // Renormalize the remainder back into a floating-point significand. - while (hx < 0x00800000) - { - hx = hx + hx; - iy -= 1; - } - if (iy >= -126) // normal result - { - hx = (hx - 0x00800000) | ((iy + 127) << 23); - } - else // subnormal result - { - hx >>= (-126 - iy); - } - return support::int32_to_float(hx | sx); - } - } // namespace impl - - constexpr float fmod_float(float x, float y) noexcept - { return impl::fmod_float_impl(x, y); } -} // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/fmod_impl.hpp b/include/ccmath/math/basic/impl/fmod_impl.hpp new file mode 100644 index 00000000..661347dc --- /dev/null +++ b/include/ccmath/math/basic/impl/fmod_impl.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) Ian Pike + * Copyright (c) CCMath contributors + * + * CCMath is provided under the Apache-2.0 License WITH LLVM-exception. + * See LICENSE for more information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +#include "ccmath/internal/predef/unlikely.hpp" +#include "ccmath/internal/support/fp/fp_bits.hpp" +#include "ccmath/math/fmanip/copysign.hpp" +#include "ccmath/math/fmanip/scalbn.hpp" + +#include +#include +#include + +namespace ccm::internal +{ + namespace impl + { + // Exact floating-point remainder for any IEEE type, read straight off FPBits. Each magnitude is + // an integer significand scaled by a power of two, abs(v) == get_explicit_mantissa(v) * + // 2^(get_explicit_exponent(v) - mant_bits), an identity FPBits gives for normals and subnormals + // alike. The remainder of abs(x) by abs(y) is then + // ((sig_x * 2^gap) mod sig_y) * 2^(exp_y - mant_bits), with gap the difference of the two binade + // exponents. The modular product is built a chunk of bits at a time with 64-bit arithmetic, so + // the loop runs in gap / chunk steps instead of one step per bit, and the result stays exact for + // every magnitude and independent of the active rounding mode. + template + constexpr T fmod_impl(T x, T y) noexcept + { + static_assert(std::is_floating_point_v, "fmod_impl requires a floating-point type"); + using FPBits_t = support::fp::FPBits; + + const FPBits_t x_bits(x); + const FPBits_t y_bits(y); + + // NaN for an infinite dividend, a zero divisor, or a NaN argument. + if (CCM_UNLIKELY(x_bits.is_inf() || y_bits.is_zero() || x_bits.is_nan() || y_bits.is_nan())) { return FPBits_t::quiet_nan().get_val(); } + + // Compare magnitudes through the exponent and significand bits. For finite values that integer + // order matches the floating-point order, so this also covers fmod(x, +/-inf) = x. + const auto x_mag = x_bits.abs().uintval(); + const auto y_mag = y_bits.abs().uintval(); + + // Any abs(x) < abs(y) returns x unchanged. + if (x_mag < y_mag) { return x; } + // abs(x) == abs(y) leaves a zero that keeps the sign of x. + if (x_mag == y_mag) { return ccm::copysign(static_cast(0), x); } + + constexpr int mant_bits = std::numeric_limits::digits - 1; + // Largest shift that keeps a reduced significand (below 2^(mant_bits + 1)) inside 64 bits. + constexpr int chunk = 63 - mant_bits; + + const int exp_x = x_bits.get_explicit_exponent(); + const int exp_y = y_bits.get_explicit_exponent(); + const std::uint64_t sig_y = static_cast(y_bits.get_explicit_mantissa()); + std::uint64_t r = static_cast(x_bits.get_explicit_mantissa()) % sig_y; + + for (int gap = exp_x - exp_y; gap > 0;) + { + const int step = gap < chunk ? gap : chunk; + r = (r << step) % sig_y; + gap -= step; + } + + if (r == 0) { return ccm::copysign(static_cast(0), x); } + return ccm::copysign(ccm::scalbn(static_cast(r), exp_y - mant_bits), x); + } + } // namespace impl + + // Thin wrapper so the public dispatch reads the same as the other basic functions. float and + // double evaluate natively. long double is reduced through the double kernel by the caller, + // matching the existing fmodl and remquol convention. + template + constexpr T fmod(T x, T y) noexcept + { return impl::fmod_impl(x, y); } +} // namespace ccm::internal diff --git a/tests/src/math/basic/fmod_test.cpp b/tests/src/math/basic/fmod_test.cpp index 78e03aa2..be251dd5 100644 --- a/tests/src/math/basic/fmod_test.cpp +++ b/tests/src/math/basic/fmod_test.cpp @@ -19,8 +19,7 @@ TEST(CcmathBasicTests, FmodLargeQuotientCompileTime) { - // The exact fdlibm bit-reduction must reduce these at compile time even though abs(x / y) is far - // above 2^53. The old constexpr x - trunc(x / y) * y formula collapsed to 0 here. + // Large quotients must still reduce exactly at compile time, even when abs(x / y) is far above 2^53. static_assert(ccm::fmod(1e30, 3.0) == 1.0, "fmod(1e30, 3) must be 1"); static_assert(ccm::fmod(-1e30, 3.0) == -1.0, "fmod(-1e30, 3) must be -1"); static_assert(ccm::fmod(1e300, 7.0) == 1.0, "fmod(1e300, 7) must reduce exactly to 1"); From d615122181c23f3547cc4ed437a6f5467df096e8 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 21 Jun 2026 13:16:35 -0400 Subject: [PATCH 47/52] Add fmod conformance tests and a binary64 kernel guard --- include/ccmath/math/basic/impl/fmod_impl.hpp | 4 + tests/conformance/fenv_exception_test.cpp | 3 + .../conformance/rounding_conformance_test.cpp | 21 +++ tests/src/math/basic/fmod_test.cpp | 166 +++++++++++++++--- tests/src/math/basic/remquo_test.cpp | 56 ++---- 5 files changed, 189 insertions(+), 61 deletions(-) diff --git a/include/ccmath/math/basic/impl/fmod_impl.hpp b/include/ccmath/math/basic/impl/fmod_impl.hpp index 661347dc..76424a19 100644 --- a/include/ccmath/math/basic/impl/fmod_impl.hpp +++ b/include/ccmath/math/basic/impl/fmod_impl.hpp @@ -35,6 +35,10 @@ namespace ccm::internal constexpr T fmod_impl(T x, T y) noexcept { static_assert(std::is_floating_point_v, "fmod_impl requires a floating-point type"); + // The reduction lifts the significand into a uint64_t and shifts it by chunk = 63 - mant_bits + // bits, so it only supports types up to binary64. Wider types (80-bit or 128-bit long double) + // would give a non-positive chunk and must reduce through the double kernel at the call site. + static_assert(std::numeric_limits::digits <= 53, "fmod_impl only supports types up to binary64"); using FPBits_t = support::fp::FPBits; const FPBits_t x_bits(x); diff --git a/tests/conformance/fenv_exception_test.cpp b/tests/conformance/fenv_exception_test.cpp index 5df4738a..9dc3b28a 100644 --- a/tests/conformance/fenv_exception_test.cpp +++ b/tests/conformance/fenv_exception_test.cpp @@ -42,6 +42,9 @@ TEST(CcmathFenvExceptionTests, DomainErrorsRaiseInvalidLikeStd) ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::log1p(runtime_value(-2.0))); }, [] { consume(std::log1p(runtime_value(-2.0))); }, FE_INVALID); ccm::test::ExpectFenvFlagsMatchStd( [] { consume(ccm::fmod(runtime_value(1.0), runtime_value(0.0))); }, [] { consume(std::fmod(runtime_value(1.0), runtime_value(0.0))); }, FE_INVALID); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::fmod(runtime_value(std::numeric_limits::infinity()), runtime_value(1.0))); }, + [] { consume(std::fmod(runtime_value(std::numeric_limits::infinity()), runtime_value(1.0))); }, + FE_INVALID); ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::remainder(runtime_value(1.0), runtime_value(0.0))); }, [] { consume(std::remainder(runtime_value(1.0), runtime_value(0.0))); }, FE_INVALID); diff --git a/tests/conformance/rounding_conformance_test.cpp b/tests/conformance/rounding_conformance_test.cpp index b23b1e19..3721431d 100644 --- a/tests/conformance/rounding_conformance_test.cpp +++ b/tests/conformance/rounding_conformance_test.cpp @@ -168,6 +168,27 @@ TEST(CcmathRoundingConformanceTests, CeilTruncRoundFloorIndependentOfMode) ccm::test::ExpectFpUnaryOverMatchesStdAllModes(ccm::test::samples::kNearbyIntProbeDouble, ccm::floor, static_cast(std::floor)); } +TEST(CcmathRoundingConformanceTests, FmodRemainderIndependentOfRoundingMode) +{ + // fmod and remainder are exact operations, so the result must not vary with the active rounding + // mode. Both operands go through runtime_value so the kernels read the dynamic mode at run time. + ccm::test::ForEachRoundingModeOrSkip( + [&](int mode) + { + ccm::test::ScopedRoundingMode scope(mode); + ASSERT_TRUE(scope.active()); + + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(10.0), runtime_value(3.0)), 1.0); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(7.5), runtime_value(2.0)), 1.5); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(-10.0), runtime_value(3.0)), -1.0); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(1e30), runtime_value(3.0)), 1.0); + ccm::test::ExpectFpEq(ccm::internal::fmod(runtime_value(1e300), runtime_value(7.0)), 1.0); + + ccm::test::ExpectFpEq(ccm::remainder(runtime_value(5.0), runtime_value(3.0)), -1.0); + ccm::test::ExpectFpEq(ccm::remainder(runtime_value(7.5), runtime_value(2.0)), -0.5); + }); +} + // The following pin the FE_TONEAREST guard in the expo runtime headers: outside round to nearest // the runtime dispatch must fall back to the generic kernel, so it matches the generic wrapper bit // for bit over normal inputs. diff --git a/tests/src/math/basic/fmod_test.cpp b/tests/src/math/basic/fmod_test.cpp index be251dd5..890fa86e 100644 --- a/tests/src/math/basic/fmod_test.cpp +++ b/tests/src/math/basic/fmod_test.cpp @@ -9,13 +9,49 @@ */ #include "utils/std_compare.hpp" +#include "utils/test_runtime.hpp" #include #include #include +#include +#include #include +#include + +using ccm::test::runtime_value; + +namespace +{ + // fmod is an exact operation, so every result must match the host libm bit for bit, not within a + // ULP band. ccm::fmod is the public entry (builtin or kernel); ccm::internal::fmod is the kernel + // the constexpr and no-builtin paths actually run. + void expect_public_exact(double x, double y) + { + SCOPED_TRACE(testing::Message() << "fmod(" << x << ", " << y << ")"); + ccm::test::ExpectFpEq(ccm::fmod(x, y), std::fmod(x, y)); + } + void expect_public_exact_f(float x, float y) + { + SCOPED_TRACE(testing::Message() << "fmodf(" << x << ", " << y << ")"); + ccm::test::ExpectFpEq(ccm::fmod(x, y), std::fmod(x, y)); + } + // Exercises the chunked-modulo kernel directly at runtime (the public path uses __builtin_fmod on + // GCC/Clang, so without this the kernel only has compile-time coverage). runtime_value defeats + // constant folding so the kernel really executes. + void expect_kernel_exact(double x, double y) + { + SCOPED_TRACE(testing::Message() << "kernel fmod(" << x << ", " << y << ")"); + ccm::test::ExpectFpEq(ccm::internal::fmod(runtime_value(x), runtime_value(y)), std::fmod(x, y)); + } + void expect_kernel_exact_f(float x, float y) + { + SCOPED_TRACE(testing::Message() << "kernel fmodf(" << x << ", " << y << ")"); + ccm::test::ExpectFpEq(ccm::internal::fmod(runtime_value(x), runtime_value(y)), std::fmod(x, y)); + } +} // namespace TEST(CcmathBasicTests, FmodLargeQuotientCompileTime) { @@ -29,35 +65,123 @@ TEST(CcmathBasicTests, FmodLargeQuotientCompileTime) static_assert(ccm::fmod(10.0, 3.0) == 1.0, "fmod(10, 3) must be 1"); static_assert(ccm::fmod(-10.0, 3.0) == -1.0, "fmod(-10, 3) must be -1"); static_assert(ccm::fmod(7.5, 2.0) == 1.5, "fmod(7.5, 2) must be 1.5"); + static_assert(ccm::fmod(1, 2) == 1, "fmod must be usable in a static_assert"); +} + +TEST(CcmathBasicTests, FmodBitExactVsStd) +{ + for (double y : { 3.0, -3.0, 2.5, 0.5, 1024.0, 1e-300 }) + { + for (double x : { 10.0, -10.0, 7.5, -7.5, 0.0, -0.0, 1.5, 1e30, -1e30, 1e300 }) + { + expect_public_exact(x, y); + expect_kernel_exact(x, y); + } + } + for (float y : { 3.0F, -3.0F, 2.5F, 0.5F }) + { + for (float x : { 10.0F, -10.0F, 7.5F, 0.0F, -0.0F, 1e30F }) + { + expect_public_exact_f(x, y); + expect_kernel_exact_f(x, y); + } + } +} + +TEST(CcmathBasicTests, FmodKernelRandomBitExactVsStd) +{ + // Stress the runtime kernel against the host libm on random finite pairs, bit for bit. + std::mt19937_64 rng(0xF110D); + std::uniform_int_distribution bd(0, ~std::uint64_t(0)); + int checked = 0; + while (checked < 6000) + { + double x; + double y; + const std::uint64_t bx = bd(rng); + const std::uint64_t by = bd(rng); + std::memcpy(&x, &bx, sizeof x); + std::memcpy(&y, &by, sizeof y); + if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0) { continue; } + ccm::test::ExpectFpEq(ccm::internal::fmod(x, y), std::fmod(x, y)); + ++checked; + } + + std::mt19937 rngf(0xF110F); + std::uniform_int_distribution bf(0, ~std::uint32_t(0)); + checked = 0; + while (checked < 6000) + { + float x; + float y; + const std::uint32_t bx = bf(rngf); + const std::uint32_t by = bf(rngf); + std::memcpy(&x, &bx, sizeof x); + std::memcpy(&y, &by, sizeof y); + if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0F) { continue; } + ccm::test::ExpectFpEq(ccm::internal::fmod(x, y), std::fmod(x, y)); + ++checked; + } } -TEST(CcmathBasicTests, Fmod) +TEST(CcmathBasicTests, FmodSignedZeroResult) { + // A zero remainder (exact multiple, or equal magnitudes) keeps the sign of x. + ccm::test::ExpectSignedZero(ccm::fmod(6.0, 3.0), false); + ccm::test::ExpectSignedZero(ccm::fmod(-6.0, 3.0), true); + ccm::test::ExpectSignedZero(ccm::fmod(3.0, 3.0), false); + ccm::test::ExpectSignedZero(ccm::fmod(-3.0, 3.0), true); + ccm::test::ExpectSignedZero(ccm::internal::fmod(runtime_value(6.0), runtime_value(3.0)), false); + ccm::test::ExpectSignedZero(ccm::internal::fmod(runtime_value(-6.0), runtime_value(3.0)), true); + ccm::test::ExpectSignedZero(ccm::fmod(6.0F, 3.0F), false); + ccm::test::ExpectSignedZero(ccm::fmod(-6.0F, 3.0F), true); - // Test that fmod works with static_assert - static_assert(ccm::fmod(1, 2) == 1, "fmod has failed testing that it is static_assert-able!"); + // fmod(+/-0, y) returns +/-0 for non-zero y. + ccm::test::ExpectSignedZero(ccm::fmod(0.0, 3.0), false); + ccm::test::ExpectSignedZero(ccm::fmod(-0.0, 3.0), true); +} - ccm::test::ExpectBinaryMatchesStd(10.0F, 3.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(10.0F, -3.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(-10.0F, 3.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(-10.0F, -3.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(0.0F, 3.0F, ccm::fmod, static_cast(std::fmod)); +TEST(CcmathBasicTests, FmodSpecialCases) +{ + constexpr double inf = std::numeric_limits::infinity(); + constexpr double nan = std::numeric_limits::quiet_NaN(); - // This is a tough test as it forces rounding precision issues. - // EXPECT_FLOAT_EQ(ccm::fmod(30.508474576271183309f, 6.1016949152542370172f), std::fmod(30.508474576271183309f, 6.1016949152542370172f)); + // fmod(x, +/-inf) == x for finite x. + expect_public_exact(10.0, inf); + expect_public_exact(10.0, -inf); + expect_public_exact(-7.5, inf); - // Test fmod with integer numbers - ccm::test::ExpectSameAsStd(ccm::fmod(10, 3), std::fmod(10, 3)); + // Domain errors yield NaN: fmod(x, 0), fmod(+/-inf, y), and NaN in either argument. + EXPECT_TRUE(std::isnan(ccm::fmod(10.0, 0.0))); + EXPECT_TRUE(std::isnan(ccm::fmod(inf, 3.0))); + EXPECT_TRUE(std::isnan(ccm::fmod(-inf, 3.0))); + EXPECT_TRUE(std::isnan(ccm::fmod(nan, 3.0))); + EXPECT_TRUE(std::isnan(ccm::fmod(3.0, nan))); + EXPECT_TRUE(std::isnan(ccm::internal::fmod(runtime_value(inf), runtime_value(3.0)))); + EXPECT_TRUE(std::isnan(ccm::internal::fmod(runtime_value(10.0), runtime_value(0.0)))); +} - ccm::test::ExpectBinaryMatchesStd(1.5, 2.781342323457793e-309, ccm::fmod, static_cast(std::fmod)); +TEST(CcmathBasicTests, FmodSubnormal) +{ + constexpr double dmin = std::numeric_limits::denorm_min(); + expect_public_exact(1.5, 2.781342323457793e-309); // normal dividend, subnormal divisor + expect_kernel_exact(1.5, 2.781342323457793e-309); + expect_kernel_exact(5.0 * dmin, 2.0 * dmin); // subnormal dividend and divisor + expect_kernel_exact(7.0 * dmin, 3.0 * dmin); + expect_kernel_exact(1e300, dmin); // largest gap (normal over min subnormal) + expect_kernel_exact(std::numeric_limits::max(), dmin); +} - /// Test Edge Cases +TEST(CcmathBasicTests, FmodTypesAndOverloads) +{ + // Integer overload promotes to double. + ccm::test::ExpectFpEq(ccm::fmod(10, 3), std::fmod(10, 3)); + static_assert(std::is_same_v, "integer fmod must return double"); - ccm::test::ExpectBinaryMatchesStd(0.0F, 1.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(-0.0F, 1.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(10.0F, 0.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(10.0F, std::numeric_limits::infinity(), ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(10.0F, -std::numeric_limits::infinity(), ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(std::numeric_limits::quiet_NaN(), 10.0F, ccm::fmod, static_cast(std::fmod)); - ccm::test::ExpectBinaryMatchesStd(10.0F, std::numeric_limits::quiet_NaN(), ccm::fmod, static_cast(std::fmod)); + // fmodf / fmodl wrappers. long double delegates to the double kernel by design, so use values that + // are exact through double. + ccm::test::ExpectFpEq(ccm::fmodf(10.0F, 3.0F), std::fmod(10.0F, 3.0F)); + EXPECT_EQ(ccm::fmodl(10.0L, 3.0L), 1.0L); + EXPECT_EQ(ccm::fmodl(7.5L, 2.0L), 1.5L); + EXPECT_EQ(ccm::fmodl(-10.0L, 3.0L), -1.0L); } diff --git a/tests/src/math/basic/remquo_test.cpp b/tests/src/math/basic/remquo_test.cpp index cddf8659..14ad3faf 100644 --- a/tests/src/math/basic/remquo_test.cpp +++ b/tests/src/math/basic/remquo_test.cpp @@ -56,7 +56,7 @@ TEST(CcmathBasicTests, Remquo) static_assert(sa_remainder == 1, "sa_quotient == 1"); // abs(x / y) well above 2^53 must reduce exactly at compile time. remquo coarse-reduces with - // ccm::fmod, so this locks in that the exact fdlibm fmod reduction propagates through remquo. + // ccm::fmod, so this locks in that the exact fmod reduction propagates through remquo. static_assert(get_ccm_rem(1e30, 3.0) == 1.0, "remquo(1e30, 3) remainder must be 1"); static_assert(get_ccm_rem(-1e30, 3.0) == -1.0, "remquo(-1e30, 3) remainder must be -1"); @@ -69,43 +69,19 @@ TEST(CcmathBasicTests, Remquo) // Test with zero values ccm::test::ExpectRemquoMatchesStd(0.0, 2.0); - /* TODO(IanP): Infinity and NaN remquo cases fail on CI but pass locally. Investigate. - // Test with infinity - bool isCcmLeftInfinityNegative = (std::signbit(get_ccm_rem(std::numeric_limits::infinity(), 2.0)) == true); // NOLINT - bool isStdLeftInfinityNegative = (std::signbit(get_std_rem(std::numeric_limits::infinity(), 2.0)) == true); // NOLINT - bool didCcmLeftInfinityReturnNan = std::isnan(get_ccm_rem(std::numeric_limits::infinity(), 2.0)); - bool didStdLeftInfinityReturnNan = std::isnan(get_std_rem(std::numeric_limits::infinity(), 2.0)); - EXPECT_EQ(isCcmLeftInfinityNegative, isStdLeftInfinityNegative); - EXPECT_EQ(didCcmLeftInfinityReturnNan, didStdLeftInfinityReturnNan); - EXPECT_EQ(get_ccm_quo(std::numeric_limits::infinity(), 2.0), get_std_quo(std::numeric_limits::infinity(), 2.0)); - - // Test with negative infinity - bool isCcmLeftNegativeInfinityNegative = (std::signbit(get_ccm_rem(-std::numeric_limits::infinity(), 2.0)) == true); // NOLINT - bool isStdLeftNegativeInfinityNegative = (std::signbit(get_std_rem(-std::numeric_limits::infinity(), 2.0)) == true); // NOLINT - bool didCcmLeftNegativeInfinityReturnNan = std::isnan(get_ccm_rem(-std::numeric_limits::infinity(), 2.0)); - bool didStdLeftNegativeInfinityReturnNan = std::isnan(get_std_rem(-std::numeric_limits::infinity(), 2.0)); - EXPECT_EQ(isCcmLeftNegativeInfinityNegative, isStdLeftNegativeInfinityNegative); - EXPECT_EQ(didCcmLeftNegativeInfinityReturnNan, didStdLeftNegativeInfinityReturnNan); - EXPECT_EQ(get_ccm_quo(-std::numeric_limits::infinity(), 2.0), get_std_quo(-std::numeric_limits::infinity(), 2.0)); - - // Test with NaN - bool isCcmLeftNanNegative = (std::signbit(get_ccm_rem(std::numeric_limits::quiet_NaN(), 2.0)) == true && - std::isnan(get_ccm_rem(std::numeric_limits::quiet_NaN(), 2.0)) == true); // NOLINT bool isStdLeftNanNegative = - (std::signbit(get_std_rem(std::numeric_limits::quiet_NaN(), 2.0)) == true && std::isnan(get_std_rem(std::numeric_limits::quiet_NaN(), 2.0)) - == true); // NOLINT bool didCcmLeftNanReturnNan = std::isnan(get_ccm_rem(std::numeric_limits::quiet_NaN(), 2.0)); bool didStdLeftNanReturnNan = - std::isnan(get_std_rem(std::numeric_limits::quiet_NaN(), 2.0)); EXPECT_EQ(isCcmLeftNanNegative, isStdLeftNanNegative); - EXPECT_EQ(didCcmLeftNanReturnNan, didStdLeftNanReturnNan); - EXPECT_EQ(get_ccm_quo(std::numeric_limits::quiet_NaN(), 2.0), get_std_quo(std::numeric_limits::quiet_NaN(), 2.0)); - - // Test with negative NaN - bool isCcmLeftNegativeNanNegative = (std::signbit(get_ccm_rem(-std::numeric_limits::quiet_NaN(), 2.0)) == true && - std::isnan(get_ccm_rem(-std::numeric_limits::quiet_NaN(), 2.0)) == true); // NOLINT bool isStdLeftNegativeNanNegative = - (std::signbit(get_std_rem(-std::numeric_limits::quiet_NaN(), 2.0)) == true && - std::isnan(get_std_rem(-std::numeric_limits::quiet_NaN(), 2.0)) == true); // NOLINT bool didCcmLeftNegativeNanReturnNan = - std::isnan(get_ccm_rem(-std::numeric_limits::quiet_NaN(), 2.0)); bool didStdLeftNegativeNanReturnNan = - std::isnan(get_std_rem(-std::numeric_limits::quiet_NaN(), 2.0)); EXPECT_EQ(isCcmLeftNegativeNanNegative, isStdLeftNegativeNanNegative); - EXPECT_EQ(didCcmLeftNegativeNanReturnNan, didStdLeftNegativeNanReturnNan); - EXPECT_EQ(get_ccm_quo(-std::numeric_limits::quiet_NaN(), 2.0), get_std_quo(-std::numeric_limits::quiet_NaN(), 2.0)); - - */ + // Domain cases: the standard returns a NaN remainder when x is infinite or y is zero, and + // propagates a NaN argument. The quotient (and the NaN sign) are unspecified in these cases, so only + // the NaN result is asserted. The earlier version compared quo and signbit here, which is what made + // it pass locally but fail on other platforms. + constexpr double inf = std::numeric_limits::infinity(); + constexpr double nan = std::numeric_limits::quiet_NaN(); + EXPECT_TRUE(std::isnan(get_ccm_rem(inf, 2.0))); + EXPECT_TRUE(std::isnan(get_ccm_rem(-inf, 2.0))); + EXPECT_TRUE(std::isnan(get_ccm_rem(2.0, 0.0))); + EXPECT_TRUE(std::isnan(get_ccm_rem(nan, 2.0))); + EXPECT_TRUE(std::isnan(get_ccm_rem(2.0, nan))); + + // std agrees that these are NaN, confirming the shared contract. + EXPECT_TRUE(std::isnan(get_std_rem(inf, 2.0))); + EXPECT_TRUE(std::isnan(get_std_rem(2.0, 0.0))); } From 5f6c1f6037c98f9d764bf54d387ae88dc047075c Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 21 Jun 2026 13:48:58 -0400 Subject: [PATCH 48/52] Avoid function-style casts in the fmod test --- tests/src/math/basic/fmod_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/math/basic/fmod_test.cpp b/tests/src/math/basic/fmod_test.cpp index 890fa86e..fe79df19 100644 --- a/tests/src/math/basic/fmod_test.cpp +++ b/tests/src/math/basic/fmod_test.cpp @@ -92,7 +92,7 @@ TEST(CcmathBasicTests, FmodKernelRandomBitExactVsStd) { // Stress the runtime kernel against the host libm on random finite pairs, bit for bit. std::mt19937_64 rng(0xF110D); - std::uniform_int_distribution bd(0, ~std::uint64_t(0)); + std::uniform_int_distribution bd(0, std::numeric_limits::max()); int checked = 0; while (checked < 6000) { @@ -108,7 +108,7 @@ TEST(CcmathBasicTests, FmodKernelRandomBitExactVsStd) } std::mt19937 rngf(0xF110F); - std::uniform_int_distribution bf(0, ~std::uint32_t(0)); + std::uniform_int_distribution bf(0, std::numeric_limits::max()); checked = 0; while (checked < 6000) { From a3de0728e6117eff85ba6bc09a36ebd42f9f0ef0 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 28 Jun 2026 19:41:23 -0400 Subject: [PATCH 49/52] Apply updated formatting --- .clang-format | 46 +- .devin/wiki.json | 392 ----- benchmarks/shared/harness.hpp | 74 +- benchmarks/shared/inputs.hpp | 20 +- benchmarks/shared/register.hpp | 44 +- benchmarks/src/math/power/sqrt.bench.cpp | 4 +- examples/common/io.hpp | 16 +- examples/src/decibel_gain.cpp | 13 +- examples/src/fp_classes.cpp | 15 +- examples/src/hypot_distance.cpp | 8 +- examples/src/rounding_edge_cases.cpp | 2 +- examples/src/signed_magnitude.cpp | 2 +- examples/src/unit_circle.cpp | 2 +- fuzzing/shared/fuzz_calls.hpp | 88 +- fuzzing/shared/harness.hpp | 86 +- fuzzing/shared/input.hpp | 90 +- fuzzing/src/math/basic_fuzz.cpp | 8 +- fuzzing/src/math/compare_fuzz.cpp | 8 +- fuzzing/src/math/expo_fuzz.cpp | 8 +- fuzzing/src/math/fmanip_fuzz.cpp | 8 +- fuzzing/src/math/misc_fuzz.cpp | 38 +- fuzzing/src/math/nearest_fuzz.cpp | 8 +- fuzzing/src/math/power_fuzz.cpp | 13 +- fuzzing/src/math/trig_fuzz.cpp | 8 +- include/ccmath/ext/align.hpp | 20 +- include/ccmath/ext/approximately.hpp | 7 +- include/ccmath/ext/ceil_div.hpp | 8 +- include/ccmath/ext/clamp.hpp | 11 +- include/ccmath/ext/cubic.hpp | 3 +- include/ccmath/ext/degrees.hpp | 7 +- include/ccmath/ext/delta_angle.hpp | 8 +- include/ccmath/ext/fract.hpp | 7 +- include/ccmath/ext/inverse_lerp.hpp | 7 +- include/ccmath/ext/is_power_of_two.hpp | 17 +- include/ccmath/ext/lerp_angle.hpp | 7 +- include/ccmath/ext/lerp_smooth.hpp | 3 +- include/ccmath/ext/move_towards.hpp | 8 +- include/ccmath/ext/move_towards_angle.hpp | 3 +- include/ccmath/ext/normalize.hpp | 15 +- include/ccmath/ext/ping_pong.hpp | 8 +- include/ccmath/ext/radians.hpp | 7 +- include/ccmath/ext/rcp.hpp | 7 +- include/ccmath/ext/remap.hpp | 9 +- include/ccmath/ext/repeat.hpp | 15 +- include/ccmath/ext/saturate.hpp | 7 +- include/ccmath/ext/sign.hpp | 7 +- include/ccmath/ext/smoothstep.hpp | 3 +- include/ccmath/ext/step.hpp | 7 +- include/ccmath/ext/unlerp.hpp | 15 +- include/ccmath/internal/config/compiler.hpp | 36 +- .../internal/config/long_double_format.hpp | 20 +- .../internal/math/common/basic/fabs.hpp | 6 +- .../internal/math/common/basic/fdim.hpp | 7 +- .../ccmath/internal/math/common/basic/fma.hpp | 29 +- .../internal/math/common/basic/fmod.hpp | 44 +- .../ccmath/internal/math/common/basic/max.hpp | 37 +- .../ccmath/internal/math/common/basic/min.hpp | 29 +- .../ccmath/internal/math/common/basic/nan.hpp | 8 +- .../internal/math/common/basic/remainder.hpp | 18 +- .../internal/math/common/basic/remquo.hpp | 13 +- .../math/common/compare/fpclassify.hpp | 23 +- .../internal/math/common/compare/isfinite.hpp | 12 +- .../math/common/compare/isgreater.hpp | 9 +- .../math/common/compare/isgreaterequal.hpp | 9 +- .../internal/math/common/compare/isinf.hpp | 12 +- .../internal/math/common/compare/isless.hpp | 9 +- .../math/common/compare/islessequal.hpp | 9 +- .../math/common/compare/islessgreater.hpp | 9 +- .../internal/math/common/compare/isnan.hpp | 12 +- .../internal/math/common/compare/isnormal.hpp | 16 +- .../math/common/compare/isunordered.hpp | 12 +- .../internal/math/common/compare/signbit.hpp | 9 +- .../ccmath/internal/math/common/expo/exp.hpp | 24 +- .../ccmath/internal/math/common/expo/exp2.hpp | 24 +- .../internal/math/common/expo/expm1.hpp | 24 +- .../ccmath/internal/math/common/expo/log.hpp | 39 +- .../internal/math/common/expo/log10.hpp | 39 +- .../internal/math/common/expo/log1p.hpp | 24 +- .../ccmath/internal/math/common/expo/log2.hpp | 34 +- .../internal/math/common/fmanip/copysign.hpp | 29 +- .../internal/math/common/fmanip/frexp.hpp | 17 +- .../internal/math/common/fmanip/ilogb.hpp | 4 +- .../internal/math/common/fmanip/ldexp.hpp | 24 +- .../internal/math/common/fmanip/logb.hpp | 4 +- .../internal/math/common/fmanip/modf.hpp | 26 +- .../internal/math/common/fmanip/nextafter.hpp | 21 +- .../math/common/fmanip/nexttoward.hpp | 24 +- .../internal/math/common/fmanip/scalbn.hpp | 78 +- .../internal/math/common/hyper/acosh.hpp | 4 +- .../internal/math/common/hyper/asinh.hpp | 4 +- .../internal/math/common/hyper/atanh.hpp | 4 +- .../internal/math/common/hyper/cosh.hpp | 4 +- .../internal/math/common/hyper/sinh.hpp | 4 +- .../internal/math/common/hyper/tanh.hpp | 4 +- .../internal/math/common/misc/gamma.hpp | 4 +- .../ccmath/internal/math/common/misc/lerp.hpp | 18 +- .../internal/math/common/misc/lgamma.hpp | 4 +- .../internal/math/common/nearest/ceil.hpp | 4 +- .../internal/math/common/nearest/floor.hpp | 71 +- .../math/common/nearest/nearbyint.hpp | 23 +- .../internal/math/common/nearest/rint.hpp | 4 +- .../internal/math/common/nearest/round.hpp | 4 +- .../internal/math/common/nearest/trunc.hpp | 44 +- .../internal/math/common/power/cbrt.hpp | 4 +- .../internal/math/common/power/hypot.hpp | 4 +- .../ccmath/internal/math/common/power/pow.hpp | 17 +- .../internal/math/common/power/sqrt.hpp | 29 +- .../math/common/special/assoc_laguerre.hpp | 4 +- .../math/common/special/assoc_legendre.hpp | 4 +- .../internal/math/common/special/beta.hpp | 4 +- .../math/common/special/comp_ellint_1.hpp | 4 +- .../math/common/special/comp_ellint_2.hpp | 4 +- .../math/common/special/comp_ellint_3.hpp | 4 +- .../math/common/special/cyl_bessel_i.hpp | 4 +- .../math/common/special/cyl_bessel_j.hpp | 4 +- .../math/common/special/cyl_bessel_k.hpp | 4 +- .../math/common/special/cyl_neumann.hpp | 4 +- .../internal/math/common/special/ellint_1.hpp | 4 +- .../internal/math/common/special/ellint_2.hpp | 4 +- .../internal/math/common/special/ellint_3.hpp | 4 +- .../internal/math/common/special/expint.hpp | 4 +- .../internal/math/common/special/hermite.hpp | 4 +- .../internal/math/common/special/laguerre.hpp | 4 +- .../internal/math/common/special/legendre.hpp | 4 +- .../math/common/special/riemann_zeta.hpp | 4 +- .../math/common/special/sph_bessel.hpp | 4 +- .../math/common/special/sph_legendre.hpp | 4 +- .../math/common/special/sph_neumann.hpp | 4 +- .../ccmath/internal/math/common/trig/acos.hpp | 4 +- .../ccmath/internal/math/common/trig/asin.hpp | 4 +- .../ccmath/internal/math/common/trig/atan.hpp | 4 +- .../internal/math/common/trig/atan2.hpp | 4 +- .../ccmath/internal/math/common/trig/cos.hpp | 29 +- .../ccmath/internal/math/common/trig/sin.hpp | 29 +- .../ccmath/internal/math/common/trig/tan.hpp | 29 +- .../math/generic/builtins/basic/fabs.hpp | 34 +- .../math/generic/builtins/basic/fdim.hpp | 34 +- .../math/generic/builtins/basic/fma.hpp | 34 +- .../math/generic/builtins/basic/fmax.hpp | 34 +- .../math/generic/builtins/basic/fmin.hpp | 34 +- .../math/generic/builtins/basic/fmod.hpp | 34 +- .../math/generic/builtins/basic/nan.hpp | 34 +- .../math/generic/builtins/basic/remainder.hpp | 34 +- .../math/generic/builtins/basic/remquo.hpp | 34 +- .../math/generic/builtins/builtin_helpers.hpp | 3 +- .../generic/builtins/compare/isfinite.hpp | 14 +- .../generic/builtins/compare/isgreater.hpp | 14 +- .../builtins/compare/isgreaterequal.hpp | 14 +- .../math/generic/builtins/compare/isinf.hpp | 16 +- .../math/generic/builtins/compare/isless.hpp | 14 +- .../generic/builtins/compare/islessequal.hpp | 14 +- .../builtins/compare/islessgreater.hpp | 14 +- .../math/generic/builtins/compare/isnan.hpp | 16 +- .../generic/builtins/compare/isnormal.hpp | 16 +- .../generic/builtins/compare/isunordered.hpp | 14 +- .../math/generic/builtins/compare/signbit.hpp | 14 +- .../math/generic/builtins/expo/exp.hpp | 34 +- .../math/generic/builtins/expo/exp2.hpp | 34 +- .../math/generic/builtins/expo/expm1.hpp | 34 +- .../math/generic/builtins/expo/log.hpp | 34 +- .../math/generic/builtins/expo/log10.hpp | 34 +- .../math/generic/builtins/expo/log1p.hpp | 34 +- .../math/generic/builtins/expo/log2.hpp | 34 +- .../math/generic/builtins/fmanip/copysign.hpp | 34 +- .../math/generic/builtins/fmanip/frexp.hpp | 34 +- .../math/generic/builtins/fmanip/ilogb.hpp | 34 +- .../math/generic/builtins/fmanip/ldexp.hpp | 34 +- .../math/generic/builtins/fmanip/logb.hpp | 34 +- .../math/generic/builtins/fmanip/modf.hpp | 34 +- .../generic/builtins/fmanip/nextafter.hpp | 34 +- .../generic/builtins/fmanip/nexttoward.hpp | 31 +- .../math/generic/builtins/fmanip/scalbn.hpp | 51 +- .../math/generic/builtins/hyper/acosh.hpp | 34 +- .../math/generic/builtins/hyper/asinh.hpp | 34 +- .../math/generic/builtins/hyper/atanh.hpp | 34 +- .../math/generic/builtins/hyper/cosh.hpp | 34 +- .../math/generic/builtins/hyper/sinh.hpp | 34 +- .../math/generic/builtins/hyper/tanh.hpp | 34 +- .../math/generic/builtins/misc/erf.hpp | 34 +- .../math/generic/builtins/misc/erfc.hpp | 34 +- .../math/generic/builtins/misc/lgamma.hpp | 17 +- .../math/generic/builtins/misc/tgamma.hpp | 34 +- .../math/generic/builtins/nearest/ceil.hpp | 34 +- .../math/generic/builtins/nearest/floor.hpp | 34 +- .../generic/builtins/nearest/nearbyint.hpp | 34 +- .../math/generic/builtins/nearest/rint.hpp | 34 +- .../math/generic/builtins/nearest/round.hpp | 68 +- .../math/generic/builtins/nearest/trunc.hpp | 34 +- .../math/generic/builtins/power/cbrt.hpp | 34 +- .../math/generic/builtins/power/hypot.hpp | 34 +- .../math/generic/builtins/power/pow.hpp | 34 +- .../math/generic/builtins/power/sqrt.hpp | 34 +- .../math/generic/builtins/trig/acos.hpp | 34 +- .../math/generic/builtins/trig/asin.hpp | 34 +- .../math/generic/builtins/trig/atan.hpp | 34 +- .../math/generic/builtins/trig/atan2.hpp | 34 +- .../math/generic/builtins/trig/cos.hpp | 34 +- .../math/generic/builtins/trig/sin.hpp | 34 +- .../math/generic/builtins/trig/tan.hpp | 34 +- .../math/generic/func/basic/abs_gen.hpp | 13 +- .../math/generic/func/basic/fdim_gen.hpp | 29 +- .../math/generic/func/basic/fmod_gen.hpp | 44 +- .../math/generic/func/basic/max_gen.hpp | 23 +- .../math/generic/func/basic/min_gen.hpp | 18 +- .../math/generic/func/basic/remainder_gen.hpp | 18 +- .../basic/remquo_impl/remquo_double_impl.hpp | 22 +- .../basic/remquo_impl/remquo_float_impl.hpp | 27 +- .../basic/remquo_impl/remquo_ldouble_impl.hpp | 4 +- .../math/generic/func/compare/fpclassify.hpp | 23 +- .../math/generic/func/compare/isfinite.hpp | 12 +- .../math/generic/func/compare/isgreater.hpp | 9 +- .../generic/func/compare/isgreaterequal.hpp | 9 +- .../math/generic/func/compare/isinf.hpp | 12 +- .../math/generic/func/compare/isless.hpp | 9 +- .../math/generic/func/compare/islessequal.hpp | 9 +- .../generic/func/compare/islessgreater.hpp | 9 +- .../math/generic/func/compare/isnan.hpp | 12 +- .../math/generic/func/compare/isnormal.hpp | 16 +- .../math/generic/func/compare/isunordered.hpp | 12 +- .../math/generic/func/compare/signbit.hpp | 9 +- .../math/generic/func/expo/exp2_gen.hpp | 13 +- .../math/generic/func/expo/exp_gen.hpp | 13 +- .../math/generic/func/expo/expm1_gen.hpp | 13 +- .../math/generic/func/expo/log10_gen.hpp | 13 +- .../math/generic/func/expo/log1p_gen.hpp | 13 +- .../math/generic/func/expo/log2_gen.hpp | 13 +- .../math/generic/func/expo/log_gen.hpp | 13 +- .../math/generic/func/fmanip/copysign_gen.hpp | 8 +- .../math/generic/func/fmanip/frexp_gen.hpp | 14 +- .../math/generic/func/fmanip/ilogb_gen.hpp | 14 +- .../generic/func/fmanip/impl/frexp_impl.hpp | 8 +- .../generic/func/fmanip/impl/ilogb_impl.hpp | 3 +- .../generic/func/fmanip/impl/logb_impl.hpp | 13 +- .../math/generic/func/fmanip/ldexp_gen.hpp | 29 +- .../math/generic/func/fmanip/logb_gen.hpp | 14 +- .../math/generic/func/fmanip/modf_gen.hpp | 14 +- .../generic/func/fmanip/nextafter_gen.hpp | 110 +- .../math/generic/func/fmanip/scalbn_gen.hpp | 96 +- .../math/generic/func/hyper/acosh_gen.hpp | 4 +- .../math/generic/func/hyper/asinh_gen.hpp | 4 +- .../math/generic/func/hyper/atanh_gen.hpp | 4 +- .../math/generic/func/hyper/cosh_gen.hpp | 4 +- .../math/generic/func/hyper/sinh_gen.hpp | 4 +- .../math/generic/func/hyper/tanh_gen.hpp | 4 +- .../math/generic/func/misc/lerp_gen.hpp | 18 +- .../math/generic/func/nearest/ceil_gen.hpp | 4 +- .../math/generic/func/nearest/floor_gen.hpp | 65 +- .../generic/func/nearest/nearbyint_gen.hpp | 4 +- .../math/generic/func/nearest/rint_gen.hpp | 4 +- .../math/generic/func/nearest/round_gen.hpp | 4 +- .../math/generic/func/nearest/trunc_gen.hpp | 38 +- .../math/generic/func/power/cbrt_gen.hpp | 14 +- .../math/generic/func/power/hypot_gen.hpp | 14 +- .../generic/func/power/impl/cbrt_impl.hpp | 18 +- .../generic/func/power/impl/hypot_impl.hpp | 23 +- .../math/generic/func/power/pow_gen.hpp | 17 +- .../generic/func/power/pow_impl/pow_data.hpp | 4 +- .../generic/func/power/pow_impl/pow_impl.hpp | 230 ++- .../func/power/pow_impl/pow_simd_impl.hpp | 57 +- .../generic/func/power/pow_impl/powf_impl.hpp | 67 +- .../func/power/pow_impl/powf_simd_impl.hpp | 19 +- .../func/power/pow_impl/powl_ld80_kernel.hpp | 39 +- .../math/generic/func/power/powl_gen.hpp | 274 +++- .../math/generic/func/power/sqrt_gen.hpp | 96 +- .../func/special/assoc_laguerre_gen.hpp | 4 +- .../func/special/assoc_legendre_gen.hpp | 4 +- .../math/generic/func/special/beta_gen.hpp | 4 +- .../func/special/comp_ellint_1_gen.hpp | 4 +- .../func/special/comp_ellint_2_gen.hpp | 4 +- .../func/special/comp_ellint_3_gen.hpp | 4 +- .../generic/func/special/cyl_bessel_i_gen.hpp | 4 +- .../generic/func/special/cyl_bessel_j_gen.hpp | 4 +- .../generic/func/special/cyl_bessel_k_gen.hpp | 4 +- .../generic/func/special/cyl_neumann_gen.hpp | 4 +- .../generic/func/special/ellint_1_gen.hpp | 4 +- .../generic/func/special/ellint_2_gen.hpp | 4 +- .../generic/func/special/ellint_3_gen.hpp | 4 +- .../math/generic/func/special/expint_gen.hpp | 4 +- .../math/generic/func/special/hermite_gen.hpp | 4 +- .../generic/func/special/laguerre_gen.hpp | 4 +- .../generic/func/special/legendre_gen.hpp | 4 +- .../generic/func/special/riemann_zeta_gen.hpp | 4 +- .../generic/func/special/sph_bessel_gen.hpp | 4 +- .../generic/func/special/sph_legendre_gen.hpp | 4 +- .../generic/func/special/sph_neumann_gen.hpp | 4 +- .../math/generic/func/trig/acos_gen.hpp | 4 +- .../math/generic/func/trig/asin_gen.hpp | 4 +- .../math/generic/func/trig/atan2_gen.hpp | 4 +- .../math/generic/func/trig/atan_gen.hpp | 4 +- .../math/generic/func/trig/cos_gen.hpp | 13 +- .../math/generic/func/trig/sin_gen.hpp | 13 +- .../math/generic/func/trig/tan_gen.hpp | 8 +- .../math/runtime/func/basic/fabs_rt.hpp | 9 +- .../math/runtime/func/basic/fdim_rt.hpp | 9 +- .../math/runtime/func/basic/fma_rt.hpp | 7 +- .../math/runtime/func/basic/fmax_rt.hpp | 9 +- .../math/runtime/func/basic/fmin_rt.hpp | 9 +- .../math/runtime/func/basic/fmod_rt.hpp | 15 +- .../math/runtime/func/basic/remainder_rt.hpp | 9 +- .../math/runtime/func/detail/system_math.hpp | 99 +- .../math/runtime/func/detail/trunc_scalar.hpp | 49 +- .../math/runtime/func/expo/exp2_rt.hpp | 16 +- .../math/runtime/func/expo/exp_rt.hpp | 16 +- .../math/runtime/func/expo/expm1_rt.hpp | 9 +- .../math/runtime/func/expo/log10_rt.hpp | 16 +- .../math/runtime/func/expo/log1p_rt.hpp | 9 +- .../math/runtime/func/expo/log2_rt.hpp | 16 +- .../math/runtime/func/expo/log_rt.hpp | 16 +- .../math/runtime/func/fmanip/copysign_rt.hpp | 17 +- .../math/runtime/func/fmanip/frexp_rt.hpp | 7 +- .../math/runtime/func/fmanip/ilogb_rt.hpp | 9 +- .../math/runtime/func/fmanip/ldexp_rt.hpp | 9 +- .../math/runtime/func/fmanip/logb_rt.hpp | 9 +- .../math/runtime/func/fmanip/modf_rt.hpp | 7 +- .../math/runtime/func/fmanip/nextafter_rt.hpp | 9 +- .../runtime/func/fmanip/nexttoward_rt.hpp | 9 +- .../math/runtime/func/fmanip/scalbn_rt.hpp | 9 +- .../math/runtime/func/misc/gamma_rt.hpp | 9 +- .../math/runtime/func/misc/lgamma_rt.hpp | 9 +- .../math/runtime/func/nearest/ceil_rt.hpp | 9 +- .../math/runtime/func/nearest/floor_rt.hpp | 9 +- .../runtime/func/nearest/nearbyint_rt.hpp | 9 +- .../math/runtime/func/nearest/rint_rt.hpp | 15 +- .../math/runtime/func/nearest/round_rt.hpp | 14 +- .../math/runtime/func/nearest/trunc_rt.hpp | 9 +- .../math/runtime/func/power/cbrt_rt.hpp | 9 +- .../math/runtime/func/power/hypot_rt.hpp | 9 +- .../math/runtime/func/power/pow_rt.hpp | 38 +- .../math/runtime/func/power/sqrt_rt.hpp | 29 +- .../math/runtime/func/rt_dispatch.hpp | 108 +- .../math/runtime/func/svml_dispatch.hpp | 19 +- .../math/runtime/func/trig/acos_rt.hpp | 9 +- .../math/runtime/func/trig/asin_rt.hpp | 9 +- .../math/runtime/func/trig/atan2_rt.hpp | 12 +- .../math/runtime/func/trig/atan_rt.hpp | 9 +- .../math/runtime/func/trig/cos_rt.hpp | 9 +- .../math/runtime/func/trig/sin_rt.hpp | 9 +- .../math/runtime/func/trig/tan_rt.hpp | 9 +- .../math/runtime/pp/assume_aligned.hpp | 3 +- .../internal/math/runtime/pp/conversion.hpp | 10 +- .../internal/math/runtime/pp/declaration.hpp | 76 +- .../ccmath/internal/math/runtime/pp/flags.hpp | 63 +- .../math/runtime/pp/mask_reductions.hpp | 60 +- .../internal/math/runtime/pp/msvc_intrin.hpp | 1420 +++++++++++++---- .../ccmath/internal/math/runtime/pp/pp.hpp | 12 +- .../internal/math/runtime/pp/reduce.hpp | 66 +- .../internal/math/runtime/pp/reference.hpp | 14 +- .../internal/math/runtime/pp/scalar.hpp | 243 ++- .../ccmath/internal/math/runtime/pp/simd.hpp | 190 ++- .../internal/math/runtime/pp/simd_cat.hpp | 24 +- .../internal/math/runtime/pp/simd_config.hpp | 4 +- .../internal/math/runtime/pp/simd_mask.hpp | 70 +- .../internal/math/runtime/pp/simd_math.hpp | 39 +- .../internal/math/runtime/pp/traits.hpp | 87 +- .../internal/math/runtime/pp/utility.hpp | 108 +- .../internal/math/runtime/pp/vec_ext.hpp | 211 ++- .../ccmath/internal/math/runtime/pp/where.hpp | 90 +- .../internal/math/runtime/simd/common.hpp | 133 +- .../math/runtime/simd/func/impl/neon/pow.hpp | 8 +- .../runtime/simd/func/impl/scalar/map.hpp | 14 +- .../runtime/simd/func/impl/scalar/pow.hpp | 7 +- .../runtime/simd/func/impl/scalar/sqrt.hpp | 10 +- .../simd/func/impl/vector_size/sqrt.hpp | 3 +- .../math/runtime/simd/func/svml_ops.hpp | 112 +- .../math/runtime/simd/instructions/avx2.hpp | 269 +++- .../math/runtime/simd/instructions/neon.hpp | 284 +++- .../math/runtime/simd/instructions/scalar.hpp | 185 ++- .../math/runtime/simd/instructions/sse2.hpp | 269 +++- .../math/runtime/simd/instructions/sse4.hpp | 268 +++- .../internal/math/runtime/simd/pack.hpp | 151 +- .../internal/math/runtime/simd/simd.hpp | 3 +- .../math/runtime/simd/vector_size.hpp | 193 ++- include/ccmath/internal/predef/assume.hpp | 10 +- .../predef/expects_bool_condition.hpp | 3 +- .../ccmath/internal/support/always_false.hpp | 3 +- include/ccmath/internal/support/bits.hpp | 200 ++- include/ccmath/internal/support/ctz.hpp | 22 +- .../internal/support/fenv/fenv_support.hpp | 49 +- .../internal/support/fenv/host_fenv.hpp | 72 +- .../internal/support/fenv/rounding_mode.hpp | 49 +- .../support/floating_point_traits.hpp | 39 +- .../internal/support/fp/bit_mask_traits.hpp | 18 +- include/ccmath/internal/support/fp/cast.hpp | 20 +- .../support/fp/directional_rounding_utils.hpp | 61 +- .../support/fp/except_value_utils.hpp | 44 +- include/ccmath/internal/support/fp/fma.hpp | 342 ++-- .../ccmath/internal/support/fp/fp_bits.hpp | 516 ++++-- .../internal/support/fp/nearest_integer.hpp | 20 +- .../ccmath/internal/support/fp/ops/add.hpp | 4 +- .../internal/support/fp/ops/add_or_sub.hpp | 67 +- .../ccmath/internal/support/fp/ops/mul.hpp | 5 +- .../ccmath/internal/support/fp/ops/sub.hpp | 4 +- .../internal/support/helpers/digit_to_int.hpp | 15 +- .../ccmath/internal/support/helpers/exp10.hpp | 25 +- .../internal/support/helpers/exp_helpers.hpp | 9 +- .../support/helpers/internal_ldexp.hpp | 8 +- .../internal/support/integer_literals.hpp | 101 +- include/ccmath/internal/support/limits.hpp | 48 +- .../ccmath/internal/support/math_support.hpp | 69 +- .../ccmath/internal/support/meta_compare.hpp | 42 +- .../ccmath/internal/support/multiply_add.hpp | 33 +- include/ccmath/internal/support/poly_eval.hpp | 31 +- include/ccmath/internal/types/big_int.hpp | 857 ++++++---- .../ccmath/internal/types/double_double.hpp | 9 +- .../ccmath/internal/types/dyadic_float.hpp | 237 ++- .../internal/types/normalized_float.hpp | 108 +- include/ccmath/internal/types/number_pair.hpp | 6 +- include/ccmath/internal/types/sign.hpp | 24 +- include/ccmath/math/basic/fabs.hpp | 44 +- include/ccmath/math/basic/fdim.hpp | 31 +- include/ccmath/math/basic/fmod.hpp | 55 +- include/ccmath/math/basic/impl/fmod_impl.hpp | 30 +- .../math/basic/impl/nan_double_impl.hpp | 18 +- .../ccmath/math/basic/impl/nan_float_impl.hpp | 18 +- .../math/basic/impl/nan_ldouble_impl.hpp | 18 +- .../math/basic/impl/remquo_double_impl.hpp | 22 +- .../math/basic/impl/remquo_float_impl.hpp | 27 +- .../math/basic/impl/remquo_ldouble_impl.hpp | 4 +- include/ccmath/math/basic/max.hpp | 45 +- include/ccmath/math/basic/min.hpp | 37 +- include/ccmath/math/basic/nan.hpp | 8 +- include/ccmath/math/basic/remainder.hpp | 23 +- include/ccmath/math/basic/remquo.hpp | 13 +- include/ccmath/math/compare/fpclassify.hpp | 23 +- include/ccmath/math/compare/isfinite.hpp | 12 +- include/ccmath/math/compare/isgreater.hpp | 9 +- .../ccmath/math/compare/isgreaterequal.hpp | 9 +- include/ccmath/math/compare/isinf.hpp | 12 +- include/ccmath/math/compare/isless.hpp | 9 +- include/ccmath/math/compare/islessequal.hpp | 9 +- include/ccmath/math/compare/islessgreater.hpp | 9 +- include/ccmath/math/compare/isnan.hpp | 12 +- include/ccmath/math/compare/isnormal.hpp | 16 +- include/ccmath/math/compare/isunordered.hpp | 12 +- include/ccmath/math/compare/signbit.hpp | 9 +- include/ccmath/math/expo/exp.hpp | 29 +- include/ccmath/math/expo/exp2.hpp | 29 +- include/ccmath/math/expo/expm1.hpp | 29 +- include/ccmath/math/expo/impl/exp2_data.hpp | 15 +- .../math/expo/impl/exp2_double_impl.hpp | 39 +- .../ccmath/math/expo/impl/exp2_float_impl.hpp | 35 +- include/ccmath/math/expo/impl/exp_data.hpp | 15 +- .../ccmath/math/expo/impl/exp_double_impl.hpp | 80 +- .../ccmath/math/expo/impl/exp_float_impl.hpp | 55 +- include/ccmath/math/expo/impl/expm1_impl.hpp | 8 +- include/ccmath/math/expo/impl/log10_impl.hpp | 16 +- include/ccmath/math/expo/impl/log1p_impl.hpp | 81 +- include/ccmath/math/expo/impl/log2_data.hpp | 15 +- .../math/expo/impl/log2_double_impl.hpp | 9 +- .../ccmath/math/expo/impl/log2_float_impl.hpp | 9 +- include/ccmath/math/expo/impl/log_data.hpp | 15 +- .../ccmath/math/expo/impl/log_double_impl.hpp | 9 +- .../ccmath/math/expo/impl/log_float_impl.hpp | 9 +- include/ccmath/math/expo/log.hpp | 43 +- include/ccmath/math/expo/log10.hpp | 43 +- include/ccmath/math/expo/log1p.hpp | 44 +- include/ccmath/math/expo/log2.hpp | 38 +- include/ccmath/math/fmanip/copysign.hpp | 34 +- include/ccmath/math/fmanip/frexp.hpp | 37 +- include/ccmath/math/fmanip/ilogb.hpp | 22 +- include/ccmath/math/fmanip/impl/modf_impl.hpp | 8 +- .../math/fmanip/impl/scalbn_double_impl.hpp | 11 +- .../math/fmanip/impl/scalbn_float_impl.hpp | 11 +- .../math/fmanip/impl/scalbn_ldouble_impl.hpp | 34 +- include/ccmath/math/fmanip/ldexp.hpp | 28 +- include/ccmath/math/fmanip/logb.hpp | 22 +- include/ccmath/math/fmanip/modf.hpp | 28 +- include/ccmath/math/fmanip/nextafter.hpp | 25 +- include/ccmath/math/fmanip/nextdown.hpp | 22 +- include/ccmath/math/fmanip/nexttoward.hpp | 28 +- include/ccmath/math/fmanip/nextup.hpp | 22 +- include/ccmath/math/fmanip/scalbn.hpp | 114 +- include/ccmath/math/hyper/acosh.hpp | 4 +- include/ccmath/math/hyper/asinh.hpp | 4 +- include/ccmath/math/hyper/atanh.hpp | 4 +- include/ccmath/math/hyper/cosh.hpp | 4 +- include/ccmath/math/hyper/sinh.hpp | 4 +- include/ccmath/math/hyper/tanh.hpp | 4 +- include/ccmath/math/misc/gamma.hpp | 58 +- .../math/misc/impl/gamma_double_impl.hpp | 24 +- .../math/misc/impl/gamma_float_impl.hpp | 5 +- include/ccmath/math/misc/impl/gamma_impl.hpp | 8 +- .../math/misc/impl/lgamma_double_impl.hpp | 33 +- include/ccmath/math/misc/impl/lgamma_impl.hpp | 8 +- include/ccmath/math/misc/lerp.hpp | 38 +- include/ccmath/math/misc/lgamma.hpp | 38 +- include/ccmath/math/nearest/ceil.hpp | 38 +- include/ccmath/math/nearest/floor.hpp | 34 +- include/ccmath/math/nearest/nearbyint.hpp | 23 +- include/ccmath/math/nearest/rint.hpp | 23 +- include/ccmath/math/nearest/round.hpp | 33 +- include/ccmath/math/nearest/trunc.hpp | 46 +- include/ccmath/math/power/cbrt.hpp | 22 +- include/ccmath/math/power/hypot.hpp | 34 +- include/ccmath/math/power/pow.hpp | 36 +- include/ccmath/math/power/sqrt.hpp | 29 +- .../ccmath/math/special/assoc_laguerre.hpp | 4 +- .../ccmath/math/special/assoc_legendre.hpp | 4 +- include/ccmath/math/special/beta.hpp | 4 +- include/ccmath/math/special/comp_ellint_1.hpp | 4 +- include/ccmath/math/special/comp_ellint_2.hpp | 4 +- include/ccmath/math/special/comp_ellint_3.hpp | 4 +- include/ccmath/math/special/cyl_bessel_i.hpp | 4 +- include/ccmath/math/special/cyl_bessel_j.hpp | 4 +- include/ccmath/math/special/cyl_bessel_k.hpp | 4 +- include/ccmath/math/special/cyl_neumann.hpp | 4 +- include/ccmath/math/special/ellint_1.hpp | 4 +- include/ccmath/math/special/ellint_2.hpp | 4 +- include/ccmath/math/special/ellint_3.hpp | 4 +- include/ccmath/math/special/expint.hpp | 4 +- include/ccmath/math/special/hermite.hpp | 4 +- include/ccmath/math/special/laguerre.hpp | 4 +- include/ccmath/math/special/legendre.hpp | 4 +- include/ccmath/math/special/riemann_zeta.hpp | 4 +- include/ccmath/math/special/sph_bessel.hpp | 4 +- include/ccmath/math/special/sph_legendre.hpp | 4 +- include/ccmath/math/special/sph_neumann.hpp | 4 +- include/ccmath/math/trig/acos.hpp | 37 +- include/ccmath/math/trig/asin.hpp | 37 +- include/ccmath/math/trig/atan.hpp | 37 +- include/ccmath/math/trig/atan2.hpp | 30 +- include/ccmath/math/trig/cos.hpp | 29 +- .../ccmath/math/trig/impl/inv_trig_impl.hpp | 160 +- .../math/trig/impl/sincos_double_impl.hpp | 36 +- .../math/trig/impl/sincos_float_impl.hpp | 36 +- .../math/trig/impl/sincos_payne_hanek.hpp | 36 +- include/ccmath/math/trig/sin.hpp | 29 +- include/ccmath/math/trig/tan.hpp | 29 +- showcase/accuracy/showcase_accuracy.cpp | 35 +- showcase/common/accuracy/corpus.hpp | 10 +- .../common/accuracy/unary_mpfr_runner.hpp | 25 +- showcase/common/backend_traits.hpp | 6 +- showcase/common/backends/ccmath_backend.hpp | 120 +- showcase/common/backends/gcem_backend.hpp | 5 +- showcase/common/backends/std_backend.hpp | 15 +- showcase/common/bench/bench_common.hpp | 8 +- showcase/common/ccmath_paths.hpp | 45 +- showcase/performance/sin.bench.cpp | 28 +- showcase/performance/sqrt.bench.cpp | 28 +- tests/conformance/fenv_exception_test.cpp | 52 +- tests/conformance/fenv_probe_test.cpp | 10 +- tests/conformance/fma_rounding_test.cpp | 20 +- tests/conformance/fma_software_fenv_test.cpp | 91 +- tests/conformance/include_policy_test.cpp | 20 +- tests/conformance/pow_errno_fenv_test.cpp | 53 +- tests/conformance/pow_path_config_test.cpp | 57 +- tests/conformance/powl_platform_test.cpp | 10 +- .../conformance/rounding_conformance_test.cpp | 101 +- tests/conformance/ulp_all_modes_test.cpp | 36 +- tests/conformance/worst_case_ulp_test.cpp | 117 +- .../proofs/math/power/pow_accuracy_probe.cpp | 58 +- tests/shared/oracle/big_int_gmp.cpp | 147 +- .../shared/oracle/coremath_oracle_harness.hpp | 42 +- tests/shared/oracle/coremath_pow_cases.hpp | 18 +- tests/shared/oracle/coremath_pow_common.hpp | 66 +- tests/shared/oracle/coremath_pow_double.cpp | 6 +- tests/shared/oracle/coremath_pow_float.cpp | 6 +- tests/shared/oracle/cross_libm_common.hpp | 31 +- tests/shared/oracle/cross_libm_pow.cpp | 54 +- tests/shared/oracle/mpfr_fma_common.hpp | 114 +- tests/shared/oracle/mpfr_fma_double.cpp | 4 +- tests/shared/oracle/mpfr_fma_float.cpp | 4 +- tests/shared/oracle/mpfr_oracle_harness.hpp | 183 ++- tests/shared/oracle/mpfr_pow_common.hpp | 51 +- tests/shared/oracle/mpfr_pow_double.cpp | 6 +- tests/shared/oracle/mpfr_pow_float.cpp | 6 +- tests/shared/oracle/mpfr_powl.cpp | 88 +- tests/shared/oracle/mpfr_unary.cpp | 95 +- tests/shared/oracle/mpfr_unary_common.hpp | 754 ++++++--- .../shared/oracle/oracle_campaign_common.hpp | 286 ++-- .../shared/oracle/oracle_campaign_ternary.hpp | 17 +- tests/shared/oracle/oracle_campaign_unary.hpp | 25 +- tests/shared/oracle/pow_adversarial_modes.hpp | 61 +- .../shared/oracle/pow_adversarial_search.cpp | 26 +- tests/shared/oracle/pow_cases.hpp | 94 +- .../shared/oracle/powf_exhaustive_domains.hpp | 120 +- tests/shared/oracle/powl_path_reporting.hpp | 86 +- .../oracle/powl_platform_characterization.cpp | 20 +- .../oracle/runners/coremath_pow_runner.hpp | 60 +- .../shared/oracle/runners/mpfr_pow_runner.hpp | 76 +- tests/shared/utils/campaign_metadata.hpp | 47 +- tests/shared/utils/conformance_suite.hpp | 100 +- tests/shared/utils/fenv_fixture.hpp | 78 +- tests/shared/utils/fp_matcher.hpp | 19 +- tests/shared/utils/helpers.hpp | 40 +- tests/shared/utils/math_samples.hpp | 3 +- tests/shared/utils/pow_path_dispatch.hpp | 81 +- tests/shared/utils/std_compare.hpp | 31 +- tests/shared/utils/test_runtime.hpp | 3 +- tests/shared/utils/ulp_distance.hpp | 43 +- tests/shared/utils/ulp_suite.hpp | 39 +- tests/shared/utils/worst_case_samples.hpp | 3 +- tests/src/math/basic/abs_test.cpp | 15 +- tests/src/math/basic/fdim_test.cpp | 12 +- .../fma_constexpr_upward_config_test.cpp | 4 +- .../math/basic/fma_correct_rounding_test.cpp | 8 +- tests/src/math/basic/fma_test.cpp | 17 +- tests/src/math/basic/fmod_test.cpp | 10 +- tests/src/math/basic/nan_test.cpp | 23 +- tests/src/math/basic/remainder_test.cpp | 18 +- tests/src/math/constexpr_smoke_test.cpp | 10 +- tests/src/math/expo/exp2_test.cpp | 20 +- tests/src/math/expo/exp_test.cpp | 20 +- .../src/math/expo/expo_ulp_accuracy_test.cpp | 40 +- tests/src/math/expo/log2_test.cpp | 10 +- tests/src/math/expo/log_test.cpp | 5 +- tests/src/math/fmanip/ldexp_test.cpp | 9 +- tests/src/math/fmanip/nexttoward_test.cpp | 4 +- tests/src/math/internal/simd/pp_simd_test.cpp | 190 ++- .../internal/support/math_support_test.cpp | 48 +- .../src/math/internal/types/big_int_test.cpp | 387 +++-- .../math/internal/types/dyadic_float_test.cpp | 4 +- .../internal/types/normalized_float_test.cpp | 4 +- tests/src/math/misc/gamma_test.cpp | 18 +- tests/src/math/misc/lerp_test_std.cpp | 4 +- tests/src/math/misc/lgamma_test.cpp | 8 +- .../src/math/nearest/ceil_round_rint_test.cpp | 18 +- tests/src/math/nearest/floor_test.cpp | 3 +- tests/src/math/nearest/nearbyint_test.cpp | 15 +- tests/src/math/nearest/trunc_test.cpp | 12 +- .../math/power/pow_correct_rounding_test.cpp | 6 +- .../src/math/power/pow_deterministic_test.cpp | 7 +- .../math/power/pow_integer_boundary_test.cpp | 12 +- tests/src/math/power/pow_simd_test.cpp | 56 +- tests/src/math/power/pow_test.cpp | 29 +- .../math/power/power_ulp_accuracy_test.cpp | 46 +- tests/src/math/power/powf_simd_test.cpp | 46 +- .../math/power/powl_fallback_policy_test.cpp | 12 +- tests/src/math/power/powl_ld64_alias_test.cpp | 5 +- .../math/power/powl_ld80_bounded_int_test.cpp | 5 +- .../power/powl_ld80_general_finite_test.cpp | 10 +- .../power/powl_ld80_integer_class_test.cpp | 13 +- .../power/powl_ld80_special_cases_test.cpp | 5 +- .../math/power/powl_unsupported_tier_test.cpp | 3 +- tests/src/math/power/sqrt_test.cpp | 12 +- .../system_math_header_coexistence_test.cpp | 3 +- .../src/math/trig/trig_ulp_accuracy_test.cpp | 54 +- tools/apply_format.sh | 0 .../accuracy/candidate_accuracy_powf_impl.cpp | 85 +- tools/asmlab/bench/golden_poly_bench.cpp | 14 +- tools/asmlab/bench/powf_impl_bench.cpp | 43 +- tools/run_format.sh | 0 642 files changed, 14766 insertions(+), 8389 deletions(-) delete mode 100644 .devin/wiki.json mode change 100644 => 100755 tools/apply_format.sh mode change 100644 => 100755 tools/run_format.sh diff --git a/.clang-format b/.clang-format index c28a1864..25eea8a6 100644 --- a/.clang-format +++ b/.clang-format @@ -2,18 +2,23 @@ AccessModifierOffset: -4 AlignConsecutiveAssignments: Consecutive AlignConsecutiveMacros: Consecutive +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: true AlignEscapedNewlines: Right AlignTrailingComments: Kind: Always -AllowShortBlocksOnASingleLine: Always +AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true -AllowShortEnumsOnASingleLine: true -AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All -AllowShortLoopsOnASingleLine: true +AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: Yes +AlwaysBreakTemplateDeclarations: MultiLine BasedOnStyle: LLVM BinPackArguments: false BinPackParameters: false @@ -31,41 +36,43 @@ BraceWrapping: BeforeElse: false BeforeLambdaBody: false BeforeWhile: false - IndentBraces: true - SplitEmptyFunction: true - SplitEmptyNamespace: true - SplitEmptyRecord: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: false + SplitEmptyRecord: false +BracedInitializerIndentWidth: 4 BreakAfterAttributes: Never -BreakBeforeBraces: Allman +BreakBeforeBraces: Custom ColumnLimit: 160 CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: true Cpp11BracedListStyle: false -DerivePointerAlignment: true +DerivePointerAlignment: false FixNamespaceComments: true IncludeBlocks: Regroup IncludeCategories: # Headers in "" with .h extension. - - Regex: '"([A-Za-z0-9\/-_])+\.h"' + - Regex: '"[-A-Za-z0-9_/]+\.h"' Priority: 10 # Headers in "" with .hpp extension. - - Regex: '"([A-Za-z0-9\/-_])+\.hpp"' + - Regex: '"[-A-Za-z0-9_/]+\.hpp"' Priority: 20 # Headers in <> with .h extension. - - Regex: '<([A-Za-z0-9\/-_])+\.h>' + - Regex: '<[-A-Za-z0-9_/]+\.h>' Priority: 30 # Headers in <> with .hpp extension. - - Regex: '<([A-Za-z0-9\/-_])+\.hpp>' + - Regex: '<[-A-Za-z0-9_/]+\.hpp>' Priority: 40 # Headers in <> without an extension. - - Regex: '<([A-Za-z0-9\/-_])+>' + - Regex: '<[-A-Za-z0-9_/]+>' Priority: 50 IndentPPDirectives: BeforeHash IndentWidth: 4 InsertNewlineAtEOF: true Language: Cpp NamespaceIndentation: All +PackConstructorInitializers: CurrentLine PointerAlignment: Middle +ReferenceAlignment: Middle SortIncludes: CaseInsensitive SpaceAfterTemplateKeyword: true SpaceBeforeParens: ControlStatements @@ -75,11 +82,10 @@ Standard: c++17 TabWidth: 4 UseTab: Always WhitespaceSensitiveMacros: - # Specific macros that suffer from formatting issues with white spaces + # Specific macros that suffer from formatting issues with white spaces. - CCM_DISABLE_CLANG_WARNING - CCM_ENABLE_CLANG_WARNING_AS_ERROR - CCM_DISABLE_GCC_WARNING - CCM_ENABLE_GCC_WARNING_AS_ERROR - CCM_ENABLE_IGNORE_GCC_WARNING - - CCM_DISABLE_IGNORE_GCC_WARNING -... + - CCM_DISABLE_IGNORE_GCC_WARNING \ No newline at end of file diff --git a/.devin/wiki.json b/.devin/wiki.json deleted file mode 100644 index b717ceb3..00000000 --- a/.devin/wiki.json +++ /dev/null @@ -1,392 +0,0 @@ -{ - "repo_notes": [ - { - "content": "CCMath is a header-only C++17 compile-time implementation of the standard library. Everything lives under the ccm namespace and is usable in constexpr contexts. Repository: https://github.com/Rinzii/ccmath. License: Apache-2.0 WITH LLVM-exception. Current version: 0.3.0. The library targets 1:1 API compatibility with while adding genuine compile-time evaluation, four IEEE rounding modes at runtime, ULP-bounded accuracy, and optional runtime SIMD acceleration. There are no runtime dependencies. The only external libraries (MPFR, GMP, CORE-MATH, Google Benchmark, GoogleTest) are pulled in for testing and benchmarking, never for consumers." - }, - { - "content": "Source layout at a glance. include/ccmath/ holds all 586 headers split into three roots: math/ (134 public API headers, the only headers consumers include), internal/ (423 implementation headers, never part of the public API), and ext/ (28 experimental non-standard extensions). Build glue lives in cmake/ (92 files split into config/, features/, helpers/, modules/, and in/ templates). Tests live in tests/ (129 .cpp files), benchmarks in benchmarks/, fuzz targets in fuzzing/, runnable samples in examples/, and standalone tooling in tools/ (asmlab, coremath, proofs). Status documentation is under docs/ (STATUS.md, ULP_STATUS.md, ROUNDING_STATUS.md)." - }, - { - "content": "Three-path dispatch is the central architectural idea. Every nontrivial function chooses among three implementations: (1) a compiler builtin path used only when it is trustworthy (compile time, or runtime under round-to-nearest), (2) a portable generic kernel under internal/math/generic/ that is constexpr-capable and correct in all four rounding modes, and (3) a runtime path under internal/math/runtime/ that may use SIMD or platform tricks. The public header in include/ccmath/math//.hpp owns the selection logic. Files suffixed _gen are generic kernels, files suffixed _rt are runtime kernels. Deterministic mode (CCMATH_ENABLE_DETERMINISTIC) forces everything through the generic kernels for bit-identical cross-hardware results." - }, - { - "content": "Build entry points: CMake is primary (3.18+), with Meson and Premake5 provided only for consuming the headers. The interface target is ccmath::ccmath. Configuration presets live in CMakePresets.json. The library is header-only so the target is INTERFACE, and building tests, benchmarks, or fuzzers is opt-in via cache options. CI runs an eight-workflow matrix over Linux, macOS, and Windows and over GCC, Clang, Apple Clang, and MSVC across C++17, C++20, and C++23, plus CodeQL, OpenSSF Scorecard, clang-format lint, and a libFuzzer campaign." - } - ], - "pages": [ - { - "title": "Overview", - "purpose": "Introduce CCMath: a header-only, dependency-free C++17 library that reimplements the C++ standard so every function works in constexpr (compile-time) contexts, exposed under the ccm namespace. State the core value proposition: genuine compile-time math, 1:1 API parity with , IEEE 754 conformance including all four rounding modes at runtime, ULP-bounded accuracy validated against MPFR and CORE-MATH oracles, and optional runtime SIMD acceleration. Summarize the high-level repository map (include/ccmath/{math,internal,ext}, cmake/, tests/, benchmarks/, fuzzing/, examples/, tools/, docs/) and the central three-path dispatch model (builtin vs generic vs runtime). Note licensing (Apache-2.0 WITH LLVM-exception), version 0.3.0, and the upstream repo https://github.com/Rinzii/ccmath. Link to child pages: Getting Started (how to consume) and Library Goals and Design Principles (the rationale). Point readers at README.md for the canonical feature list and docs/STATUS.md for per-function implementation status.", - "page_notes": [ - { - "content": "Source the summary and feature list from README.md (about 11.6 KB). Confirm the namespace is ccm (not ccmath) for functions. The CMake target is ccmath::ccmath. Version 0.3.0 is declared in the root CMakeLists.txt and emitted via cmake/in/version.hpp.in. Any function family that is not yet fully complete should be flagged by pointing to docs/STATUS.md rather than asserting completeness here." - }, - { - "content": "The monolithic include is include/ccmath/ccmath.hpp (a small aggregate, about 44 lines) which pulls in the per-family aggregate headers (basic.hpp, compare.hpp, expo.hpp, fmanip.hpp, hyper.hpp, misc.hpp, nearest.hpp, power.hpp, special.hpp, trig.hpp) plus numbers.hpp. Mention that granular includes are preferred for compile time." - } - ] - }, - { - "title": "Getting Started", - "purpose": "Give a consumer the fastest path to using CCMath. Cover three consumption routes: (1) CMake FetchContent pulling https://github.com/Rinzii/ccmath and linking target ccmath::ccmath, (2) vcpkg via the manifest at cmake/vcpkg/vcpkg.json, and (3) plain header inclusion since the library is header-only (add include/ to the include path). Explain the include strategy: the monolithic include/ccmath/ccmath.hpp pulls everything, while granular per-family headers (for example include/ccmath/math/power/sqrt.hpp) cut compile time and are the recommended default. Show a minimal example using ccm:: functions in a constexpr context (for example static_assert(ccm::sqrt(4.0) == 2.0)). Note the minimum toolchain: CMake 3.18+, and a C++17 compiler (GCC 11+, Clang 9+, Apple Clang 14+, MSVC 19.26+). Point to examples/ for runnable programs (constexpr_abs.cpp, hypot_distance.cpp, unit_circle.cpp, fp_classes.cpp, rounding_edge_cases.cpp, and others) and to integration/vendored-consumer/ for a worked external-project setup. Mention that Meson (meson.build) and Premake5 (premake5.lua) are available for consumption but CMake is the supported primary.", - "parent": "Overview", - "page_notes": [ - { - "content": "The FetchContent snippet should reference target ccmath::ccmath. Confirm the alias name in the root CMakeLists.txt and cmake/in/ccmath-config.cmake.in before publishing the exact target string. examples/ has matching .run.xml manifests describing expected output. examples/common/io.hpp is shared helper code, and examples/docs/compiler_support.md documents per-compiler caveats." - }, - { - "content": "integration/vendored-consumer/ is a real consumer project used by the ci-integration.yml workflow to prove the install and export work. Use it as the authoritative example of downstream usage rather than inventing CMake snippets." - } - ] - }, - { - "title": "Library Goals and Design Principles", - "purpose": "Explain the non-negotiable principles that shape every line of CCMath. (1) Constexpr-first: every public function must be usable during constant evaluation, and runtime-only optimizations are layered behind is_constant_evaluated checks, never replacing the compile-time path. (2) 1:1 API compatibility: signatures, overload sets, special-case behavior (NaN, signed zero, infinities), and errno and fenv semantics match the C++ standard exactly, with no silently wrong values. (3) Accuracy targets: normal accuracy is bounded (the project tracks ULP per function in docs/ULP_STATUS.md), with correctly-rounded results pursued where feasible and validated against MPFR and CORE-MATH oracles. (4) All four IEEE 754 rounding modes work at runtime, not just round-to-nearest, tracked in docs/ROUNDING_STATUS.md. The implementation reads but never sets the rounding mode inside kernels. (5) Zero runtime dependencies and no include in the public headers. (6) Cross-platform and cross-compiler portability across the CI matrix. (7) Performance via optional runtime SIMD without compromising the portable path. Reference the examples/ directory and examples/docs/compiler_support.md for the support matrix. Link to the Architecture and Implementation page for how these principles are realized.", - "parent": "Overview", - "page_notes": [ - { - "content": "Accuracy and rounding claims must be sourced from docs/STATUS.md, docs/ULP_STATUS.md (about 31 KB), and docs/ROUNDING_STATUS.md (about 18 KB) rather than stated as blanket guarantees. These three files are the living source of truth for what is correctly rounded versus in-contract versus work-in-progress per function and per type. double pow is correctly rounded in all four modes, while powf and ld80 powl are not yet." - }, - { - "content": "The no-fesetround-inside-kernels rule and the no-FENV_ACCESS-pragma rule are firm project constraints. Kernels only read the current rounding mode and adjust bit manipulation and final rounding accordingly. Long double overloads may delegate to double, but double overloads must be real double-width, and float results are never widened to satisfy double." - } - ] - }, - { - "title": "Build System", - "purpose": "Orient the reader on how CCMath is built, tested, and validated. Identify the three build front-ends: CMake (primary, 3.18+), Meson (meson.build, consumption only), and Premake5 (premake5.lua, consumption only). Explain that because the library is header-only, the default build produces only an INTERFACE target, and tests, benchmarks, fuzzers, examples, and oracle suites are each gated behind their own cache option. Map out the cmake/ directory's four functional groups: config/ (project options, install rules, module registries, find-modules for MPFR, GMP, and CORE-MATH), features/ (compile-time detection of builtins and SIMD instruction sets), helpers/ (the ccm_* function helpers that register headers, tests, benchmarks, fuzzers, examples, and oracles), and in/ (configure-time templates for the package config and the generated version.hpp). Summarize the CI/CD surface: eight GitHub Actions workflows covering the platform, compiler, and standard matrix plus security scanning and linting. Point to child pages for CMake configuration detail, the full build-option catalog, and the CI/CD workflows.", - "page_notes": [ - { - "content": "cmake/ contains 92 files. The helper entry points consumers and contributors actually call live in cmake/helpers/: CcmAddHeaders.cmake, CcmTestHelpers.cmake, CcmBenchHelpers.cmake, CcmFuzzHelpers.cmake, CcmExampleHelpers.cmake, CcmOracleHelpers.cmake, and CcmOptionalTooling.cmake. The registries in cmake/config/ (TestModuleRegistry, BenchmarkModuleRegistry, FuzzModuleRegistry, ExampleModuleRegistry, OracleCampaignRegistry) enumerate what gets built." - }, - { - "content": "Meson and Premake5 are explicitly secondary and exist only so downstreams on those build systems can consume the headers. cmake/gen/meson.build is generated. Do not present Meson or Premake as a way to build the test or benchmark suites." - } - ] - }, - { - "title": "CMake Configuration", - "purpose": "Detail how the CMake build is wired. The root CMakeLists.txt (about 7.3 KB) declares project ccmath version 0.3.0, defines the INTERFACE library and the ccmath::ccmath alias, and conditionally includes the test, benchmark, fuzz, example, and oracle subtrees. Explain the header-registration flow: the ccm_add_headers helper (cmake/helpers/CcmAddHeaders.cmake) collects headers into the interface target and drives installation. Cover install and export: cmake/config/InstallRules.cmake plus the templates cmake/in/ccmath-config.cmake.in (the package config consumers find via find_package) and cmake/in/version.hpp.in (configured into a generated version header carrying 0.3.0). Describe ProjectIsTopLevel.cmake, ProjectOptions.cmake, ProjectCompileDefinitions.cmake, and UserOptions.cmake as the layer that decides defaults differently when CCMath is the top-level project versus a subproject. Explain feature detection: cmake/features/ runs compile checks (CheckAllSupportedBuiltinFeatures, CheckAllSupportedSimdFeatures, builtin/support/CheckBuiltinBitCastSupport, CheckBuiltinFmaSupport, simd/SelectSimdInstructionSet, CheckFMASupport, CheckSVMLSupport) and feeds the results into compile definitions. Mention the find-modules FindMpfrGmp.cmake and FindCoreMath.cmake (and cmake/coremath/) used only when oracle or rigorous testing is enabled, and the BuildManifest and EmitBuildManifest pair that records the resolved configuration.", - "parent": "Build System", - "page_notes": [ - { - "content": "Read the root CMakeLists.txt to confirm the exact option names and the alias target before documenting them verbatim. The version string flows from the root CMakeLists project() call into cmake/in/version.hpp.in and then into the generated header. Confirm where the generated version.hpp lands (the build directory) so the doc points to the template, not a checked-in artifact." - }, - { - "content": "ProjectIsTopLevel.cmake is the standard guard that suppresses building tests and tools when CCMath is pulled in via FetchContent or add_subdirectory. This is why a downstream FetchContent consumer does not accidentally build the 129-file test suite." - } - ] - }, - { - "title": "Build Options", - "purpose": "Catalog the cache options a builder can toggle, defined primarily in cmake/config/ (ProjectOptions.cmake, UserOptions.cmake, TestValidationOptions.cmake) and consumed across the build. Document each option's name, default, and effect: building the optional subtrees (tests, benchmarks, fuzzers, examples), enabling the experimental extension headers, the deterministic and cross-hardware mode flag (CCMATH_ENABLE_DETERMINISTIC, which forces generic kernels), disabling runtime SIMD, disabling errno semantics, and the warnings-as-errors, aggressive-warnings, and sanitizer toggles used in CI. Explain the rigorous-testing options that turn on MPFR and CORE-MATH oracle campaigns and the worst-case ULP suites (these pull in external dependencies via the find-modules). Note that defaults flip based on top-level versus subproject status so downstreams get a lean header-only consume by default. Direct readers to the CI/CD Workflows page to see which option combinations each workflow exercises.", - "parent": "Build System", - "page_notes": [ - { - "content": "Do not publish exact option names or defaults without checking the source. Grep cmake/config/ProjectOptions.cmake, cmake/config/UserOptions.cmake, and cmake/config/TestValidationOptions.cmake for the real CCMATH_* and option() declarations, and check the root CMakeLists.txt for any defined inline. Candidate names such as CCMATH_BUILD_TESTS, CCMATH_DISABLE_RUNTIME_SIMD, CCMATH_ENABLE_EXTENSIONS, and CCMATH_DISABLE_ERRNO are unverified. Confirm each against the source before stating it as fact." - }, - { - "content": "Werror builds are real: there is a build-werror/ output directory and CI builds with warnings-as-errors plus aggressive warnings (-Wconversion and related). Deterministic mode is CCMATH_ENABLE_DETERMINISTIC and routes through generic kernels, so accuracy in that mode is capped by the generic implementations." - } - ] - }, - { - "title": "CI/CD Workflows", - "purpose": "Explain the continuous-integration surface under .github/workflows/. Document the eight workflows: ci-linux.yml (GCC and Clang across C++17, C++20, and C++23), ci-macos.yml (Apple Clang), ci-windows.yml (MSVC), ci-integration.yml (builds the integration/vendored-consumer/ project to prove install and export work for downstreams), fuzz.yml (runs the libFuzzer campaign and smoke targets), codeql.yml (CodeQL static analysis), scorecard.yml (OpenSSF Scorecard supply-chain posture), and lint.yml (clang-format enforcement). State the compiler and standard matrix: Linux GCC 11+ and Clang 9+, macOS Apple Clang 14+, Windows MSVC 19.26+, each across C++17, C++20, and C++23. Describe how the workflows reuse the CMake build options to enable tests and the strict-warnings build, and how the fuzz workflow ties into fuzzing/cmake/run_fuzz_campaign.cmake and run_fuzz_smoke.cmake. Mention the pre-commit configuration (.pre-commit-config.yaml) and the lint.sh and lint.bat scripts contributors run locally to match lint.yml. Note that Dependabot was removed, so dependency updates are manual. Security scanning is covered by CodeQL and Scorecard.", - "parent": "Build System", - "page_notes": [ - { - "content": "Open each file in .github/workflows/ to confirm exact job names, the compiler version floors, and which standards each matrix leg actually runs before stating them. The eight filenames are confirmed: ci-linux.yml, ci-macos.yml, ci-windows.yml, ci-integration.yml, fuzz.yml, codeql.yml, lint.yml, scorecard.yml." - }, - { - "content": "Dependabot was intentionally removed (see commit 'Remove dependabot configuration (#145)'). Do not document Dependabot as active. Clang-format is pinned, and the repo runs a clang-format check on every pull request (recent commit '575fd55f Run clang-format check on every pull request')." - } - ] - }, - { - "title": "Core Components", - "purpose": "Provide the index of CCMath's public mathematical API, all under include/ccmath/math/ and surfaced in the ccm namespace. Enumerate the ten function families, each with its own subdirectory: basic/ (abs, fdim, fma, fmod, fmax, fmin, nan, remainder, remquo), compare/ (fpclassify, isfinite, isinf, isnan, isnormal, signbit, isgreater, isgreaterequal, isless, islessequal, islessgreater, isunordered), expo/ (exp, exp2, expm1, log, log2, log10, log1p), fmanip/ (copysign, frexp, ilogb, ldexp, logb, modf, nextafter, nexttoward, scalbn), hyper/ (sinh, cosh, tanh, asinh, acosh, atanh), misc/ (gamma, lgamma, lerp), nearest/ (ceil, floor, round, rint, nearbyint, trunc), power/ (sqrt, cbrt, hypot, pow), special/ (the C++17 special mathematical functions: Bessel, elliptic integrals, Legendre, Laguerre, Hermite, beta, Riemann zeta, and the exponential integral, 21 headers), and trig/ (sin, cos, tan, asin, acos, atan, atan2). Explain the consistent file pattern: include/ccmath/math//.hpp is the public entry that performs three-path dispatch into internal/math/generic/func// and internal/math/runtime/func//. Note the per-family aggregate headers (basic.hpp, compare.hpp, special.hpp, and the rest), with misc/ as the exception: it has no aggregate header, and include/ccmath/ccmath.hpp pulls in gamma.hpp, lgamma.hpp, and lerp.hpp individually. Mention the master include/ccmath/ccmath.hpp and numbers.hpp for constants. Direct readers to the per-family child pages and to docs/STATUS.md for the authoritative completion and accuracy state of each function.", - "page_notes": [ - { - "content": "Header counts: math/ has 134 public headers total across basic/ (11), compare/ (14), expo/ (10), fmanip/ (14), hyper/ (6), misc/ (3), nearest/ (8), power/ (4), special/ (21), and trig/ (10). The special/ family is the largest and implements the C++17 special-math set (IS 29124), not plain . erf and erfc are not exposed as public functions, and only internal builtin wrappers exist under internal/math/generic/builtins/misc/. Accuracy here is the least mature in the library, so confirm which functions are in-contract via docs/STATUS.md and docs/ULP_STATUS.md before describing them as complete." - }, - { - "content": "numbers.hpp provides mathematical constants analogous to the C++20 header but constexpr-clean for C++17. The public headers must not include . Verify with the conformance include-policy test (tests/conformance/include_policy_test.cpp)." - } - ] - }, - { - "title": "Basic Math Functions", - "purpose": "Document the basic family in include/ccmath/math/basic/ (11 public headers): abs, fdim, fma, fmod, fmax, fmin, nan, remainder, and remquo. For each, describe the standard semantics it mirrors, the special-case handling (NaN propagation, signed zero, infinities), and how the public header dispatches across the three paths into internal/math/generic/func/basic/ (constexpr-capable kernels) and internal/math/runtime/func/basic/ (runtime and SIMD paths). Give fma special attention: it must be a true fused multiply-add (single rounding), is correctly rounded, and is validated by dedicated conformance tests (tests/conformance/fma_rounding_test.cpp, fma_software_fenv_test.cpp) and MPFR oracles (tests/shared/oracle/mpfr_fma_double.cpp, mpfr_fma_float.cpp). Explain remquo's quotient-bits output and remainder's IEEE remainder definition. Note that the basic family is also where the runtime SIMD story is most visible (abs, fdim, and fma have benchmarks under benchmarks/src/math/basic/).", - "parent": "Core Components", - "page_notes": [ - { - "content": "fma was already correctly rounded before the pow correct-rounding work. It relies on internal/support/fp/fma.hpp and the EFT (error-free transform) ops in internal/support/fp/ops/. The software-fenv FMA test exists because some platforms lack a hardware FMA and the generic path must still honor the rounding mode." - }, - { - "content": "Benchmarks exist for abs, fdim, and fma in benchmarks/src/math/basic/. Unit tests are in tests/src/math/basic/ (abs, fdim, fma, fmod, max, min, nan, remainder, remquo). Confirm the exact 11 header filenames in include/ccmath/math/basic/ before listing them." - } - ] - }, - { - "title": "Comparison Functions", - "purpose": "Document the compare family in include/ccmath/math/compare/ (14 public headers): the classification predicates fpclassify, isfinite, isinf, isnan, isnormal, and signbit, plus the relational predicates isgreater, isgreaterequal, isless, islessequal, islessgreater, and isunordered. Explain that these are the functions where compiler builtins are most often trustworthy even in constexpr, so the dispatch frequently prefers __builtin_* paths gated by CCM_HAS_CONST_BUILTIN, falling back to bit-level generic kernels (for example fpclassify uses the FPBits representation and a helper at internal/support/helpers/fpclassify_helper.hpp). Cover the critical correctness details: signbit must report the sign bit of negative zero and NaN correctly, the relational predicates must not raise the invalid exception for quiet NaNs the way a naive less-than or greater-than would, and isunordered defines the NaN-aware ordering. Point to internal/math/generic/func/compare/ for the portable kernels and tests/src/math/compare/ for the unit tests.", - "parent": "Core Components", - "page_notes": [ - { - "content": "fpclassify returns the standard FP_* categories and is backed by internal/support/helpers/fpclassify_helper.hpp and FPBits. signbit correctness on -0.0 and NaN is a common failure point. The trig overhaul work fixed sign-of-zero issues elsewhere, so cross-link to FPBits handling." - }, - { - "content": "There are 14 headers here including the six relational predicates and the classification ones. Unit tests in tests/src/math/compare/ include fpclassify, isfinite, isgreater variants, isinf, isless variants, isnan, isnormal, and signbit. Verify the full filename set in the directory." - } - ] - }, - { - "title": "Exponential and Logarithmic Functions", - "purpose": "Document the expo family in include/ccmath/math/expo/ (10 public headers): exp, exp2, expm1, log, log2, log10, and log1p, each with float, double, and long-double overloads. Explain the implementation strategy: argument reduction plus polynomial and minimax approximation evaluated in the generic kernels under internal/math/generic/func/expo/, with constants chosen and validated through the Sollya and Gappa workflow documented in docs/approximating_functions/. Describe the accuracy posture (ULP bounds tracked in docs/ULP_STATUS.md) and the special-case requirements (exp of positive or negative infinity, log of zero, negative, or NaN, and expm1 and log1p near zero for catastrophic-cancellation avoidance). Note the dedicated ULP-accuracy unit tests in tests/src/math/expo/ and that these functions lean on shared helpers (internal/support/helpers/exp_helpers.hpp, exp10.hpp, poly_eval.hpp, multiply_add.hpp). Cross-link to Floating-Point Representation for how FPBits and DyadicFloat support the high-precision intermediate steps.", - "parent": "Core Components", - "page_notes": [ - { - "content": "expm1 and log1p exist precisely for the near-zero accuracy case and must not be implemented as exp(x) minus 1 or log(1 plus x). Polynomial constants should be demystified and reproducible via the Sollya scripts under docs/approximating_functions/. The proof-freshness check (tests/proofs/proof_freshness_check.py) guards that the committed constants still match their derivation." - }, - { - "content": "Helpers used: internal/support/poly_eval.hpp (Horner and Estrin evaluation), internal/support/multiply_add.hpp, internal/support/helpers/exp_helpers.hpp, and exp10.hpp. Unit tests in tests/src/math/expo/ include exp, exp2, expm1, log, log10, log1p, log2, and a ULP-accuracy test." - } - ] - }, - { - "title": "Power Functions", - "purpose": "Document the power family in include/ccmath/math/power/ (4 public headers): sqrt, cbrt, hypot, and pow. Sqrt shows the dispatch pattern in its simplest form: the public header selects among a correctly-rounded hardware or builtin path, a generic sqrt_gen fallback (Newton or Goldschmidt style with a final correctly-rounded step), and a runtime path. Other nontrivial functions follow the same shape. Pow is the most complex and most heavily validated function in the library: the double overload is correctly rounded in all four IEEE rounding modes (achieved by fixing four classes of bugs), implemented via a log and exp decomposition with extended-precision (DyadicFloat) intermediates under internal/math/generic/func/power/pow_impl/, and exercised by oracle campaigns against MPFR and CORE-MATH plus Gappa proofs. Hypot must avoid intermediate overflow and underflow and be correctly rounded. Describe the runtime SIMD pow paths, the generic constexpr paths, and the deterministic-mode behavior. Direct readers to docs/STATUS.md, docs/ULP_STATUS.md, docs/ROUNDING_STATUS.md, tests/proofs/math/power/, tests/rigorous/, and the conformance pow tests for the authoritative correctness picture.", - "parent": "Core Components", - "page_notes": [ - { - "content": "Accuracy facts: double pow is correctly rounded in all four modes after fixing four bug classes, while powf (float) and ld80 powl (80-bit long double) are not yet correctly rounded and are left as-is. fma was already correctly rounded. Do not claim float or long-double pow correctness. The current branch is fix/fuzz-guard-ld80-powl-near-one, so ld80 powl near 1.0 is actively being hardened." - }, - { - "content": "Pow testing has a trap worth calling out: some test campaigns measure the system libm via __builtin_pow rather than ccmath's kernel (the public_default and simple campaigns hit libm, while the generic_runtime campaign hits the ccmath kernel). MSVC UCRT std::pow ignores the rounding mode at range thresholds, so never use the system libm as a directed-rounding oracle. Use MPFR or exact IEEE values. See tests/conformance/pow_path_config_test.cpp and pow_errno_fenv_test.cpp, tests/proofs/math/power/ (pow_double_proof.py, the Gappa .g files), and tests/rigorous/oracle_logs/." - }, - { - "content": "pow_impl/ under internal/math/generic/func/power/ holds the layered Ziv-style implementation. Unit tests in tests/src/math/power/ are extensive (19 files): pow correct-rounding, deterministic, and boundary variants, plus sqrt, hypot, cbrt, a SIMD test, and accuracy tests. The sqrt benchmark is at benchmarks/src/math/power/sqrt.bench.cpp." - } - ] - }, - { - "title": "Nearest Integer Functions", - "purpose": "Document the nearest family in include/ccmath/math/nearest/ (8 public headers): ceil, floor, trunc, round, rint, and nearbyint. Explain the shared mechanism: these are implemented by direct manipulation of the floating-point representation via FPBits rather than arithmetic, masking off the fractional mantissa bits according to the unbiased exponent (see the truncation and nearest-integer logic in internal/support/fp/nearest_integer.hpp and directional_rounding_utils.hpp). Clarify the semantic differences that matter: trunc rounds toward zero, floor toward negative infinity, ceil toward positive infinity, round is round-half-away-from-zero (and is not affected by the current rounding mode), rint rounds using the current rounding mode and may raise the inexact exception, and nearbyint behaves like rint but never raises inexact. Describe the constexpr versus runtime dispatch and that these functions must preserve the sign of zero and pass NaN and infinity through unchanged. Point to tests/src/math/nearest/ and the rounding conformance tests.", - "parent": "Core Components", - "page_notes": [ - { - "content": "The rint-versus-nearbyint distinction (the inexact exception) and round being mode-independent are the two most common correctness traps, both covered by tests/conformance/rounding_conformance_test.cpp and ulp_all_modes_test.cpp. The bit manipulation lives in internal/support/fp/nearest_integer.hpp. Cross-link to Floating-Point Representation." - }, - { - "content": "trunc has a benchmark at benchmarks/src/math/nearest/trunc.bench.cpp. Unit tests in tests/src/math/nearest/ cover ceil, floor, nearbyint, round, rint, and trunc. Confirm the 8 header filenames in the directory (six functions plus possibly aggregate or forwarding headers)." - } - ] - }, - { - "title": "Floating-Point Manipulation", - "purpose": "Document the fmanip family in include/ccmath/math/fmanip/ (14 public headers): copysign, frexp, ldexp, modf, scalbn (and scalbln), logb, ilogb, nextafter, and nexttoward. Explain that these functions decompose or recompose the floating-point representation, so they are implemented almost entirely through FPBits and exponent and mantissa manipulation in internal/math/generic/func/fmanip/ with helpers like internal/support/helpers/internal_ldexp.hpp. Cover the per-function contracts: copysign composes magnitude with sign bit (must work for NaN and zero), frexp splits into normalized fraction and exponent, ldexp and scalbn scale by a power of two efficiently and correctly across the subnormal range with proper overflow, underflow, errno, and fenv, modf splits integral and fractional parts preserving sign, logb and ilogb extract the unbiased exponent (with the special ilogb return values FP_ILOGB0 and FP_ILOGBNAN), and nextafter and nexttoward step to the adjacent representable value (raising overflow, underflow, or inexact as required). Note the long-double specifics for nexttoward and the type-precision rule that double overloads are real double-width. Point to tests/src/math/fmanip/.", - "parent": "Core Components", - "page_notes": [ - { - "content": "scalbn and ldexp must traverse subnormals correctly and set errno or raise fenv on overflow and underflow, a frequent bug source. ilogb has the special returns FP_ILOGB0 and FP_ILOGBNAN. nextafter and nexttoward differ in the type of the second argument (long double for nexttoward) and in the exact representable-neighbor stepping." - }, - { - "content": "There are 14 headers and unit tests in tests/src/math/fmanip/ for copysign, frexp, ilogb, ldexp, logb, modf, nextafter, nexttoward, and scalbn. Confirm whether scalbln has its own header. Helper: internal/support/helpers/internal_ldexp.hpp." - } - ] - }, - { - "title": "Trigonometric and Hyperbolic Functions", - "purpose": "Document two families. Trigonometric: include/ccmath/math/trig/ (10 public headers) with sin, cos, tan, asin, acos, atan, and atan2. Hyperbolic: include/ccmath/math/hyper/ with sinh, cosh, tanh, asinh, acosh, and atanh. Explain the trig implementation overhaul: double sin, cos, and tan are in-contract across all argument ranges using real binary64 constants plus Payne-Hanek reduction for large arguments (so accuracy does not collapse far from zero), and the sign-of-zero and inverse-trig edge cases were fixed and committed. Describe the dispatch into internal/math/generic/func/trig/ and internal/math/generic/func/hyper/, the use of poly_eval and multiply_add helpers, and the special cases (sin or cos of infinity is NaN, atan2 quadrant and signed-zero rules, asin and acos domain of negative one to one, acosh domain of one or greater, atanh domain between negative one and one). Note that builtin dispatch headers exist but the generic kernels hold the actual implementation. Point to tests/src/math/trig/ (including a kernel-regression test and a ULP-accuracy test) and docs/ULP_STATUS.md.", - "parent": "Core Components", - "page_notes": [ - { - "content": "Trig overhaul facts (committed): double sin, cos, and tan use real binary64 constants and Payne-Hanek large-argument reduction, with sign-of-zero and inverse-trig edge cases fixed. The large-argument reduction is the key to staying in-contract for huge inputs. Cross-link to Extended Precision Types since the reduction needs high-precision pi multiples. There is a kernel-regression test in tests/src/math/trig/ guarding against re-introducing the fixed bugs." - }, - { - "content": "Confirm the exact header set: trig/ has 10 headers (sin, cos, tan, asin, acos, atan, atan2, plus possibly forwarding or builtin headers). The hyper/ count is not pinned, so list sinh, cosh, tanh, asinh, acosh, and atanh and verify. Generic kernels are at internal/math/generic/func/trig/ and internal/math/generic/func/hyper/." - } - ] - }, - { - "title": "Special Functions", - "purpose": "Document the special family in include/ccmath/math/special/ (21 public headers), CCMath's implementation of the C++17 special mathematical functions (IS 29124). Group them by kind: cylindrical and spherical Bessel functions (cyl_bessel_i, cyl_bessel_j, cyl_bessel_k, cyl_neumann, sph_bessel, sph_neumann), Legendre and associated and spherical Legendre (legendre, assoc_legendre, sph_legendre), Laguerre and associated Laguerre (laguerre, assoc_laguerre), Hermite polynomials (hermite), complete and incomplete elliptic integrals of the first, second, and third kinds (ellint_1, ellint_2, ellint_3, comp_ellint_1, comp_ellint_2, comp_ellint_3), the beta function (beta), the Riemann zeta function (riemann_zeta), and the exponential integral (expint). State plainly that erf and erfc are not part of this public set, since only internal builtin wrappers for them exist under internal/math/generic/builtins/misc/. Explain that each public header dispatches into a generic kernel under internal/math/generic/func/special/ (the matching _gen files), that the family is aggregated by include/ccmath/math/special.hpp, and that accuracy here is the least mature in the library. The per-function status in docs/STATUS.md and docs/ULP_STATUS.md is authoritative. Read the impl headers and status docs to state domains, recurrences, and accuracy rather than assuming them, and stress MPFR oracle validation for these higher-complexity routines.", - "parent": "Core Components", - "page_notes": [ - { - "content": "Header set (21, all listed in special.hpp): assoc_laguerre, assoc_legendre, beta, comp_ellint_1, comp_ellint_2, comp_ellint_3, cyl_bessel_i, cyl_bessel_j, cyl_bessel_k, cyl_neumann, ellint_1, ellint_2, ellint_3, expint, hermite, laguerre, legendre, riemann_zeta, sph_bessel, sph_legendre, sph_neumann. The generic kernels are the matching _gen files under internal/math/generic/func/special/." - }, - { - "content": "This is the C++17 special-math set (IS 29124), not part of plain , so frame it that way for readers. Accuracy and completeness vary by function, so do not assert correctness or ULP bounds without checking docs/STATUS.md and docs/ULP_STATUS.md. Given their complexity, these functions are the strongest candidates for MPFR oracle validation." - } - ] - }, - { - "title": "Miscellaneous Functions", - "purpose": "Document the misc family in include/ccmath/math/misc/: gamma (the true gamma function, matching tgamma), lgamma (the natural log of the absolute value of gamma), and lerp (linear interpolation, matching the C++20 std::lerp). Unlike the other families, misc/ has no aggregate header, so include/ccmath/ccmath.hpp includes gamma.hpp, lgamma.hpp, and lerp.hpp individually under a comment that labels them uncategorized. Explain the implementations: gamma and lgamma have type-specific kernels and a shared data table under include/ccmath/math/misc/impl/ (gamma_data.hpp, gamma_double_impl.hpp, gamma_float_impl.hpp, gamma_impl.hpp, lgamma_double_impl.hpp, lgamma_impl.hpp), and the writeup should read those headers to state the actual approximation method, the reflection handling for negative arguments, and the pole behavior at non-positive integers rather than assuming a specific algorithm. Cover lerp's standard guarantees: it returns a at t equal to zero, b at t equal to one, and is monotonic. Point to tests/src/math/misc/ for the gamma, lgamma, and lerp tests, including the std versus no_std comparison variants.", - "parent": "Core Components", - "page_notes": [ - { - "content": "misc/ holds gamma, lgamma, and lerp. There is no misc.hpp aggregate, and the three headers are included individually by include/ccmath/ccmath.hpp under an uncategorized comment. The gamma and lgamma kernels live under include/ccmath/math/misc/impl/ with separate float and double implementations plus a shared gamma_data.hpp table." - }, - { - "content": "Confirm gamma maps to tgamma semantics by reading include/ccmath/math/misc/gamma.hpp, and read the impl headers before naming the approximation method. lerp matches C++20 std::lerp, so verify the endpoint exactness and the handling of out-of-range t. Tests in tests/src/math/misc/ include std and no_std variants." - } - ] - }, - { - "title": "Extension Functions", - "purpose": "Document the non-standard extension library under include/ccmath/ext/ (28 headers), gated as experimental with an explicitly unstable API (see include/ccmath/ext/README.md). These are convenience and graphics or gameplay-oriented helpers not found in : clamp, saturate, lerp helpers (mix, lerp_smooth, inverse_lerp and unlerp, remap), angle helpers (degrees, radians, delta_angle, lerp_angle, move_towards, move_towards_angle), interpolation and easing (smoothstep, cubic, step), periodic helpers (ping_pong, repeat, fract), and assorted utilities (abs_diff, align, approximately, ceil_div, chgsign, normalize, rcp, sign, is_power_of_two). Explain that, like the core API, they are constexpr-capable and live under the ccm namespace (typically ccm::ext or ccm:: per the headers), and that enabling them may be controlled by a build option. Stress their experimental status: signatures and behavior may change, and they are not held to the same -conformance bar as the core families. Point to tests/src/math/ext/ (28 matching test files plus a constexpr smoke test) for usage examples.", - "parent": "Core Components", - "page_notes": [ - { - "content": "Full 28-header list: abs_diff, align, approximately, ceil_div, chgsign, clamp, cubic, degrees, delta_angle, fract, inverse_lerp, is_power_of_two, lerp_angle, lerp_smooth, mix, move_towards, move_towards_angle, normalize, ping_pong, radians, rcp, remap, repeat, saturate, sign, smoothstep, step, unlerp. The README at include/ccmath/ext/README.md states the stability policy. Quote it rather than paraphrasing the guarantee." - }, - { - "content": "Verify the exact namespace these land in (ccm:: versus ccm::ext) by reading one header such as include/ccmath/ext/clamp.hpp. Each extension has a 1:1 unit test in tests/src/math/ext/ plus a constexpr_smoke_test.cpp confirming constant-evaluation usability." - } - ] - }, - { - "title": "Architecture and Implementation", - "purpose": "Give the internal-architecture overview that ties the implementation pages together. Center it on the three-path dispatch model: each public header in include/ccmath/math/ selects among (1) a compiler-builtin path (trusted only at compile time or under round-to-nearest at runtime), (2) a portable generic kernel under include/ccmath/internal/math/generic/ that is constexpr-capable and correct in all four rounding modes (files often suffixed _gen), and (3) a runtime kernel under include/ccmath/internal/math/runtime/ that may use SIMD or platform tricks (files often suffixed _rt). Explain the supporting subsystems housed under include/ccmath/internal/: predef/ and config/ for compiler, platform, and builtin detection, support/ for the shared numeric machinery (FPBits, fenv, EFT ops, helpers, type traits, extended-precision types), and types/ for internal type definitions. Describe how is_constant_evaluated gates compile-time versus runtime selection and how deterministic mode forces the generic path for bit-identical results. Link to the child pages: Type System, Compiler Support and Builtins, SIMD Optimization, Floating-Point Representation, and Extended Precision Types.", - "page_notes": [ - { - "content": "Naming conventions to verify and document: kernels under internal/math/generic/func// versus internal/math/runtime/func//, the _gen and _rt suffixes, and builtin wrappers under internal/math/generic/builtins/. The dispatch decision lives in the public header, not in a separate dispatch layer, and the project explicitly forbids adding new parallel layers. Cross-link to the sqrt pattern as the canonical dispatch reference." - }, - { - "content": "internal/ holds 423 of the 586 headers. The rule for contributors: constexpr only goes as deep as the lowest primitive needs it, and every non-generic (runtime) path in a _rt.hpp file must guard against the current rounding mode. is_constant_evaluated lives at internal/support/is_constant_evaluated.hpp." - } - ] - }, - { - "title": "Type System", - "purpose": "Describe CCMath's internal type infrastructure under include/ccmath/internal/support/ and include/ccmath/internal/types/. Cover the type-trait and type-support layer: type_traits.hpp (constexpr-friendly traits), limits.hpp (a constexpr std::numeric_limits stand-in), is_constant_evaluated.hpp (the C++17 and C++20 portable wrapper), floating_point_traits.hpp, meta_compare.hpp, integer_literals.hpp, and the always_false idiom for dependent static_assert in templates. Explain the small domain types that model numeric concepts precisely: a Sign type (positive or negative sign abstraction), a NumberPair or pair-like structure for returning two related values (for example the high and low parts of an EFT), and a NormalizedFloat type used in reductions. Note that these types exist so the kernels can be written generically over float, double, and long double while staying constexpr and dependency-free (no pulled into the public surface where it can be avoided). Cross-link to Floating-Point Representation (FPBits) and Extended Precision Types (BigInt, DyadicFloat), which build on this layer. Point to tests/src/math/internal/ for the type unit tests (big_int, dyadic_float, normalized_float).", - "parent": "Architecture and Implementation", - "page_notes": [ - { - "content": "Confirm the exact names and locations of Sign, NumberPair, and NormalizedFloat by grepping include/ccmath/internal/ (likely under internal/types/ or internal/support/). These type names are unverified, so confirm they exist as named types before documenting their APIs. NormalizedFloat has a dedicated test (tests/src/math/internal/types/), so it is real." - }, - { - "content": "limits.hpp and is_constant_evaluated.hpp are the portability backbone for C++17 where std::is_constant_evaluated is C++20. always_false is the standard trick for making static_assert fire only on instantiation. type_traits.hpp re-implements the subset of the kernels need." - } - ] - }, - { - "title": "Compiler Support and Builtins", - "purpose": "Explain how CCMath detects and uses compiler builtins and how it falls back when they are absent or untrustworthy. Cover the detection macros: CCM_HAS_BUILTIN (is the builtin available) and CCM_HAS_CONST_BUILTIN (is it usable in a constant expression), implemented in include/ccmath/internal/predef/has_builtin.hpp and has_const_builtin.hpp on top of has_attribute.hpp. Describe the builtin-support headers under internal/math/generic/builtins/ that wrap __builtin_* math functions behind a uniform interface, and the compile-time feature checks mirrored in cmake/features/builtin/support/. Cover the predef attribute layer (include/ccmath/internal/predef/attributes/ and related): always_inline, the likely and unlikely branch hints (likely.hpp, unlikely.hpp), assume.hpp (assumption hint), and optnone and optimization controls, plus the compiler-detection and versioning headers (internal/predef/versioning/) that identify GCC, Clang, MSVC, and Apple Clang and their versions. Explain the policy: builtins are trusted only at compile time or under FE_TONEAREST, and every non-generic runtime path that uses a builtin must guard for the active rounding mode. Cross-link to SIMD Optimization (which has its own feature-detection layer) and to Library Goals (the trust-builtins rule).", - "parent": "Architecture and Implementation", - "page_notes": [ - { - "content": "The builtin-trust rule is a core numeric-correctness invariant: __builtin_* math functions are trusted only at compile time or under round-to-nearest. This is why the three-path model exists. internal/predef/ holds has_builtin.hpp, has_const_builtin.hpp, has_attribute.hpp, assume.hpp, likely.hpp, and unlikely.hpp, with subdirectories attributes/, compiler_suppression/, and versioning/." - }, - { - "content": "Verify the exact macro spellings (CCM_HAS_BUILTIN, CCM_HAS_CONST_BUILTIN) and the attribute header filenames by reading internal/predef/. The compiler-detection header name is unverified (a likely candidate had a typo such as 'compiler_suppport_detection.hpp'), so confirm the real filename before linking it." - } - ] - }, - { - "title": "SIMD Optimization", - "purpose": "Detail the runtime SIMD subsystem under include/ccmath/internal/math/runtime/simd/. Explain the two-stage story: compile-time instruction-set selection (driven by cmake/features/simd/SelectSimdInstructionSet.cmake, CheckFMASupport.cmake, CheckSVMLSupport.cmake) and header-level feature detection (a check_simd_support-style header exposing macros for the available ISA). Describe the abstraction: a native-ABI dispatcher (the ccm::intrin abi::native concept) and a basic_simd vector type that the runtime kernels program against, with per-ISA backends under runtime/simd/func/impl/: scalar/ (portable fallback), sse2/, sse4/, avx2/, neon/ (ARM), and vector_size/ (the GCC and Clang generic vector extension). Explain that SIMD is strictly a runtime acceleration: it is never used during constant evaluation and is disabled in deterministic mode and when the disable-runtime-SIMD build option is set, with the scalar backend always available as the fallback. Note the instruction-abstraction headers under runtime/simd/instructions/. Mention the parallel ccm::pp effort (a C++17 port of C++26 std::simd under runtime/pp/) that is standalone and not yet wired into the kernels. Point to tests/src/math/internal/ (the SIMD test) and the abs and sqrt SIMD-path benchmarks.", - "parent": "Architecture and Implementation", - "page_notes": [ - { - "content": "Six backends are present: scalar, sse2, sse4, avx2, neon, and vector_size under internal/math/runtime/simd/func/impl/. There is both a live ccm::intrin SIMD layer and a newer standalone ccm::pp port of C++26 std::simd under runtime/pp/ that is not yet wired in. Document ccm::intrin as the active path and ccm::pp as forthcoming and experimental." - }, - { - "content": "Verify the exact names basic_simd, abi::native, and the check_simd_support header by reading internal/math/runtime/simd/. CMake side: cmake/features/CheckAllSupportedSimdFeatures.cmake, simd/SelectSimdInstructionSet.cmake, CheckFMASupport.cmake, and CheckSVMLSupport.cmake (SVML is the Intel short vector math library). Disabling SIMD is a build option (verify its real name) and deterministic mode bypasses SIMD entirely." - } - ] - }, - { - "title": "Floating-Point Representation", - "purpose": "Explain how CCMath inspects and constructs floating-point values bit-for-bit, the foundation under nearly every kernel. Center on FPBits and the FPRepImpl hierarchy in include/ccmath/internal/support/fp/fp_bits.hpp: a type that decomposes an IEEE value into sign, exponent, and mantissa with constexpr accessors, parameterized over the format. Describe FPType and FPLayout for the four IEEE formats binary32, binary64, binary80 (x87 extended, with its explicit integer bit), and binary128, and the BiasedExponent versus Exponent distinction (stored-with-bias versus unbiased). Cover the EFT and rounding machinery: the error-free-transform operations under internal/support/fp/ops/ (add, sub, mul, add_or_sub) used for two-sum and two-product, the correctly-rounded helpers in internal/support/fp/ (fma.hpp, cast.hpp, nearest_integer.hpp), and the directional rounding utilities (directional_rounding_utils.hpp) that let kernels emit correctly-rounded results in any mode without changing the FPU mode. Describe the fenv abstraction under internal/support/fenv/ (reading the current rounding mode, raising exceptions) and the errno handling, both honoring semantics while remaining constexpr-friendly. Cross-link to Extended Precision Types and to the rounding and conformance tests (tests/conformance/).", - "parent": "Architecture and Implementation", - "page_notes": [ - { - "content": "FPBits is the core primitive. The project review standard says to reuse FPBits and the EFT ops rather than reinventing bit manipulation. internal/support/fp/ contains fp_bits.hpp, fma.hpp, cast.hpp, directional_rounding_utils.hpp, nearest_integer.hpp, and ops/ (add, sub, mul, add_or_sub). binary80 (long double on x86) has an explicit integer bit that makes it special-case-heavy, which is why ld80 powl is still being hardened." - }, - { - "content": "Firm rules to state: kernels never call fesetround and only read the rounding mode to adjust bit manipulation and final rounding. The #pragma STDC FENV_ACCESS ON pragma is forbidden (unportable and widely ignored). The fenv layer is at internal/support/fenv/. errno gating exists but set_errno_if_required is not constexpr (a known gap, deliberately left unfixed), so compile-time error paths are ill-formed." - } - ] - }, - { - "title": "Extended Precision Types", - "purpose": "Document the multiword and extended-precision numeric types under include/ccmath/internal/support/ (and types/) that the hard functions rely on for accurate intermediates. Cover BigInt: a constexpr fixed-width big-integer built from an array of machine words, supporting the full arithmetic, shift, and comparison set, used where 64-bit (or 128-bit) integers are insufficient (for example Payne-Hanek trig reduction and pow's mantissa work). Cover DyadicFloat: a high-precision floating-point type (a sign plus a BigInt mantissa plus an exponent) used as the extended-precision intermediate in pow, exp and log, and the trig reductions to push results inside correctly-rounded tolerance before the final round-back to the target type. Cover the fixed-width integer wrappers float128 and int128 and uint128 (with portable fallbacks where the compiler lacks __int128 or __float128), and the bit-counting support utilities ctz.hpp (count trailing zeros) and popcount used by the big-integer routines (internal/support/ctz.hpp, bits.hpp). Explain that these are all constexpr and dependency-free so they work during constant evaluation. Point to tests/src/math/internal/types/ for the big_int, dyadic_float, and normalized_float unit tests.", - "parent": "Architecture and Implementation", - "page_notes": [ - { - "content": "BigInt and DyadicFloat both have dedicated unit tests (tests/src/math/internal/types/big_int and dyadic_float), so their APIs are real and exercised. DyadicFloat carries the extended-precision intermediate for correctly-rounded pow and for the trig Payne-Hanek reduction, which needs many bits of pi. Confirm the exact template signatures BigInt and DyadicFloat by reading the headers." - }, - { - "content": "float128 and int128 are portability wrappers: they use the compiler __int128 and __float128 where available and fall back to BigInt-based emulation otherwise (verify the fallback path). ctz.hpp and bits.hpp provide constexpr count-trailing-zeros and popcount used by BigInt normalization. These types are the bridge between the Type System page and the correctly-rounded results described on the Power and Exponential pages." - } - ] - }, - { - "title": "Testing and Benchmarks", - "purpose": "Overview the quality-assurance machinery: 129 test files plus benchmark and fuzzing trees, all wired through CMake helpers. Summarize the layered testing strategy as follows. Fast unit tests under tests/src/ (mirroring the public family layout) run on every CI. A conformance suite under tests/conformance/ checks fenv exceptions, rounding-mode behavior, ULP across all modes, and include policy. A rigorous suite under tests/rigorous/ validates against external oracles (MPFR, CORE-MATH, cross-libm) with results captured in tests/rigorous/oracle_logs/. A proofs tree under tests/proofs/ ties implementation constants to Gappa and Sollya derivations with a freshness check. Shared infrastructure under tests/shared/ holds oracle harnesses and utils such as the ULP suite, fp_matcher, fenv_fixture, and conformance_suite. Separately summarize the Google Benchmark performance harness under benchmarks/ and the libFuzzer differential-fuzzing harness under fuzzing/. State the policy that tests land in the same change as the implementation. Link to the three child pages: Testing Framework, Benchmark System, and Fuzz Testing.", - "page_notes": [ - { - "content": "Test policy: tests land in the same change as the implementation and must cover ULP, edge cases, fenv flags, and worst-case inputs. The rigorous and oracle layer requires MPFR and GMP and optionally CORE-MATH (found via cmake/config/FindMpfrGmp.cmake and FindCoreMath.cmake) and is gated behind a build option, so it does not run in the default consumer build." - }, - { - "content": "tests/ structure: src/ (unit tests, the bulk of the 129 files), conformance/, proofs/, rigorous/, shared/ (oracle/ and utils/), plus ccmath_test_main.cpp and CMakeLists.txt. The suite uses GoogleTest. The shared utils worth naming: ulp_suite.hpp, ulp_distance.hpp, fp_matcher.hpp, fenv_fixture.hpp, conformance_suite.hpp, worst_case_samples.hpp, pow_path_dispatch.hpp, and campaign_metadata.hpp." - } - ] - }, - { - "title": "Testing Framework", - "purpose": "Explain how the GoogleTest-based suite is organized and how to add a test. Cover the CMake helper ccmath_add_test (cmake/helpers/CcmTestHelpers.cmake) and the registry (cmake/config/TestModuleRegistry.cmake, TestCtestTargets.cmake) that turn a source file into a CTest target, plus the shared entry point tests/ccmath_test_main.cpp. Enumerate the unit-test categories mirroring the public API under tests/src/math/ (basic, compare, expo, fmanip, nearest, power, trig, misc) and tests/src/math/ext/ (28 extension tests), plus the internal-type tests under tests/src/math/internal/ (big_int, dyadic_float, normalized_float, math_support, SIMD). Describe the conformance suite (tests/conformance/): fenv_exception_test, fenv_probe_test, fma_rounding_test, fma_software_fenv_test, include_policy_test, pow_errno_fenv_test, pow_path_config_test, powl_platform_test, rounding_conformance_test, ulp_all_modes_test, ulp_distance_test, and worst_case_ulp_test. Explain the ULP-accuracy framework in tests/shared/utils/ (ulp_suite.hpp, ulp_distance.hpp, fp_matcher.hpp) and how worst-case inputs (worst_case_samples.hpp) and math samples (math_samples.hpp) feed it. Note the campaign concept (campaign_metadata.hpp, pow_path_dispatch.hpp) that selects which code path a given test actually measures.", - "parent": "Testing and Benchmarks", - "page_notes": [ - { - "content": "A pitfall worth documenting prominently: a test can silently measure the system libm instead of the ccmath kernel depending on the campaign and path. The simple and public_default campaigns hit libm via __builtin_*, while the generic_runtime campaign hits the ccmath kernel. pow_path_dispatch.hpp and campaign_metadata.hpp control this. Always confirm which path a new numeric test exercises. Cross-link to the Power Functions page note about this." - }, - { - "content": "MSVC test-portability notes: watch for C4127 (conditional expression is constant) and use CCM_NEVER_INLINE where needed. The ULP policy and the oracle exceptional-value policy are documented. Verify exact ULP thresholds against docs/ULP_STATUS.md rather than hardcoding numbers here. include_policy_test.cpp enforces that public headers never include ." - } - ] - }, - { - "title": "Benchmark System", - "purpose": "Document the performance-measurement harness under benchmarks/. Explain the Google Benchmark integration and the add_benchmark CMake helper (cmake/helpers/CcmBenchHelpers.cmake, registered via cmake/config/BenchmarkModuleRegistry.cmake) gated behind the build-benchmarks option. Describe the shared harness in benchmarks/shared/: harness.hpp (the benchmark framework glue), inputs.hpp (randomized and representative input generation), paths.hpp (selecting which implementation path to benchmark, mirroring the three-path model so builtin, generic, and runtime or SIMD can be compared), and register.hpp (target registration). Enumerate the current benchmark targets under benchmarks/src/math/: basic/abs.bench.cpp, basic/fdim.bench.cpp, basic/fma.bench.cpp, nearest/trunc.bench.cpp, and power/sqrt.bench.cpp. Note that benchmarks compare CCMath against std:: and libm baselines and across paths, and mention any visualization tooling (plot scripts) if present. Cross-link to the tools/asmlab/ directory, which provides deeper assembly, accuracy, and throughput analysis beyond the Google Benchmark numbers.", - "parent": "Testing and Benchmarks", - "page_notes": [ - { - "content": "Confirmed benchmark targets: abs, fdim, and fma (basic), trunc (nearest), and sqrt (power). Possible visualization scripts such as plot.py or run_benchmark_and_plot are unverified, so confirm they exist under benchmarks/ before documenting them (they may live in tools/asmlab/scripts/ instead). benchmarks/shared/paths.hpp is what makes the harness useful: it lets a benchmark target a specific dispatch path, so builtin, generic, and runtime numbers are directly comparable." - }, - { - "content": "tools/asmlab/ is a separate analysis lab (accuracy/, bench/, golden/, harness/, knowledge/, refdata/, registry/, scripts/, docker/) for assembly inspection and golden-output regression. By project convention, asmlab shipped scripts and its public README stay command-focused and free of internal-tooling references. Keep asmlab documentation factual and command-oriented." - } - ] - }, - { - "title": "Fuzz Testing", - "purpose": "Document the libFuzzer-based differential-fuzzing infrastructure under fuzzing/. Explain the goal: feed random bit patterns as floating-point inputs into ccmath functions and compare against a reference (the system libm or an oracle), flagging ULP regressions, wrong special-case handling, or divergent fenv or errno behavior. Cover the shared harness in fuzzing/shared/: entry.hpp (the LLVMFuzzerTestOneInput entry points), input.hpp (decoding raw bytes into typed FP inputs), fuzz_calls.hpp (the per-function call wrappers), and harness.hpp (the differential-comparison logic including a ulp_difference utility and a check_same_floating equality that treats NaN and sign-of-zero correctly). Enumerate the per-category fuzz targets under fuzzing/src/math/: basic_fuzz.cpp, compare_fuzz.cpp, expo_fuzz.cpp, fmanip_fuzz.cpp, misc_fuzz.cpp, nearest_fuzz.cpp, power_fuzz.cpp, and trig_fuzz.cpp. Describe the supporting pieces: the libFuzzer dictionary fuzzing/dicts/ccmath.dict, the seed generator fuzzing/scripts/generate_seeds.py, the CMake driver cmake/modules/CcmLibFuzzer.cmake and helper CcmFuzzHelpers.cmake, and the campaign and smoke runners fuzzing/cmake/run_fuzz_campaign.cmake and run_fuzz_smoke.cmake. Note that the smoke targets are wired into CTest for quick regression and the full campaign runs in the fuzz.yml GitHub workflow. Mention that fuzzing has already surfaced real kernel bugs.", - "parent": "Testing and Benchmarks", - "page_notes": [ - { - "content": "Eight fuzz targets are present (basic, compare, expo, fmanip, misc, nearest, power, trig). Differential fuzzing has found real kernel bugs, so treat it as an active bug-finding tool rather than a formality. The current git branch fix/fuzz-guard-ld80-powl-near-one is a direct result of fuzzing the ld80 powl path near 1.0." - }, - { - "content": "check_same_floating and ulp_difference in fuzzing/shared/harness.hpp must handle NaN payloads and signed zero correctly to avoid false positives. fuzzing/cmake/run_fuzz_smoke.cmake feeds CTest (fast, every build) while run_fuzz_campaign.cmake is the long-running campaign used by .github/workflows/fuzz.yml. A libFuzzer-capable compiler (Clang) is required. See cmake/modules/CcmLibFuzzer.cmake for the gating." - } - ] - }, - { - "title": "Glossary", - "purpose": "Define the codebase-specific vocabulary an onboarding engineer needs, each with code pointers. Terms to define. ccm: the public namespace for all functions. three-path dispatch: builtin versus generic versus runtime selection in the public header. generic kernel and _gen: portable constexpr implementation under internal/math/generic/. runtime kernel and _rt: runtime path, possibly SIMD, under internal/math/runtime/. FPBits: bit-level FP decomposition, internal/support/fp/fp_bits.hpp. FPRepImpl, FPType, and FPLayout: the format-parameterized representation hierarchy. BiasedExponent versus Exponent: stored-with-bias versus unbiased exponent. EFT or error-free transform: two-sum and two-product ops in internal/support/fp/ops/. ULP: unit in the last place, the accuracy metric, see docs/ULP_STATUS.md and tests/shared/utils/ulp_distance.hpp. correctly rounded or CR: the result equals the infinitely-precise value rounded once to the target format in the active mode. in-contract: meets the project's stated accuracy bound for that function, not necessarily CR. Ziv strategy: layered recompute at higher precision when the rounding is ambiguous. BigInt and DyadicFloat: extended-precision intermediates, internal/support/. Payne-Hanek: large-argument trig reduction. deterministic mode: CCMATH_ENABLE_DETERMINISTIC, forces the generic path for cross-hardware bit-identical results. CCM_HAS_BUILTIN and CCM_HAS_CONST_BUILTIN: builtin-availability macros, internal/predef/. ccm::intrin: the live SIMD layer. ccm::pp: the standalone C++26 std::simd port under runtime/pp/, not yet wired. SVML: the Intel Short Vector Math Library, detected in cmake/features/simd/. campaign or path dispatch: which implementation a test actually measures, tests/shared/utils/pow_path_dispatch.hpp. oracle: a high-precision reference such as MPFR or CORE-MATH used in tests/shared/oracle/. fenv and errno semantics: IEEE exception flags and C error reporting honored per .", - "page_notes": [ - { - "content": "Keep each glossary entry to a one-or-two-sentence definition plus a concrete file pointer so it doubles as a navigation index. The CR-versus-in-contract distinction is the most important pair to get right for this codebase and should be crisp. CR means rounded once correctly in the active mode, while in-contract means within the documented ULP bound (see docs/ULP_STATUS.md and docs/ROUNDING_STATUS.md for which functions achieve which)." - }, - { - "content": "Keep all pointers to tracked files only, since the wiki is committed. Do not reference local-only or untracked tooling paths that the project keeps out of version control. Verify the ccm::pp and ccm::intrin naming against include/ccmath/internal/math/runtime/ before finalizing." - } - ] - } - ] -} diff --git a/benchmarks/shared/harness.hpp b/benchmarks/shared/harness.hpp index 0756a669..5917e56c 100644 --- a/benchmarks/shared/harness.hpp +++ b/benchmarks/shared/harness.hpp @@ -14,89 +14,107 @@ namespace ccm::bench inline constexpr std::int64_t kRangeMin = 8; inline constexpr std::int64_t kRangeMax = 8 << 10; - template - T load_arg(T value) + template T load_arg(T value) { volatile T storage = value; return storage; } - template - void unary_fixed(benchmark::State& state, T value, Fn&& fn) + template void unary_fixed(benchmark::State & state, T value, Fn && fn) { T const x = load_arg(value); - for (auto _ : state) { benchmark::DoNotOptimize(fn(x)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x)); + } } - template - void binary_fixed(benchmark::State& state, T x_value, T y_value, Fn&& fn) + template void binary_fixed(benchmark::State & state, T x_value, T y_value, Fn && fn) { T const x = load_arg(x_value); T const y = load_arg(y_value); - for (auto _ : state) { benchmark::DoNotOptimize(fn(x, y)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y)); + } } - template - void ternary_fixed(benchmark::State& state, T x_value, T y_value, T z_value, Fn&& fn) + template void ternary_fixed(benchmark::State & state, T x_value, T y_value, T z_value, Fn && fn) { T const x = load_arg(x_value); T const y = load_arg(y_value); T const z = load_arg(z_value); - for (auto _ : state) { benchmark::DoNotOptimize(fn(x, y, z)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y, z)); + } } - template - void unary_scalar(benchmark::State& state, Fn&& fn) + template void unary_scalar(benchmark::State & state, Fn && fn) { - for (auto _ : state) { benchmark::DoNotOptimize(fn(static_cast(state.range(0)))); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(static_cast(state.range(0)))); + } state.SetComplexityN(state.range(0)); } - template - void unary_vector(benchmark::State& state, Fn&& fn, Vec const& inputs) + template void unary_vector(benchmark::State & state, Fn && fn, Vec const & inputs) { for (auto _ : state) { - for (auto const& x : inputs) { benchmark::DoNotOptimize(fn(x)); } + for (auto const & x : inputs) + { + benchmark::DoNotOptimize(fn(x)); + } } state.SetComplexityN(state.range(0)); } - template - void binary_scalar(benchmark::State& state, Fn&& fn) + template void binary_scalar(benchmark::State & state, Fn && fn) { T const x = static_cast(state.range(0)); T const y = static_cast(state.range(0)); - for (auto _ : state) { benchmark::DoNotOptimize(fn(x, y)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y)); + } state.SetComplexityN(state.range(0)); } - template - void ternary_scalar(benchmark::State& state, Fn&& fn) + template void ternary_scalar(benchmark::State & state, Fn && fn) { T const x = static_cast(state.range(0)); T const y = static_cast(state.range(0)); T const z = static_cast(state.range(0)); - for (auto _ : state) { benchmark::DoNotOptimize(fn(x, y, z)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y, z)); + } state.SetComplexityN(state.range(0)); } template - void ternary_vector(benchmark::State& state, Fn&& fn, VecX const& xs, VecY const& ys, VecZ const& zs) + void ternary_vector(benchmark::State & state, Fn && fn, VecX const & xs, VecY const & ys, VecZ const & zs) { for (auto _ : state) { - for (std::size_t i = 0; i < xs.size(); ++i) { benchmark::DoNotOptimize(fn(xs[i], ys[i], zs[i])); } + for (std::size_t i = 0; i < xs.size(); ++i) + { + benchmark::DoNotOptimize(fn(xs[i], ys[i], zs[i])); + } } state.SetComplexityN(state.range(0)); } - template - void binary_vector(benchmark::State& state, Fn&& fn, VecX const& xs, VecY const& ys) + template void binary_vector(benchmark::State & state, Fn && fn, VecX const & xs, VecY const & ys) { for (auto _ : state) { - for (std::size_t i = 0; i < xs.size(); ++i) { benchmark::DoNotOptimize(fn(xs[i], ys[i])); } + for (std::size_t i = 0; i < xs.size(); ++i) + { + benchmark::DoNotOptimize(fn(xs[i], ys[i])); + } } state.SetComplexityN(state.range(0)); } diff --git a/benchmarks/shared/inputs.hpp b/benchmarks/shared/inputs.hpp index 55fdbbae..ff0fda63 100644 --- a/benchmarks/shared/inputs.hpp +++ b/benchmarks/shared/inputs.hpp @@ -18,7 +18,10 @@ namespace ccm::bench std::vector out; out.reserve(static_cast(count)); std::uniform_int_distribution dist(min, max); - for (std::int64_t i = 0; i < count; ++i) { out.push_back(dist(m_gen)); } + for (std::int64_t i = 0; i < count; ++i) + { + out.push_back(dist(m_gen)); + } return out; } @@ -28,7 +31,10 @@ namespace ccm::bench std::vector out; out.reserve(static_cast(count)); std::uniform_real_distribution dist(min, max); - for (std::int64_t i = 0; i < count; ++i) { out.push_back(dist(m_gen)); } + for (std::int64_t i = 0; i < count; ++i) + { + out.push_back(dist(m_gen)); + } return out; } @@ -36,16 +42,20 @@ namespace ccm::bench std::mt19937 m_gen; }; - inline Randomizer& thread_rng() + inline Randomizer & thread_rng() { static thread_local Randomizer rng; return rng; } inline std::vector random_integers(std::int64_t count) - { return thread_rng().integers(count); } + { + return thread_rng().integers(count); + } inline std::vector random_doubles(std::int64_t count) - { return thread_rng().doubles(count); } + { + return thread_rng().doubles(count); + } } // namespace ccm::bench diff --git a/benchmarks/shared/register.hpp b/benchmarks/shared/register.hpp index bdaff79b..6a933aac 100644 --- a/benchmarks/shared/register.hpp +++ b/benchmarks/shared/register.hpp @@ -10,30 +10,30 @@ #define CCMATH_BENCH_APPLY_RANGE ->RangeMultiplier(2)->Range(ccm::bench::kRangeMin, ccm::bench::kRangeMax)->Complexity() #define CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ - static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::unary_scalar(state, [&](std::int64_t x) { StdBody; }); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::unary_scalar(state, [&](std::int64_t x) { CcmBody; }); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ { \ auto inputs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::unary_vector(state, [&](int x) { StdBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ { \ auto inputs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::unary_vector(state, [&](int x) { CcmBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ { \ auto inputs = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::unary_vector(state, [&](double x) { StdBody; }, inputs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ { \ auto inputs = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::unary_vector(state, [&](double x) { CcmBody; }, inputs); \ @@ -48,33 +48,33 @@ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ - static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::binary_scalar(state, [&](double x, double y) { StdBody; }); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::binary_scalar(state, [&](double x, double y) { CcmBody; }); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ { \ auto xs = ccm::bench::random_integers(state.range(0)); \ auto ys = ccm::bench::random_integers(state.range(0)); \ ccm::bench::binary_vector(state, [&](int x, int y) { StdBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ { \ auto xs = ccm::bench::random_integers(state.range(0)); \ auto ys = ccm::bench::random_integers(state.range(0)); \ ccm::bench::binary_vector(state, [&](int x, int y) { CcmBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ { \ auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::binary_vector(state, [&](double x, double y) { StdBody; }, xs, ys); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ { \ auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ @@ -90,36 +90,36 @@ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ - static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { StdBody; }); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ { \ ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { CcmBody; }); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ { \ auto xs = ccm::bench::random_integers(state.range(0)); \ auto ys = ccm::bench::random_integers(state.range(0)); \ auto zs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::ternary_vector(state, [&](int x, int y, int z) { StdBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ { \ auto xs = ccm::bench::random_integers(state.range(0)); \ auto ys = ccm::bench::random_integers(state.range(0)); \ auto zs = ccm::bench::random_integers(state.range(0)); \ ccm::bench::ternary_vector(state, [&](int x, int y, int z) { CcmBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ { \ auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ auto zs = ccm::bench::random_doubles(state.range(0)); \ ccm::bench::ternary_vector(state, [&](double x, double y, double z) { StdBody; }, xs, ys, zs); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ { \ auto xs = ccm::bench::random_doubles(state.range(0)); \ auto ys = ccm::bench::random_doubles(state.range(0)); \ @@ -136,11 +136,11 @@ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; #define CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, Suffix, Profile, Value, StdBody, CcmBody) \ - static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State & state) \ { \ ccm::bench::unary_fixed(state, Value, [&](double x) { StdBody; }); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State & state) \ { \ ccm::bench::unary_fixed(state, Value, [&](double x) { CcmBody; }); \ } @@ -150,11 +150,11 @@ BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_##Profile); #define CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, Suffix, Profile, X, Y, StdBody, CcmBody) \ - static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State& state) \ + static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State & state) \ { \ ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { StdBody; }); \ } \ - static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State& state) \ + static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State & state) \ { \ ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { CcmBody; }); \ } diff --git a/benchmarks/src/math/power/sqrt.bench.cpp b/benchmarks/src/math/power/sqrt.bench.cpp index 45d9d101..be00db56 100644 --- a/benchmarks/src/math/power/sqrt.bench.cpp +++ b/benchmarks/src/math/power/sqrt.bench.cpp @@ -7,7 +7,9 @@ namespace { inline double sqrt_rt(double v) - { return ccm::sqrt(v); } + { + return ccm::sqrt(v); + } } // namespace CCMATH_BENCH_UNARY_COMPARE_PATHS( diff --git a/examples/common/io.hpp b/examples/common/io.hpp index 8ee813d2..4e43e9eb 100644 --- a/examples/common/io.hpp +++ b/examples/common/io.hpp @@ -8,13 +8,16 @@ namespace ccm::examples { inline void banner(std::string_view title) - { std::cout << "\n=== " << title << " ===\n"; } + { + std::cout << "\n=== " << title << " ===\n"; + } inline void section(std::string_view name) - { std::cout << '\n' << name << '\n'; } + { + std::cout << '\n' << name << '\n'; + } - template - void print_row(std::string_view label, T value) + template void print_row(std::string_view label, T value) { std::cout << std::left << std::setw(28) << label << std::right << std::setw(16); if constexpr (std::is_floating_point_v) @@ -27,7 +30,10 @@ namespace ccm::examples inline void table_header(std::string_view col_a, std::string_view col_b, std::string_view col_c = {}) { std::cout << std::left << std::setw(14) << col_a << std::setw(18) << col_b; - if (!col_c.empty()) { std::cout << std::setw(18) << col_c; } + if (!col_c.empty()) + { + std::cout << std::setw(18) << col_c; + } std::cout << '\n'; } diff --git a/examples/src/decibel_gain.cpp b/examples/src/decibel_gain.cpp index 628b23e8..5102ca77 100644 --- a/examples/src/decibel_gain.cpp +++ b/examples/src/decibel_gain.cpp @@ -12,10 +12,14 @@ namespace { double power_ratio_to_db(double ratio) - { return 10.0 * ccm::log10(ratio); } + { + return 10.0 * ccm::log10(ratio); + } double amplitude_ratio_to_db(double ratio) - { return 20.0 * ccm::log10(ratio); } + { + return 20.0 * ccm::log10(ratio); + } } // namespace int main() @@ -26,7 +30,10 @@ int main() ccm::examples::table_header("power ratio", "dB (10 log10)"); - for (double ratio : ratios) { std::cout << std::left << std::setw(14) << ratio << std::setw(18) << power_ratio_to_db(ratio) << '\n'; } + for (double ratio : ratios) + { + std::cout << std::left << std::setw(14) << ratio << std::setw(18) << power_ratio_to_db(ratio) << '\n'; + } ccm::examples::section("amplitude form (20 log10)"); ccm::examples::print_row("2x amplitude", amplitude_ratio_to_db(2.0)); diff --git a/examples/src/fp_classes.cpp b/examples/src/fp_classes.cpp index 7fefddde..cb6a5629 100644 --- a/examples/src/fp_classes.cpp +++ b/examples/src/fp_classes.cpp @@ -19,12 +19,12 @@ namespace { switch (ccm::fpclassify(x)) { - case FP_INFINITE: return "infinite"; - case FP_NAN: return "nan"; - case FP_NORMAL: return "normal"; + case FP_INFINITE : return "infinite"; + case FP_NAN : return "nan"; + case FP_NORMAL : return "normal"; case FP_SUBNORMAL: return "subnormal"; - case FP_ZERO: return "zero"; - default: return "unknown"; + case FP_ZERO : return "zero"; + default : return "unknown"; } } @@ -45,7 +45,10 @@ int main() ccm::examples::table_header("value", "class", "signbit"); - for (double v : kValues) { std::cout << std::left << std::setw(14) << v << std::setw(18) << classify(v) << std::setw(18) << ccm::signbit(v) << '\n'; } + for (double v : kValues) + { + std::cout << std::left << std::setw(14) << v << std::setw(18) << classify(v) << std::setw(18) << ccm::signbit(v) << '\n'; + } ccm::examples::section("ordering predicates"); ccm::examples::print_row("isfinite(1)", ccm::isfinite(1.0)); diff --git a/examples/src/hypot_distance.cpp b/examples/src/hypot_distance.cpp index 1a68f43f..a62ffa6e 100644 --- a/examples/src/hypot_distance.cpp +++ b/examples/src/hypot_distance.cpp @@ -21,10 +21,12 @@ namespace Vec3 const kSensor{ 12.0, -4.5, 1.25 }; Vec3 const kTarget{ 27.0, 18.0, 6.0 }; - double planar_distance(Vec3 const& a, Vec3 const& b) - { return ccm::hypot(b.x - a.x, b.y - a.y); } + double planar_distance(Vec3 const & a, Vec3 const & b) + { + return ccm::hypot(b.x - a.x, b.y - a.y); + } - double spatial_distance(Vec3 const& a, Vec3 const& b) + double spatial_distance(Vec3 const & a, Vec3 const & b) { double const dx = b.x - a.x; double const dy = b.y - a.y; diff --git a/examples/src/rounding_edge_cases.cpp b/examples/src/rounding_edge_cases.cpp index be6a630f..6b0696b1 100644 --- a/examples/src/rounding_edge_cases.cpp +++ b/examples/src/rounding_edge_cases.cpp @@ -25,7 +25,7 @@ int main() ccm::examples::table_header("x", "nearbyint", "round"); - for (auto const& s : kSamples) + for (auto const & s : kSamples) { std::cout << std::left << std::setw(14) << s.x << std::setw(18) << ccm::nearbyint(s.x) << std::setw(18) << ccm::round(s.x) << '\n'; } diff --git a/examples/src/signed_magnitude.cpp b/examples/src/signed_magnitude.cpp index 843116e3..688e7ad4 100644 --- a/examples/src/signed_magnitude.cpp +++ b/examples/src/signed_magnitude.cpp @@ -30,7 +30,7 @@ int main() ccm::examples::table_header("a", "b", "fdim(a,b)"); - for (auto const& c : kCases) + for (auto const & c : kCases) { double const positive_diff = ccm::fdim(c.a, c.b); double const magnitude = ccm::fabs(c.a); diff --git a/examples/src/unit_circle.cpp b/examples/src/unit_circle.cpp index 55f31922..180a9aff 100644 --- a/examples/src/unit_circle.cpp +++ b/examples/src/unit_circle.cpp @@ -30,7 +30,7 @@ int main() ccm::examples::table_header("deg", "sin", "cos"); - for (auto const& angle : kAngles) + for (auto const & angle : kAngles) { double const s = ccm::sin(angle.radians); double const c = ccm::cos(angle.radians); diff --git a/fuzzing/shared/fuzz_calls.hpp b/fuzzing/shared/fuzz_calls.hpp index 4362d611..2565bf53 100644 --- a/fuzzing/shared/fuzz_calls.hpp +++ b/fuzzing/shared/fuzz_calls.hpp @@ -45,54 +45,66 @@ namespace ccm::fuzz::calls // The kernel mappings reproduce that contract: special operands route through the // public entry point and ordinary operands hit the kernel, so the kernel is fuzzed // strictly inside its own domain. - template - bool kernel_domain_finite(T x) - { return x == x && x != std::numeric_limits::infinity() && x != -std::numeric_limits::infinity(); } + template bool kernel_domain_finite(T x) + { + return x == x && x != std::numeric_limits::infinity() && x != -std::numeric_limits::infinity(); + } #define CCMATH_FUZZ_UNARY_CALL(name) \ - template \ - T name(T x) \ + template T name(T x) \ { \ - if (!kernel_domain_finite(x)) { return ccm::name(x); } \ + if (!kernel_domain_finite(x)) \ + { \ + return ccm::name(x); \ + } \ return ccm::gen::name##_gen(x); \ } #define CCMATH_FUZZ_UNARY_CALL_POSITIVE(name) \ - template \ - T name(T x) \ + template T name(T x) \ { \ - if (!kernel_domain_finite(x) || !(x > T(0))) { return ccm::name(x); } \ + if (!kernel_domain_finite(x) || !(x > T(0))) \ + { \ + return ccm::name(x); \ + } \ return ccm::gen::name##_gen(x); \ } #define CCMATH_FUZZ_UNARY_CALL_ABOVE_NEG_ONE(name) \ - template \ - T name(T x) \ + template T name(T x) \ { \ - if (!kernel_domain_finite(x) || !(x > T(-1))) { return ccm::name(x); } \ + if (!kernel_domain_finite(x) || !(x > T(-1))) \ + { \ + return ccm::name(x); \ + } \ return ccm::gen::name##_gen(x); \ } #define CCMATH_FUZZ_BINARY_CALL(name) \ - template \ - T name(T x, T y) \ + template T name(T x, T y) \ { \ - if (!kernel_domain_finite(x) || !kernel_domain_finite(y)) { return ccm::name(x, y); } \ + if (!kernel_domain_finite(x) || !kernel_domain_finite(y)) \ + { \ + return ccm::name(x, y); \ + } \ return ccm::gen::name##_gen(x, y); \ } // The pow kernel owns its full special-case matrix, so it is fuzzed unfiltered. #define CCMATH_FUZZ_BINARY_CALL_COMPLETE(name) \ - template \ - T name(T x, T y) \ - { return ccm::gen::name##_gen(x, y); } + template T name(T x, T y) \ + { \ + return ccm::gen::name##_gen(x, y); \ + } #else #define CCMATH_FUZZ_UNARY_CALL(name) \ - template \ - T name(T x) \ - { return ccm::name(x); } + template T name(T x) \ + { \ + return ccm::name(x); \ + } #define CCMATH_FUZZ_UNARY_CALL_POSITIVE(name) CCMATH_FUZZ_UNARY_CALL(name) #define CCMATH_FUZZ_UNARY_CALL_ABOVE_NEG_ONE(name) CCMATH_FUZZ_UNARY_CALL(name) #define CCMATH_FUZZ_BINARY_CALL(name) \ - template \ - T name(T x, T y) \ - { return ccm::name(x, y); } + template T name(T x, T y) \ + { \ + return ccm::name(x, y); \ + } #define CCMATH_FUZZ_BINARY_CALL_COMPLETE(name) CCMATH_FUZZ_BINARY_CALL(name) #endif @@ -115,18 +127,22 @@ namespace ccm::fuzz::calls // Inverse trig has no generic kernels yet, so the public dispatch is fuzzed in both // build flavors and these calls run under round to nearest only. - template - T asin(T x) - { return ccm::asin(x); } - template - T acos(T x) - { return ccm::acos(x); } - template - T atan(T x) - { return ccm::atan(x); } - template - T atan2(T x, T y) - { return ccm::atan2(x, y); } + template T asin(T x) + { + return ccm::asin(x); + } + template T acos(T x) + { + return ccm::acos(x); + } + template T atan(T x) + { + return ccm::atan(x); + } + template T atan2(T x, T y) + { + return ccm::atan2(x, y); + } #undef CCMATH_FUZZ_UNARY_CALL #undef CCMATH_FUZZ_UNARY_CALL_POSITIVE diff --git a/fuzzing/shared/harness.hpp b/fuzzing/shared/harness.hpp index fbd83180..93389b7c 100644 --- a/fuzzing/shared/harness.hpp +++ b/fuzzing/shared/harness.hpp @@ -42,8 +42,7 @@ namespace ccm::fuzz } \ } while (0) - template - int64_t ulp_difference(T a, T b) + template int64_t ulp_difference(T a, T b) { static_assert(std::is_floating_point_v, "T must be floating-point"); @@ -53,47 +52,65 @@ namespace ccm::fuzz fp_bits_t fp_a(a); fp_bits_t fp_b(b); - if (fp_a.is_nan() && fp_b.is_nan()) { return 0; } - if (fp_a.is_nan() || fp_b.is_nan()) { return std::numeric_limits::max(); } + if (fp_a.is_nan() && fp_b.is_nan()) + { + return 0; + } + if (fp_a.is_nan() || fp_b.is_nan()) + { + return std::numeric_limits::max(); + } if (fp_a.is_inf() && fp_b.is_inf()) { - if (fp_a.sign() == fp_b.sign()) { return 0; } + if (fp_a.sign() == fp_b.sign()) + { + return 0; + } + return std::numeric_limits::max(); + } + if (fp_a.is_inf() || fp_b.is_inf()) + { return std::numeric_limits::max(); } - if (fp_a.is_inf() || fp_b.is_inf()) { return std::numeric_limits::max(); } using uint_type = typename fp_bits_t::storage_type; constexpr uint_type sign_mask = uint_type{ 1 } << (sizeof(uint_type) * 8 - 1); - auto ordered_bits = [=](fp_bits_t bits) - { + auto ordered_bits = [=](fp_bits_t bits) { uint_type const raw = bits.uintval(); - if ((raw & sign_mask) != 0) { return sign_mask - (raw & ~sign_mask); } + if ((raw & sign_mask) != 0) + { + return sign_mask - (raw & ~sign_mask); + } return sign_mask + raw; }; uint_type const a_bits = ordered_bits(fp_a); uint_type const b_bits = ordered_bits(fp_b); uint_type const diff = (a_bits > b_bits) ? a_bits - b_bits : b_bits - a_bits; - if (diff > static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } + if (diff > static_cast(std::numeric_limits::max())) + { + return std::numeric_limits::max(); + } return static_cast(diff); } - template - bool values_equal(T a, T b) + template bool values_equal(T a, T b) { using fp_bits_t = ccm::support::fp::FPBits; fp_bits_t fp_a(a); fp_bits_t fp_b(b); - if (fp_a.is_nan() || fp_b.is_nan()) { return fp_a.is_nan() && fp_b.is_nan(); } + if (fp_a.is_nan() || fp_b.is_nan()) + { + return fp_a.is_nan() && fp_b.is_nan(); + } return fp_a.uintval() == fp_b.uintval(); } - template - void check_same_floating(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) + template void check_same_floating(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) { using fp_bits_t = ccm::support::fp::FPBits; @@ -120,20 +137,19 @@ namespace ccm::fuzz FUZZ_CHECK(ulp_difference(actual, expected) <= max_ulp); } - template - void check_ccm_negative_domain_nan(T input, CcmFn ccm_fn) + template void check_ccm_negative_domain_nan(T input, CcmFn ccm_fn) { T const result = ccm_fn(input); FUZZ_CHECK(std::isnan(result)); FUZZ_CHECK(std::signbit(result)); } - template - void fuzz_unary_vs_std(T input, CcmFn && ccm_fn, StdFn && std_fn, int64_t max_ulp = kMaxAllowedUlp) - { check_same_floating(ccm_fn(input), std_fn(input), max_ulp); } + template void fuzz_unary_vs_std(T input, CcmFn && ccm_fn, StdFn && std_fn, int64_t max_ulp = kMaxAllowedUlp) + { + check_same_floating(ccm_fn(input), std_fn(input), max_ulp); + } - template - void fuzz_unary_log_family(T input, CcmFn && ccm_fn, StdFn && std_fn) + template void fuzz_unary_log_family(T input, CcmFn && ccm_fn, StdFn && std_fn) { if (input < T{} && !std::isnan(input)) { @@ -144,8 +160,7 @@ namespace ccm::fuzz fuzz_unary_vs_std(input, std::forward(ccm_fn), std::forward(std_fn)); } - template - void fuzz_unary_log1p_family(T input, CcmFn && ccm_fn, StdFn && std_fn) + template void fuzz_unary_log1p_family(T input, CcmFn && ccm_fn, StdFn && std_fn) { if (input < static_cast(-1) && !std::isnan(input)) { @@ -156,16 +171,17 @@ namespace ccm::fuzz fuzz_unary_vs_std(input, std::forward(ccm_fn), std::forward(std_fn)); } - template - void fuzz_binary_vs_std(T x, T y, CcmFn && ccm_fn, StdFn && std_fn, int64_t max_ulp = kMaxAllowedUlp) - { check_same_floating(ccm_fn(x, y), std_fn(x, y), max_ulp); } + template void fuzz_binary_vs_std(T x, T y, CcmFn && ccm_fn, StdFn && std_fn, int64_t max_ulp = kMaxAllowedUlp) + { + check_same_floating(ccm_fn(x, y), std_fn(x, y), max_ulp); + } - template - void fuzz_binary_exact_int(T x, T y, CcmFn && ccm_fn, StdFn && std_fn) - { FUZZ_CHECK(ccm_fn(x, y) == std_fn(x, y)); } + template void fuzz_binary_exact_int(T x, T y, CcmFn && ccm_fn, StdFn && std_fn) + { + FUZZ_CHECK(ccm_fn(x, y) == std_fn(x, y)); + } - template - void check_remquo_vs_std(T x, T y) + template void check_remquo_vs_std(T x, T y) { using fp_bits_t = ccm::support::fp::FPBits; @@ -194,8 +210,7 @@ namespace ccm::fuzz FUZZ_CHECK(values_equal(ccm_rem, std_rem)); } - template - void check_frexp_vs_std(T x) + template void check_frexp_vs_std(T x) { int ccm_exp{}; int std_exp{}; @@ -203,8 +218,7 @@ namespace ccm::fuzz FUZZ_CHECK(ccm_exp == std_exp); } - template - void check_modf_vs_std(T x) + template void check_modf_vs_std(T x) { T ccm_intpart{}; T std_intpart{}; diff --git a/fuzzing/shared/input.hpp b/fuzzing/shared/input.hpp index d377dfd9..d3211d2e 100644 --- a/fuzzing/shared/input.hpp +++ b/fuzzing/shared/input.hpp @@ -27,8 +27,7 @@ namespace ccm::fuzz // soundness reason the harness does not cycle rounding modes). Raw byte mutation lands on // them constantly, so reject them. Every binary32/binary64/binary128 bit pattern is a valid // encoding, so this is a no-op for those types. - template - bool is_canonical_encoding(T value) + template bool is_canonical_encoding(T value) { if constexpr (std::numeric_limits::digits == 64 && sizeof(T) >= 10) { @@ -38,8 +37,7 @@ namespace ccm::fuzz const unsigned exponent = (static_cast(bytes[9] & 0x7FU) << 8) | bytes[8]; // Canonical iff the integer bit is set exactly when the biased exponent is non-zero. return integer_bit == (exponent != 0); - } - else + } else { static_cast(value); return true; @@ -51,17 +49,20 @@ namespace ccm::fuzz // example glibc returns NaN for pow(sNaN, 0) while the Annex F rule that ccmath follows // returns 1. Reject signaling-NaN operands so the comparison only covers behavior the // standard actually pins down (quiet NaN classification is still exercised). - template - bool is_testable_operand(T value) - { return is_canonical_encoding(value) && !ccm::support::fp::FPBits(value).is_signaling_nan(); } + template bool is_testable_operand(T value) + { + return is_canonical_encoding(value) && !ccm::support::fp::FPBits(value).is_signaling_nan(); + } - template - T load_floating(uint8_t const * data, size_t size, size_t byte_offset = 0) + template T load_floating(uint8_t const * data, size_t size, size_t byte_offset = 0) { static_assert(std::is_floating_point_v, "T must be floating-point"); T value{}; - if (byte_offset + sizeof(T) > size) { return value; } + if (byte_offset + sizeof(T) > size) + { + return value; + } std::memcpy(&value, data + byte_offset, sizeof(T)); return value; @@ -70,7 +71,10 @@ namespace ccm::fuzz inline int32_t load_i32(uint8_t const * data, size_t size, size_t byte_offset = 0) { int32_t value{}; - if (byte_offset + sizeof(value) > size) { return 0; } + if (byte_offset + sizeof(value) > size) + { + return 0; + } std::memcpy(&value, data + byte_offset, sizeof(value)); return value; @@ -79,22 +83,21 @@ namespace ccm::fuzz // Adversarial values raw byte mutation essentially never lands on exactly: format // boundaries, integer and half-integer parity thresholds, kernel range-reduction and // clamp constants. Indexed by the low selector bits so every entry stays reachable. - template - T special_value(uint8_t index) + template T special_value(uint8_t index) { using limits = std::numeric_limits; switch (index % 24) { - case 0: return T(0); - case 1: return limits::denorm_min(); - case 2: return limits::min() - limits::denorm_min(); // largest subnormal - case 3: return limits::min(); - case 4: return T(1); - case 5: return T(1) + limits::epsilon(); - case 6: return T(1) - limits::epsilon() / T(2); // largest value below one - case 7: return T(2); - case 8: return T(10); - case 9: return T(0.5); + case 0 : return T(0); + case 1 : return limits::denorm_min(); + case 2 : return limits::min() - limits::denorm_min(); // largest subnormal + case 3 : return limits::min(); + case 4 : return T(1); + case 5 : return T(1) + limits::epsilon(); + case 6 : return T(1) - limits::epsilon() / T(2); // largest value below one + case 7 : return T(2); + case 8 : return T(10); + case 9 : return T(0.5); case 10: return limits::max(); case 11: return limits::infinity(); case 12: return limits::quiet_NaN(); @@ -116,19 +119,26 @@ namespace ccm::fuzz // Optional per-operand selector bytes trail the raw operands. A set high bit swaps the // operand for a table entry. Bit 6 flips the sign. Inputs without trailing bytes (and // every pre-existing seed) decode exactly as before. - template - void apply_selector(T & value, uint8_t const * data, size_t size, size_t selector_offset) + template void apply_selector(T & value, uint8_t const * data, size_t size, size_t selector_offset) { - if (selector_offset >= size) { return; } + if (selector_offset >= size) + { + return; + } const uint8_t selector = data[selector_offset]; - if ((selector & 0x80U) == 0) { return; } + if ((selector & 0x80U) == 0) + { + return; + } T substituted = special_value(static_cast(selector & 0x3FU)); - if ((selector & 0x40U) != 0) { substituted = -substituted; } + if ((selector & 0x40U) != 0) + { + substituted = -substituted; + } value = substituted; } - template - struct Inputs + template struct Inputs { T x{}; T y{}; @@ -137,7 +147,10 @@ namespace ccm::fuzz bool load_x(uint8_t const * data, size_t size) { - if (size < sizeof(T)) { return false; } + if (size < sizeof(T)) + { + return false; + } x = load_floating(data, size, 0); apply_selector(x, data, size, sizeof(T)); return is_testable_operand(x); @@ -145,7 +158,10 @@ namespace ccm::fuzz bool load_xy(uint8_t const * data, size_t size) { - if (size < 2 * sizeof(T)) { return false; } + if (size < 2 * sizeof(T)) + { + return false; + } x = load_floating(data, size, 0); y = load_floating(data, size, sizeof(T)); apply_selector(x, data, size, 2 * sizeof(T)); @@ -155,7 +171,10 @@ namespace ccm::fuzz bool load_xyz(uint8_t const * data, size_t size) { - if (size < 3 * sizeof(T)) { return false; } + if (size < 3 * sizeof(T)) + { + return false; + } x = load_floating(data, size, 0); y = load_floating(data, size, sizeof(T)); z = load_floating(data, size, 2 * sizeof(T)); @@ -167,7 +186,10 @@ namespace ccm::fuzz bool load_xy_i(uint8_t const * data, size_t size) { - if (size < 2 * sizeof(T) + sizeof(int32_t)) { return false; } + if (size < 2 * sizeof(T) + sizeof(int32_t)) + { + return false; + } x = load_floating(data, size, 0); y = load_floating(data, size, sizeof(T)); i = load_i32(data, size, 2 * sizeof(T)); diff --git a/fuzzing/src/math/basic_fuzz.cpp b/fuzzing/src/math/basic_fuzz.cpp index a05db9ba..5a3564bb 100644 --- a/fuzzing/src/math/basic_fuzz.cpp +++ b/fuzzing/src/math/basic_fuzz.cpp @@ -17,11 +17,13 @@ namespace { - template - void basic(uint8_t const * data, size_t size) + template void basic(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xyz(data, size)) { return; } + if (!in.load_xyz(data, size)) + { + return; + } ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::fabs, [](T v) { return std::fabs(v); }); ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::fdim, [](T a, T b) { return std::fdim(a, b); }); diff --git a/fuzzing/src/math/compare_fuzz.cpp b/fuzzing/src/math/compare_fuzz.cpp index 28ac9cb8..e31447ab 100644 --- a/fuzzing/src/math/compare_fuzz.cpp +++ b/fuzzing/src/math/compare_fuzz.cpp @@ -17,11 +17,13 @@ namespace { - template - void compare(uint8_t const * data, size_t size) + template void compare(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xy(data, size)) { return; } + if (!in.load_xy(data, size)) + { + return; + } FUZZ_CHECK(ccm::fpclassify(in.x) == std::fpclassify(in.x)); FUZZ_CHECK(static_cast(ccm::isfinite(in.x)) == static_cast(std::isfinite(in.x))); diff --git a/fuzzing/src/math/expo_fuzz.cpp b/fuzzing/src/math/expo_fuzz.cpp index 6e1a02b8..c93a1614 100644 --- a/fuzzing/src/math/expo_fuzz.cpp +++ b/fuzzing/src/math/expo_fuzz.cpp @@ -19,11 +19,13 @@ namespace { - template - void expo(uint8_t const * data, size_t size) + template void expo(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_x(data, size)) { return; } + if (!in.load_x(data, size)) + { + return; + } // fuzz_calls routes to the generic kernels in the generic build and to the public // dispatch otherwise. The exp and log kernels meet the contract for both float and diff --git a/fuzzing/src/math/fmanip_fuzz.cpp b/fuzzing/src/math/fmanip_fuzz.cpp index 1edc14a2..af98bf29 100644 --- a/fuzzing/src/math/fmanip_fuzz.cpp +++ b/fuzzing/src/math/fmanip_fuzz.cpp @@ -17,11 +17,13 @@ namespace { - template - void fmanip(uint8_t const * data, size_t size) + template void fmanip(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xy_i(data, size)) { return; } + if (!in.load_xy_i(data, size)) + { + return; + } ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::copysign, [](T a, T b) { return std::copysign(a, b); }); ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::nextafter, [](T a, T b) { return std::nextafter(a, b); }); diff --git a/fuzzing/src/math/misc_fuzz.cpp b/fuzzing/src/math/misc_fuzz.cpp index f1ea7a8e..710b45a5 100644 --- a/fuzzing/src/math/misc_fuzz.cpp +++ b/fuzzing/src/math/misc_fuzz.cpp @@ -17,28 +17,44 @@ namespace { - template - T reference_lerp(T a, T b, T t) + template T reference_lerp(T a, T b, T t) { - if (std::isnan(a) || std::isnan(b) || std::isnan(t)) { return a + b + t; } - - if ((a <= T{} && b >= T{}) || (a >= T{} && b <= T{})) { return t * b + (static_cast(1) - t) * a; } - if (t == static_cast(1)) { return b; } + if (std::isnan(a) || std::isnan(b) || std::isnan(t)) + { + return a + b + t; + } + + if ((a <= T{} && b >= T{}) || (a >= T{} && b <= T{})) + { + return t * b + (static_cast(1) - t) * a; + } + if (t == static_cast(1)) + { + return b; + } T const x = a + t * (b - a); - if ((t > static_cast(1)) == (b > a)) { return b < x ? x : b; } + if ((t > static_cast(1)) == (b > a)) + { + return b < x ? x : b; + } return x < b ? x : b; } - template - void misc(uint8_t const * data, size_t size) + template void misc(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xyz(data, size)) { return; } + if (!in.load_xyz(data, size)) + { + return; + } ccm::fuzz::check_same_floating(ccm::lerp(in.x, in.y, in.z), reference_lerp(in.x, in.y, in.z)); - if (in.x < T{} && in.x == std::trunc(in.x)) { return; } + if (in.x < T{} && in.x == std::trunc(in.x)) + { + return; + } ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::gamma, [](T v) { return std::tgamma(v); }); ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::lgamma, [](T v) { return std::lgamma(v); }); } diff --git a/fuzzing/src/math/nearest_fuzz.cpp b/fuzzing/src/math/nearest_fuzz.cpp index 39e90dec..33d74109 100644 --- a/fuzzing/src/math/nearest_fuzz.cpp +++ b/fuzzing/src/math/nearest_fuzz.cpp @@ -17,11 +17,13 @@ namespace { - template - void nearest(uint8_t const * data, size_t size) + template void nearest(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_x(data, size)) { return; } + if (!in.load_x(data, size)) + { + return; + } ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::floor, [](T v) { return std::floor(v); }); ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::trunc, [](T v) { return std::trunc(v); }); diff --git a/fuzzing/src/math/power_fuzz.cpp b/fuzzing/src/math/power_fuzz.cpp index 4bbc927e..b9fc7ed3 100644 --- a/fuzzing/src/math/power_fuzz.cpp +++ b/fuzzing/src/math/power_fuzz.cpp @@ -19,11 +19,13 @@ namespace { - template - void power(uint8_t const * data, size_t size) + template void power(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xy(data, size)) { return; } + if (!in.load_xy(data, size)) + { + return; + } ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::fuzz::calls::sqrt, [](T v) { return std::sqrt(v); }); ccm::fuzz::fuzz_unary_vs_std(in.x, ccm::fuzz::calls::cbrt, [](T v) { return std::cbrt(v); }); @@ -62,7 +64,10 @@ namespace static_cast(size); #else ccm::fuzz::Inputs in; - if (!in.load_xy(data, size)) { return; } + if (!in.load_xy(data, size)) + { + return; + } ccm::fuzz::fuzz_binary_vs_std(in.x, in.y, ccm::fuzz::calls::pow, [](long double a, long double b) { return std::pow(a, b); }); #endif diff --git a/fuzzing/src/math/trig_fuzz.cpp b/fuzzing/src/math/trig_fuzz.cpp index bc12c09f..fdeb448e 100644 --- a/fuzzing/src/math/trig_fuzz.cpp +++ b/fuzzing/src/math/trig_fuzz.cpp @@ -18,11 +18,13 @@ namespace { - template - void trig(uint8_t const * data, size_t size) + template void trig(uint8_t const * data, size_t size) { ccm::fuzz::Inputs in; - if (!in.load_xy(data, size)) { return; } + if (!in.load_xy(data, size)) + { + return; + } #ifndef CCMATH_FUZZ_GENERIC // Public-dispatch build: sin / cos / tan are differential-tested against libm. diff --git a/include/ccmath/ext/align.hpp b/include/ccmath/ext/align.hpp index d2690a7f..a6c7f135 100644 --- a/include/ccmath/ext/align.hpp +++ b/include/ccmath/ext/align.hpp @@ -36,8 +36,7 @@ namespace ccm::ext * @param value Value to align. * @return value rounded to the nearest aligned multiple according to mode. */ - template , bool> = true> - constexpr T align(T value) noexcept + template , bool> = true> constexpr T align(T value) noexcept { static_assert(alignment != 0, "Alignment must be non-zero"); static_assert(support::has_single_bit(alignment), "Alignment must be a power of two"); @@ -63,8 +62,7 @@ namespace ccm::ext */ const T align_mask = static_cast(alignment - 1); return (value + align_mask) & ~align_mask; - } - else + } else { /* * What is happening here: @@ -92,9 +90,10 @@ namespace ccm::ext * @param value Value to align. * @return value rounded up to the alignment boundary. */ - template , bool> = true> - constexpr T align_up(T value) noexcept - { return align(value); } + template , bool> = true> constexpr T align_up(T value) noexcept + { + return align(value); + } /** * @brief Aligns a value downward to the previous multiple of alignment. @@ -103,7 +102,8 @@ namespace ccm::ext * @param value Value to align. * @return value rounded down to the alignment boundary. */ - template , bool> = true> - constexpr T align_down(T value) noexcept - { return align(value); } + template , bool> = true> constexpr T align_down(T value) noexcept + { + return align(value); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/approximately.hpp b/include/ccmath/ext/approximately.hpp index e4a7f679..8a685a1c 100644 --- a/include/ccmath/ext/approximately.hpp +++ b/include/ccmath/ext/approximately.hpp @@ -24,7 +24,8 @@ namespace ccm::ext * @param epsilon The maximum allowed absolute difference. * @return True if the absolute difference between lhs and rhs is less than or equal to epsilon. */ - template , bool> = true> - constexpr bool approximately(T lhs, T rhs, T epsilon) noexcept - { return ccm::abs(lhs - rhs) <= epsilon; } + template , bool> = true> constexpr bool approximately(T lhs, T rhs, T epsilon) noexcept + { + return ccm::abs(lhs - rhs) <= epsilon; + } } // namespace ccm::ext diff --git a/include/ccmath/ext/ceil_div.hpp b/include/ccmath/ext/ceil_div.hpp index e1b770fb..dac6b646 100644 --- a/include/ccmath/ext/ceil_div.hpp +++ b/include/ccmath/ext/ceil_div.hpp @@ -30,15 +30,17 @@ namespace ccm::ext template && !std::is_same_v, bool>, bool> = true> constexpr T ceil_div(T value, T divisor) noexcept { - if (divisor == T(0)) { return T(0); } + if (divisor == T(0)) + { + return T(0); + } // Divide first, then add the correction, so there is no pre-division addition that can overflow. if constexpr (std::is_signed_v) { const bool quotient_positive = (value < 0) == (divisor < 0); return static_cast(value / divisor + (value % divisor != 0 && quotient_positive)); - } - else + } else { return static_cast(value / divisor + (value % divisor != 0)); } diff --git a/include/ccmath/ext/clamp.hpp b/include/ccmath/ext/clamp.hpp index b363059b..c43d76c7 100644 --- a/include/ccmath/ext/clamp.hpp +++ b/include/ccmath/ext/clamp.hpp @@ -25,9 +25,10 @@ namespace ccm::ext * @param hi Maximum value. * @return The clamped value. */ - template - constexpr T clamp(T v, T lo = T{ 0 }, T hi = T{ 1 }) - { return ccm::min(ccm::max(v, lo), hi); } + template constexpr T clamp(T v, T lo = T{ 0 }, T hi = T{ 1 }) + { + return ccm::min(ccm::max(v, lo), hi); + } /** * @brief Clamps a value between a minimum and maximum value. @@ -41,5 +42,7 @@ namespace ccm::ext */ template constexpr std::common_type_t clamp(TVal v, TLow lo = TLow{ 0 }, THigh hi = THigh{ 1 }) - { return ccm::min(ccm::max(v, lo), hi); } + { + return ccm::min(ccm::max(v, lo), hi); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/cubic.hpp b/include/ccmath/ext/cubic.hpp index 5f8f3bc4..2f03f0ab 100644 --- a/include/ccmath/ext/cubic.hpp +++ b/include/ccmath/ext/cubic.hpp @@ -24,8 +24,7 @@ namespace ccm::ext * @param t The interpolation value. * @return The interpolated value. */ - template , bool> = true> - constexpr T cubic(T y0, T y1, T y2, T y3, T t) + template , bool> = true> constexpr T cubic(T y0, T y1, T y2, T y3, T t) { const T a0 = y3 - y2 - y0 + y1; const T a1 = y0 - y1 - a0; diff --git a/include/ccmath/ext/degrees.hpp b/include/ccmath/ext/degrees.hpp index fdb4cc86..c65cc35b 100644 --- a/include/ccmath/ext/degrees.hpp +++ b/include/ccmath/ext/degrees.hpp @@ -22,7 +22,8 @@ namespace ccm::ext * @param radians Angle in radians. * @return Angle in degrees. */ - template , bool> = true> - constexpr T degrees(T radians) noexcept - { return (static_cast(180) * radians) / ccm::numbers::pi_v; } + template , bool> = true> constexpr T degrees(T radians) noexcept + { + return (static_cast(180) * radians) / ccm::numbers::pi_v; + } } // namespace ccm::ext diff --git a/include/ccmath/ext/delta_angle.hpp b/include/ccmath/ext/delta_angle.hpp index 603dc486..b495d6b5 100644 --- a/include/ccmath/ext/delta_angle.hpp +++ b/include/ccmath/ext/delta_angle.hpp @@ -24,14 +24,16 @@ namespace ccm::ext * @param target The target angle, in radians. * @return The shortest signed angular difference in the range [-pi, pi]. */ - template , bool> = true> - constexpr T delta_angle(T current, T target) noexcept + template , bool> = true> constexpr T delta_angle(T current, T target) noexcept { const T two_pi = T(2) * ccm::numbers::pi_v; T delta = ext::repeat(target - current, two_pi); - if (delta > ccm::numbers::pi_v) { delta -= two_pi; } + if (delta > ccm::numbers::pi_v) + { + delta -= two_pi; + } return delta; } diff --git a/include/ccmath/ext/fract.hpp b/include/ccmath/ext/fract.hpp index 5d9db21c..461ddda7 100644 --- a/include/ccmath/ext/fract.hpp +++ b/include/ccmath/ext/fract.hpp @@ -22,7 +22,8 @@ namespace ccm::ext * @param x Value to get the fractional part of. * @return The fractional part of the input. */ - template , bool> = true> - constexpr T fract(T x) noexcept - { return x - ccm::floor(x); } + template , bool> = true> constexpr T fract(T x) noexcept + { + return x - ccm::floor(x); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/inverse_lerp.hpp b/include/ccmath/ext/inverse_lerp.hpp index 05023d1d..b63542f5 100644 --- a/include/ccmath/ext/inverse_lerp.hpp +++ b/include/ccmath/ext/inverse_lerp.hpp @@ -31,7 +31,8 @@ namespace ccm::ext * @param value The value to evaluate. * @return The interpolation parameter of value in the range [start, end]. */ - template , bool> = true> - constexpr T inverse_lerp(T start, T end, T value) noexcept - { return ext::unlerp(start, end, value); } + template , bool> = true> constexpr T inverse_lerp(T start, T end, T value) noexcept + { + return ext::unlerp(start, end, value); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/is_power_of_two.hpp b/include/ccmath/ext/is_power_of_two.hpp index 3e5aeac0..4b425bc5 100644 --- a/include/ccmath/ext/is_power_of_two.hpp +++ b/include/ccmath/ext/is_power_of_two.hpp @@ -23,9 +23,10 @@ namespace ccm::ext * @param n Value to check. * @return True if the input is a power of two, false otherwise. */ - template && !std::is_same_v, bool> = true> - constexpr bool is_power_of_two(T n) noexcept - { return support::has_single_bit(n); } + template && !std::is_same_v, bool> = true> constexpr bool is_power_of_two(T n) noexcept + { + return support::has_single_bit(n); + } /** * @brief Determines if a signed integer value is a power of two. @@ -38,8 +39,7 @@ namespace ccm::ext * @param n Value to check. * @return True if the input is a power of two, false otherwise. */ - template && !std::is_same_v, bool> = true> - constexpr bool is_power_of_two(T n) noexcept + template && !std::is_same_v, bool> = true> constexpr bool is_power_of_two(T n) noexcept { using unsigned_type = std::make_unsigned_t; @@ -53,7 +53,8 @@ namespace ccm::ext * @param n Value to check. * @return True if the input is a power of two, false otherwise. */ - template && !std::is_same_v, bool> = true> - constexpr bool ispow2(T n) noexcept - { return is_power_of_two(n); } + template && !std::is_same_v, bool> = true> constexpr bool ispow2(T n) noexcept + { + return is_power_of_two(n); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/lerp_angle.hpp b/include/ccmath/ext/lerp_angle.hpp index 2bf24dfc..a28aaaac 100644 --- a/include/ccmath/ext/lerp_angle.hpp +++ b/include/ccmath/ext/lerp_angle.hpp @@ -27,7 +27,8 @@ namespace ccm::ext * @param t The interpolation parameter. * @return The interpolated angle, in radians. */ - template , bool> = true> - constexpr T lerp_angle(T start, T end, T t) noexcept - { return start + (ext::delta_angle(start, end) * t); } + template , bool> = true> constexpr T lerp_angle(T start, T end, T t) noexcept + { + return start + (ext::delta_angle(start, end) * t); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/lerp_smooth.hpp b/include/ccmath/ext/lerp_smooth.hpp index 2ecc6fcf..52a97f7f 100644 --- a/include/ccmath/ext/lerp_smooth.hpp +++ b/include/ccmath/ext/lerp_smooth.hpp @@ -23,8 +23,7 @@ namespace ccm::ext * @param h Half-life, time until halfway, in seconds. * @return The smoothed value. */ - template - constexpr T lerp_smooth(T a, T b, T t, T h) + template constexpr T lerp_smooth(T a, T b, T t, T h) { // ReSharper disable once CppRedundantParentheses return b + ((a - b) * ccm::exp2(-t / h)); diff --git a/include/ccmath/ext/move_towards.hpp b/include/ccmath/ext/move_towards.hpp index 564a0443..7b9ea643 100644 --- a/include/ccmath/ext/move_towards.hpp +++ b/include/ccmath/ext/move_towards.hpp @@ -24,12 +24,14 @@ namespace ccm::ext * @param max_delta The maximum change to apply. * @return The moved value. */ - template , bool> = true> - constexpr T move_towards(T current, T target, T max_delta) noexcept + template , bool> = true> constexpr T move_towards(T current, T target, T max_delta) noexcept { const T delta = target - current; - if (ccm::abs(delta) <= max_delta) { return target; } + if (ccm::abs(delta) <= max_delta) + { + return target; + } return current + ((delta > T(0)) ? max_delta : -max_delta); } diff --git a/include/ccmath/ext/move_towards_angle.hpp b/include/ccmath/ext/move_towards_angle.hpp index 0f1d25d8..21465885 100644 --- a/include/ccmath/ext/move_towards_angle.hpp +++ b/include/ccmath/ext/move_towards_angle.hpp @@ -25,8 +25,7 @@ namespace ccm::ext * @param max_delta The maximum angular change to apply, in radians. * @return The moved angle, in radians. */ - template , bool> = true> - constexpr T move_towards_angle(T current, T target, T max_delta) noexcept + template , bool> = true> constexpr T move_towards_angle(T current, T target, T max_delta) noexcept { const T delta = ext::delta_angle(current, target); return ext::move_towards(current, current + delta, max_delta); diff --git a/include/ccmath/ext/normalize.hpp b/include/ccmath/ext/normalize.hpp index cbb80933..6bd0922f 100644 --- a/include/ccmath/ext/normalize.hpp +++ b/include/ccmath/ext/normalize.hpp @@ -31,9 +31,10 @@ namespace ccm::ext * @param max The maximum value of the range. * @return The normalized value. */ - template , bool> = true> - constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept - { return ext::clamp((value - min) / (max - min), T(0), T(1)); } + template , bool> = true> constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept + { + return ext::clamp((value - min) / (max - min), T(0), T(1)); + } } // namespace unsafe /** @@ -51,12 +52,14 @@ namespace ccm::ext * @param max The maximum value of the range. * @return The normalized value. */ - template , bool> = true> - constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept + template , bool> = true> constexpr T normalize(T value, T min = T(0), T max = T(1)) noexcept { const T length = max - min; - if (length == T(0)) { return T(0); } + if (length == T(0)) + { + return T(0); + } return ext::unsafe::normalize(value, min, max); } diff --git a/include/ccmath/ext/ping_pong.hpp b/include/ccmath/ext/ping_pong.hpp index 1d1e61d0..2eff8ec7 100644 --- a/include/ccmath/ext/ping_pong.hpp +++ b/include/ccmath/ext/ping_pong.hpp @@ -24,10 +24,12 @@ namespace ccm::ext * @param b Range to ping-pong within. * @return The ping-ponged value. */ - template , bool> = true> - constexpr T ping_pong(T a, T b) noexcept + template , bool> = true> constexpr T ping_pong(T a, T b) noexcept { - if (b == 0) { return T(0); } + if (b == 0) + { + return T(0); + } return ccm::abs((ccm::ext::fract((a - b) / (b * 2.0)) * b * 2.0) - b); } diff --git a/include/ccmath/ext/radians.hpp b/include/ccmath/ext/radians.hpp index b1034630..5521310c 100644 --- a/include/ccmath/ext/radians.hpp +++ b/include/ccmath/ext/radians.hpp @@ -22,7 +22,8 @@ namespace ccm::ext * @param degrees Angle in degrees. * @return The angle in radians. */ - template , bool> = true> - constexpr T radians(T degrees) noexcept - { return (ccm::numbers::pi_v * degrees) / static_cast(180); } + template , bool> = true> constexpr T radians(T degrees) noexcept + { + return (ccm::numbers::pi_v * degrees) / static_cast(180); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/rcp.hpp b/include/ccmath/ext/rcp.hpp index e6527b4b..1c6a7037 100644 --- a/include/ccmath/ext/rcp.hpp +++ b/include/ccmath/ext/rcp.hpp @@ -20,7 +20,8 @@ namespace ccm::ext * @param x Value to get the reciprocal of. * @return The reciprocal of the input. */ - template , bool> = true> - constexpr T rcp(T x) noexcept - { return static_cast(1) / x; } + template , bool> = true> constexpr T rcp(T x) noexcept + { + return static_cast(1) / x; + } } // namespace ccm::ext diff --git a/include/ccmath/ext/remap.hpp b/include/ccmath/ext/remap.hpp index e3fa25c0..a24711e8 100644 --- a/include/ccmath/ext/remap.hpp +++ b/include/ccmath/ext/remap.hpp @@ -38,7 +38,9 @@ namespace ccm::ext */ template , bool> = true> constexpr T remap(T src_start, T src_end, T dst_start, T dst_end, T x) noexcept - { return dst_start + ((x - src_start) * (dst_end - dst_start)) / (src_end - src_start); } + { + return dst_start + ((x - src_start) * (dst_end - dst_start)) / (src_end - src_start); + } } // namespace unsafe /** @@ -67,7 +69,10 @@ namespace ccm::ext { const T length = src_end - src_start; - if (length == T(0)) { return dst_start; } + if (length == T(0)) + { + return dst_start; + } return ext::unsafe::remap(src_start, src_end, dst_start, dst_end, x); } diff --git a/include/ccmath/ext/repeat.hpp b/include/ccmath/ext/repeat.hpp index ed9f5315..8622db7a 100644 --- a/include/ccmath/ext/repeat.hpp +++ b/include/ccmath/ext/repeat.hpp @@ -29,9 +29,10 @@ namespace ccm::ext * @param length The length of the repeating interval. * @return The repeated value in the range [0, length). */ - template , bool> = true> - constexpr T repeat(T value, T length) noexcept - { return value - (ccm::floor(value / length) * length); } + template , bool> = true> constexpr T repeat(T value, T length) noexcept + { + return value - (ccm::floor(value / length) * length); + } } // namespace unsafe /** @@ -45,10 +46,12 @@ namespace ccm::ext * @param length The length of the repeating interval. * @return The repeated value in the range [0, length). */ - template , bool> = true> - constexpr T repeat(T value, T length) noexcept + template , bool> = true> constexpr T repeat(T value, T length) noexcept { - if (length == T(0)) { return T(0); } + if (length == T(0)) + { + return T(0); + } return ext::unsafe::repeat(value, length); } diff --git a/include/ccmath/ext/saturate.hpp b/include/ccmath/ext/saturate.hpp index d8e95630..a75e7c6b 100644 --- a/include/ccmath/ext/saturate.hpp +++ b/include/ccmath/ext/saturate.hpp @@ -22,7 +22,8 @@ namespace ccm::ext * @param value The value to saturate. * @return The value clamped to the range [0, 1]. */ - template , bool> = true> - constexpr T saturate(T value) noexcept - { return ext::clamp(value, T(0), T(1)); } + template , bool> = true> constexpr T saturate(T value) noexcept + { + return ext::clamp(value, T(0), T(1)); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/sign.hpp b/include/ccmath/ext/sign.hpp index 10984724..82937453 100644 --- a/include/ccmath/ext/sign.hpp +++ b/include/ccmath/ext/sign.hpp @@ -20,7 +20,8 @@ namespace ccm::ext * @param value The value whose sign is queried. * @return -1 if value is negative, 0 if value is zero, otherwise 1. */ - template && !std::is_same_v, bool> = true> - constexpr T sign(T value) noexcept - { return static_cast((T(0) < value) - (value < T(0))); } + template && !std::is_same_v, bool> = true> constexpr T sign(T value) noexcept + { + return static_cast((T(0) < value) - (value < T(0))); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/smoothstep.hpp b/include/ccmath/ext/smoothstep.hpp index e8c61c8f..ac9d0d06 100644 --- a/include/ccmath/ext/smoothstep.hpp +++ b/include/ccmath/ext/smoothstep.hpp @@ -24,8 +24,7 @@ namespace ccm::ext * @param x Value to interpolate. * @return The interpolated value. */ - template , bool> = true> - constexpr T smoothstep(T edge0, T edge1, T x) + template , bool> = true> constexpr T smoothstep(T edge0, T edge1, T x) { // Scale, bias and saturate x to 0..1 range x = ccm::ext::clamp((x - edge0) / (edge1 - edge0)); diff --git a/include/ccmath/ext/step.hpp b/include/ccmath/ext/step.hpp index 35e32f6a..f2eb194a 100644 --- a/include/ccmath/ext/step.hpp +++ b/include/ccmath/ext/step.hpp @@ -21,7 +21,8 @@ namespace ccm::ext * @param value The value to compare against edge. * @return 0 if value is less than edge, otherwise 1. */ - template && !std::is_same_v, bool> = true> - constexpr T step(T edge, T value) noexcept - { return value < edge ? T(0) : T(1); } + template && !std::is_same_v, bool> = true> constexpr T step(T edge, T value) noexcept + { + return value < edge ? T(0) : T(1); + } } // namespace ccm::ext diff --git a/include/ccmath/ext/unlerp.hpp b/include/ccmath/ext/unlerp.hpp index efeacaee..7e59b85d 100644 --- a/include/ccmath/ext/unlerp.hpp +++ b/include/ccmath/ext/unlerp.hpp @@ -31,9 +31,10 @@ namespace ccm::ext * @param value The value to evaluate. * @return The interpolation parameter of value in the range [start, end]. */ - template , bool> = true> - constexpr T unlerp(T start, T end, T value) noexcept - { return (value - start) / (end - start); } + template , bool> = true> constexpr T unlerp(T start, T end, T value) noexcept + { + return (value - start) / (end - start); + } } // namespace unsafe /** @@ -52,12 +53,14 @@ namespace ccm::ext * @param value The value to evaluate. * @return The interpolation parameter of value in the range [start, end]. */ - template , bool> = true> - constexpr T unlerp(T start, T end, T value) noexcept + template , bool> = true> constexpr T unlerp(T start, T end, T value) noexcept { const T length = end - start; - if (length == T(0)) { return T(0); } + if (length == T(0)) + { + return T(0); + } return ext::unsafe::unlerp(start, end, value); } diff --git a/include/ccmath/internal/config/compiler.hpp b/include/ccmath/internal/config/compiler.hpp index caa3aa84..ce22e35e 100644 --- a/include/ccmath/internal/config/compiler.hpp +++ b/include/ccmath/internal/config/compiler.hpp @@ -126,42 +126,30 @@ namespace ccm::internal::platform eNvidiaHPC }; - template - struct native_compiler : std::false_type - { - }; + template struct native_compiler : std::false_type + {}; - template <> - struct native_compiler : std::false_type - { - }; + template <> struct native_compiler : std::false_type + {}; #ifdef CCMATH_COMPILER_GCC - template <> - struct native_compiler : std::true_type - { - }; + template <> struct native_compiler : std::true_type + {}; #endif #ifdef CCMATH_COMPILER_CLANG - template <> - struct native_compiler : std::true_type - { - }; + template <> struct native_compiler : std::true_type + {}; #endif #ifdef CCMATH_COMPILER_MSVC - template <> - struct native_compiler : std::true_type - { - }; + template <> struct native_compiler : std::true_type + {}; #endif #ifdef CCMATH_COMPILER_CLANG_CL - template <> - struct native_compiler : std::true_type - { - }; + template <> struct native_compiler : std::true_type + {}; #endif } // namespace ccm::internal::platform diff --git a/include/ccmath/internal/config/long_double_format.hpp b/include/ccmath/internal/config/long_double_format.hpp index a2a07816..b044e6ce 100644 --- a/include/ccmath/internal/config/long_double_format.hpp +++ b/include/ccmath/internal/config/long_double_format.hpp @@ -28,10 +28,16 @@ namespace ccm::config constexpr int digits = std::numeric_limits::digits; constexpr int max_exponent = std::numeric_limits::max_exponent; - if constexpr (digits == 53 && max_exponent == 1024) { return LongDoubleFormat::Double; } - else if constexpr (digits == 64 && max_exponent == 16384) { return LongDoubleFormat::X87Extended; } - else if constexpr (digits == 113 && max_exponent == 16384) { return LongDoubleFormat::IEEEBinary128; } - else + if constexpr (digits == 53 && max_exponent == 1024) + { + return LongDoubleFormat::Double; + } else if constexpr (digits == 64 && max_exponent == 16384) + { + return LongDoubleFormat::X87Extended; + } else if constexpr (digits == 113 && max_exponent == 16384) + { + return LongDoubleFormat::IEEEBinary128; + } else { return LongDoubleFormat::Unknown; } @@ -41,10 +47,10 @@ namespace ccm::config { switch (format) { - case LongDoubleFormat::Double: return "double_shaped"; - case LongDoubleFormat::X87Extended: return "x87_float80"; + case LongDoubleFormat::Double : return "double_shaped"; + case LongDoubleFormat::X87Extended : return "x87_float80"; case LongDoubleFormat::IEEEBinary128: return "ieee_binary128"; - case LongDoubleFormat::Unknown: return "unknown"; + case LongDoubleFormat::Unknown : return "unknown"; } return "unknown"; } diff --git a/include/ccmath/internal/math/common/basic/fabs.hpp b/include/ccmath/internal/math/common/basic/fabs.hpp index ec4ae40c..7f34c2a2 100644 --- a/include/ccmath/internal/math/common/basic/fabs.hpp +++ b/include/ccmath/internal/math/common/basic/fabs.hpp @@ -22,8 +22,10 @@ namespace ccm::func template constexpr auto fabs(T num) -> std::enable_if_t && (std::is_floating_point_v || std::is_signed_v || std::is_unsigned_v), T> { - if constexpr (ccm::builtin::has_constexpr_abs) { return ccm::builtin::abs_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_abs) + { + return ccm::builtin::abs_ct(num); + } else { return gen::abs(num); } diff --git a/include/ccmath/internal/math/common/basic/fdim.hpp b/include/ccmath/internal/math/common/basic/fdim.hpp index ca6284ed..e052ea20 100644 --- a/include/ccmath/internal/math/common/basic/fdim.hpp +++ b/include/ccmath/internal/math/common/basic/fdim.hpp @@ -21,8 +21,9 @@ namespace ccm::func * @internal * Internal implementation that switches between compile time and runtime implementations. */ - template - constexpr auto fdim(T x, T y) -> std::enable_if_t, T> - { return gen::fdim(x, y); } + template constexpr auto fdim(T x, T y) -> std::enable_if_t, T> + { + return gen::fdim(x, y); + } } // namespace ccm::func diff --git a/include/ccmath/internal/math/common/basic/fma.hpp b/include/ccmath/internal/math/common/basic/fma.hpp index 1c9fc4e3..091c30b1 100644 --- a/include/ccmath/internal/math/common/basic/fma.hpp +++ b/include/ccmath/internal/math/common/basic/fma.hpp @@ -26,13 +26,15 @@ namespace ccm * @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively, * calculated as a single ternary floating-point operation). */ - template , bool> = true> - constexpr T fma(T x, T y, T z) noexcept // NOLINT(bugprone-exception-escape) - { return support::fp::dispatch_fma(x, y, z); } + template , bool> = true> constexpr T fma(T x, T y, T z) noexcept // NOLINT(bugprone-exception-escape) + { + return support::fp::dispatch_fma(x, y, z); + } - template , bool> = true> - constexpr Integer fma(Integer x, Integer y, Integer z) noexcept - { return (x * y) + z; } + template , bool> = true> constexpr Integer fma(Integer x, Integer y, Integer z) noexcept + { + return (x * y) + z; + } /** * @brief Fused multiply-add operation. @@ -45,8 +47,7 @@ namespace ccm * @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively, * calculated as a single ternary floating-point operation). */ - template - constexpr auto fma(T x, U y, V z) noexcept + template constexpr auto fma(T x, U y, V z) noexcept { constexpr auto TCommon = std::numeric_limits::epsilon() > 0 ? std::numeric_limits::epsilon() : 1; constexpr auto UCommon = std::numeric_limits::epsilon() > 0 ? std::numeric_limits::epsilon() : 1; @@ -91,7 +92,9 @@ namespace ccm * calculated as a single ternary floating-point operation). */ constexpr double fma(double x, double y, double z) noexcept - { return ccm::fma(x, y, z); } + { + return ccm::fma(x, y, z); + } /** * @brief Fused multiply-add operation. @@ -102,7 +105,9 @@ namespace ccm * calculated as a single ternary floating-point operation). */ constexpr float fmaf(float x, float y, float z) noexcept - { return ccm::fma(x, y, z); } + { + return ccm::fma(x, y, z); + } /** * @brief Fused multiply-add operation. @@ -113,7 +118,9 @@ namespace ccm * calculated as a single ternary floating-point operation). */ constexpr long double fmal(long double x, long double y, long double z) noexcept - { return ccm::fma(x, y, z); } + { + return ccm::fma(x, y, z); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/common/basic/fmod.hpp b/include/ccmath/internal/math/common/basic/fmod.hpp index d7aaa88f..e782daf0 100644 --- a/include/ccmath/internal/math/common/basic/fmod.hpp +++ b/include/ccmath/internal/math/common/basic/fmod.hpp @@ -21,8 +21,7 @@ namespace ccm /// @cond CCMATH_INTERNAL namespace internal::impl { - template - constexpr T fmod_impl_check(T x, T y) noexcept + template constexpr T fmod_impl_check(T x, T y) noexcept { // Special edge cases for floating-point types. if constexpr (std::numeric_limits::is_iec559) @@ -50,7 +49,10 @@ namespace ccm } // If y is ±∞ and x is finite, x is returned. - if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) { return x; } + if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) + { + return x; + } // If either argument is NaN, NaN is returned. if (CCM_UNLIKELY(x_bits.is_nan() || y_bits.is_nan())) @@ -63,9 +65,10 @@ namespace ccm return static_cast(x - (ccm::trunc(x / y) * y)); } - template > - constexpr TC fmod_impl_type_check(T x, U y) noexcept - { return fmod_impl_check(static_cast(x), static_cast(y)); } + template > constexpr TC fmod_impl_type_check(T x, U y) noexcept + { + return fmod_impl_check(static_cast(x), static_cast(y)); + } } // namespace internal::impl /// @endcond @@ -77,9 +80,10 @@ namespace ccm * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr Real fmod(Real x, Real y) - { return internal::impl::fmod_impl_check(x, y); } + template , bool> = true> constexpr Real fmod(Real x, Real y) + { + return internal::impl::fmod_impl_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -88,9 +92,10 @@ namespace ccm * @param y An integral value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr double fmod(Integer x, Integer y) - { return internal::impl::fmod_impl_type_check(x, y); } + template , bool> = true> constexpr double fmod(Integer x, Integer y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -100,9 +105,10 @@ namespace ccm * @param y A floating-point or integral value. * @return The floating-point remainder of the division operation x/y. */ - template - constexpr auto fmod(T x, T y) - { return internal::impl::fmod_impl_type_check(x, y); } + template constexpr auto fmod(T x, T y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -111,7 +117,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr float fmodf(float x, float y) - { return fmod(x, y); } + { + return fmod(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -120,7 +128,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr long double fmodl(long double x, long double y) - { return fmod(x, y); } + { + return fmod(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/common/basic/max.hpp b/include/ccmath/internal/math/common/basic/max.hpp index ec9696db..b8de567d 100644 --- a/include/ccmath/internal/math/common/basic/max.hpp +++ b/include/ccmath/internal/math/common/basic/max.hpp @@ -27,11 +27,12 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T max(T x, T y) noexcept + template constexpr T max(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_fmax) { return ccm::builtin::fmax_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_fmax) + { + return ccm::builtin::fmax_ct(x, y); + } else { return ccm::gen::max(x, y); } @@ -45,8 +46,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return */ - template - constexpr auto max(T x, U y) noexcept + template constexpr auto max(T x, U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -62,9 +62,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T fmax(T x, T y) noexcept - { return max(x, y); } + template constexpr T fmax(T x, T y) noexcept + { + return max(x, y); + } /** * @brief Computes the larger of the two values. @@ -74,8 +75,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fmax(T x, U y) noexcept + template constexpr auto fmax(T x, U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -90,9 +90,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr double fmax(Integer x, Integer y) noexcept - { return max(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double fmax(Integer x, Integer y) noexcept + { + return max(static_cast(x), static_cast(y)); + } /** * @brief Computes the larger of the two floating point values. @@ -101,7 +102,9 @@ namespace ccm * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ constexpr float fmaxf(float x, float y) noexcept - { return fmax(x, y); } + { + return fmax(x, y); + } /** * @brief Computes the larger of the two floating point values. @@ -110,7 +113,9 @@ namespace ccm * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ constexpr long double fmaxl(long double x, long double y) noexcept - { return fmax(x, y); } + { + return fmax(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/common/basic/min.hpp b/include/ccmath/internal/math/common/basic/min.hpp index 0d64e832..c3184f2e 100644 --- a/include/ccmath/internal/math/common/basic/min.hpp +++ b/include/ccmath/internal/math/common/basic/min.hpp @@ -26,11 +26,12 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T min(const T x, const T y) noexcept + template constexpr T min(const T x, const T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_fmin) { return ccm::builtin::fmin_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_fmin) + { + return ccm::builtin::fmin_ct(x, y); + } else { return ccm::gen::min(x, y); } @@ -44,8 +45,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T min(const T x, const U y) noexcept + template constexpr T min(const T x, const U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -61,9 +61,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr Real fmin(const Real x, const Real y) noexcept - { return min(x, y); } + template , bool> = true> constexpr Real fmin(const Real x, const Real y) noexcept + { + return min(x, y); + } /** * @brief Computes the smaller of the two values. @@ -73,8 +74,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fmin(const T x, const U y) noexcept + template constexpr auto fmin(const T x, const U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -90,9 +90,10 @@ namespace ccm * @param y Left-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr Integer fmin(const Integer x, const Integer y) noexcept - { return min(x, y); } + template , bool> = true> constexpr Integer fmin(const Integer x, const Integer y) noexcept + { + return min(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/common/basic/nan.hpp b/include/ccmath/internal/math/common/basic/nan.hpp index 2a679872..4ba2ee66 100644 --- a/include/ccmath/internal/math/common/basic/nan.hpp +++ b/include/ccmath/internal/math/common/basic/nan.hpp @@ -22,7 +22,9 @@ namespace ccm * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. */ constexpr double nan(const char * arg) noexcept - { return internal::impl::nan_double_impl(arg); } + { + return internal::impl::nan_double_impl(arg); + } /** * @brief not-a-number (NaN) function @@ -30,7 +32,9 @@ namespace ccm * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. */ constexpr float nanf(const char * arg) noexcept - { return internal::impl::nan_float_impl(arg); } + { + return internal::impl::nan_float_impl(arg); + } /** * @brief not-a-number (NaN) function diff --git a/include/ccmath/internal/math/common/basic/remainder.hpp b/include/ccmath/internal/math/common/basic/remainder.hpp index 554050da..76e0e7a0 100644 --- a/include/ccmath/internal/math/common/basic/remainder.hpp +++ b/include/ccmath/internal/math/common/basic/remainder.hpp @@ -23,8 +23,7 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y. */ - template , bool> = true> - constexpr T remainder(T x, T y) + template , bool> = true> constexpr T remainder(T x, T y) { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t x_bits(x); @@ -52,9 +51,10 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y as a double. */ - template , bool> = true> - constexpr double remainder(Integer x, Integer y) - { return ccm::remainder(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double remainder(Integer x, Integer y) + { + return ccm::remainder(static_cast(x), static_cast(y)); + } /** * @brief Returns the remainder of the division of x by y. @@ -63,7 +63,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr float remainderf(float x, float y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } /** * @brief Returns the remainder of the division of x by y. @@ -72,7 +74,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr long double remainderl(long double x, long double y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/common/basic/remquo.hpp b/include/ccmath/internal/math/common/basic/remquo.hpp index d0eb7c14..90efcfcd 100644 --- a/include/ccmath/internal/math/common/basic/remquo.hpp +++ b/include/ccmath/internal/math/common/basic/remquo.hpp @@ -43,11 +43,12 @@ namespace ccm * } * @endcode */ - template , bool> = true> - constexpr T remquo(T x, T y, int * quo) + template , bool> = true> constexpr T remquo(T x, T y, int * quo) { - if constexpr (std::is_same_v) { return internal::remquo_float(x, y, quo); } - else + if constexpr (std::is_same_v) + { + return internal::remquo_float(x, y, quo); + } else { return internal::remquo_double(x, y, quo); } @@ -117,7 +118,9 @@ namespace ccm * @endcode */ constexpr float remquof(float x, float y, int * quo) - { return ccm::remquo(x, y, quo); } + { + return ccm::remquo(x, y, quo); + } /** * @brief Signed remainder as well as the three last bits of the division operation diff --git a/include/ccmath/internal/math/common/compare/fpclassify.hpp b/include/ccmath/internal/math/common/compare/fpclassify.hpp index ebdf0c5a..f627716c 100644 --- a/include/ccmath/internal/math/common/compare/fpclassify.hpp +++ b/include/ccmath/internal/math/common/compare/fpclassify.hpp @@ -24,13 +24,24 @@ namespace ccm * @param num The number to classify * @return The classification of the number as an integer */ - template , bool> = true> - constexpr int fpclassify(T num) + template , bool> = true> constexpr int fpclassify(T num) { - if (ccm::isnan(num)) { return ccm::support::helpers::floating_point_defines::eFP_NAN; } - if (ccm::isinf(num)) { return ccm::support::helpers::floating_point_defines::eFP_INFINITE; } - if (num == static_cast(0)) { return ccm::support::helpers::floating_point_defines::eFP_ZERO; } - if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) { return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; } + if (ccm::isnan(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_NAN; + } + if (ccm::isinf(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_INFINITE; + } + if (num == static_cast(0)) + { + return ccm::support::helpers::floating_point_defines::eFP_ZERO; + } + if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) + { + return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; + } return ccm::support::helpers::floating_point_defines::eFP_NORMAL; } } // namespace ccm diff --git a/include/ccmath/internal/math/common/compare/isfinite.hpp b/include/ccmath/internal/math/common/compare/isfinite.hpp index 6eb64321..c7c79d17 100644 --- a/include/ccmath/internal/math/common/compare/isfinite.hpp +++ b/include/ccmath/internal/math/common/compare/isfinite.hpp @@ -21,11 +21,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(T num) + template , bool> = true> constexpr bool isfinite(T num) { - if constexpr (ccm::builtin::has_constexpr_isfinite) { return ccm::builtin::isfinite_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isfinite) + { + return ccm::builtin::isfinite_ct(num); + } else { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t num_bits(num); @@ -38,8 +39,7 @@ namespace ccm * @tparam Integer The type of the integer. * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(Integer /* x */) + template , bool> = true> constexpr bool isfinite(Integer /* x */) { return false; // All integers are finite } diff --git a/include/ccmath/internal/math/common/compare/isgreater.hpp b/include/ccmath/internal/math/common/compare/isgreater.hpp index 6daf6328..71eb684d 100644 --- a/include/ccmath/internal/math/common/compare/isgreater.hpp +++ b/include/ccmath/internal/math/common/compare/isgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreater(T x, T y) noexcept + template , bool> = true> constexpr bool isgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreater) { return ccm::builtin::isgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreater) + { + return ccm::builtin::isgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x > y); } diff --git a/include/ccmath/internal/math/common/compare/isgreaterequal.hpp b/include/ccmath/internal/math/common/compare/isgreaterequal.hpp index c2d8a3c2..4fc0942d 100644 --- a/include/ccmath/internal/math/common/compare/isgreaterequal.hpp +++ b/include/ccmath/internal/math/common/compare/isgreaterequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreaterequal(T x, T y) noexcept + template , bool> = true> constexpr bool isgreaterequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreaterequal) { return ccm::builtin::isgreaterequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreaterequal) + { + return ccm::builtin::isgreaterequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x >= y); } diff --git a/include/ccmath/internal/math/common/compare/isinf.hpp b/include/ccmath/internal/math/common/compare/isinf.hpp index c66df7d9..7cc1d9c8 100644 --- a/include/ccmath/internal/math/common/compare/isinf.hpp +++ b/include/ccmath/internal/math/common/compare/isinf.hpp @@ -24,11 +24,12 @@ namespace ccm * @param num The number to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(T num) noexcept + template , bool> = true> constexpr bool isinf(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isinf) { return ccm::builtin::isinf_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isinf) + { + return ccm::builtin::isinf_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -42,8 +43,7 @@ namespace ccm * @tparam Integer The type of the integer to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(Integer /* num */) noexcept + template , bool> = true> constexpr bool isinf(Integer /* num */) noexcept { return false; // Integers cannot be infinite } diff --git a/include/ccmath/internal/math/common/compare/isless.hpp b/include/ccmath/internal/math/common/compare/isless.hpp index 91972a5d..1478a63c 100644 --- a/include/ccmath/internal/math/common/compare/isless.hpp +++ b/include/ccmath/internal/math/common/compare/isless.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second, false otherwise. */ - template , bool> = true> - constexpr bool isless(T x, T y) noexcept + template , bool> = true> constexpr bool isless(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isless) { return ccm::builtin::isless_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isless) + { + return ccm::builtin::isless_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y); } diff --git a/include/ccmath/internal/math/common/compare/islessequal.hpp b/include/ccmath/internal/math/common/compare/islessequal.hpp index fe2df043..ab9438bb 100644 --- a/include/ccmath/internal/math/common/compare/islessequal.hpp +++ b/include/ccmath/internal/math/common/compare/islessequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool islessequal(T x, T y) noexcept + template , bool> = true> constexpr bool islessequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessequal) { return ccm::builtin::islessequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessequal) + { + return ccm::builtin::islessequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x <= y); } diff --git a/include/ccmath/internal/math/common/compare/islessgreater.hpp b/include/ccmath/internal/math/common/compare/islessgreater.hpp index 97b092b3..cf389c6e 100644 --- a/include/ccmath/internal/math/common/compare/islessgreater.hpp +++ b/include/ccmath/internal/math/common/compare/islessgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second or greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool islessgreater(T x, T y) noexcept + template , bool> = true> constexpr bool islessgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessgreater) { return ccm::builtin::islessgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessgreater) + { + return ccm::builtin::islessgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y || x > y); } diff --git a/include/ccmath/internal/math/common/compare/isnan.hpp b/include/ccmath/internal/math/common/compare/isnan.hpp index 7533279a..4bdf7fce 100644 --- a/include/ccmath/internal/math/common/compare/isnan.hpp +++ b/include/ccmath/internal/math/common/compare/isnan.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num The number to check. * @return True if the number is NaN, false otherwise. */ - template , bool> = true> - [[nodiscard]] constexpr bool isnan(T num) noexcept + template , bool> = true> [[nodiscard]] constexpr bool isnan(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnan) { return ccm::builtin::isnan_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnan) + { + return ccm::builtin::isnan_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -41,8 +42,7 @@ namespace ccm * @tparam Integer The type of the number to check. * @return False, as integers can never be NaN. */ - template , bool> = true> - [[nodiscard]] constexpr bool isnan(Integer /* num */) + template , bool> = true> [[nodiscard]] constexpr bool isnan(Integer /* num */) { return false; // Integers can never be NaN. } diff --git a/include/ccmath/internal/math/common/compare/isnormal.hpp b/include/ccmath/internal/math/common/compare/isnormal.hpp index 07df0e64..ded9d3bf 100644 --- a/include/ccmath/internal/math/common/compare/isnormal.hpp +++ b/include/ccmath/internal/math/common/compare/isnormal.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(T num) noexcept + template , bool> = true> constexpr bool isnormal(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnormal) { return ccm::builtin::isnormal_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnormal) + { + return ccm::builtin::isnormal_ct(num); + } else { return num != static_cast(0) && !ccm::isnan(num) && !ccm::isinf(num) && ccm::abs(num) >= std::numeric_limits::min(); } @@ -39,8 +40,9 @@ namespace ccm * @param num A integer value to check. * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(Integer num) noexcept - { return num != static_cast(0); } + template , bool> = true> constexpr bool isnormal(Integer num) noexcept + { + return num != static_cast(0); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/compare/isunordered.hpp b/include/ccmath/internal/math/common/compare/isunordered.hpp index fd5ac05b..463d30b1 100644 --- a/include/ccmath/internal/math/common/compare/isunordered.hpp +++ b/include/ccmath/internal/math/common/compare/isunordered.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value * @return true if either x or y is NaN, false otherwise. */ - template , bool> = true> - constexpr bool isunordered(T x, T y) noexcept + template , bool> = true> constexpr bool isunordered(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isunordered) { return ccm::builtin::isunordered_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isunordered) + { + return ccm::builtin::isunordered_ct(x, y); + } else { return ccm::isnan(x) || ccm::isnan(y); } @@ -53,8 +54,7 @@ namespace ccm * @param y Value of the right-hand side of the comparison. * @return true if either x or y is NaN, false otherwise. */ - template - constexpr bool isunordered(T x, U y) noexcept + template constexpr bool isunordered(T x, U y) noexcept { using shared_type = std::common_type_t; return static_cast(isunordered(static_cast(x), static_cast(y))); diff --git a/include/ccmath/internal/math/common/compare/signbit.hpp b/include/ccmath/internal/math/common/compare/signbit.hpp index 9d77c5f7..f844fb5a 100644 --- a/include/ccmath/internal/math/common/compare/signbit.hpp +++ b/include/ccmath/internal/math/common/compare/signbit.hpp @@ -30,11 +30,12 @@ namespace ccm * @warning ccm::signbit may fail * to compile if you are not using the cmake build for ccmath or do not have access to __builtin_bit_cast. */ - template , bool> = true> - [[nodiscard]] constexpr bool signbit(T num) noexcept + template , bool> = true> [[nodiscard]] constexpr bool signbit(T num) noexcept { - if constexpr (builtin::has_constexpr_signbit) { return builtin::signbit_ct(num); } - else + if constexpr (builtin::has_constexpr_signbit) + { + return builtin::signbit_ct(num); + } else { // Check for the sign of +0.0 and -0.0 with bit_cast if (num == static_cast(0) || ccm::isnan(num)) diff --git a/include/ccmath/internal/math/common/expo/exp.hpp b/include/ccmath/internal/math/common/expo/exp.hpp index ce36bf1e..1a3bd32a 100644 --- a/include/ccmath/internal/math/common/expo/exp.hpp +++ b/include/ccmath/internal/math/common/expo/exp.hpp @@ -26,11 +26,12 @@ namespace ccm * @param num floating-point or integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned. */ - template , bool> = true> - constexpr T exp(T num) + template , bool> = true> constexpr T exp(T num) { - if constexpr (ccm::builtin::has_constexpr_exp) { return ccm::builtin::exp_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_exp) + { + return ccm::builtin::exp_ct(num); + } else { return gen::exp_gen(num); } @@ -42,9 +43,10 @@ namespace ccm * @param num integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. */ - template , bool> = true> - constexpr double exp(Integer num) - { return ccm::exp(static_cast(num)); } + template , bool> = true> constexpr double exp(Integer num) + { + return ccm::exp(static_cast(num)); + } /** * @brief Computes e raised to the given power @@ -52,7 +54,9 @@ namespace ccm * @return If no errors occur, the base-e exponential of num (e^num) is returned as float. */ constexpr float expf(float num) - { return ccm::exp(num); } + { + return ccm::exp(num); + } /** * @brief Computes e raised to the given power @@ -60,7 +64,9 @@ namespace ccm * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. */ constexpr long double expl(long double num) - { return ccm::exp(num); } + { + return ccm::exp(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/expo/exp2.hpp b/include/ccmath/internal/math/common/expo/exp2.hpp index c7ff655b..75c8e5cd 100644 --- a/include/ccmath/internal/math/common/expo/exp2.hpp +++ b/include/ccmath/internal/math/common/expo/exp2.hpp @@ -29,11 +29,12 @@ namespace ccm * @param num Floating-point or integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned. */ - template , bool> = true> - constexpr T exp2(T num) + template , bool> = true> constexpr T exp2(T num) { - if constexpr (ccm::builtin::has_constexpr_exp2) { return ccm::builtin::exp2_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_exp2) + { + return ccm::builtin::exp2_ct(num); + } else { return gen::exp2_gen(num); } @@ -45,9 +46,10 @@ namespace ccm * @param num Integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. */ - template , bool> = true> - constexpr double exp2(Integer num) - { return ccm::exp2(static_cast(num)); } + template , bool> = true> constexpr double exp2(Integer num) + { + return ccm::exp2(static_cast(num)); + } /** * @brief Returns 2 raised to the given power (2^x) @@ -55,7 +57,9 @@ namespace ccm * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a float. */ constexpr float exp2f(float num) - { return ccm::exp2(num); } + { + return ccm::exp2(num); + } /** * @brief Returns 2 raised to the given power (2^x) @@ -63,7 +67,9 @@ namespace ccm * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. */ constexpr long double exp2l(long double num) - { return ccm::exp2(num); } + { + return ccm::exp2(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/math/common/expo/expm1.hpp b/include/ccmath/internal/math/common/expo/expm1.hpp index 71f9768b..34a427fa 100644 --- a/include/ccmath/internal/math/common/expo/expm1.hpp +++ b/include/ccmath/internal/math/common/expo/expm1.hpp @@ -17,23 +17,29 @@ namespace ccm { - template , bool> = true> - constexpr T expm1(T num) + template , bool> = true> constexpr T expm1(T num) { - if constexpr (ccm::builtin::has_constexpr_expm1) { return ccm::builtin::expm1_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_expm1) + { + return ccm::builtin::expm1_ct(num); + } else { return gen::expm1_gen(num); } } - template , bool> = true> - constexpr double expm1(Integer num) - { return ccm::expm1(static_cast(num)); } + template , bool> = true> constexpr double expm1(Integer num) + { + return ccm::expm1(static_cast(num)); + } constexpr float expm1f(float num) - { return ccm::expm1(num); } + { + return ccm::expm1(num); + } constexpr long double expm1l(long double num) - { return ccm::expm1(num); } + { + return ccm::expm1(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/expo/log.hpp b/include/ccmath/internal/math/common/expo/log.hpp index c08366a3..83103d43 100644 --- a/include/ccmath/internal/math/common/expo/log.hpp +++ b/include/ccmath/internal/math/common/expo/log.hpp @@ -27,14 +27,18 @@ namespace ccm * @param num A floating-point or integer value to find the natural logarithm of. * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ - template , bool> = true> - constexpr T log(const T num) noexcept + template , bool> = true> constexpr T log(const T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_log) { return ccm::builtin::log_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_log) + { + return ccm::builtin::log_ct(num); + } else { // If the number is 1, return +0. - if (num == static_cast(1)) { return static_cast(0); } + if (num == static_cast(1)) + { + return static_cast(0); + } // If the argument is ±0, -∞ is returned. if (num == static_cast(0)) @@ -53,10 +57,16 @@ namespace ccm } // If the argument is +∞, +∞ is returned. - if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) { return std::numeric_limits::infinity(); } + if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) + { + return std::numeric_limits::infinity(); + } // If the argument is NaN, NaN is returned. - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } return gen::log_gen(num); } @@ -68,9 +78,10 @@ namespace ccm * @param num An integer value to find the natural logarithm of. * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ - template , bool> = true> - constexpr double log(const Integer num) noexcept - { return ccm::log(static_cast(num)); } + template , bool> = true> constexpr double log(const Integer num) noexcept + { + return ccm::log(static_cast(num)); + } /** * @brief Computes the natural (base e) logarithm (lnx) of a number. @@ -78,7 +89,9 @@ namespace ccm * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ constexpr float logf(const float num) noexcept - { return ccm::log(num); } + { + return ccm::log(num); + } /** * @brief Computes the natural (base e) logarithm (lnx) of a number. @@ -86,7 +99,9 @@ namespace ccm * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ constexpr long double logl(long double num) noexcept - { return ccm::log(num); } + { + return ccm::log(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/math/common/expo/log10.hpp b/include/ccmath/internal/math/common/expo/log10.hpp index 9cc10574..839ce8a5 100644 --- a/include/ccmath/internal/math/common/expo/log10.hpp +++ b/include/ccmath/internal/math/common/expo/log10.hpp @@ -21,13 +21,17 @@ namespace ccm { - template , bool> = true> - constexpr T log10(T num) + template , bool> = true> constexpr T log10(T num) { - if constexpr (ccm::builtin::has_constexpr_log10) { return ccm::builtin::log10_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_log10) { - if (num == static_cast(1)) { return static_cast(0); } + return ccm::builtin::log10_ct(num); + } else + { + if (num == static_cast(1)) + { + return static_cast(0); + } if (num == static_cast(0)) { ccm::support::fenv::set_errno_if_required(ERANGE); @@ -40,20 +44,31 @@ namespace ccm ccm::support::fenv::raise_except_if_required(FE_INVALID); return -std::numeric_limits::quiet_NaN(); } - if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) { return std::numeric_limits::infinity(); } - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) + { + return std::numeric_limits::infinity(); + } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } return gen::log10_gen(num); } } - template , bool> = true> - constexpr double log10(Integer num) - { return ccm::log10(static_cast(num)); } + template , bool> = true> constexpr double log10(Integer num) + { + return ccm::log10(static_cast(num)); + } constexpr float log10f(float num) - { return ccm::log10(num); } + { + return ccm::log10(num); + } constexpr long double log10l(long double num) - { return ccm::log10(num); } + { + return ccm::log10(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/expo/log1p.hpp b/include/ccmath/internal/math/common/expo/log1p.hpp index 9a732082..b25a73b6 100644 --- a/include/ccmath/internal/math/common/expo/log1p.hpp +++ b/include/ccmath/internal/math/common/expo/log1p.hpp @@ -17,23 +17,29 @@ namespace ccm { - template , bool> = true> - constexpr T log1p(T num) + template , bool> = true> constexpr T log1p(T num) { - if constexpr (ccm::builtin::has_constexpr_log1p) { return ccm::builtin::log1p_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_log1p) + { + return ccm::builtin::log1p_ct(num); + } else { return gen::log1p_gen(num); } } - template , bool> = true> - constexpr double log1p(Integer num) - { return ccm::log1p(static_cast(num)); } + template , bool> = true> constexpr double log1p(Integer num) + { + return ccm::log1p(static_cast(num)); + } constexpr float log1pf(float num) - { return ccm::log1p(num); } + { + return ccm::log1p(num); + } constexpr long double log1pl(long double num) - { return ccm::log1p(num); } + { + return ccm::log1p(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/expo/log2.hpp b/include/ccmath/internal/math/common/expo/log2.hpp index 5807eb35..b1b833de 100644 --- a/include/ccmath/internal/math/common/expo/log2.hpp +++ b/include/ccmath/internal/math/common/expo/log2.hpp @@ -33,11 +33,12 @@ namespace ccm * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number. */ - template , bool> = true> - constexpr T log2(T num) noexcept + template , bool> = true> constexpr T log2(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_log2) { return ccm::builtin::log2_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_log2) + { + return ccm::builtin::log2_ct(num); + } else { // If the argument is ±0, -∞ is returned if (num == static_cast(0)) @@ -48,10 +49,16 @@ namespace ccm } // If the argument is 1, +0 is returned. - if (num == static_cast(1)) { return 0; } + if (num == static_cast(1)) + { + return 0; + } // If the argument is NaN, NaN is returned. - if (ccm::isnan(num) || num == std::numeric_limits::infinity()) { return num; } + if (ccm::isnan(num) || num == std::numeric_limits::infinity()) + { + return num; + } // If the argument is negative, -NaN is returned #ifdef CCMATH_COMPILER_APPLE_CLANG // Apple clang returns +qNaN @@ -80,9 +87,10 @@ namespace ccm * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number as a double. */ - template , bool> = true> - constexpr double log2(Integer num) noexcept - { return ccm::log2(static_cast(num)); } + template , bool> = true> constexpr double log2(Integer num) noexcept + { + return ccm::log2(static_cast(num)); + } /** * @brief Returns the base 2 logarithm of a number. @@ -90,7 +98,9 @@ namespace ccm * @return The base 2 logarithm of the number as a float. */ constexpr float log2f(float num) - { return ccm::log2(num); } + { + return ccm::log2(num); + } /** * @brief Returns the base 2 logarithm of a number. @@ -98,7 +108,9 @@ namespace ccm * @return The base 2 logarithm of the number as a double. */ constexpr long double log2l(long double num) - { return ccm::log2(num); } + { + return ccm::log2(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/math/common/fmanip/copysign.hpp b/include/ccmath/internal/math/common/fmanip/copysign.hpp index 2eda3775..0e366aac 100644 --- a/include/ccmath/internal/math/common/fmanip/copysign.hpp +++ b/include/ccmath/internal/math/common/fmanip/copysign.hpp @@ -24,15 +24,19 @@ namespace ccm * @param sgn A floating-point or integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ - template , bool> = true> - constexpr T copysign(T mag, T sgn) + template , bool> = true> constexpr T copysign(T mag, T sgn) { - if constexpr (ccm::builtin::has_constexpr_copysign) { return ccm::builtin::copysign_ct(mag, sgn); } - else + if constexpr (ccm::builtin::has_constexpr_copysign) + { + return ccm::builtin::copysign_ct(mag, sgn); + } else { if (ccm::isnan(mag) || ccm::isnan(sgn)) { - if (ccm::signbit(sgn)) { return -std::numeric_limits::quiet_NaN(); } + if (ccm::signbit(sgn)) + { + return -std::numeric_limits::quiet_NaN(); + } return std::numeric_limits::quiet_NaN(); } @@ -48,9 +52,10 @@ namespace ccm * @param sgn A integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ - template , bool> = true> - constexpr double copysign(Integer mag, Integer sgn) - { return ccm::copysign(static_cast(mag), static_cast(sgn)); } + template , bool> = true> constexpr double copysign(Integer mag, Integer sgn) + { + return ccm::copysign(static_cast(mag), static_cast(sgn)); + } /** * @brief Copies the sign of a floating point value. @@ -59,7 +64,9 @@ namespace ccm * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ constexpr float copysignf(float mag, float sgn) - { return ccm::copysign(mag, sgn); } + { + return ccm::copysign(mag, sgn); + } /** * @brief Copies the sign of a floating point value. @@ -68,5 +75,7 @@ namespace ccm * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ constexpr long double copysignl(long double mag, long double sgn) - { return ccm::copysign(mag, sgn); } + { + return ccm::copysign(mag, sgn); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/frexp.hpp b/include/ccmath/internal/math/common/fmanip/frexp.hpp index 1a684f4c..d4fac480 100644 --- a/include/ccmath/internal/math/common/fmanip/frexp.hpp +++ b/include/ccmath/internal/math/common/fmanip/frexp.hpp @@ -17,19 +17,24 @@ namespace ccm { - template , bool> = true> - constexpr T frexp(T x, int & exp) + template , bool> = true> constexpr T frexp(T x, int & exp) { - if constexpr (ccm::builtin::has_constexpr_frexp) { return ccm::builtin::frexp_ct(x, &exp); } - else + if constexpr (ccm::builtin::has_constexpr_frexp) + { + return ccm::builtin::frexp_ct(x, &exp); + } else { return gen::frexp_gen(x, exp); } } constexpr float frexpf(float x, int & exp) - { return ccm::frexp(x, exp); } + { + return ccm::frexp(x, exp); + } constexpr long double frexpl(long double x, int & exp) - { return ccm::frexp(x, exp); } + { + return ccm::frexp(x, exp); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/ilogb.hpp b/include/ccmath/internal/math/common/fmanip/ilogb.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/fmanip/ilogb.hpp +++ b/include/ccmath/internal/math/common/fmanip/ilogb.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/ldexp.hpp b/include/ccmath/internal/math/common/fmanip/ldexp.hpp index cb05ba81..89212bde 100644 --- a/include/ccmath/internal/math/common/fmanip/ldexp.hpp +++ b/include/ccmath/internal/math/common/fmanip/ldexp.hpp @@ -57,11 +57,12 @@ namespace ccm * @return \f$\text{num} \times 2^\text{exp}\f$. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ - template , bool> = true> - constexpr T ldexp(T num, int exp) noexcept + template , bool> = true> constexpr T ldexp(T num, int exp) noexcept { - if constexpr (ccm::builtin::has_constexpr_ldexp) { return builtin::ldexp_ct(num, exp); } - else + if constexpr (ccm::builtin::has_constexpr_ldexp) + { + return builtin::ldexp_ct(num, exp); + } else { return support::helpers::internal_ldexp(num, exp); } @@ -79,9 +80,10 @@ namespace ccm * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ - template , bool> = true> - constexpr double ldexp(Integer num, int exp) noexcept - { return ccm::ldexp(static_cast(num), exp); } + template , bool> = true> constexpr double ldexp(Integer num, int exp) noexcept + { + return ccm::ldexp(static_cast(num), exp); + } /** * @brief Scales a \c float \p num by \f$2^\text{exp}\f$. Equivalent to \c ldexp(num, exp). @@ -94,7 +96,9 @@ namespace ccm * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ constexpr float ldexpf(float num, int exp) noexcept - { return ccm::ldexp(num, exp); } + { + return ccm::ldexp(num, exp); + } /** * @brief Scales a \c long double \p num by \f$2^\text{exp}\f$ using native long double support. @@ -110,6 +114,8 @@ namespace ccm * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ constexpr long double ldexpl(long double num, int exp) noexcept - { return ccm::ldexp(num, exp); } + { + return ccm::ldexp(num, exp); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/logb.hpp b/include/ccmath/internal/math/common/fmanip/logb.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/fmanip/logb.hpp +++ b/include/ccmath/internal/math/common/fmanip/logb.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/modf.hpp b/include/ccmath/internal/math/common/fmanip/modf.hpp index 1892b922..04e9621c 100644 --- a/include/ccmath/internal/math/common/fmanip/modf.hpp +++ b/include/ccmath/internal/math/common/fmanip/modf.hpp @@ -17,13 +17,19 @@ namespace ccm { - template , bool> = true> - constexpr T modf(T x, T * iptr) noexcept + template , bool> = true> constexpr T modf(T x, T * iptr) noexcept { - if constexpr (ccm::builtin::has_constexpr_modf) { return ccm::builtin::modf_ct(x, iptr); } - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } - else + if constexpr (ccm::builtin::has_constexpr_modf) + { + return ccm::builtin::modf_ct(x, iptr); + } else if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } else { double integer_part{}; const double fractional = internal::impl::modf_impl(static_cast(x), &integer_part); @@ -33,8 +39,12 @@ namespace ccm } constexpr float modff(float x, float * iptr) noexcept - { return ccm::modf(x, iptr); } + { + return ccm::modf(x, iptr); + } constexpr long double modfl(long double x, long double * iptr) noexcept - { return ccm::modf(x, iptr); } + { + return ccm::modf(x, iptr); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/nextafter.hpp b/include/ccmath/internal/math/common/fmanip/nextafter.hpp index 8de235e3..734f1e79 100644 --- a/include/ccmath/internal/math/common/fmanip/nextafter.hpp +++ b/include/ccmath/internal/math/common/fmanip/nextafter.hpp @@ -17,12 +17,13 @@ namespace ccm { - template , bool> = true> - constexpr T nextafter(T from, T to) noexcept + template , bool> = true> constexpr T nextafter(T from, T to) noexcept { // TODO: Better define how this interacts with the builtin. - if constexpr (ccm::builtin::has_constexpr_nextafter) { return ccm::builtin::nextafter_ct(from, to); } - else + if constexpr (ccm::builtin::has_constexpr_nextafter) + { + return ccm::builtin::nextafter_ct(from, to); + } else { return gen::nextafter_gen(from, to); } @@ -30,11 +31,17 @@ namespace ccm template && std::is_arithmetic_v, bool> = true> constexpr auto nextafter(Arithmetic1 from, Arithmetic2 to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } constexpr float nextafterf(float from, float to) noexcept - { return ccm::nextafter(from, to); } + { + return ccm::nextafter(from, to); + } constexpr long double nextafterl(long double from, long double to) noexcept - { return ccm::nextafter(from, to); } + { + return ccm::nextafter(from, to); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/nexttoward.hpp b/include/ccmath/internal/math/common/fmanip/nexttoward.hpp index 00aeae8a..9ee193c3 100644 --- a/include/ccmath/internal/math/common/fmanip/nexttoward.hpp +++ b/include/ccmath/internal/math/common/fmanip/nexttoward.hpp @@ -17,24 +17,30 @@ namespace ccm { - template , bool> = true> - constexpr T nexttoward(T from, long double to) noexcept + template , bool> = true> constexpr T nexttoward(T from, long double to) noexcept { - if constexpr (ccm::builtin::has_constexpr_nexttoward) { return ccm::builtin::nexttoward_ct(from, to); } - else + if constexpr (ccm::builtin::has_constexpr_nexttoward) + { + return ccm::builtin::nexttoward_ct(from, to); + } else { return gen::nextafter_gen(from, to); } } - template , bool> = true> - constexpr double nexttoward(Integer from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + template , bool> = true> constexpr double nexttoward(Integer from, long double to) noexcept + { + return gen::nextafter_gen(from, to); + } constexpr float nexttowardf(float from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } constexpr long double nexttowardl(long double from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/fmanip/scalbn.hpp b/include/ccmath/internal/math/common/fmanip/scalbn.hpp index 3757c2ab..516d4cd8 100644 --- a/include/ccmath/internal/math/common/fmanip/scalbn.hpp +++ b/include/ccmath/internal/math/common/fmanip/scalbn.hpp @@ -29,15 +29,25 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, int exp) noexcept + template , bool> = true> constexpr T scalbn(T num, int exp) noexcept { - if constexpr (ccm::builtin::has_constexpr_scalbn) { return ccm::builtin::scalbn_ct(num, exp); } - else + if constexpr (ccm::builtin::has_constexpr_scalbn) { - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + return ccm::builtin::scalbn_ct(num, exp); + } else + { + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); } } @@ -49,15 +59,25 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, long exp) noexcept + template , bool> = true> constexpr T scalbn(T num, long exp) noexcept { - if constexpr (ccm::builtin::has_constexpr_scalbn) { return ccm::builtin::scalbn_ct(num, exp); } - else + if constexpr (ccm::builtin::has_constexpr_scalbn) + { + return ccm::builtin::scalbn_ct(num, exp); + } else { - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); } } @@ -69,9 +89,10 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, int exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, int exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -80,9 +101,10 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, long exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, long exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -91,7 +113,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -100,7 +124,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -109,7 +135,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -118,7 +146,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/math/common/hyper/acosh.hpp b/include/ccmath/internal/math/common/hyper/acosh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/acosh.hpp +++ b/include/ccmath/internal/math/common/hyper/acosh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/hyper/asinh.hpp b/include/ccmath/internal/math/common/hyper/asinh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/asinh.hpp +++ b/include/ccmath/internal/math/common/hyper/asinh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/hyper/atanh.hpp b/include/ccmath/internal/math/common/hyper/atanh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/atanh.hpp +++ b/include/ccmath/internal/math/common/hyper/atanh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/hyper/cosh.hpp b/include/ccmath/internal/math/common/hyper/cosh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/cosh.hpp +++ b/include/ccmath/internal/math/common/hyper/cosh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/hyper/sinh.hpp b/include/ccmath/internal/math/common/hyper/sinh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/sinh.hpp +++ b/include/ccmath/internal/math/common/hyper/sinh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/hyper/tanh.hpp b/include/ccmath/internal/math/common/hyper/tanh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/hyper/tanh.hpp +++ b/include/ccmath/internal/math/common/hyper/tanh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/misc/gamma.hpp b/include/ccmath/internal/math/common/misc/gamma.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/misc/gamma.hpp +++ b/include/ccmath/internal/math/common/misc/gamma.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/misc/lerp.hpp b/include/ccmath/internal/math/common/misc/lerp.hpp index 27d50315..478e6de0 100644 --- a/include/ccmath/internal/math/common/misc/lerp.hpp +++ b/include/ccmath/internal/math/common/misc/lerp.hpp @@ -14,8 +14,7 @@ namespace ccm { - template - constexpr T lerp(T a, T b, T t) noexcept + template constexpr T lerp(T a, T b, T t) noexcept { // Optimized version of lerp // https://developer.nvidia.com/blog/lerp-faster-cuda/ @@ -36,12 +35,21 @@ namespace ccm "Do not use ccm::lerp_old it is only being kept as a fallback until ccm::lerp has been validated as conforming to std::lerp")]] constexpr T old_lerp(T a, T b, T t) noexcept { - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return (t * b) + ((1 - t) * a); } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) + { + return (t * b) + ((1 - t) * a); + } - if (t == 1) { return b; } + if (t == 1) + { + return b; + } const T x = a + (t * (b - a)); - if ((t > 1) == (b > a)) { return b < x ? x : b; } + if ((t > 1) == (b > a)) + { + return b < x ? x : b; + } return x < b ? x : b; } diff --git a/include/ccmath/internal/math/common/misc/lgamma.hpp b/include/ccmath/internal/math/common/misc/lgamma.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/misc/lgamma.hpp +++ b/include/ccmath/internal/math/common/misc/lgamma.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/nearest/ceil.hpp b/include/ccmath/internal/math/common/nearest/ceil.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/nearest/ceil.hpp +++ b/include/ccmath/internal/math/common/nearest/ceil.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/nearest/floor.hpp b/include/ccmath/internal/math/common/nearest/floor.hpp index 9b5dcd31..fa5b0f78 100644 --- a/include/ccmath/internal/math/common/nearest/floor.hpp +++ b/include/ccmath/internal/math/common/nearest/floor.hpp @@ -21,37 +21,56 @@ namespace ccm { namespace internal::impl { - template - constexpr T floor_pos_impl(T num) noexcept + template constexpr T floor_pos_impl(T num) noexcept { // At or above 1/epsilon, num is already integral (or indistinguishable from one) in T. constexpr auto max_comparable_val = T(1) / std::numeric_limits::epsilon(); - if (num >= max_comparable_val) { return num; } + if (num >= max_comparable_val) + { + return num; + } T result = 1; - if (result == num) { return num; } + if (result == num) + { + return num; + } // Bracket num by doubling from 1, then step down to the floor. - while (result < num) { result *= 2; } + while (result < num) + { + result *= 2; + } - while (result > num) { --result; } + while (result > num) + { + --result; + } return result; } - template - constexpr T floor_neg_impl(T num) noexcept + template constexpr T floor_neg_impl(T num) noexcept { T result = -1; if (result > num) { // Bracket num by doubling from -1, then adjust to the largest integer not greater than num. - while (result > num) { result *= 2; } - while (result < num) { ++result; } - if (result != num) { --result; } + while (result > num) + { + result *= 2; + } + while (result < num) + { + ++result; + } + if (result != num) + { + --result; + } } return result; @@ -64,17 +83,24 @@ namespace ccm * @param num A floating-point or integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr T floor(T num) noexcept + template , bool> = true> constexpr T floor(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_floor) { return ccm::builtin::floor_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_floor) + { + return ccm::builtin::floor_ct(num); + } else { // If num is NaN, NaN is returned. // If num is ±∞ or ±0, num is returned, unmodified. - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } - if (num > 0) { return internal::impl::floor_pos_impl(num); } + if (num > 0) + { + return internal::impl::floor_pos_impl(num); + } return internal::impl::floor_neg_impl(num); } } @@ -84,8 +110,7 @@ namespace ccm * @param num A integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr double floor(Integer num) noexcept + template , bool> = true> constexpr double floor(Integer num) noexcept { return static_cast(num); // All integers already have a floor value. Just cast to double and return. } @@ -96,7 +121,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr float floorf(float num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } /** * @brief Computes the largest integer value not greater than num. @@ -104,7 +131,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr double floorl(double num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/internal/math/common/nearest/nearbyint.hpp b/include/ccmath/internal/math/common/nearest/nearbyint.hpp index 9d31518e..12897494 100644 --- a/include/ccmath/internal/math/common/nearest/nearbyint.hpp +++ b/include/ccmath/internal/math/common/nearest/nearbyint.hpp @@ -28,10 +28,12 @@ namespace ccm * @param num A floating-point value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input floating point value unmodified. */ - template , bool> = true> - constexpr T nearbyint(T num) noexcept + template , bool> = true> constexpr T nearbyint(T num) noexcept { - if (!ccm::support::is_constant_evaluated()) { return ccm::support::fp::directional_round(num, ccm::support::fenv::get_rounding_mode()); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::support::fp::directional_round(num, ccm::support::fenv::get_rounding_mode()); + } constexpr auto rounding_mode{ ccm::support::fenv::get_rounding_mode() }; return ccm::support::fp::directional_round(num, rounding_mode); @@ -43,9 +45,10 @@ namespace ccm * @param num An integral value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ - template , bool> = true> - constexpr double nearbyint(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double nearbyint(Integer num) noexcept + { + return static_cast(num); + } /** * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. @@ -53,7 +56,9 @@ namespace ccm * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ constexpr float nearbyintf(float num) noexcept - { return ccm::nearbyint(num); } + { + return ccm::nearbyint(num); + } /** * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. @@ -61,6 +66,8 @@ namespace ccm * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ constexpr long double nearbyintl(long double num) noexcept - { return ccm::nearbyint(num); } + { + return ccm::nearbyint(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/nearest/rint.hpp b/include/ccmath/internal/math/common/nearest/rint.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/nearest/rint.hpp +++ b/include/ccmath/internal/math/common/nearest/rint.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/nearest/round.hpp b/include/ccmath/internal/math/common/nearest/round.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/nearest/round.hpp +++ b/include/ccmath/internal/math/common/nearest/round.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/nearest/trunc.hpp b/include/ccmath/internal/math/common/nearest/trunc.hpp index 0b7a1290..62146394 100644 --- a/include/ccmath/internal/math/common/nearest/trunc.hpp +++ b/include/ccmath/internal/math/common/nearest/trunc.hpp @@ -22,11 +22,12 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr T trunc(T num) noexcept + template , bool> = true> constexpr T trunc(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_trunc) { return ccm::builtin::trunc_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_trunc) + { + return ccm::builtin::trunc_ct(num); + } else { using FPBits_t = ccm::support::fp::FPBits; using Storage_t = typename FPBits_t::storage_type; @@ -35,18 +36,30 @@ namespace ccm // If x == ±∞ then return num // If x == ±NaN then return num - if (CCM_UNLIKELY(bits.is_inf_or_nan())) { return num; } + if (CCM_UNLIKELY(bits.is_inf_or_nan())) + { + return num; + } // If x == ±0 then return num - if (CCM_UNLIKELY(num == 0.0)) { return num; } + if (CCM_UNLIKELY(num == 0.0)) + { + return num; + } const int exponent = bits.get_exponent(); // If the exponent is greater than or equal to the fraction length, then we will return the number as is since it is already an integer. - if (exponent >= FPBits_t::fraction_length) { return num; } + if (exponent >= FPBits_t::fraction_length) + { + return num; + } // If our exponent is set up such that the abs(x) is less than 1 we will instead return 0. - if (exponent <= -1) { return FPBits_t::zero(bits.sign()).get_val(); } + if (exponent <= -1) + { + return FPBits_t::zero(bits.sign()).get_val(); + } // Perform the truncation const int trimming_size = FPBits_t::fraction_length - exponent; @@ -62,9 +75,10 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr double trunc(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double trunc(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Specialization for float that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to x. @@ -72,7 +86,9 @@ namespace ccm * @return Returns a truncated float. */ constexpr float truncf(float num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } /** * @brief Specialization for long double that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to @@ -81,5 +97,7 @@ namespace ccm * @return Returns a truncated long double. */ constexpr long double truncl(long double num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/common/power/cbrt.hpp b/include/ccmath/internal/math/common/power/cbrt.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/power/cbrt.hpp +++ b/include/ccmath/internal/math/common/power/cbrt.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/power/hypot.hpp b/include/ccmath/internal/math/common/power/hypot.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/power/hypot.hpp +++ b/include/ccmath/internal/math/common/power/hypot.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/power/pow.hpp b/include/ccmath/internal/math/common/power/pow.hpp index 0810e701..77b39e25 100644 --- a/include/ccmath/internal/math/common/power/pow.hpp +++ b/include/ccmath/internal/math/common/power/pow.hpp @@ -19,21 +19,24 @@ namespace ccm { - template , bool> = true> - constexpr T pow(T base, T exp) + template , bool> = true> constexpr T pow(T base, T exp) { - if constexpr (ccm::builtin::has_constexpr_pow) { return ccm::builtin::pow_ct(base, exp); } - else + if constexpr (ccm::builtin::has_constexpr_pow) + { + return ccm::builtin::pow_ct(base, exp); + } else { // TODO: Add in usage of builtins that meet ccmath standards. - if (support::is_constant_evaluated()) { return gen::pow_gen(base, exp); } + if (support::is_constant_evaluated()) + { + return gen::pow_gen(base, exp); + } return rt::pow_rt(base, exp); } } - template , bool> = true> - constexpr double pow(Integer base, Integer exp) + template , bool> = true> constexpr double pow(Integer base, Integer exp) { // TODO: Add integer specific optimization with exponentiation of a square return ccm::pow(static_cast(base), static_cast(exp)); diff --git a/include/ccmath/internal/math/common/power/sqrt.hpp b/include/ccmath/internal/math/common/power/sqrt.hpp index e78b81bd..afb31c20 100644 --- a/include/ccmath/internal/math/common/power/sqrt.hpp +++ b/include/ccmath/internal/math/common/power/sqrt.hpp @@ -26,13 +26,17 @@ namespace ccm * @param num Floating-point or integer number. * @return If no errors occur, square root of num (√num), is returned. */ - template , bool> = true> - constexpr T sqrt(T num) + template , bool> = true> constexpr T sqrt(T num) { - if constexpr (ccm::builtin::has_constexpr_sqrt) { return ccm::builtin::sqrt_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_sqrt) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::sqrt_gen(num); } + return ccm::builtin::sqrt_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::sqrt_gen(num); + } return ccm::rt::sqrt_rt(num); } } @@ -43,9 +47,10 @@ namespace ccm * @param num Integer number. * @return If no errors occur, square root of num (√num), is returned. */ - template , bool> = true> - constexpr double sqrt(Integer num) - { return ccm::sqrt(static_cast(num)); } + template , bool> = true> constexpr double sqrt(Integer num) + { + return ccm::sqrt(static_cast(num)); + } /** * @brief Calculates the square root of a number. @@ -53,7 +58,9 @@ namespace ccm * @return If no errors occur, square root of num (√num), is returned. */ constexpr float sqrtf(float num) - { return ccm::sqrt(num); } + { + return ccm::sqrt(num); + } /** * @brief Calculates the square root of a number. @@ -61,7 +68,9 @@ namespace ccm * @return If no errors occur, square root of num (√num), is returned. */ constexpr long double sqrtl(long double num) - { return ccm::sqrt(num); } + { + return ccm::sqrt(num); + } } // namespace ccm /// @ingroup power diff --git a/include/ccmath/internal/math/common/special/assoc_laguerre.hpp b/include/ccmath/internal/math/common/special/assoc_laguerre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/assoc_laguerre.hpp +++ b/include/ccmath/internal/math/common/special/assoc_laguerre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/assoc_legendre.hpp b/include/ccmath/internal/math/common/special/assoc_legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/assoc_legendre.hpp +++ b/include/ccmath/internal/math/common/special/assoc_legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/beta.hpp b/include/ccmath/internal/math/common/special/beta.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/beta.hpp +++ b/include/ccmath/internal/math/common/special/beta.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/comp_ellint_1.hpp b/include/ccmath/internal/math/common/special/comp_ellint_1.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/comp_ellint_1.hpp +++ b/include/ccmath/internal/math/common/special/comp_ellint_1.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/comp_ellint_2.hpp b/include/ccmath/internal/math/common/special/comp_ellint_2.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/comp_ellint_2.hpp +++ b/include/ccmath/internal/math/common/special/comp_ellint_2.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/comp_ellint_3.hpp b/include/ccmath/internal/math/common/special/comp_ellint_3.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/comp_ellint_3.hpp +++ b/include/ccmath/internal/math/common/special/comp_ellint_3.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/cyl_bessel_i.hpp b/include/ccmath/internal/math/common/special/cyl_bessel_i.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/cyl_bessel_i.hpp +++ b/include/ccmath/internal/math/common/special/cyl_bessel_i.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/cyl_bessel_j.hpp b/include/ccmath/internal/math/common/special/cyl_bessel_j.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/cyl_bessel_j.hpp +++ b/include/ccmath/internal/math/common/special/cyl_bessel_j.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/cyl_bessel_k.hpp b/include/ccmath/internal/math/common/special/cyl_bessel_k.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/cyl_bessel_k.hpp +++ b/include/ccmath/internal/math/common/special/cyl_bessel_k.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/cyl_neumann.hpp b/include/ccmath/internal/math/common/special/cyl_neumann.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/cyl_neumann.hpp +++ b/include/ccmath/internal/math/common/special/cyl_neumann.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/ellint_1.hpp b/include/ccmath/internal/math/common/special/ellint_1.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/ellint_1.hpp +++ b/include/ccmath/internal/math/common/special/ellint_1.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/ellint_2.hpp b/include/ccmath/internal/math/common/special/ellint_2.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/ellint_2.hpp +++ b/include/ccmath/internal/math/common/special/ellint_2.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/ellint_3.hpp b/include/ccmath/internal/math/common/special/ellint_3.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/ellint_3.hpp +++ b/include/ccmath/internal/math/common/special/ellint_3.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/expint.hpp b/include/ccmath/internal/math/common/special/expint.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/expint.hpp +++ b/include/ccmath/internal/math/common/special/expint.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/hermite.hpp b/include/ccmath/internal/math/common/special/hermite.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/hermite.hpp +++ b/include/ccmath/internal/math/common/special/hermite.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/laguerre.hpp b/include/ccmath/internal/math/common/special/laguerre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/laguerre.hpp +++ b/include/ccmath/internal/math/common/special/laguerre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/legendre.hpp b/include/ccmath/internal/math/common/special/legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/legendre.hpp +++ b/include/ccmath/internal/math/common/special/legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/riemann_zeta.hpp b/include/ccmath/internal/math/common/special/riemann_zeta.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/riemann_zeta.hpp +++ b/include/ccmath/internal/math/common/special/riemann_zeta.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/sph_bessel.hpp b/include/ccmath/internal/math/common/special/sph_bessel.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/sph_bessel.hpp +++ b/include/ccmath/internal/math/common/special/sph_bessel.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/sph_legendre.hpp b/include/ccmath/internal/math/common/special/sph_legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/sph_legendre.hpp +++ b/include/ccmath/internal/math/common/special/sph_legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/special/sph_neumann.hpp b/include/ccmath/internal/math/common/special/sph_neumann.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/special/sph_neumann.hpp +++ b/include/ccmath/internal/math/common/special/sph_neumann.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/trig/acos.hpp b/include/ccmath/internal/math/common/trig/acos.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/trig/acos.hpp +++ b/include/ccmath/internal/math/common/trig/acos.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/trig/asin.hpp b/include/ccmath/internal/math/common/trig/asin.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/trig/asin.hpp +++ b/include/ccmath/internal/math/common/trig/asin.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/trig/atan.hpp b/include/ccmath/internal/math/common/trig/atan.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/trig/atan.hpp +++ b/include/ccmath/internal/math/common/trig/atan.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/trig/atan2.hpp b/include/ccmath/internal/math/common/trig/atan2.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/common/trig/atan2.hpp +++ b/include/ccmath/internal/math/common/trig/atan2.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/common/trig/cos.hpp b/include/ccmath/internal/math/common/trig/cos.hpp index 7b3ed0ea..ad9ffba4 100644 --- a/include/ccmath/internal/math/common/trig/cos.hpp +++ b/include/ccmath/internal/math/common/trig/cos.hpp @@ -19,26 +19,35 @@ namespace ccm { - template , bool> = true> - constexpr T cos(T num) + template , bool> = true> constexpr T cos(T num) { - if constexpr (ccm::builtin::has_constexpr_cos) { return ccm::builtin::cos_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_cos) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::cos_gen(num); } + return ccm::builtin::cos_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::cos_gen(num); + } return ccm::rt::cos_rt(num); } } - template , bool> = true> - constexpr double cos(Integer num) - { return ccm::cos(static_cast(num)); } + template , bool> = true> constexpr double cos(Integer num) + { + return ccm::cos(static_cast(num)); + } constexpr float cosf(float num) - { return ccm::cos(num); } + { + return ccm::cos(num); + } constexpr long double cosl(long double num) - { return ccm::cos(num); } + { + return ccm::cos(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/internal/math/common/trig/sin.hpp b/include/ccmath/internal/math/common/trig/sin.hpp index 9ba8f42d..08a02d0a 100644 --- a/include/ccmath/internal/math/common/trig/sin.hpp +++ b/include/ccmath/internal/math/common/trig/sin.hpp @@ -19,26 +19,35 @@ namespace ccm { - template , bool> = true> - constexpr T sin(T num) + template , bool> = true> constexpr T sin(T num) { - if constexpr (ccm::builtin::has_constexpr_sin) { return ccm::builtin::sin_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_sin) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::sin_gen(num); } + return ccm::builtin::sin_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::sin_gen(num); + } return ccm::rt::sin_rt(num); } } - template , bool> = true> - constexpr double sin(Integer num) - { return ccm::sin(static_cast(num)); } + template , bool> = true> constexpr double sin(Integer num) + { + return ccm::sin(static_cast(num)); + } constexpr float sinf(float num) - { return ccm::sin(num); } + { + return ccm::sin(num); + } constexpr long double sinl(long double num) - { return ccm::sin(num); } + { + return ccm::sin(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/internal/math/common/trig/tan.hpp b/include/ccmath/internal/math/common/trig/tan.hpp index 551b8b1d..27e4251d 100644 --- a/include/ccmath/internal/math/common/trig/tan.hpp +++ b/include/ccmath/internal/math/common/trig/tan.hpp @@ -19,26 +19,35 @@ namespace ccm { - template , bool> = true> - constexpr T tan(T num) + template , bool> = true> constexpr T tan(T num) { - if constexpr (ccm::builtin::has_constexpr_tan) { return ccm::builtin::tan_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_tan) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::tan_gen(num); } + return ccm::builtin::tan_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::tan_gen(num); + } return ccm::rt::tan_rt(num); } } - template , bool> = true> - constexpr double tan(Integer num) - { return ccm::tan(static_cast(num)); } + template , bool> = true> constexpr double tan(Integer num) + { + return ccm::tan(static_cast(num)); + } constexpr float tanf(float num) - { return ccm::tan(num); } + { + return ccm::tan(num); + } constexpr long double tanl(long double num) - { return ccm::tan(num); } + { + return ccm::tan(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp index 09f64770..2b24009c 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fabs.hpp @@ -95,13 +95,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_fabs functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto abs_ct(T x) -> std::enable_if_t, T> + template constexpr auto abs_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fabsf(x); } - else if constexpr (std::is_same_v) { return __builtin_fabs(x); } - else if constexpr (std::is_same_v) { return __builtin_fabsl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_fabsf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_fabs(x); + } else if constexpr (std::is_same_v) + { + return __builtin_fabsl(x); + } else { // This should never be reached static_assert(support::always_false, "Unsupported type for abs"); @@ -109,13 +114,18 @@ namespace ccm::builtin } } - template - auto abs_rt(T x) -> std::enable_if_t, T> + template auto abs_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fabsf(x); } - else if constexpr (std::is_same_v) { return __builtin_fabs(x); } - else if constexpr (std::is_same_v) { return __builtin_fabsl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_fabsf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_fabs(x); + } else if constexpr (std::is_same_v) + { + return __builtin_fabsl(x); + } else { // This should never be reached static_assert(support::always_false, "Unsupported type for abs"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/fdim.hpp b/include/ccmath/internal/math/generic/builtins/basic/fdim.hpp index 86ee3ef6..08b9e86a 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fdim.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fdim.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_fdim functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto fdim_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto fdim_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fdimf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fdim(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fdiml(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fdimf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fdim(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fdiml(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fdim"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto fdim_rt(T x, T y) -> std::enable_if_t, T> + template auto fdim_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fdimf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fdim(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fdiml(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fdimf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fdim(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fdiml(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fdim"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp index 5a6305bc..5f158d97 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fma.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fma.hpp @@ -141,13 +141,18 @@ namespace ccm::builtin * when the compiler does not support it. * This is thanks to taking advantage of ADL. */ - template - constexpr auto fma_ct(T x, T y, T z) -> std::enable_if_t, T> + template constexpr auto fma_ct(T x, T y, T z) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmaf(x, y, z); } - else if constexpr (std::is_same_v) { return __builtin_fma(x, y, z); } - else if constexpr (std::is_same_v) { return __builtin_fmal(x, y, z); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmaf(x, y, z); + } else if constexpr (std::is_same_v) + { + return __builtin_fma(x, y, z); + } else if constexpr (std::is_same_v) + { + return __builtin_fmal(x, y, z); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fma"); @@ -163,13 +168,18 @@ namespace ccm::builtin * when the compiler does not support it. * This is thanks to taking advantage of ADL. */ - template - auto fma_rt(T x, T y, T z) -> std::enable_if_t, T> + template auto fma_rt(T x, T y, T z) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmaf(x, y, z); } - else if constexpr (std::is_same_v) { return __builtin_fma(x, y, z); } - else if constexpr (std::is_same_v) { return __builtin_fmal(x, y, z); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmaf(x, y, z); + } else if constexpr (std::is_same_v) + { + return __builtin_fma(x, y, z); + } else if constexpr (std::is_same_v) + { + return __builtin_fmal(x, y, z); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fma"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp b/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp index 9b283aec..9752b145 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fmax.hpp @@ -88,13 +88,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_fmax functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto fmax_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto fmax_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmaxf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmax(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmaxl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmaxf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmax(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmaxl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fmax"); @@ -102,13 +107,18 @@ namespace ccm::builtin } } - template - auto fmax_rt(T x, T y) -> std::enable_if_t, T> + template auto fmax_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmaxf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmax(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmaxl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmaxf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmax(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmaxl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fmax"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp b/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp index 0f8933e0..471a826d 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fmin.hpp @@ -86,13 +86,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_fmin functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto fmin_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto fmin_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fminf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmin(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fminl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fminf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmin(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fminl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fmin"); @@ -100,13 +105,18 @@ namespace ccm::builtin } } - template - auto fmin_rt(T x, T y) -> std::enable_if_t, T> + template auto fmin_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fminf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmin(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fminl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fminf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmin(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fminl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fmin"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/fmod.hpp b/include/ccmath/internal/math/generic/builtins/basic/fmod.hpp index 89932550..a46a3ab2 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/fmod.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/fmod.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_fmod functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto fmod_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto fmod_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmodf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmod(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmodl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmodf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmod(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmodl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for fmod"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto fmod_rt(T x, T y) -> std::enable_if_t, T> + template auto fmod_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_fmodf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmod(x, y); } - else if constexpr (std::is_same_v) { return __builtin_fmodl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_fmodf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmod(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_fmodl(x, y); + } else { static_assert(ccm::support::always_false, "Unsupported type for fmod"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/basic/nan.hpp b/include/ccmath/internal/math/generic/builtins/basic/nan.hpp index d7aa0493..7f0fa2b7 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/nan.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/nan.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_nan functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto nan_ct(const char * tag) -> std::enable_if_t, T> + template constexpr auto nan_ct(const char * tag) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nanf(tag); } - else if constexpr (std::is_same_v) { return __builtin_nan(tag); } - else if constexpr (std::is_same_v) { return __builtin_nanl(tag); } - else + if constexpr (std::is_same_v) + { + return __builtin_nanf(tag); + } else if constexpr (std::is_same_v) + { + return __builtin_nan(tag); + } else if constexpr (std::is_same_v) + { + return __builtin_nanl(tag); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nan"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto nan_rt(const char * tag) -> std::enable_if_t, T> + template auto nan_rt(const char * tag) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nanf(tag); } - else if constexpr (std::is_same_v) { return __builtin_nan(tag); } - else if constexpr (std::is_same_v) { return __builtin_nanl(tag); } - else + if constexpr (std::is_same_v) + { + return __builtin_nanf(tag); + } else if constexpr (std::is_same_v) + { + return __builtin_nan(tag); + } else if constexpr (std::is_same_v) + { + return __builtin_nanl(tag); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nan"); diff --git a/include/ccmath/internal/math/generic/builtins/basic/remainder.hpp b/include/ccmath/internal/math/generic/builtins/basic/remainder.hpp index 751c261f..10b0c92e 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/remainder.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/remainder.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_remainder functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto remainder_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto remainder_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_remainderf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_remainder(x, y); } - else if constexpr (std::is_same_v) { return __builtin_remainderl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_remainderf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_remainder(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_remainderl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for remainder"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto remainder_rt(T x, T y) -> std::enable_if_t, T> + template auto remainder_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_remainderf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_remainder(x, y); } - else if constexpr (std::is_same_v) { return __builtin_remainderl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_remainderf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_remainder(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_remainderl(x, y); + } else { static_assert(ccm::support::always_false, "Unsupported type for remainder"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/basic/remquo.hpp b/include/ccmath/internal/math/generic/builtins/basic/remquo.hpp index f9003bb5..86baf8d7 100644 --- a/include/ccmath/internal/math/generic/builtins/basic/remquo.hpp +++ b/include/ccmath/internal/math/generic/builtins/basic/remquo.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_remquo functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto remquo_ct(T x, T y, int * quo) -> std::enable_if_t, T> + template constexpr auto remquo_ct(T x, T y, int * quo) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_remquof(x, y, quo); } - else if constexpr (std::is_same_v) { return __builtin_remquo(x, y, quo); } - else if constexpr (std::is_same_v) { return __builtin_remquol(x, y, quo); } - else + if constexpr (std::is_same_v) + { + return __builtin_remquof(x, y, quo); + } else if constexpr (std::is_same_v) + { + return __builtin_remquo(x, y, quo); + } else if constexpr (std::is_same_v) + { + return __builtin_remquol(x, y, quo); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for remquo"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto remquo_rt(T x, T y, int * quo) -> std::enable_if_t, T> + template auto remquo_rt(T x, T y, int * quo) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_remquof(x, y, quo); } - else if constexpr (std::is_same_v) { return __builtin_remquo(x, y, quo); } - else if constexpr (std::is_same_v) { return __builtin_remquol(x, y, quo); } - else + if constexpr (std::is_same_v) + { + return __builtin_remquof(x, y, quo); + } else if constexpr (std::is_same_v) + { + return __builtin_remquo(x, y, quo); + } else if constexpr (std::is_same_v) + { + return __builtin_remquol(x, y, quo); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for remquo"); diff --git a/include/ccmath/internal/math/generic/builtins/builtin_helpers.hpp b/include/ccmath/internal/math/generic/builtins/builtin_helpers.hpp index da92d47b..ac46b407 100644 --- a/include/ccmath/internal/math/generic/builtins/builtin_helpers.hpp +++ b/include/ccmath/internal/math/generic/builtins/builtin_helpers.hpp @@ -8,8 +8,7 @@ namespace ccm::builtin * @internal * @brief Helper to check that a valid builtin type is used */ - template - inline constexpr bool is_valid_builtin_type = std::is_same_v || std::is_same_v || std::is_same_v; + template inline constexpr bool is_valid_builtin_type = std::is_same_v || std::is_same_v || std::is_same_v; /** * @internal diff --git a/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp b/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp index 9ce91303..5f9b4ebc 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isfinite.hpp @@ -87,13 +87,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isfinite functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto isfinite_ct(T x) -> std::enable_if_t, bool> - { return __builtin_isfinite(x); } + template constexpr auto isfinite_ct(T x) -> std::enable_if_t, bool> + { + return __builtin_isfinite(x); + } - template - auto isfinite_rt(T x) -> std::enable_if_t, bool> - { return __builtin_isfinite(x); } + template auto isfinite_rt(T x) -> std::enable_if_t, bool> + { + return __builtin_isfinite(x); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isgreater.hpp b/include/ccmath/internal/math/generic/builtins/compare/isgreater.hpp index de3377c4..a9cb32c9 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isgreater.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isgreater.hpp @@ -70,13 +70,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isgreater without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto isgreater_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isgreater(x, y); } + template constexpr auto isgreater_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isgreater(x, y); + } - template - auto isgreater_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isgreater(x, y); } + template auto isgreater_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isgreater(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isgreaterequal.hpp b/include/ccmath/internal/math/generic/builtins/compare/isgreaterequal.hpp index 3cad218a..e86728b3 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isgreaterequal.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isgreaterequal.hpp @@ -73,13 +73,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isgreaterequal without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto isgreaterequal_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isgreaterequal(x, y); } + template constexpr auto isgreaterequal_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isgreaterequal(x, y); + } - template - auto isgreaterequal_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isgreaterequal(x, y); } + template auto isgreaterequal_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isgreaterequal(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp b/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp index 0183d8e8..ecd2bbd4 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isinf.hpp @@ -94,13 +94,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isinf functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto isinf_ct(T x) -> std::enable_if_t, bool> - { return __builtin_isinf(x); } - - template - auto isinf_rt(T x) -> std::enable_if_t, bool> - { return __builtin_isinf(x); } + template constexpr auto isinf_ct(T x) -> std::enable_if_t, bool> + { + return __builtin_isinf(x); + } + + template auto isinf_rt(T x) -> std::enable_if_t, bool> + { + return __builtin_isinf(x); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isless.hpp b/include/ccmath/internal/math/generic/builtins/compare/isless.hpp index a8e7c977..574bcd39 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isless.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isless.hpp @@ -73,13 +73,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isless without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto isless_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isless(x, y); } + template constexpr auto isless_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isless(x, y); + } - template - auto isless_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isless(x, y); } + template auto isless_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isless(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/islessequal.hpp b/include/ccmath/internal/math/generic/builtins/compare/islessequal.hpp index a280bbe3..de5ffbd3 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/islessequal.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/islessequal.hpp @@ -73,13 +73,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_islessequal without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto islessequal_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_islessequal(x, y); } + template constexpr auto islessequal_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_islessequal(x, y); + } - template - auto islessequal_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_islessequal(x, y); } + template auto islessequal_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_islessequal(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/islessgreater.hpp b/include/ccmath/internal/math/generic/builtins/compare/islessgreater.hpp index 594e07ee..afde7dcd 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/islessgreater.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/islessgreater.hpp @@ -73,13 +73,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_islessgreater without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto islessgreater_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_islessgreater(x, y); } + template constexpr auto islessgreater_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_islessgreater(x, y); + } - template - auto islessgreater_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_islessgreater(x, y); } + template auto islessgreater_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_islessgreater(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isnan.hpp b/include/ccmath/internal/math/generic/builtins/compare/isnan.hpp index 60904869..a1de01bd 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isnan.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isnan.hpp @@ -94,13 +94,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isnan functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto isnan_ct(T x) -> std::enable_if_t, bool> - { return __builtin_isnan(x); } - - template - auto isnan_rt(T x) -> std::enable_if_t, bool> - { return __builtin_isnan(x); } + template constexpr auto isnan_ct(T x) -> std::enable_if_t, bool> + { + return __builtin_isnan(x); + } + + template auto isnan_rt(T x) -> std::enable_if_t, bool> + { + return __builtin_isnan(x); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp b/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp index 7dadcb1f..75cd2366 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isnormal.hpp @@ -95,13 +95,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isnormal functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto isnormal_ct(T x) -> std::enable_if_t, bool> - { return __builtin_isnormal(x); } - - template - auto isnormal_rt(T x) -> std::enable_if_t, bool> - { return __builtin_isnormal(x); } + template constexpr auto isnormal_ct(T x) -> std::enable_if_t, bool> + { + return __builtin_isnormal(x); + } + + template auto isnormal_rt(T x) -> std::enable_if_t, bool> + { + return __builtin_isnormal(x); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/isunordered.hpp b/include/ccmath/internal/math/generic/builtins/compare/isunordered.hpp index 3ebfa7d2..78a3d176 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/isunordered.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/isunordered.hpp @@ -73,13 +73,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_isunordered functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto isunordered_ct(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isunordered(x, y); } + template constexpr auto isunordered_ct(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isunordered(x, y); + } - template - auto isunordered_rt(T x, T y) -> std::enable_if_t, bool> - { return __builtin_isunordered(x, y); } + template auto isunordered_rt(T x, T y) -> std::enable_if_t, bool> + { + return __builtin_isunordered(x, y); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/compare/signbit.hpp b/include/ccmath/internal/math/generic/builtins/compare/signbit.hpp index 6bd57175..ac247cba 100644 --- a/include/ccmath/internal/math/generic/builtins/compare/signbit.hpp +++ b/include/ccmath/internal/math/generic/builtins/compare/signbit.hpp @@ -80,13 +80,15 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_signbit functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto signbit_ct(T x) -> std::enable_if_t, bool> - { return __builtin_signbit(x); } + template constexpr auto signbit_ct(T x) -> std::enable_if_t, bool> + { + return __builtin_signbit(x); + } - template - auto signbit_rt(T x) -> std::enable_if_t, bool> - { return __builtin_signbit(x); } + template auto signbit_rt(T x) -> std::enable_if_t, bool> + { + return __builtin_signbit(x); + } } // namespace ccm::builtin // Cleanup the global namespace diff --git a/include/ccmath/internal/math/generic/builtins/expo/exp.hpp b/include/ccmath/internal/math/generic/builtins/expo/exp.hpp index ece271fe..7dc45db4 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/exp.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/exp.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_exp functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto exp_ct(T x) -> std::enable_if_t, T> + template constexpr auto exp_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_expf(x); } - else if constexpr (std::is_same_v) { return __builtin_exp(x); } - else if constexpr (std::is_same_v) { return __builtin_expl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_expf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for exp"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto exp_rt(T x) -> std::enable_if_t, T> + template auto exp_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_expf(x); } - else if constexpr (std::is_same_v) { return __builtin_exp(x); } - else if constexpr (std::is_same_v) { return __builtin_expl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_expf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expl(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for exp"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/expo/exp2.hpp b/include/ccmath/internal/math/generic/builtins/expo/exp2.hpp index 9216a33b..661701e8 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/exp2.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/exp2.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_exp2 functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto exp2_ct(T x) -> std::enable_if_t, T> + template constexpr auto exp2_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_exp2f(x); } - else if constexpr (std::is_same_v) { return __builtin_exp2(x); } - else if constexpr (std::is_same_v) { return __builtin_exp2l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_exp2f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp2(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp2l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for exp2"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto exp2_rt(T x) -> std::enable_if_t, T> + template auto exp2_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_exp2f(x); } - else if constexpr (std::is_same_v) { return __builtin_exp2(x); } - else if constexpr (std::is_same_v) { return __builtin_exp2l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_exp2f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp2(x); + } else if constexpr (std::is_same_v) + { + return __builtin_exp2l(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for exp2"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/expo/expm1.hpp b/include/ccmath/internal/math/generic/builtins/expo/expm1.hpp index 34bf200a..9937c222 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/expm1.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/expm1.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_expm1 functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto expm1_ct(T x) -> std::enable_if_t, T> + template constexpr auto expm1_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_expm1f(x); } - else if constexpr (std::is_same_v) { return __builtin_expm1(x); } - else if constexpr (std::is_same_v) { return __builtin_expm1l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_expm1f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expm1(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expm1l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for expm1"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto expm1_rt(T x) -> std::enable_if_t, T> + template auto expm1_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_expm1f(x); } - else if constexpr (std::is_same_v) { return __builtin_expm1(x); } - else if constexpr (std::is_same_v) { return __builtin_expm1l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_expm1f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expm1(x); + } else if constexpr (std::is_same_v) + { + return __builtin_expm1l(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for expm1"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/expo/log.hpp b/include/ccmath/internal/math/generic/builtins/expo/log.hpp index 5d838ef2..f56c2d94 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/log.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/log.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_log functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto log_ct(T x) -> std::enable_if_t, T> + template constexpr auto log_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_logf(x); } - else if constexpr (std::is_same_v) { return __builtin_log(x); } - else if constexpr (std::is_same_v) { return __builtin_logl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_logf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto log_rt(T x) -> std::enable_if_t, T> + template auto log_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_logf(x); } - else if constexpr (std::is_same_v) { return __builtin_log(x); } - else if constexpr (std::is_same_v) { return __builtin_logl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_logf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log"); diff --git a/include/ccmath/internal/math/generic/builtins/expo/log10.hpp b/include/ccmath/internal/math/generic/builtins/expo/log10.hpp index 7b8607b6..3183d8a6 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/log10.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/log10.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_log10 functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto log10_ct(T x) -> std::enable_if_t, T> + template constexpr auto log10_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log10f(x); } - else if constexpr (std::is_same_v) { return __builtin_log10(x); } - else if constexpr (std::is_same_v) { return __builtin_log10l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log10f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log10(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log10l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log10"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto log10_rt(T x) -> std::enable_if_t, T> + template auto log10_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log10f(x); } - else if constexpr (std::is_same_v) { return __builtin_log10(x); } - else if constexpr (std::is_same_v) { return __builtin_log10l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log10f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log10(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log10l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log10"); diff --git a/include/ccmath/internal/math/generic/builtins/expo/log1p.hpp b/include/ccmath/internal/math/generic/builtins/expo/log1p.hpp index 11dd0221..eca96d0d 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/log1p.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/log1p.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_log1p functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto log1p_ct(T x) -> std::enable_if_t, T> + template constexpr auto log1p_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log1pf(x); } - else if constexpr (std::is_same_v) { return __builtin_log1p(x); } - else if constexpr (std::is_same_v) { return __builtin_log1pl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log1pf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log1p(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log1pl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log1p"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto log1p_rt(T x) -> std::enable_if_t, T> + template auto log1p_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log1pf(x); } - else if constexpr (std::is_same_v) { return __builtin_log1p(x); } - else if constexpr (std::is_same_v) { return __builtin_log1pl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log1pf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log1p(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log1pl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log1p"); diff --git a/include/ccmath/internal/math/generic/builtins/expo/log2.hpp b/include/ccmath/internal/math/generic/builtins/expo/log2.hpp index 4eaadb63..9fb4ea7b 100644 --- a/include/ccmath/internal/math/generic/builtins/expo/log2.hpp +++ b/include/ccmath/internal/math/generic/builtins/expo/log2.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_log2 functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto log2_ct(T x) -> std::enable_if_t, T> + template constexpr auto log2_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log2f(x); } - else if constexpr (std::is_same_v) { return __builtin_log2(x); } - else if constexpr (std::is_same_v) { return __builtin_log2l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log2f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log2(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log2l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log2"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto log2_rt(T x) -> std::enable_if_t, T> + template auto log2_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_log2f(x); } - else if constexpr (std::is_same_v) { return __builtin_log2(x); } - else if constexpr (std::is_same_v) { return __builtin_log2l(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_log2f(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log2(x); + } else if constexpr (std::is_same_v) + { + return __builtin_log2l(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for log2"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp index da8af3ed..2af1e5d1 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/copysign.hpp @@ -95,13 +95,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_copysign functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto copysign_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto copysign_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_copysignf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_copysign(x, y); } - else if constexpr (std::is_same_v) { return __builtin_copysignl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_copysignf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_copysign(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_copysignl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for copysign"); @@ -109,13 +114,18 @@ namespace ccm::builtin } } - template - auto copysign_rt(T x, T y) -> std::enable_if_t, T> + template auto copysign_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_copysignf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_copysign(x, y); } - else if constexpr (std::is_same_v) { return __builtin_copysignl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_copysignf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_copysign(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_copysignl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for copysign"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/frexp.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/frexp.hpp index 042fd4db..01ed6e5b 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/frexp.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/frexp.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_frexp functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto frexp_ct(T x, int * exp) -> std::enable_if_t, T> + template constexpr auto frexp_ct(T x, int * exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_frexpf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_frexp(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_frexpl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_frexpf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_frexp(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_frexpl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for frexp"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto frexp_rt(T x, int * exp) -> std::enable_if_t, T> + template auto frexp_rt(T x, int * exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_frexpf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_frexp(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_frexpl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_frexpf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_frexp(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_frexpl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for frexp"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/ilogb.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/ilogb.hpp index 7f743aae..dc20a080 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/ilogb.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/ilogb.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_ilogb functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto ilogb_ct(T x) -> std::enable_if_t, int> + template constexpr auto ilogb_ct(T x) -> std::enable_if_t, int> { - if constexpr (std::is_same_v) { return __builtin_ilogbf(x); } - else if constexpr (std::is_same_v) { return __builtin_ilogb(x); } - else if constexpr (std::is_same_v) { return __builtin_ilogbl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_ilogbf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ilogb(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ilogbl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ilogb"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto ilogb_rt(T x) -> std::enable_if_t, int> + template auto ilogb_rt(T x) -> std::enable_if_t, int> { - if constexpr (std::is_same_v) { return __builtin_ilogbf(x); } - else if constexpr (std::is_same_v) { return __builtin_ilogb(x); } - else if constexpr (std::is_same_v) { return __builtin_ilogbl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_ilogbf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ilogb(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ilogbl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ilogb"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/ldexp.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/ldexp.hpp index ce7e43f9..870e90d5 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/ldexp.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/ldexp.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_ldexp functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto ldexp_ct(T x, int exp) -> std::enable_if_t, T> + template constexpr auto ldexp_ct(T x, int exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_ldexpf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_ldexp(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_ldexpl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_ldexpf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_ldexp(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_ldexpl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ldexp"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto ldexp_rt(T x, int exp) -> std::enable_if_t, T> + template auto ldexp_rt(T x, int exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_ldexpf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_ldexp(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_ldexpl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_ldexpf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_ldexp(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_ldexpl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ldexp"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/logb.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/logb.hpp index dee49a5c..95136ca7 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/logb.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/logb.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_logb functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto logb_ct(T x) -> std::enable_if_t, T> + template constexpr auto logb_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_logbf(x); } - else if constexpr (std::is_same_v) { return __builtin_logb(x); } - else if constexpr (std::is_same_v) { return __builtin_logbl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_logbf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logb(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logbl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for logb"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto logb_rt(T x) -> std::enable_if_t, T> + template auto logb_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_logbf(x); } - else if constexpr (std::is_same_v) { return __builtin_logb(x); } - else if constexpr (std::is_same_v) { return __builtin_logbl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_logbf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logb(x); + } else if constexpr (std::is_same_v) + { + return __builtin_logbl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for logb"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/modf.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/modf.hpp index c03e308c..6b1d2896 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/modf.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/modf.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_modf functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto modf_ct(T x, T * intpart) -> std::enable_if_t, T> + template constexpr auto modf_ct(T x, T * intpart) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_modff(x, intpart); } - else if constexpr (std::is_same_v) { return __builtin_modf(x, intpart); } - else if constexpr (std::is_same_v) { return __builtin_modfl(x, intpart); } - else + if constexpr (std::is_same_v) + { + return __builtin_modff(x, intpart); + } else if constexpr (std::is_same_v) + { + return __builtin_modf(x, intpart); + } else if constexpr (std::is_same_v) + { + return __builtin_modfl(x, intpart); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for modf"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto modf_rt(T x, T * intpart) -> std::enable_if_t, T> + template auto modf_rt(T x, T * intpart) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_modff(x, intpart); } - else if constexpr (std::is_same_v) { return __builtin_modf(x, intpart); } - else if constexpr (std::is_same_v) { return __builtin_modfl(x, intpart); } - else + if constexpr (std::is_same_v) + { + return __builtin_modff(x, intpart); + } else if constexpr (std::is_same_v) + { + return __builtin_modf(x, intpart); + } else if constexpr (std::is_same_v) + { + return __builtin_modfl(x, intpart); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for modf"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/nextafter.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/nextafter.hpp index 3c053772..8cf82430 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/nextafter.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/nextafter.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_nextafter functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto nextafter_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto nextafter_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nextafterf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nextafter(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nextafterl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_nextafterf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nextafter(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nextafterl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nextafter"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto nextafter_rt(T x, T y) -> std::enable_if_t, T> + template auto nextafter_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nextafterf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nextafter(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nextafterl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_nextafterf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nextafter(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nextafterl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nextafter"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/nexttoward.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/nexttoward.hpp index cfd5985f..dc2451f8 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/nexttoward.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/nexttoward.hpp @@ -78,10 +78,16 @@ namespace ccm::builtin constexpr auto nexttoward_ct(T x, long double y) -> std::enable_if_t || std::is_same_v || std::is_same_v, T> { - if constexpr (std::is_same_v) { return __builtin_nexttowardf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nexttoward(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nexttowardl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_nexttowardf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nexttoward(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nexttowardl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nexttoward"); @@ -89,13 +95,18 @@ namespace ccm::builtin } } - template - auto nexttoward_rt(T x, long double y) -> std::enable_if_t, T> + template auto nexttoward_rt(T x, long double y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nexttowardf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nexttoward(x, y); } - else if constexpr (std::is_same_v) { return __builtin_nexttowardl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_nexttowardf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nexttoward(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_nexttowardl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nexttoward"); diff --git a/include/ccmath/internal/math/generic/builtins/fmanip/scalbn.hpp b/include/ccmath/internal/math/generic/builtins/fmanip/scalbn.hpp index cb8dce6b..c975cbad 100644 --- a/include/ccmath/internal/math/generic/builtins/fmanip/scalbn.hpp +++ b/include/ccmath/internal/math/generic/builtins/fmanip/scalbn.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_scalbn functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto scalbn_ct(T x, int exp) -> std::enable_if_t, T> + template constexpr auto scalbn_ct(T x, int exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_scalbnf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalbn(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalbnl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_scalbnf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalbn(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalbnl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for scalbn"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto scalbn_rt(T x, int exp) -> std::enable_if_t, T> + template auto scalbn_rt(T x, int exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_scalbnf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalbn(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalbnl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_scalbnf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalbn(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalbnl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for scalbn"); @@ -109,13 +119,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_scalbn functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto scalbn_ct(T x, long exp) -> std::enable_if_t, T> + template constexpr auto scalbn_ct(T x, long exp) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_scalblnf(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalbln(x, exp); } - else if constexpr (std::is_same_v) { return __builtin_scalblnl(x, exp); } - else + if constexpr (std::is_same_v) + { + return __builtin_scalblnf(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalbln(x, exp); + } else if constexpr (std::is_same_v) + { + return __builtin_scalblnl(x, exp); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for scalbn"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/acosh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/acosh.hpp index 2d4eb8a3..4592b6b0 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/acosh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/acosh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_acosh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto acosh_ct(T x) -> std::enable_if_t, T> + template constexpr auto acosh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_acoshf(x); } - else if constexpr (std::is_same_v) { return __builtin_acosh(x); } - else if constexpr (std::is_same_v) { return __builtin_acoshl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_acoshf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acosh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acoshl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for acosh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto acosh_rt(T x) -> std::enable_if_t, T> + template auto acosh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_acoshf(x); } - else if constexpr (std::is_same_v) { return __builtin_acosh(x); } - else if constexpr (std::is_same_v) { return __builtin_acoshl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_acoshf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acosh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acoshl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for acosh"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/asinh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/asinh.hpp index 143e63ac..0d9f5201 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/asinh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/asinh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_asinh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto asinh_ct(T x) -> std::enable_if_t, T> + template constexpr auto asinh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_asinhf(x); } - else if constexpr (std::is_same_v) { return __builtin_asinh(x); } - else if constexpr (std::is_same_v) { return __builtin_asinhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_asinhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for asinh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto asinh_rt(T x) -> std::enable_if_t, T> + template auto asinh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_asinhf(x); } - else if constexpr (std::is_same_v) { return __builtin_asinh(x); } - else if constexpr (std::is_same_v) { return __builtin_asinhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_asinhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for asinh"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/atanh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/atanh.hpp index ba5196f5..2d2f2541 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/atanh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/atanh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_atanh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto atanh_ct(T x) -> std::enable_if_t, T> + template constexpr auto atanh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atanhf(x); } - else if constexpr (std::is_same_v) { return __builtin_atanh(x); } - else if constexpr (std::is_same_v) { return __builtin_atanhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atanhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atanh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto atanh_rt(T x) -> std::enable_if_t, T> + template auto atanh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atanhf(x); } - else if constexpr (std::is_same_v) { return __builtin_atanh(x); } - else if constexpr (std::is_same_v) { return __builtin_atanhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atanhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atanh"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/cosh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/cosh.hpp index ec91002c..30890768 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/cosh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/cosh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_cosh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto cosh_ct(T x) -> std::enable_if_t, T> + template constexpr auto cosh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_coshf(x); } - else if constexpr (std::is_same_v) { return __builtin_cosh(x); } - else if constexpr (std::is_same_v) { return __builtin_coshl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_coshf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cosh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_coshl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for cosh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto cosh_rt(T x) -> std::enable_if_t, T> + template auto cosh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_coshf(x); } - else if constexpr (std::is_same_v) { return __builtin_cosh(x); } - else if constexpr (std::is_same_v) { return __builtin_coshl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_coshf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cosh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_coshl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for cosh"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/sinh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/sinh.hpp index 9dc84e37..0626449c 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/sinh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/sinh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_sinh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto sinh_ct(T x) -> std::enable_if_t, T> + template constexpr auto sinh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sinhf(x); } - else if constexpr (std::is_same_v) { return __builtin_sinh(x); } - else if constexpr (std::is_same_v) { return __builtin_sinhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sinhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sinh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto sinh_rt(T x) -> std::enable_if_t, T> + template auto sinh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sinhf(x); } - else if constexpr (std::is_same_v) { return __builtin_sinh(x); } - else if constexpr (std::is_same_v) { return __builtin_sinhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sinhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sinh"); diff --git a/include/ccmath/internal/math/generic/builtins/hyper/tanh.hpp b/include/ccmath/internal/math/generic/builtins/hyper/tanh.hpp index 4ede5fdc..d9581cf5 100644 --- a/include/ccmath/internal/math/generic/builtins/hyper/tanh.hpp +++ b/include/ccmath/internal/math/generic/builtins/hyper/tanh.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_tanh functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto tanh_ct(T x) -> std::enable_if_t, T> + template constexpr auto tanh_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tanhf(x); } - else if constexpr (std::is_same_v) { return __builtin_tanh(x); } - else if constexpr (std::is_same_v) { return __builtin_tanhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tanhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for tanh"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto tanh_rt(T x) -> std::enable_if_t, T> + template auto tanh_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tanhf(x); } - else if constexpr (std::is_same_v) { return __builtin_tanh(x); } - else if constexpr (std::is_same_v) { return __builtin_tanhl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tanhf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanh(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanhl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for tanh"); diff --git a/include/ccmath/internal/math/generic/builtins/misc/erf.hpp b/include/ccmath/internal/math/generic/builtins/misc/erf.hpp index 38498f6d..92b6eb65 100644 --- a/include/ccmath/internal/math/generic/builtins/misc/erf.hpp +++ b/include/ccmath/internal/math/generic/builtins/misc/erf.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_erf without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto erf_ct(T x) -> std::enable_if_t, T> + template constexpr auto erf_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_erff(x); } - else if constexpr (std::is_same_v) { return __builtin_erf(x); } - else if constexpr (std::is_same_v) { return __builtin_erfl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_erff(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for erf"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto erf_rt(T x) -> std::enable_if_t, T> + template auto erf_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_erff(x); } - else if constexpr (std::is_same_v) { return __builtin_erf(x); } - else if constexpr (std::is_same_v) { return __builtin_erfl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_erff(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for erf"); diff --git a/include/ccmath/internal/math/generic/builtins/misc/erfc.hpp b/include/ccmath/internal/math/generic/builtins/misc/erfc.hpp index 79f8bb85..0bfb176d 100644 --- a/include/ccmath/internal/math/generic/builtins/misc/erfc.hpp +++ b/include/ccmath/internal/math/generic/builtins/misc/erfc.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_erfc without triggering a compiler error * when the compiler does not support it. */ - template - constexpr auto erfc_ct(T x) -> std::enable_if_t, T> + template constexpr auto erfc_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_erfcf(x); } - else if constexpr (std::is_same_v) { return __builtin_erfc(x); } - else if constexpr (std::is_same_v) { return __builtin_erfcl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_erfcf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfc(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfcl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for erfc"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto erfc_rt(T x) -> std::enable_if_t, T> + template auto erfc_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_erfcf(x); } - else if constexpr (std::is_same_v) { return __builtin_erfc(x); } - else if constexpr (std::is_same_v) { return __builtin_erfcl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_erfcf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfc(x); + } else if constexpr (std::is_same_v) + { + return __builtin_erfcl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for erfc"); diff --git a/include/ccmath/internal/math/generic/builtins/misc/lgamma.hpp b/include/ccmath/internal/math/generic/builtins/misc/lgamma.hpp index 99158062..d71de008 100644 --- a/include/ccmath/internal/math/generic/builtins/misc/lgamma.hpp +++ b/include/ccmath/internal/math/generic/builtins/misc/lgamma.hpp @@ -41,13 +41,18 @@ namespace ccm::builtin false; #endif - template - auto lgamma_rt(T x) -> std::enable_if_t, T> + template auto lgamma_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_lgammaf(x); } - else if constexpr (std::is_same_v) { return __builtin_lgamma(x); } - else if constexpr (std::is_same_v) { return __builtin_lgammal(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_lgammaf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lgamma(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lgammal(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for lgamma"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/misc/tgamma.hpp b/include/ccmath/internal/math/generic/builtins/misc/tgamma.hpp index dac1edeb..12a1687a 100644 --- a/include/ccmath/internal/math/generic/builtins/misc/tgamma.hpp +++ b/include/ccmath/internal/math/generic/builtins/misc/tgamma.hpp @@ -74,26 +74,36 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_tgamma functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto gamma_ct(T x) -> std::enable_if_t, T> + template constexpr auto gamma_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tgammaf(x); } - else if constexpr (std::is_same_v) { return __builtin_tgamma(x); } - else if constexpr (std::is_same_v) { return __builtin_tgammal(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tgammaf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tgamma(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tgammal(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for gamma"); return T{}; } } - template - auto gamma_rt(T x) -> std::enable_if_t, T> + template auto gamma_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tgammaf(x); } - else if constexpr (std::is_same_v) { return __builtin_tgamma(x); } - else if constexpr (std::is_same_v) { return __builtin_tgammal(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tgammaf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tgamma(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tgammal(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for gamma"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/nearest/ceil.hpp b/include/ccmath/internal/math/generic/builtins/nearest/ceil.hpp index 2fde4ce5..af15292c 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/ceil.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/ceil.hpp @@ -81,13 +81,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_ceil functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto ceil_ct(T x) -> std::enable_if_t, T> + template constexpr auto ceil_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_ceilf(x); } - else if constexpr (std::is_same_v) { return __builtin_ceil(x); } - else if constexpr (std::is_same_v) { return __builtin_ceill(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_ceilf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ceil(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ceill(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ceil"); @@ -95,13 +100,18 @@ namespace ccm::builtin } } - template - auto ceil_rt(T x) -> std::enable_if_t, T> + template auto ceil_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_ceilf(x); } - else if constexpr (std::is_same_v) { return __builtin_ceil(x); } - else if constexpr (std::is_same_v) { return __builtin_ceill(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_ceilf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ceil(x); + } else if constexpr (std::is_same_v) + { + return __builtin_ceill(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for ceil"); diff --git a/include/ccmath/internal/math/generic/builtins/nearest/floor.hpp b/include/ccmath/internal/math/generic/builtins/nearest/floor.hpp index 37f896a3..5e345094 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/floor.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/floor.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_floor functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto floor_ct(T x) -> std::enable_if_t, T> + template constexpr auto floor_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_floorf(x); } - else if constexpr (std::is_same_v) { return __builtin_floor(x); } - else if constexpr (std::is_same_v) { return __builtin_floorl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_floorf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_floor(x); + } else if constexpr (std::is_same_v) + { + return __builtin_floorl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for floor"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto floor_rt(T x) -> std::enable_if_t, T> + template auto floor_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_floorf(x); } - else if constexpr (std::is_same_v) { return __builtin_floor(x); } - else if constexpr (std::is_same_v) { return __builtin_floorl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_floorf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_floor(x); + } else if constexpr (std::is_same_v) + { + return __builtin_floorl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for floor"); diff --git a/include/ccmath/internal/math/generic/builtins/nearest/nearbyint.hpp b/include/ccmath/internal/math/generic/builtins/nearest/nearbyint.hpp index 4231a529..7e283e76 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/nearbyint.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/nearbyint.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_nearbyint functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto nearbyint_ct(T x) -> std::enable_if_t, T> + template constexpr auto nearbyint_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nearbyintf(x); } - else if constexpr (std::is_same_v) { return __builtin_nearbyint(x); } - else if constexpr (std::is_same_v) { return __builtin_nearbyintl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_nearbyintf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_nearbyint(x); + } else if constexpr (std::is_same_v) + { + return __builtin_nearbyintl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for nearbyint"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto nearbyint_rt(T x) -> std::enable_if_t, T> + template auto nearbyint_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_nearbyintf(x); } - else if constexpr (std::is_same_v) { return __builtin_nearbyint(x); } - else if constexpr (std::is_same_v) { return __builtin_nearbyintl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_nearbyintf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_nearbyint(x); + } else if constexpr (std::is_same_v) + { + return __builtin_nearbyintl(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for nearbyint"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/nearest/rint.hpp b/include/ccmath/internal/math/generic/builtins/nearest/rint.hpp index e33601df..e6c13184 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/rint.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/rint.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_rint functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto rint_ct(T x) -> std::enable_if_t, T> + template constexpr auto rint_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_rintf(x); } - else if constexpr (std::is_same_v) { return __builtin_rint(x); } - else if constexpr (std::is_same_v) { return __builtin_rintl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_rintf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_rint(x); + } else if constexpr (std::is_same_v) + { + return __builtin_rintl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for rint"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto rint_rt(T x) -> std::enable_if_t, T> + template auto rint_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_rintf(x); } - else if constexpr (std::is_same_v) { return __builtin_rint(x); } - else if constexpr (std::is_same_v) { return __builtin_rintl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_rintf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_rint(x); + } else if constexpr (std::is_same_v) + { + return __builtin_rintl(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for rint"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/nearest/round.hpp b/include/ccmath/internal/math/generic/builtins/nearest/round.hpp index 78f1ce71..8da259ea 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/round.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/round.hpp @@ -121,13 +121,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_round functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto round_ct(T x) -> std::enable_if_t, T> + template constexpr auto round_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_roundf(x); } - else if constexpr (std::is_same_v) { return __builtin_round(x); } - else if constexpr (std::is_same_v) { return __builtin_roundl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_roundf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_round(x); + } else if constexpr (std::is_same_v) + { + return __builtin_roundl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for round"); @@ -142,13 +147,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_lround functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto lround_ct(T x) -> std::enable_if_t, T> + template constexpr auto lround_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_lroundf(x); } - else if constexpr (std::is_same_v) { return __builtin_lround(x); } - else if constexpr (std::is_same_v) { return __builtin_lroundl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_lroundf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lround(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lroundl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for lround"); @@ -156,26 +166,36 @@ namespace ccm::builtin } } - template - auto round_rt(T x) -> std::enable_if_t, T> + template auto round_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_roundf(x); } - else if constexpr (std::is_same_v) { return __builtin_round(x); } - else if constexpr (std::is_same_v) { return __builtin_roundl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_roundf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_round(x); + } else if constexpr (std::is_same_v) + { + return __builtin_roundl(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for round"); return T{}; } } - template - auto lround_rt(T x) -> std::enable_if_t, T> + template auto lround_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_lroundf(x); } - else if constexpr (std::is_same_v) { return __builtin_lround(x); } - else if constexpr (std::is_same_v) { return __builtin_lroundl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_lroundf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lround(x); + } else if constexpr (std::is_same_v) + { + return __builtin_lroundl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for lround"); diff --git a/include/ccmath/internal/math/generic/builtins/nearest/trunc.hpp b/include/ccmath/internal/math/generic/builtins/nearest/trunc.hpp index b1734cf8..039440c3 100644 --- a/include/ccmath/internal/math/generic/builtins/nearest/trunc.hpp +++ b/include/ccmath/internal/math/generic/builtins/nearest/trunc.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_trunc functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto trunc_ct(T x) -> std::enable_if_t, T> + template constexpr auto trunc_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_truncf(x); } - else if constexpr (std::is_same_v) { return __builtin_trunc(x); } - else if constexpr (std::is_same_v) { return __builtin_truncl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_truncf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_trunc(x); + } else if constexpr (std::is_same_v) + { + return __builtin_truncl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for trunc"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto trunc_rt(T x) -> std::enable_if_t, T> + template auto trunc_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_truncf(x); } - else if constexpr (std::is_same_v) { return __builtin_trunc(x); } - else if constexpr (std::is_same_v) { return __builtin_truncl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_truncf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_trunc(x); + } else if constexpr (std::is_same_v) + { + return __builtin_truncl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for trunc"); diff --git a/include/ccmath/internal/math/generic/builtins/power/cbrt.hpp b/include/ccmath/internal/math/generic/builtins/power/cbrt.hpp index 02980047..5195c9d2 100644 --- a/include/ccmath/internal/math/generic/builtins/power/cbrt.hpp +++ b/include/ccmath/internal/math/generic/builtins/power/cbrt.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_cbrt functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto cbrt_ct(T x) -> std::enable_if_t, T> + template constexpr auto cbrt_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_cbrtf(x); } - else if constexpr (std::is_same_v) { return __builtin_cbrt(x); } - else if constexpr (std::is_same_v) { return __builtin_cbrtl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_cbrtf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cbrt(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cbrtl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for cbrt"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto cbrt_rt(T x) -> std::enable_if_t, T> + template auto cbrt_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_cbrtf(x); } - else if constexpr (std::is_same_v) { return __builtin_cbrt(x); } - else if constexpr (std::is_same_v) { return __builtin_cbrtl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_cbrtf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cbrt(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cbrtl(x); + } else { static_assert(ccm::support::always_false, "Unsupported type for cbrt"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/power/hypot.hpp b/include/ccmath/internal/math/generic/builtins/power/hypot.hpp index a20e0f41..f47cb3c9 100644 --- a/include/ccmath/internal/math/generic/builtins/power/hypot.hpp +++ b/include/ccmath/internal/math/generic/builtins/power/hypot.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_hypot functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto hypot_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto hypot_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_hypotf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_hypot(x, y); } - else if constexpr (std::is_same_v) { return __builtin_hypotl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_hypotf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_hypot(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_hypotl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for hypot"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto hypot_rt(T x, T y) -> std::enable_if_t, T> + template auto hypot_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_hypotf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_hypot(x, y); } - else if constexpr (std::is_same_v) { return __builtin_hypotl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_hypotf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_hypot(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_hypotl(x, y); + } else { static_assert(ccm::support::always_false, "Unsupported type for hypot"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/power/pow.hpp b/include/ccmath/internal/math/generic/builtins/power/pow.hpp index acdf3d2c..3f52f435 100644 --- a/include/ccmath/internal/math/generic/builtins/power/pow.hpp +++ b/include/ccmath/internal/math/generic/builtins/power/pow.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_pow functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto pow_ct(T x, T y) -> std::enable_if_t, T> + template constexpr auto pow_ct(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_powf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_pow(x, y); } - else if constexpr (std::is_same_v) { return __builtin_powl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_powf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_pow(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_powl(x, y); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for pow"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto pow_rt(T x, T y) -> std::enable_if_t, T> + template auto pow_rt(T x, T y) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_powf(x, y); } - else if constexpr (std::is_same_v) { return __builtin_pow(x, y); } - else if constexpr (std::is_same_v) { return __builtin_powl(x, y); } - else + if constexpr (std::is_same_v) + { + return __builtin_powf(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_pow(x, y); + } else if constexpr (std::is_same_v) + { + return __builtin_powl(x, y); + } else { static_assert(ccm::support::always_false, "Unsupported type for pow"); return T{}; diff --git a/include/ccmath/internal/math/generic/builtins/power/sqrt.hpp b/include/ccmath/internal/math/generic/builtins/power/sqrt.hpp index c8c68fa7..c70e8cd2 100644 --- a/include/ccmath/internal/math/generic/builtins/power/sqrt.hpp +++ b/include/ccmath/internal/math/generic/builtins/power/sqrt.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_sqrt functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto sqrt_ct(T x) -> std::enable_if_t, T> + template constexpr auto sqrt_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sqrtf(x); } - else if constexpr (std::is_same_v) { return __builtin_sqrt(x); } - else if constexpr (std::is_same_v) { return __builtin_sqrtl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sqrtf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sqrt(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sqrtl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sqrt"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto sqrt_rt(T x) -> std::enable_if_t, T> + template auto sqrt_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sqrtf(x); } - else if constexpr (std::is_same_v) { return __builtin_sqrt(x); } - else if constexpr (std::is_same_v) { return __builtin_sqrtl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sqrtf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sqrt(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sqrtl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sqrt"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/acos.hpp b/include/ccmath/internal/math/generic/builtins/trig/acos.hpp index 11776386..730f8b18 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/acos.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/acos.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_acos functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto acos_ct(T x) -> std::enable_if_t, T> + template constexpr auto acos_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_acosf(x); } - else if constexpr (std::is_same_v) { return __builtin_acos(x); } - else if constexpr (std::is_same_v) { return __builtin_acosl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_acosf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acos(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acosl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for acos"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto acos_rt(T x) -> std::enable_if_t, T> + template auto acos_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_acosf(x); } - else if constexpr (std::is_same_v) { return __builtin_acos(x); } - else if constexpr (std::is_same_v) { return __builtin_acosl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_acosf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acos(x); + } else if constexpr (std::is_same_v) + { + return __builtin_acosl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for acos"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/asin.hpp b/include/ccmath/internal/math/generic/builtins/trig/asin.hpp index e513c507..30a414bb 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/asin.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/asin.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_asin functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto asin_ct(T x) -> std::enable_if_t, T> + template constexpr auto asin_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_asinf(x); } - else if constexpr (std::is_same_v) { return __builtin_asin(x); } - else if constexpr (std::is_same_v) { return __builtin_asinl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_asinf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asin(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for asin"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto asin_rt(T x) -> std::enable_if_t, T> + template auto asin_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_asinf(x); } - else if constexpr (std::is_same_v) { return __builtin_asin(x); } - else if constexpr (std::is_same_v) { return __builtin_asinl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_asinf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asin(x); + } else if constexpr (std::is_same_v) + { + return __builtin_asinl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for asin"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/atan.hpp b/include/ccmath/internal/math/generic/builtins/trig/atan.hpp index f01b9a95..8539e971 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/atan.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/atan.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_atan functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto atan_ct(T x) -> std::enable_if_t, T> + template constexpr auto atan_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atanf(x); } - else if constexpr (std::is_same_v) { return __builtin_atan(x); } - else if constexpr (std::is_same_v) { return __builtin_atanl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atanf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atan"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto atan_rt(T x) -> std::enable_if_t, T> + template auto atan_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atanf(x); } - else if constexpr (std::is_same_v) { return __builtin_atan(x); } - else if constexpr (std::is_same_v) { return __builtin_atanl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atanf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan(x); + } else if constexpr (std::is_same_v) + { + return __builtin_atanl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atan"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/atan2.hpp b/include/ccmath/internal/math/generic/builtins/trig/atan2.hpp index 0f851345..667c3533 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/atan2.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/atan2.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_atan2 functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto atan2_ct(T y, T x) -> std::enable_if_t, T> + template constexpr auto atan2_ct(T y, T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atan2f(y, x); } - else if constexpr (std::is_same_v) { return __builtin_atan2(y, x); } - else if constexpr (std::is_same_v) { return __builtin_atan2l(y, x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atan2f(y, x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan2(y, x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan2l(y, x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atan2"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto atan2_rt(T y, T x) -> std::enable_if_t, T> + template auto atan2_rt(T y, T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_atan2f(y, x); } - else if constexpr (std::is_same_v) { return __builtin_atan2(y, x); } - else if constexpr (std::is_same_v) { return __builtin_atan2l(y, x); } - else + if constexpr (std::is_same_v) + { + return __builtin_atan2f(y, x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan2(y, x); + } else if constexpr (std::is_same_v) + { + return __builtin_atan2l(y, x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for atan2"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/cos.hpp b/include/ccmath/internal/math/generic/builtins/trig/cos.hpp index 9e41867b..1f95abdc 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/cos.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/cos.hpp @@ -70,13 +70,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_cos functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto cos_ct(T x) -> std::enable_if_t, T> + template constexpr auto cos_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_cosf(x); } - else if constexpr (std::is_same_v) { return __builtin_cos(x); } - else if constexpr (std::is_same_v) { return __builtin_cosl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_cosf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cos(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cosl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for cos"); @@ -84,13 +89,18 @@ namespace ccm::builtin } } - template - auto cos_rt(T x) -> std::enable_if_t, T> + template auto cos_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_cosf(x); } - else if constexpr (std::is_same_v) { return __builtin_cos(x); } - else if constexpr (std::is_same_v) { return __builtin_cosl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_cosf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cos(x); + } else if constexpr (std::is_same_v) + { + return __builtin_cosl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for cos"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/sin.hpp b/include/ccmath/internal/math/generic/builtins/trig/sin.hpp index 45367ed0..64f5b1f0 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/sin.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/sin.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_sin functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto sin_ct(T x) -> std::enable_if_t, T> + template constexpr auto sin_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sinf(x); } - else if constexpr (std::is_same_v) { return __builtin_sin(x); } - else if constexpr (std::is_same_v) { return __builtin_sinl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sinf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sin(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sin"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto sin_rt(T x) -> std::enable_if_t, T> + template auto sin_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_sinf(x); } - else if constexpr (std::is_same_v) { return __builtin_sin(x); } - else if constexpr (std::is_same_v) { return __builtin_sinl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_sinf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sin(x); + } else if constexpr (std::is_same_v) + { + return __builtin_sinl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for sin"); diff --git a/include/ccmath/internal/math/generic/builtins/trig/tan.hpp b/include/ccmath/internal/math/generic/builtins/trig/tan.hpp index bfbdfcad..4f446a8e 100644 --- a/include/ccmath/internal/math/generic/builtins/trig/tan.hpp +++ b/include/ccmath/internal/math/generic/builtins/trig/tan.hpp @@ -74,13 +74,18 @@ namespace ccm::builtin * It exists only to allow for usage of __builtin_tan functions without triggering a compiler error * when the compiler does not support them. */ - template - constexpr auto tan_ct(T x) -> std::enable_if_t, T> + template constexpr auto tan_ct(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tanf(x); } - else if constexpr (std::is_same_v) { return __builtin_tan(x); } - else if constexpr (std::is_same_v) { return __builtin_tanl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tanf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tan(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for tan"); @@ -88,13 +93,18 @@ namespace ccm::builtin } } - template - auto tan_rt(T x) -> std::enable_if_t, T> + template auto tan_rt(T x) -> std::enable_if_t, T> { - if constexpr (std::is_same_v) { return __builtin_tanf(x); } - else if constexpr (std::is_same_v) { return __builtin_tan(x); } - else if constexpr (std::is_same_v) { return __builtin_tanl(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_tanf(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tan(x); + } else if constexpr (std::is_same_v) + { + return __builtin_tanl(x); + } else { // This should never be reached static_assert(ccm::support::always_false, "Unsupported type for tan"); diff --git a/include/ccmath/internal/math/generic/func/basic/abs_gen.hpp b/include/ccmath/internal/math/generic/func/basic/abs_gen.hpp index 5160f925..693a500c 100644 --- a/include/ccmath/internal/math/generic/func/basic/abs_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/abs_gen.hpp @@ -25,17 +25,22 @@ namespace ccm::gen * @param num Floating-point or integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> + template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> { using FPBits_t = support::fp::FPBits; const FPBits_t num_bits(num); // If num is NaN, return a quiet NaN. - if (CCM_UNLIKELY(num_bits.is_nan())) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(num_bits.is_nan())) + { + return std::numeric_limits::quiet_NaN(); + } // If num is equal to ±zero, return +zero. - if (num_bits.is_zero()) { return static_cast(0); } + if (num_bits.is_zero()) + { + return static_cast(0); + } // If num is less than zero, return -num, otherwise return num. return num < 0 ? -num : num; diff --git a/include/ccmath/internal/math/generic/func/basic/fdim_gen.hpp b/include/ccmath/internal/math/generic/func/basic/fdim_gen.hpp index d23e4be7..32142fc6 100644 --- a/include/ccmath/internal/math/generic/func/basic/fdim_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/fdim_gen.hpp @@ -27,20 +27,33 @@ namespace ccm::gen * @param y A floating-point or integer values * @return If successful, returns the positive difference between x and y. */ - template - constexpr auto fdim(T x, T y) -> std::enable_if_t, T> + template constexpr auto fdim(T x, T y) -> std::enable_if_t, T> { - if constexpr (builtin::has_constexpr_fdim) { return builtin::fdim_ct(x, y); } - else + if constexpr (builtin::has_constexpr_fdim) + { + return builtin::fdim_ct(x, y); + } else { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t x_bits(x); const FPBits_t y_bits(y); - if (CCM_UNLIKELY(x_bits.is_nan())) { return x; } - if (CCM_UNLIKELY(y_bits.is_nan())) { return y; } - if (x <= y) { return static_cast(+0.0); } - if (y < static_cast(0.0) && x > std::numeric_limits::max() + y) { return std::numeric_limits::infinity(); } + if (CCM_UNLIKELY(x_bits.is_nan())) + { + return x; + } + if (CCM_UNLIKELY(y_bits.is_nan())) + { + return y; + } + if (x <= y) + { + return static_cast(+0.0); + } + if (y < static_cast(0.0) && x > std::numeric_limits::max() + y) + { + return std::numeric_limits::infinity(); + } return x - y; } } diff --git a/include/ccmath/internal/math/generic/func/basic/fmod_gen.hpp b/include/ccmath/internal/math/generic/func/basic/fmod_gen.hpp index d7aaa88f..e782daf0 100644 --- a/include/ccmath/internal/math/generic/func/basic/fmod_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/fmod_gen.hpp @@ -21,8 +21,7 @@ namespace ccm /// @cond CCMATH_INTERNAL namespace internal::impl { - template - constexpr T fmod_impl_check(T x, T y) noexcept + template constexpr T fmod_impl_check(T x, T y) noexcept { // Special edge cases for floating-point types. if constexpr (std::numeric_limits::is_iec559) @@ -50,7 +49,10 @@ namespace ccm } // If y is ±∞ and x is finite, x is returned. - if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) { return x; } + if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) + { + return x; + } // If either argument is NaN, NaN is returned. if (CCM_UNLIKELY(x_bits.is_nan() || y_bits.is_nan())) @@ -63,9 +65,10 @@ namespace ccm return static_cast(x - (ccm::trunc(x / y) * y)); } - template > - constexpr TC fmod_impl_type_check(T x, U y) noexcept - { return fmod_impl_check(static_cast(x), static_cast(y)); } + template > constexpr TC fmod_impl_type_check(T x, U y) noexcept + { + return fmod_impl_check(static_cast(x), static_cast(y)); + } } // namespace internal::impl /// @endcond @@ -77,9 +80,10 @@ namespace ccm * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr Real fmod(Real x, Real y) - { return internal::impl::fmod_impl_check(x, y); } + template , bool> = true> constexpr Real fmod(Real x, Real y) + { + return internal::impl::fmod_impl_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -88,9 +92,10 @@ namespace ccm * @param y An integral value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr double fmod(Integer x, Integer y) - { return internal::impl::fmod_impl_type_check(x, y); } + template , bool> = true> constexpr double fmod(Integer x, Integer y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -100,9 +105,10 @@ namespace ccm * @param y A floating-point or integral value. * @return The floating-point remainder of the division operation x/y. */ - template - constexpr auto fmod(T x, T y) - { return internal::impl::fmod_impl_type_check(x, y); } + template constexpr auto fmod(T x, T y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -111,7 +117,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr float fmodf(float x, float y) - { return fmod(x, y); } + { + return fmod(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -120,7 +128,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr long double fmodl(long double x, long double y) - { return fmod(x, y); } + { + return fmod(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/generic/func/basic/max_gen.hpp b/include/ccmath/internal/math/generic/func/basic/max_gen.hpp index 67019f04..53f01467 100644 --- a/include/ccmath/internal/math/generic/func/basic/max_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/max_gen.hpp @@ -25,8 +25,7 @@ namespace ccm::gen * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T max(T x, T y) noexcept + template constexpr T max(T x, T y) noexcept { if constexpr (std::is_floating_point_v) { @@ -37,15 +36,27 @@ namespace ccm::gen const bool x_is_nan = x_bits.is_nan(); const bool y_is_nan = y_bits.is_nan(); - if (CCM_UNLIKELY(x_is_nan && y_is_nan)) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(x_is_nan && y_is_nan)) + { + return std::numeric_limits::quiet_NaN(); + } - if (CCM_UNLIKELY(x_is_nan)) { return y; } + if (CCM_UNLIKELY(x_is_nan)) + { + return y; + } - if (CCM_UNLIKELY(y_is_nan)) { return x; } + if (CCM_UNLIKELY(y_is_nan)) + { + return x; + } // Preserve IEC 60559 signed-zero behavior for fmax: if both are zero, // return +0 when either argument is +0. - if (CCM_UNLIKELY(x_bits.is_zero() && y_bits.is_zero())) { return x_bits.is_neg() ? y : x; } + if (CCM_UNLIKELY(x_bits.is_zero() && y_bits.is_zero())) + { + return x_bits.is_neg() ? y : x; + } } return (x > y) ? x : y; diff --git a/include/ccmath/internal/math/generic/func/basic/min_gen.hpp b/include/ccmath/internal/math/generic/func/basic/min_gen.hpp index f3564956..712ac327 100644 --- a/include/ccmath/internal/math/generic/func/basic/min_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/min_gen.hpp @@ -24,8 +24,7 @@ namespace ccm::gen * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T min(const T x, const T y) noexcept + template constexpr T min(const T x, const T y) noexcept { // If we are comparing floating point numbers, we need to check for NaN if constexpr (std::is_floating_point_v) @@ -37,12 +36,21 @@ namespace ccm::gen const bool x_is_nan = x_bits.is_nan(); const bool y_is_nan = y_bits.is_nan(); - if (CCM_UNLIKELY(x_is_nan)) { return y; } - if (CCM_UNLIKELY(y_is_nan)) { return x; } + if (CCM_UNLIKELY(x_is_nan)) + { + return y; + } + if (CCM_UNLIKELY(y_is_nan)) + { + return x; + } // Preserve IEC 60559 signed-zero behavior for fmin: if both are zero, // return -0 when either argument is -0. - if (CCM_UNLIKELY(x_bits.is_zero() && y_bits.is_zero())) { return x_bits.is_neg() ? x : y; } + if (CCM_UNLIKELY(x_bits.is_zero() && y_bits.is_zero())) + { + return x_bits.is_neg() ? x : y; + } } return (x < y) ? x : y; diff --git a/include/ccmath/internal/math/generic/func/basic/remainder_gen.hpp b/include/ccmath/internal/math/generic/func/basic/remainder_gen.hpp index 554050da..76e0e7a0 100644 --- a/include/ccmath/internal/math/generic/func/basic/remainder_gen.hpp +++ b/include/ccmath/internal/math/generic/func/basic/remainder_gen.hpp @@ -23,8 +23,7 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y. */ - template , bool> = true> - constexpr T remainder(T x, T y) + template , bool> = true> constexpr T remainder(T x, T y) { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t x_bits(x); @@ -52,9 +51,10 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y as a double. */ - template , bool> = true> - constexpr double remainder(Integer x, Integer y) - { return ccm::remainder(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double remainder(Integer x, Integer y) + { + return ccm::remainder(static_cast(x), static_cast(y)); + } /** * @brief Returns the remainder of the division of x by y. @@ -63,7 +63,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr float remainderf(float x, float y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } /** * @brief Returns the remainder of the division of x by y. @@ -72,7 +74,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr long double remainderl(long double x, long double y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp index b4f0c088..b4740906 100644 --- a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp +++ b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp @@ -42,7 +42,10 @@ namespace ccm::internal y_i64 &= 0x7fffffffffffffffULL; // If y is zero. - if (CCM_UNLIKELY(y_i64 == 0)) { return (x * y) / (x * y); } + if (CCM_UNLIKELY(y_i64 == 0)) + { + return (x * y) / (x * y); + } // GCC and Clang do not like comparing signed and unsigned integers. // The outcome of these comparisons is well-defined, so we can safely disable these warnings. @@ -100,8 +103,7 @@ namespace ccm::internal ++computed_quotient; } } - } - else + } else { const double y_half = 0.5 * y; if (x > y_half) @@ -122,13 +124,21 @@ namespace ccm::internal *quo = quotient_sign != 0U ? -computed_quotient : computed_quotient; // Make sure that the correct sign of zero results in round down mode. - if (x == 0.0) { x = 0.0; } - if (x_sign != 0U) { x = -x; } + if (x == 0.0) + { + x = 0.0; + } + if (x_sign != 0U) + { + x = -x; + } return x; } } // namespace impl constexpr double remquo_double(double x, double y, int * quo) noexcept - { return impl::remquo_double_impl(x, y, quo); } + { + return impl::remquo_double_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_float_impl.hpp b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_float_impl.hpp index 1f2e9090..b101d564 100644 --- a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_float_impl.hpp +++ b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_float_impl.hpp @@ -41,10 +41,16 @@ namespace ccm::internal y_i32 &= 0x7fffffff; // If y is zero. - if (CCM_UNLIKELY(y_i32 == 0)) { return (x * y) / (x * y); } + if (CCM_UNLIKELY(y_i32 == 0)) + { + return (x * y) / (x * y); + } // If x is not finite or y is NaN. - if (CCM_UNLIKELY(x_i32 >= 0x7f800000 || y_i32 > 0x7f800000)) { return (x * y) / (x * y); } // NOLINT(readability-simplify-boolean-expr) + if (CCM_UNLIKELY(x_i32 >= 0x7f800000 || y_i32 > 0x7f800000)) + { + return (x * y) / (x * y); + } // NOLINT(readability-simplify-boolean-expr) if (y_i32 <= 0x7dffffff) { @@ -83,8 +89,7 @@ namespace ccm::internal ++computed_quotient; } } - } - else + } else { const float y_half = 0.5F * y; if (x > y_half) @@ -101,13 +106,21 @@ namespace ccm::internal *quo = quotient_sign != 0 ? -computed_quotient : computed_quotient; // Make sure that the correct sign of zero results in round down mode. - if (x == 0.0F) { x = 0.0F; } - if (x_sign != 0U) { x = -x; } + if (x == 0.0F) + { + x = 0.0F; + } + if (x_sign != 0U) + { + x = -x; + } return x; } } // namespace impl constexpr float remquo_float(float x, float y, int * quo) noexcept - { return impl::remquo_float_impl(x, y, quo); } + { + return impl::remquo_float_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_ldouble_impl.hpp b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_ldouble_impl.hpp index b0627439..7d86260f 100644 --- a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_ldouble_impl.hpp +++ b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_ldouble_impl.hpp @@ -47,5 +47,7 @@ namespace ccm::internal } // namespace impl constexpr long double remquo_ldouble(long double x, long double y, int * quo) noexcept - { return impl::remquo_ldouble_impl(x, y, quo); } + { + return impl::remquo_ldouble_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/internal/math/generic/func/compare/fpclassify.hpp b/include/ccmath/internal/math/generic/func/compare/fpclassify.hpp index ebdf0c5a..f627716c 100644 --- a/include/ccmath/internal/math/generic/func/compare/fpclassify.hpp +++ b/include/ccmath/internal/math/generic/func/compare/fpclassify.hpp @@ -24,13 +24,24 @@ namespace ccm * @param num The number to classify * @return The classification of the number as an integer */ - template , bool> = true> - constexpr int fpclassify(T num) + template , bool> = true> constexpr int fpclassify(T num) { - if (ccm::isnan(num)) { return ccm::support::helpers::floating_point_defines::eFP_NAN; } - if (ccm::isinf(num)) { return ccm::support::helpers::floating_point_defines::eFP_INFINITE; } - if (num == static_cast(0)) { return ccm::support::helpers::floating_point_defines::eFP_ZERO; } - if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) { return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; } + if (ccm::isnan(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_NAN; + } + if (ccm::isinf(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_INFINITE; + } + if (num == static_cast(0)) + { + return ccm::support::helpers::floating_point_defines::eFP_ZERO; + } + if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) + { + return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; + } return ccm::support::helpers::floating_point_defines::eFP_NORMAL; } } // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/compare/isfinite.hpp b/include/ccmath/internal/math/generic/func/compare/isfinite.hpp index 6eb64321..c7c79d17 100644 --- a/include/ccmath/internal/math/generic/func/compare/isfinite.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isfinite.hpp @@ -21,11 +21,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(T num) + template , bool> = true> constexpr bool isfinite(T num) { - if constexpr (ccm::builtin::has_constexpr_isfinite) { return ccm::builtin::isfinite_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isfinite) + { + return ccm::builtin::isfinite_ct(num); + } else { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t num_bits(num); @@ -38,8 +39,7 @@ namespace ccm * @tparam Integer The type of the integer. * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(Integer /* x */) + template , bool> = true> constexpr bool isfinite(Integer /* x */) { return false; // All integers are finite } diff --git a/include/ccmath/internal/math/generic/func/compare/isgreater.hpp b/include/ccmath/internal/math/generic/func/compare/isgreater.hpp index 6daf6328..71eb684d 100644 --- a/include/ccmath/internal/math/generic/func/compare/isgreater.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreater(T x, T y) noexcept + template , bool> = true> constexpr bool isgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreater) { return ccm::builtin::isgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreater) + { + return ccm::builtin::isgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x > y); } diff --git a/include/ccmath/internal/math/generic/func/compare/isgreaterequal.hpp b/include/ccmath/internal/math/generic/func/compare/isgreaterequal.hpp index c2d8a3c2..4fc0942d 100644 --- a/include/ccmath/internal/math/generic/func/compare/isgreaterequal.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isgreaterequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreaterequal(T x, T y) noexcept + template , bool> = true> constexpr bool isgreaterequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreaterequal) { return ccm::builtin::isgreaterequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreaterequal) + { + return ccm::builtin::isgreaterequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x >= y); } diff --git a/include/ccmath/internal/math/generic/func/compare/isinf.hpp b/include/ccmath/internal/math/generic/func/compare/isinf.hpp index c66df7d9..7cc1d9c8 100644 --- a/include/ccmath/internal/math/generic/func/compare/isinf.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isinf.hpp @@ -24,11 +24,12 @@ namespace ccm * @param num The number to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(T num) noexcept + template , bool> = true> constexpr bool isinf(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isinf) { return ccm::builtin::isinf_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isinf) + { + return ccm::builtin::isinf_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -42,8 +43,7 @@ namespace ccm * @tparam Integer The type of the integer to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(Integer /* num */) noexcept + template , bool> = true> constexpr bool isinf(Integer /* num */) noexcept { return false; // Integers cannot be infinite } diff --git a/include/ccmath/internal/math/generic/func/compare/isless.hpp b/include/ccmath/internal/math/generic/func/compare/isless.hpp index 91972a5d..1478a63c 100644 --- a/include/ccmath/internal/math/generic/func/compare/isless.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isless.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second, false otherwise. */ - template , bool> = true> - constexpr bool isless(T x, T y) noexcept + template , bool> = true> constexpr bool isless(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isless) { return ccm::builtin::isless_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isless) + { + return ccm::builtin::isless_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y); } diff --git a/include/ccmath/internal/math/generic/func/compare/islessequal.hpp b/include/ccmath/internal/math/generic/func/compare/islessequal.hpp index fe2df043..ab9438bb 100644 --- a/include/ccmath/internal/math/generic/func/compare/islessequal.hpp +++ b/include/ccmath/internal/math/generic/func/compare/islessequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool islessequal(T x, T y) noexcept + template , bool> = true> constexpr bool islessequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessequal) { return ccm::builtin::islessequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessequal) + { + return ccm::builtin::islessequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x <= y); } diff --git a/include/ccmath/internal/math/generic/func/compare/islessgreater.hpp b/include/ccmath/internal/math/generic/func/compare/islessgreater.hpp index 97b092b3..cf389c6e 100644 --- a/include/ccmath/internal/math/generic/func/compare/islessgreater.hpp +++ b/include/ccmath/internal/math/generic/func/compare/islessgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second or greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool islessgreater(T x, T y) noexcept + template , bool> = true> constexpr bool islessgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessgreater) { return ccm::builtin::islessgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessgreater) + { + return ccm::builtin::islessgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y || x > y); } diff --git a/include/ccmath/internal/math/generic/func/compare/isnan.hpp b/include/ccmath/internal/math/generic/func/compare/isnan.hpp index 7533279a..4bdf7fce 100644 --- a/include/ccmath/internal/math/generic/func/compare/isnan.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isnan.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num The number to check. * @return True if the number is NaN, false otherwise. */ - template , bool> = true> - [[nodiscard]] constexpr bool isnan(T num) noexcept + template , bool> = true> [[nodiscard]] constexpr bool isnan(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnan) { return ccm::builtin::isnan_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnan) + { + return ccm::builtin::isnan_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -41,8 +42,7 @@ namespace ccm * @tparam Integer The type of the number to check. * @return False, as integers can never be NaN. */ - template , bool> = true> - [[nodiscard]] constexpr bool isnan(Integer /* num */) + template , bool> = true> [[nodiscard]] constexpr bool isnan(Integer /* num */) { return false; // Integers can never be NaN. } diff --git a/include/ccmath/internal/math/generic/func/compare/isnormal.hpp b/include/ccmath/internal/math/generic/func/compare/isnormal.hpp index 07df0e64..ded9d3bf 100644 --- a/include/ccmath/internal/math/generic/func/compare/isnormal.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isnormal.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(T num) noexcept + template , bool> = true> constexpr bool isnormal(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnormal) { return ccm::builtin::isnormal_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnormal) + { + return ccm::builtin::isnormal_ct(num); + } else { return num != static_cast(0) && !ccm::isnan(num) && !ccm::isinf(num) && ccm::abs(num) >= std::numeric_limits::min(); } @@ -39,8 +40,9 @@ namespace ccm * @param num A integer value to check. * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(Integer num) noexcept - { return num != static_cast(0); } + template , bool> = true> constexpr bool isnormal(Integer num) noexcept + { + return num != static_cast(0); + } } // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/compare/isunordered.hpp b/include/ccmath/internal/math/generic/func/compare/isunordered.hpp index fd5ac05b..463d30b1 100644 --- a/include/ccmath/internal/math/generic/func/compare/isunordered.hpp +++ b/include/ccmath/internal/math/generic/func/compare/isunordered.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value * @return true if either x or y is NaN, false otherwise. */ - template , bool> = true> - constexpr bool isunordered(T x, T y) noexcept + template , bool> = true> constexpr bool isunordered(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isunordered) { return ccm::builtin::isunordered_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isunordered) + { + return ccm::builtin::isunordered_ct(x, y); + } else { return ccm::isnan(x) || ccm::isnan(y); } @@ -53,8 +54,7 @@ namespace ccm * @param y Value of the right-hand side of the comparison. * @return true if either x or y is NaN, false otherwise. */ - template - constexpr bool isunordered(T x, U y) noexcept + template constexpr bool isunordered(T x, U y) noexcept { using shared_type = std::common_type_t; return static_cast(isunordered(static_cast(x), static_cast(y))); diff --git a/include/ccmath/internal/math/generic/func/compare/signbit.hpp b/include/ccmath/internal/math/generic/func/compare/signbit.hpp index 4bdf6b70..4832e7ad 100644 --- a/include/ccmath/internal/math/generic/func/compare/signbit.hpp +++ b/include/ccmath/internal/math/generic/func/compare/signbit.hpp @@ -30,11 +30,12 @@ namespace ccm * @warning ccm::signbit may fail * to compile if you are not using the cmake build for ccmath or do not have access to __builtin_bit_cast. */ - template , bool> = true> - [[nodiscard]] constexpr bool signbit(T num) noexcept + template , bool> = true> [[nodiscard]] constexpr bool signbit(T num) noexcept { - if constexpr (builtin::has_constexpr_signbit) { return builtin::signbit_ct(num); } - else + if constexpr (builtin::has_constexpr_signbit) + { + return builtin::signbit_ct(num); + } else { // Check for the sign of +0.0 and -0.0 with bit_cast if (num == static_cast(0) || ccm::isnan(num)) diff --git a/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp b/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp index 8481f0a3..6bceaed2 100644 --- a/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/exp2_gen.hpp @@ -18,12 +18,15 @@ namespace ccm::gen { // TODO(IanP): Wire internal poly_approx_* kernels here instead of delegating to exp2_*_impl. - template , bool> = true> - constexpr T exp2_gen(T num) noexcept // NOLINT(bugprone-exception-escape) + template , bool> = true> constexpr T exp2_gen(T num) noexcept // NOLINT(bugprone-exception-escape) { - if constexpr (std::is_same_v) { return ccm::internal::exp2_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::exp2_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::exp2_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::exp2_double(num); + } else { return static_cast(ccm::internal::exp2_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp b/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp index 66a51e97..8b6c7357 100644 --- a/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/exp_gen.hpp @@ -17,12 +17,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T exp_gen(T num) noexcept // NOLINT(bugprone-exception-escape) + template , bool> = true> constexpr T exp_gen(T num) noexcept // NOLINT(bugprone-exception-escape) { - if constexpr (std::is_same_v) { return ccm::internal::impl::exp_float_impl(num); } - else if constexpr (std::is_same_v) { return ccm::internal::impl::exp_double_impl(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::exp_float_impl(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::impl::exp_double_impl(num); + } else { return static_cast(ccm::internal::impl::exp_double_impl(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/expm1_gen.hpp b/include/ccmath/internal/math/generic/func/expo/expm1_gen.hpp index 898df938..ffb8eb0d 100644 --- a/include/ccmath/internal/math/generic/func/expo/expm1_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/expm1_gen.hpp @@ -16,12 +16,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T expm1_gen(T num) noexcept + template , bool> = true> constexpr T expm1_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::expm1_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::expm1_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::expm1_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::expm1_double(num); + } else { return static_cast(ccm::internal::expm1_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/log10_gen.hpp b/include/ccmath/internal/math/generic/func/expo/log10_gen.hpp index 3938956c..3cd57a91 100644 --- a/include/ccmath/internal/math/generic/func/expo/log10_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/log10_gen.hpp @@ -16,12 +16,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T log10_gen(T num) noexcept + template , bool> = true> constexpr T log10_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::log10_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::log10_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::log10_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::log10_double(num); + } else { return static_cast(ccm::internal::log10_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/log1p_gen.hpp b/include/ccmath/internal/math/generic/func/expo/log1p_gen.hpp index 188a6d75..cb40a9c4 100644 --- a/include/ccmath/internal/math/generic/func/expo/log1p_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/log1p_gen.hpp @@ -16,12 +16,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T log1p_gen(T num) noexcept + template , bool> = true> constexpr T log1p_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::log1p_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::log1p_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::log1p_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::log1p_double(num); + } else { return static_cast(ccm::internal::log1p_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/log2_gen.hpp b/include/ccmath/internal/math/generic/func/expo/log2_gen.hpp index c0ca4562..1405edff 100644 --- a/include/ccmath/internal/math/generic/func/expo/log2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/log2_gen.hpp @@ -17,12 +17,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T log2_gen(T num) noexcept + template , bool> = true> constexpr T log2_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::log2_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::log2_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::log2_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::log2_double(num); + } else { return static_cast(ccm::internal::log2_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/expo/log_gen.hpp b/include/ccmath/internal/math/generic/func/expo/log_gen.hpp index aa902b3e..8500c0c6 100644 --- a/include/ccmath/internal/math/generic/func/expo/log_gen.hpp +++ b/include/ccmath/internal/math/generic/func/expo/log_gen.hpp @@ -17,12 +17,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T log_gen(T num) noexcept + template , bool> = true> constexpr T log_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::log_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::log_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::log_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::log_double(num); + } else { return static_cast(ccm::internal::log_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/copysign_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/copysign_gen.hpp index fc936514..089684dc 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/copysign_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/copysign_gen.hpp @@ -16,12 +16,14 @@ namespace ccm { - template , bool> = true> - constexpr T copysign(T mag, T sgn) + template , bool> = true> constexpr T copysign(T mag, T sgn) { if (ccm::isnan(mag) || ccm::isnan(sgn)) { - if (ccm::signbit(sgn)) { return -std::numeric_limits::quiet_NaN(); } + if (ccm::signbit(sgn)) + { + return -std::numeric_limits::quiet_NaN(); + } return std::numeric_limits::quiet_NaN(); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp index 7ae6c57c..dec57464 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/frexp_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr T frexp_gen(T x, int & exp) + template , bool> = true> constexpr T frexp_gen(T x, int & exp) { - if constexpr (std::is_same_v) { return ccm::internal::impl::frexp_impl(x, exp); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return ccm::internal::impl::frexp_impl(x, exp); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::frexp_impl(x, exp); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return ccm::internal::impl::frexp_impl(x, exp); + } else { return static_cast(ccm::internal::impl::frexp_impl(static_cast(x), exp)); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp index e99cf744..aa68203b 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/ilogb_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr int ilogb_gen(T num) noexcept + template , bool> = true> constexpr int ilogb_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::ilogb_impl(num); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return ccm::internal::impl::ilogb_impl(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::ilogb_impl(num); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return ccm::internal::impl::ilogb_impl(num); + } else { return ccm::internal::impl::ilogb_impl(static_cast(num)); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/frexp_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/frexp_impl.hpp index 429931af..c0a86b66 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/frexp_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/frexp_impl.hpp @@ -15,8 +15,7 @@ namespace ccm::internal::impl { - template - constexpr T frexp_impl(T x, int & exp) noexcept + template constexpr T frexp_impl(T x, int & exp) noexcept { using fp_bits_t = ccm::support::fp::FPBits; @@ -28,7 +27,10 @@ namespace ccm::internal::impl return x; } - if (CCM_UNLIKELY(bits.is_inf_or_nan())) { return x; } + if (CCM_UNLIKELY(bits.is_inf_or_nan())) + { + return x; + } int exponent = bits.get_exponent(); diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp index 1e04a0ef..c9e26ab2 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/ilogb_impl.hpp @@ -40,8 +40,7 @@ namespace ccm::internal::impl #endif } - template - constexpr int ilogb_impl(T x) noexcept + template constexpr int ilogb_impl(T x) noexcept { using fp_bits_t = ccm::support::fp::FPBits; diff --git a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp index 95637c2d..0f7be830 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/impl/logb_impl.hpp @@ -18,16 +18,21 @@ namespace ccm::internal::impl { - template - constexpr T logb_impl(T x) noexcept + template constexpr T logb_impl(T x) noexcept { using fp_bits_t = ccm::support::fp::FPBits; fp_bits_t const bits(x); - if (CCM_UNLIKELY(bits.is_nan())) { return x; } + if (CCM_UNLIKELY(bits.is_nan())) + { + return x; + } - if (CCM_UNLIKELY(bits.is_inf())) { return fp_bits_t::inf().get_val(); } + if (CCM_UNLIKELY(bits.is_inf())) + { + return fp_bits_t::inf().get_val(); + } if (CCM_UNLIKELY(bits.is_zero())) { diff --git a/include/ccmath/internal/math/generic/func/fmanip/ldexp_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/ldexp_gen.hpp index 90ac084e..5e46b644 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/ldexp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/ldexp_gen.hpp @@ -36,14 +36,22 @@ namespace ccm * @return If no errors occur, num multiplied by 2 to the power of exp (num×2exp) is returned.\nIf a range error due to overflow occurs, ±HUGE_VAL, * ±HUGE_VALF, or ±HUGE_VALL is returned.\nIf a range error due to underflow occurs, the correct result (after rounding) is returned. */ - template , bool> = true> - constexpr T ldexp(T num, int exp) noexcept + template , bool> = true> constexpr T ldexp(T num, int exp) noexcept { // NOLINTNEXTLINE #if defined(CCMATH_HAS_CONSTEXPR_BUILTIN_LDEXP) || CCM_HAS_CONST_BUILTIN(__builtin_ldexp) - if constexpr (std::is_same_v) { return __builtin_ldexpf(num, exp); } - if constexpr (std::is_same_v) { return __builtin_ldexp(num, exp); } - if constexpr (std::is_same_v) { return __builtin_ldexpl(num, exp); } + if constexpr (std::is_same_v) + { + return __builtin_ldexpf(num, exp); + } + if constexpr (std::is_same_v) + { + return __builtin_ldexp(num, exp); + } + if constexpr (std::is_same_v) + { + return __builtin_ldexpl(num, exp); + } return static_cast(__builtin_ldexpl(num, exp)); #else return support::helpers::internal_ldexp(num, exp); @@ -117,9 +125,10 @@ namespace ccm * @return If no errors occur, num multiplied by 2 to the power of exp (num×2exp) is returned.\nIf a range error due to overflow occurs, ±HUGE_VAL, * ±HUGE_VALF, or ±HUGE_VALL is returned.\nIf a range error due to underflow occurs, the correct result (after rounding) is returned. */ - template , bool> = true> - constexpr double ldexp(Integer num, int exp) noexcept - { return ccm::ldexp(static_cast(num), exp); } + template , bool> = true> constexpr double ldexp(Integer num, int exp) noexcept + { + return ccm::ldexp(static_cast(num), exp); + } /** * @brief Returns the floating-point remainder of the division operation x/y. * @note @@ -128,7 +137,9 @@ namespace ccm * @return The result of parameter num multiplied by 2 to the power of parameter exp. */ constexpr float ldexpf(float num, int exp) noexcept - { return ccm::ldexp(num, exp); } + { + return ccm::ldexp(num, exp); + } /** * @brief Returns the floating-point remainder of the division operation x/y. * @note diff --git a/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp index 28376791..c853ce3a 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/logb_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr T logb_gen(T num) noexcept + template , bool> = true> constexpr T logb_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::logb_impl(num); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return ccm::internal::impl::logb_impl(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::logb_impl(num); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return ccm::internal::impl::logb_impl(num); + } else { return static_cast(ccm::internal::impl::logb_impl(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp index 2c507702..ee422775 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/modf_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr T modf_gen(T x, T * iptr) noexcept + template , bool> = true> constexpr T modf_gen(T x, T * iptr) noexcept { - if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } - else + if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } else { double integer_part{}; const double fractional = internal::impl::modf_impl(static_cast(x), &integer_part); diff --git a/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp index 9aeb4bca..5356b28c 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/nextafter_gen.hpp @@ -24,32 +24,47 @@ namespace ccm::gen { ccm::support::fp::FPBits from_bits(from); - if (from_bits.is_nan()) { return from; } + if (from_bits.is_nan()) + { + return from; + } ccm::support::fp::FPBits const to_bits(to); - if (to_bits.is_nan()) { return static_cast(to); } + if (to_bits.is_nan()) + { + return static_cast(to); + } // This evaluation will only work so long as 'Arithmetic2' is a greater than or equal to the precision of 'Arithmetic1' // If 'Arithmetic2' is a lower precision than 'Arithmetic1', then the following statement could incorrectly evaluate to true // This is due to loss of precision in the conversion of 'from' to Arithmetic2 - if (static_cast(from) == to) { return static_cast(to); } + if (static_cast(from) == to) + { + return static_cast(to); + } using Storage_t = typename ccm::support::fp::FPBits::storage_type; if (from != TFrom(0)) { - if ((static_cast(from) < to) == (from > TFrom(0))) { from_bits = ccm::support::fp::FPBits(Storage_t(from_bits.uintval() + 1)); } - else + if ((static_cast(from) < to) == (from > TFrom(0))) + { + from_bits = ccm::support::fp::FPBits(Storage_t(from_bits.uintval() + 1)); + } else { from_bits = ccm::support::fp::FPBits(Storage_t(from_bits.uintval() - 1)); } - } - else + } else { from_bits = ccm::support::fp::FPBits::min_subnormal(to_bits.sign()); } - if (from_bits.is_subnormal()) { ccm::support::fenv::raise_except_if_required(FE_UNDERFLOW | FE_INEXACT); } - else if (from_bits.is_inf()) { ccm::support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); } + if (from_bits.is_subnormal()) + { + ccm::support::fenv::raise_except_if_required(FE_UNDERFLOW | FE_INEXACT); + } else if (from_bits.is_inf()) + { + ccm::support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); + } return from_bits.get_val(); } @@ -61,14 +76,26 @@ namespace ccm::gen { using FPBits_t = ccm::support::fp::FPBits; FPBits_t from_bits(from); - if (from_bits.is_nan()) { return from; } + if (from_bits.is_nan()) + { + return from; + } const FPBits_t to_bits(to); - if (to_bits.is_nan()) { return to; } - if (from == to) { return to; } + if (to_bits.is_nan()) + { + return to; + } + if (from == to) + { + return to; + } // Convert pseudo subnormal number to normal number. - if (static_cast(from_bits.get_implicit_bit()) == 1 && from_bits.is_subnormal()) { from_bits.set_biased_exponent(1); } + if (static_cast(from_bits.get_implicit_bit()) == 1 && from_bits.is_subnormal()) + { + from_bits.set_biased_exponent(1); + } using Storage_t = FPBits_t::storage_type; constexpr Storage_t fraction_mask = FPBits_t::fraction_mask; @@ -76,8 +103,7 @@ namespace ccm::gen if (from == 0.0L) // +/-0.0 { from_bits = FPBits_t::min_subnormal(from > to ? types::Sign::NEG : types::Sign::POS); - } - else if (from < 0.0L) + } else if (from < 0.0L) { if (to < from) // toward -Inf { @@ -85,79 +111,83 @@ namespace ccm::gen { // We deal with normal/subnormal boundary separately to avoid dealing with the implicit bit. from_bits = FPBits_t::min_normal(types::Sign::NEG); - } - else if (from_bits.get_mantissa() == fraction_mask) + } else if (from_bits.get_mantissa() == fraction_mask) { from_bits.set_mantissa(0); // When incrementing the exponent, there is a possibility that the value will overflow to infinity which is expected. // Since NaNs are handled separately, it will never overflow "beyond" infinity. from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); - if (from_bits.is_inf()) { support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); } + if (from_bits.is_inf()) + { + support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); + } return from_bits.get_val(); - } - else + } else { from_bits = FPBits_t(static_cast(from_bits.uintval() + 1)); } - } - else // toward +Inf + } else // toward +Inf { if (from_bits == FPBits_t::min_normal(types::Sign::NEG)) { // We deal with normal/subnormal boundary separately to avoid // dealing with the implicit bit. from_bits = FPBits_t::max_subnormal(types::Sign::NEG); - } - else if (from_bits.get_mantissa() == 0) + } else if (from_bits.get_mantissa() == 0) { from_bits.set_mantissa(fraction_mask); // Since we handle from == 0 separately, we can decrement the exponent and not worry about underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); return from_bits.get_val(); - } - else + } else { from_bits = FPBits_t(static_cast(from_bits.uintval() - 1)); } } - } - else + } else { if (to < from) // toward -Inf { - if (from_bits == FPBits_t::min_normal(types::Sign::POS)) { from_bits = FPBits_t::max_subnormal(types::Sign::POS); } - else if (from_bits.get_mantissa() == 0) + if (from_bits == FPBits_t::min_normal(types::Sign::POS)) + { + from_bits = FPBits_t::max_subnormal(types::Sign::POS); + } else if (from_bits.get_mantissa() == 0) { from_bits.set_mantissa(fraction_mask); // Since we handle from == 0 separately, we can decrement the exponent and not worry about underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); return from_bits.get_val(); - } - else + } else { from_bits = FPBits_t(static_cast(from_bits.uintval() - 1)); } - } - else // toward +Inf + } else // toward +Inf { - if (from_bits == FPBits_t::max_subnormal(types::Sign::POS)) { from_bits = FPBits_t::min_normal(types::Sign::POS); } - else if (from_bits.get_mantissa() == fraction_mask) + if (from_bits == FPBits_t::max_subnormal(types::Sign::POS)) + { + from_bits = FPBits_t::min_normal(types::Sign::POS); + } else if (from_bits.get_mantissa() == fraction_mask) { from_bits.set_mantissa(0); // When incrementing the exponent, there is a possibility that the value will overflow to infinity which is expected. // Since NaNs are handled separately, it will never overflow "beyond" infinity. from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); - if (from_bits.is_inf()) { support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); } + if (from_bits.is_inf()) + { + support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); + } return from_bits.get_val(); - } - else + } else { from_bits = FPBits_t(static_cast(from_bits.uintval() + 1)); } } } - if (!from_bits.get_implicit_bit()) { support::fenv::raise_except_if_required(FE_UNDERFLOW | FE_INEXACT); } + if (!from_bits.get_implicit_bit()) + { + support::fenv::raise_except_if_required(FE_UNDERFLOW | FE_INEXACT); + } return from_bits.get_val(); } diff --git a/include/ccmath/internal/math/generic/func/fmanip/scalbn_gen.hpp b/include/ccmath/internal/math/generic/func/fmanip/scalbn_gen.hpp index 940d069a..3b75458b 100644 --- a/include/ccmath/internal/math/generic/func/fmanip/scalbn_gen.hpp +++ b/include/ccmath/internal/math/generic/func/fmanip/scalbn_gen.hpp @@ -23,18 +23,35 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, int exp) noexcept + template , bool> = true> constexpr T scalbn(T num, int exp) noexcept { #if defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__) - if constexpr (std::is_same_v) { return __builtin_scalbnf(num, exp); } - if constexpr (std::is_same_v) { return __builtin_scalbn(num, exp); } - if constexpr (std::is_same_v) { return __builtin_scalbnl(num, exp); } + if constexpr (std::is_same_v) + { + return __builtin_scalbnf(num, exp); + } + if constexpr (std::is_same_v) + { + return __builtin_scalbn(num, exp); + } + if constexpr (std::is_same_v) + { + return __builtin_scalbnl(num, exp); + } return static_cast(__builtin_scalbnl(num, exp)); #else - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); #endif } @@ -46,18 +63,35 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, long exp) noexcept + template , bool> = true> constexpr T scalbn(T num, long exp) noexcept { #if defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__) - if constexpr (std::is_same_v) { return __builtin_scalbnf(num, static_cast(exp)); } - if constexpr (std::is_same_v) { return __builtin_scalbn(num, static_cast(exp)); } - if constexpr (std::is_same_v) { return __builtin_scalbnl(num, static_cast(exp)); } + if constexpr (std::is_same_v) + { + return __builtin_scalbnf(num, static_cast(exp)); + } + if constexpr (std::is_same_v) + { + return __builtin_scalbn(num, static_cast(exp)); + } + if constexpr (std::is_same_v) + { + return __builtin_scalbnl(num, static_cast(exp)); + } return static_cast(__builtin_scalbnl(num, static_cast(exp))); #else - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); #endif } @@ -69,9 +103,10 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, int exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, int exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -80,9 +115,10 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, long exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, long exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -91,7 +127,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -100,7 +138,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -109,7 +149,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -118,5 +160,7 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } } // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/acosh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/acosh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/acosh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/acosh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/asinh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/asinh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/asinh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/asinh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/atanh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/atanh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/atanh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/atanh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/cosh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/cosh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/cosh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/cosh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/sinh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/sinh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/sinh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/sinh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/hyper/tanh_gen.hpp b/include/ccmath/internal/math/generic/func/hyper/tanh_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/hyper/tanh_gen.hpp +++ b/include/ccmath/internal/math/generic/func/hyper/tanh_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp b/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp index f2ada2bc..4e2c2f5b 100644 --- a/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp +++ b/include/ccmath/internal/math/generic/func/misc/lerp_gen.hpp @@ -14,8 +14,7 @@ namespace ccm { - template - constexpr T lerp(T a, T b, T t) noexcept + template constexpr T lerp(T a, T b, T t) noexcept { // Optimized version of lerp // https://developer.nvidia.com/blog/lerp-faster-cuda/ @@ -38,12 +37,21 @@ namespace ccm "Do not use ccm::lerp_old it is only being kept as a fallback until ccm::lerp has been validated as conforming to std::lerp")]] constexpr T old_lerp(T a, T b, T t) noexcept { - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return (t * b) + ((1 - t) * a); } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) + { + return (t * b) + ((1 - t) * a); + } - if (t == 1) { return b; } + if (t == 1) + { + return b; + } const T x = a + (t * (b - a)); - if ((t > 1) == (b > a)) { return b < x ? x : b; } + if ((t > 1) == (b > a)) + { + return b < x ? x : b; + } return x < b ? x : b; } diff --git a/include/ccmath/internal/math/generic/func/nearest/ceil_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/ceil_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/nearest/ceil_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/ceil_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/nearest/floor_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/floor_gen.hpp index 6f5fd37c..b12d8904 100644 --- a/include/ccmath/internal/math/generic/func/nearest/floor_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/floor_gen.hpp @@ -20,37 +20,56 @@ namespace ccm { namespace internal::impl { - template - constexpr T floor_pos_impl(T num) noexcept + template constexpr T floor_pos_impl(T num) noexcept { // At or above 1/epsilon, num is already integral (or indistinguishable from one) in T. constexpr auto max_comparable_val = T(1) / std::numeric_limits::epsilon(); - if (num >= max_comparable_val) { return num; } + if (num >= max_comparable_val) + { + return num; + } T result = 1; - if (result == num) { return num; } + if (result == num) + { + return num; + } // Bracket num by doubling from 1, then step down to the floor. - while (result < num) { result *= 2; } + while (result < num) + { + result *= 2; + } - while (result > num) { --result; } + while (result > num) + { + --result; + } return result; } - template - constexpr T floor_neg_impl(T num) noexcept + template constexpr T floor_neg_impl(T num) noexcept { T result = -1; if (result > num) { // Bracket num by doubling from -1, then adjust to the largest integer not greater than num. - while (result > num) { result *= 2; } - while (result < num) { ++result; } - if (result != num) { --result; } + while (result > num) + { + result *= 2; + } + while (result < num) + { + ++result; + } + if (result != num) + { + --result; + } } return result; @@ -63,16 +82,21 @@ namespace ccm * @param num A floating-point or integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr T floor(T num) noexcept + template , bool> = true> constexpr T floor(T num) noexcept { // If num is NaN, NaN is returned. // If num is ±∞ or ±0, num is returned, unmodified. - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } // TODO: This approach should work with long double perfectly, but is slow. // at some consider adding a faster approach that is just as consistent. - if (num > 0) { return internal::impl::floor_pos_impl(num); } + if (num > 0) + { + return internal::impl::floor_pos_impl(num); + } return internal::impl::floor_neg_impl(num); } @@ -81,8 +105,7 @@ namespace ccm * @param num A integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr double floor(Integer num) noexcept + template , bool> = true> constexpr double floor(Integer num) noexcept { return static_cast(num); // All integers already have a floor value. Just cast to double and return. } @@ -93,7 +116,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr float floorf(float num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } /** * @brief Computes the largest integer value not greater than num. @@ -101,7 +126,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr double floorl(double num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/internal/math/generic/func/nearest/nearbyint_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/nearbyint_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/nearest/nearbyint_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/nearbyint_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/nearest/rint_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/rint_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/nearest/rint_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/rint_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/nearest/round_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/round_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/nearest/round_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/round_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/nearest/trunc_gen.hpp b/include/ccmath/internal/math/generic/func/nearest/trunc_gen.hpp index 05fae403..431b65e3 100644 --- a/include/ccmath/internal/math/generic/func/nearest/trunc_gen.hpp +++ b/include/ccmath/internal/math/generic/func/nearest/trunc_gen.hpp @@ -21,8 +21,7 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr T trunc(T num) noexcept + template , bool> = true> constexpr T trunc(T num) noexcept { using FPBits_t = ccm::support::fp::FPBits; using Storage_t = typename FPBits_t::storage_type; @@ -31,18 +30,30 @@ namespace ccm // If x == ±∞ then return num // If x == ±NaN then return num - if (CCM_UNLIKELY(bits.is_inf_or_nan())) { return num; } + if (CCM_UNLIKELY(bits.is_inf_or_nan())) + { + return num; + } // If x == ±0 then return num - if (CCM_UNLIKELY(num == 0.0)) { return num; } + if (CCM_UNLIKELY(num == 0.0)) + { + return num; + } const int exponent = bits.get_exponent(); // If the exponent is greater than or equal to the fraction length, then we will return the number as is since it is already an integer. - if (exponent >= FPBits_t::fraction_length) { return num; } + if (exponent >= FPBits_t::fraction_length) + { + return num; + } // If our exponent is set up such that the abs(x) is less than 1 we will instead return 0. - if (exponent <= -1) { return FPBits_t::zero(bits.sign()).get_val(); } + if (exponent <= -1) + { + return FPBits_t::zero(bits.sign()).get_val(); + } // Perform the truncation const int trimming_size = FPBits_t::fraction_length - exponent; @@ -57,9 +68,10 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr double trunc(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double trunc(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Specialization for float that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to x. @@ -67,7 +79,9 @@ namespace ccm * @return Returns a truncated float. */ constexpr float truncf(float num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } /** * @brief Specialization for long double that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to @@ -76,5 +90,7 @@ namespace ccm * @return Returns a truncated long double. */ constexpr long double truncl(long double num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } } // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp b/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp index 6c42b89b..d8efbe29 100644 --- a/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/cbrt_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr T cbrt_gen(T num) noexcept + template , bool> = true> constexpr T cbrt_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::cbrt_impl(num); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return ccm::internal::impl::cbrt_impl(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::cbrt_impl(num); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return ccm::internal::impl::cbrt_impl(num); + } else { return static_cast(ccm::internal::impl::cbrt_impl(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp b/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp index 5d946dbc..e9df8a6c 100644 --- a/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/hypot_gen.hpp @@ -16,12 +16,16 @@ namespace ccm::gen { - template , bool> = true> - constexpr T hypot_gen(T x, T y) noexcept + template , bool> = true> constexpr T hypot_gen(T x, T y) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::impl::hypot_impl(x, y); } // NOLINT(bugprone-branch-clone) - else if constexpr (std::is_same_v) { return ccm::internal::impl::hypot_impl(x, y); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::impl::hypot_impl(x, y); + } // NOLINT(bugprone-branch-clone) + else if constexpr (std::is_same_v) + { + return ccm::internal::impl::hypot_impl(x, y); + } else { return static_cast(ccm::internal::impl::hypot_impl(static_cast(x), static_cast(y))); } diff --git a/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp b/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp index f6558fd5..5e205f9a 100644 --- a/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/impl/cbrt_impl.hpp @@ -23,18 +23,26 @@ namespace ccm::internal::impl { - template - constexpr T cbrt_impl(T x) noexcept + template constexpr T cbrt_impl(T x) noexcept { using fp_bits_t = ccm::support::fp::FPBits; fp_bits_t const bits(x); - if (CCM_UNLIKELY(bits.is_nan())) { return x; } + if (CCM_UNLIKELY(bits.is_nan())) + { + return x; + } - if (CCM_UNLIKELY(bits.is_inf())) { return x; } + if (CCM_UNLIKELY(bits.is_inf())) + { + return x; + } - if (CCM_UNLIKELY(bits.is_zero())) { return x; } + if (CCM_UNLIKELY(bits.is_zero())) + { + return x; + } const bool negative = bits.is_neg(); const T ax = ccm::fabs(x); diff --git a/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp b/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp index babcd9a3..c7d232f8 100644 --- a/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/impl/hypot_impl.hpp @@ -21,20 +21,31 @@ namespace ccm::internal::impl { - template - constexpr T hypot_impl(T x, T y) noexcept + template constexpr T hypot_impl(T x, T y) noexcept { using fp_bits_t = ccm::support::fp::FPBits; - if (CCM_UNLIKELY(ccm::isinf(x) || ccm::isinf(y))) { return fp_bits_t::inf().get_val(); } + if (CCM_UNLIKELY(ccm::isinf(x) || ccm::isinf(y))) + { + return fp_bits_t::inf().get_val(); + } - if (CCM_UNLIKELY(ccm::isnan(x))) { return x + y; } - if (CCM_UNLIKELY(ccm::isnan(y))) { return x + y; } + if (CCM_UNLIKELY(ccm::isnan(x))) + { + return x + y; + } + if (CCM_UNLIKELY(ccm::isnan(y))) + { + return x + y; + } x = ccm::fabs(x); y = ccm::fabs(y); - if (CCM_UNLIKELY(x == static_cast(0) && y == static_cast(0))) { return static_cast(0); } + if (CCM_UNLIKELY(x == static_cast(0) && y == static_cast(0))) + { + return static_cast(0); + } if (y > x) { diff --git a/include/ccmath/internal/math/generic/func/power/pow_gen.hpp b/include/ccmath/internal/math/generic/func/power/pow_gen.hpp index 9c1e50ed..3a518b9d 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_gen.hpp @@ -18,14 +18,19 @@ namespace ccm::gen { - template , bool> = true> - constexpr T pow_gen(T base, T exp) noexcept + template , bool> = true> constexpr T pow_gen(T base, T exp) noexcept { // TODO: Maybe add more specific optimizations for integers? - if constexpr (std::is_same_v) { return impl::powf_impl(base, exp); } - else if constexpr (std::is_same_v) { return impl::pow_impl(base, exp); } - else if constexpr (std::is_same_v) { return powl_gen(base, exp); } - else + if constexpr (std::is_same_v) + { + return impl::powf_impl(base, exp); + } else if constexpr (std::is_same_v) + { + return impl::pow_impl(base, exp); + } else if constexpr (std::is_same_v) + { + return powl_gen(base, exp); + } else { return static_cast(impl::pow_impl(static_cast(base), static_cast(exp))); } diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp index 5dcf2d85..d59d5c5b 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_data.hpp @@ -2,6 +2,4 @@ #pragma once namespace ccm::gen::impl::internal::impl -{ - -} // namespace ccm::gen::impl::internal::impl +{} // namespace ccm::gen::impl::internal::impl diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp index faef6b0b..b9d58e40 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_impl.hpp @@ -143,15 +143,23 @@ namespace ccm::gen::impl DoubleDouble result{ 1.0, 0.0 }; while (e > 0U) { - if ((e & 1U) != 0U) { result = mul(result, factor); } + if ((e & 1U) != 0U) + { + result = mul(result, factor); + } e >>= 1U; - if (e > 0U) { factor = mul(factor, factor); } + if (e > 0U) + { + factor = mul(factor, factor); + } } return result; } constexpr DoubleDouble ipow(double base, std::uint64_t e) noexcept - { return ipow(DoubleDouble{ base, 0.0 }, e); } + { + return ipow(DoubleDouble{ base, 0.0 }, e); + } // sqrt(x) refined to a double-double via one Newton correction on a correctly // rounded seed: x^(k + 1/2) = sqrt(x)^(2k + 1) is then exact integer exponentiation. @@ -179,17 +187,22 @@ namespace ccm::gen::impl const DoubleDouble divisor = ccm::types::exact_add(p.hi, p.lo); const double r0 = 1.0 / divisor.hi; const FPBits_t r0_bits(r0); - if (r0 == 0.0 || !r0_bits.is_finite()) { return r0; } + if (r0 == 0.0 || !r0_bits.is_finite()) + { + return r0; + } // An exact single-double divisor (e.g. x^-1) is reciprocated correctly by IEEE // division in every rounding mode. The Newton refinement below targets a genuine // double-double divisor and would re-round r0 to the wrong neighbor here. - if (divisor.lo == 0.0) { return r0; } + if (divisor.lo == 0.0) + { + return r0; + } // Keep both Newton steps fused so software-FMA backends such as MSVC preserve the // full double-double correction instead of bleeding precision through intermediate // rounding in the residual and update terms. - auto refine = [&divisor](double r) constexpr noexcept - { + auto refine = [&divisor](double r) constexpr noexcept { const DoubleDouble pr = two_prod(divisor.hi, r); const double err_hi = (1.0 - pr.hi) - pr.lo; const double err = ccm::types::exact_fma(-divisor.lo, r, err_hi); @@ -261,10 +274,14 @@ namespace ccm::gen::impl } constexpr ScaledPow ipow_scaled(double base, std::uint64_t e) noexcept - { return ipow_scaled(load_scaled(DoubleDouble{ base, 0.0 }), e); } + { + return ipow_scaled(load_scaled(DoubleDouble{ base, 0.0 }), e); + } constexpr ScaledPow ipow_scaled(const DoubleDouble & base, std::uint64_t e) noexcept - { return ipow_scaled(load_scaled(base), e); } + { + return ipow_scaled(load_scaled(base), e); + } // Round (mant.hi + mant.lo) * 2^exp2 to a correctly rounded double in the active mode, // with mant.hi in [1, 2) (or its negation). A normal result is a single power-of-two @@ -297,13 +314,16 @@ namespace ccm::gen::impl bool round_up_mag = false; switch (mode) { - case FE_TONEAREST: round_up_mag = frac > 0.5 || (frac == 0.5 && (units & 1) != 0); break; - case FE_UPWARD: round_up_mag = !neg && inexact; break; - case FE_DOWNWARD: round_up_mag = neg && inexact; break; + case FE_TONEAREST : round_up_mag = frac > 0.5 || (frac == 0.5 && (units & 1) != 0); break; + case FE_UPWARD : round_up_mag = !neg && inexact; break; + case FE_DOWNWARD : round_up_mag = neg && inexact; break; case FE_TOWARDZERO: - default: break; + default : break; + } + if (round_up_mag) + { + units += 1; } - if (round_up_mag) { units += 1; } const double magnitude = static_cast(units) * 0x1.0p-1074; return neg ? -magnitude : magnitude; } @@ -324,19 +344,33 @@ namespace ccm::gen::impl for (int i = 0; i < n; ++i) { const FPBits_t b(terms[i]); - if (b.is_zero()) { continue; } + if (b.is_zero()) + { + continue; + } const std::uint64_t significand = static_cast(b.get_explicit_mantissa()); const int shift = (b.get_explicit_exponent() - static_cast(FPBits_t::fraction_length)) + kSumScale; - if (shift < 0) { continue; } // below the accumulator resolution, never reached for the fed terms + if (shift < 0) + { + continue; + } // below the accumulator resolution, never reached for the fed terms const Wide term = Wide(significand) << static_cast(shift); - if (b.sign().is_neg()) { neg += term; } - else + if (b.sign().is_neg()) + { + neg += term; + } else { pos += term; } } - if (pos > neg) { return 1; } - if (pos < neg) { return -1; } + if (pos > neg) + { + return 1; + } + if (pos < neg) + { + return -1; + } return 0; } @@ -375,7 +409,10 @@ namespace ccm::gen::impl { using FPBits_t = support::fp::FPBits; double r = 1.0 / ccm::gen::sqrt_gen(x); - if (const FPBits_t rb(r); !rb.is_finite() || rb.is_zero() || rb.is_subnormal()) { return r; } + if (const FPBits_t rb(r); !rb.is_finite() || rb.is_zero() || rb.is_subnormal()) + { + return r; + } const auto next_up = [](double v) { return FPBits_t(FPBits_t(v).uintval() + 1U).get_val(); }; const auto next_down = [](double v) { return FPBits_t(FPBits_t(v).uintval() - 1U).get_val(); }; @@ -385,23 +422,43 @@ namespace ccm::gen::impl int const s = rsqrt_residual_sign(r, 0.0, x); if (s > 0) { - do { r = next_down(r); } while (rsqrt_residual_sign(r, 0.0, x) > 0); // NOLINT(cppcoreguidelines-avoid-do-while) - } - else if (s < 0) + do + { + r = next_down(r); + } while (rsqrt_residual_sign(r, 0.0, x) > 0); // NOLINT(cppcoreguidelines-avoid-do-while) + } else if (s < 0) { - while (rsqrt_residual_sign(next_up(r), 0.0, x) <= 0) { r = next_up(r); } + while (rsqrt_residual_sign(next_up(r), 0.0, x) <= 0) + { + r = next_up(r); + } } - if (rsqrt_residual_sign(r, 0.0, x) == 0) { return r; } // exact + if (rsqrt_residual_sign(r, 0.0, x) == 0) + { + return r; + } // exact const double rp = next_up(r); // r < 1/sqrt(x) < rp - if (mode == FE_UPWARD) { return rp; } - if (mode != FE_TONEAREST) { return r; } // toward zero / downward + if (mode == FE_UPWARD) + { + return rp; + } + if (mode != FE_TONEAREST) + { + return r; + } // toward zero / downward const double half = 0.5 * (rp - r); // half ulp, a power of two const int sm = rsqrt_residual_sign(r, half, x); - if (sm > 0) { return r; } // midpoint above the true value -> nearest is r - if (sm < 0) { return rp; } // midpoint below -> nearest is rp + if (sm > 0) + { + return r; + } // midpoint above the true value -> nearest is r + if (sm < 0) + { + return rp; + } // midpoint below -> nearest is rp return (FPBits_t(r).uintval() & 1U) == 0U ? r : rp; // exact midpoint -> ties to even } } // namespace pow_int_detail @@ -471,7 +528,10 @@ namespace ccm::gen::impl // the binary exponent so the power-of-two reconstruction below stays representable. // The result is reassembled by multiplying the (normal) reconstruction by scale, // which is exact for a normal final value, so it stays correctly rounded. - if (scale != 1.0) { y6_log2_x.hi += (scale > 1.0) ? -512.0 * 0x1.0p6 : 512.0 * 0x1.0p6; } + if (scale != 1.0) + { + y6_log2_x.hi += (scale > 1.0) ? -512.0 * 0x1.0p6 : 512.0 * 0x1.0p6; + } const double hm = support::fp::nearest_integer(y6_log2_x.hi); const double lo6_hi = y6_log2_x.hi - hm; // Exact, |lo6_hi| <= 0.5 @@ -548,13 +608,16 @@ namespace ccm::gen::impl bool round_up_mag = false; switch (support::fenv::get_rounding_mode()) { - case FE_TONEAREST: round_up_mag = frac > 0.5 || (frac == 0.5 && (units & 1) != 0); break; - case FE_UPWARD: round_up_mag = !neg && inexact; break; - case FE_DOWNWARD: round_up_mag = neg && inexact; break; + case FE_TONEAREST : round_up_mag = frac > 0.5 || (frac == 0.5 && (units & 1) != 0); break; + case FE_UPWARD : round_up_mag = !neg && inexact; break; + case FE_DOWNWARD : round_up_mag = neg && inexact; break; case FE_TOWARDZERO: - default: break; + default : break; + } + if (round_up_mag) + { + units += 1; } - if (round_up_mag) { units += 1; } const double magnitude = static_cast(units) * kUnitToResult; // exact, units <= 2^52 result = neg ? -magnitude : magnitude; @@ -594,15 +657,13 @@ namespace ccm::gen::impl dx = support::multiply_add( support::constants::RD.at(static_cast(idx_x)), m_x - c, support::constants::CD.at(static_cast(idx_x))); dx_c0 = ccm::types::exact_mult(POW_LOG2_COEFFS[0], dx); - } - else + } else { if constexpr (ccm::builtin::target_cpu_has_fma) { dx = pow_kernel_detail::fma_dx(support::constants::RD.at(static_cast(idx_x)), m_x, -1.0); dx_c0 = ccm::types::exact_mult(POW_LOG2_COEFFS[0], dx); - } - else + } else { const typename FPBits_t::storage_type masked_mantissa = static_cast(FPBits_t(m_x).uintval() & static_cast(0x3fff'e000'0000'0000ULL)); @@ -650,8 +711,7 @@ namespace ccm::gen::impl y6_log2_x.lo = 0.0; clamped = true; } - } - else + } else { scale = 0x1.0p-512; y6_log2_x.hi += 512.0 * 64.0; @@ -682,8 +742,7 @@ namespace ccm::gen::impl { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); - } - else if (dd_bits.is_zero() || dd_bits.abs().uintval() < FPBits_t::min_normal().uintval()) + } else if (dd_bits.is_zero() || dd_bits.abs().uintval() < FPBits_t::min_normal().uintval()) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); @@ -731,16 +790,14 @@ namespace ccm::gen::impl support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); final = FPBits_t::inf(types::Sign::POS).get_val(); - } - else + } else { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); final = 0.0; } - } - else if (scale == 0x1.0p-512 && final_bits.is_finite() && !final_bits.is_zero() && - final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) + } else if (scale == 0x1.0p-512 && final_bits.is_finite() && !final_bits.is_zero() && + final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); @@ -763,13 +820,25 @@ namespace ccm::gen::impl // signaling NaN. UCRT's pow returns quietly for sNaN operands while glibc and // Apple libm signal invalid, so the raise is explicit and platform scoped. #ifndef _WIN32 - if (base_bits.is_signaling_nan() || exp_bits.is_signaling_nan()) { support::fenv::raise_except_if_required(FE_INVALID); } + if (base_bits.is_signaling_nan() || exp_bits.is_signaling_nan()) + { + support::fenv::raise_except_if_required(FE_INVALID); + } #endif - if (exp_bits.is_zero()) { return 1.0; } // pow(x, +/-0) = 1, including NaN x - if (base_bits.uintval() == FPBits_t(1.0).uintval()) { return 1.0; } // pow(1, y) = 1, including NaN y + if (exp_bits.is_zero()) + { + return 1.0; + } // pow(x, +/-0) = 1, including NaN x + if (base_bits.uintval() == FPBits_t(1.0).uintval()) + { + return 1.0; + } // pow(1, y) = 1, including NaN y - if (base_bits.is_nan() || exp_bits.is_nan()) { return std::numeric_limits::quiet_NaN(); } + if (base_bits.is_nan() || exp_bits.is_nan()) + { + return std::numeric_limits::quiet_NaN(); + } if (base_bits.is_zero()) { @@ -791,7 +860,10 @@ namespace ccm::gen::impl // result sign when the exponent is an odd integer, otherwise the magnitude rule for // +inf applies (including non-integer exponents, e.g. pow(-inf, 0.5) = +inf). const bool out_is_neg = base_bits.sign().is_neg() && is_odd_integer(exp); - if (exp < 0.0) { return out_is_neg ? -0.0 : 0.0; } + if (exp < 0.0) + { + return out_is_neg ? -0.0 : 0.0; + } return FPBits_t::inf(out_is_neg ? Sign::NEG : Sign::POS).get_val(); } @@ -800,9 +872,15 @@ namespace ccm::gen::impl const std::uint64_t base_abs = base_bits.abs().uintval(); const std::uint64_t one_bits = FPBits_t(1.0).uintval(); - if (base_abs == one_bits) { return 1.0; } + if (base_abs == one_bits) + { + return 1.0; + } - if (exp_bits.sign().is_neg()) { return base_abs < one_bits ? std::numeric_limits::infinity() : 0.0; } + if (exp_bits.sign().is_neg()) + { + return base_abs < one_bits ? std::numeric_limits::infinity() : 0.0; + } return base_abs < one_bits ? 0.0 : std::numeric_limits::infinity(); } @@ -817,7 +895,10 @@ namespace ccm::gen::impl std::uint64_t sign = 0; if (base < 0.0) { - if (is_odd_integer(exp)) { sign = FPBits_t::sign_mask; } + if (is_odd_integer(exp)) + { + sign = FPBits_t::sign_mask; + } base = base_bits.abs().get_val(); } @@ -849,9 +930,11 @@ namespace ccm::gen::impl // the reciprocal, or a subnormal that the single reciprocal cannot grid-round // without double rounding) fall through to the kernel. const double r = support::helpers::internal_ldexp(pow_int_detail::reciprocal(sp.mantissa), static_cast(-sp.exp2)); - if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) { return r; } - } - else + if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) + { + return r; + } + } else { // Positive exponent: reconstruct_scaled rounds once in the active mode, using the // subnormal grid below the normal floor so a tiny result does not double-round. @@ -884,8 +967,14 @@ namespace ccm::gen::impl // is correctly rounded by an exact residual test (a bare double-double 1/sqrt sits a // directed-mode ULP short for x just past a power of two). The double-double squaring // below cannot resolve those two, so they are intercepted first. - if (exp == 0.5) { return ccm::gen::sqrt_gen(base); } - if (exp == -0.5) { return pow_int_detail::cr_rsqrt(base, support::fenv::get_rounding_mode()); } + if (exp == 0.5) + { + return ccm::gen::sqrt_gen(base); + } + if (exp == -0.5) + { + return pow_int_detail::cr_rsqrt(base, support::fenv::get_rounding_mode()); + } const auto m = static_cast(two_exp); const std::uint64_t mag = m < 0 ? static_cast(-(m + 1)) + 1U : static_cast(m); @@ -895,9 +984,11 @@ namespace ccm::gen::impl // Negative half-integer: reciprocal of the odd power then an exact scale. A // non-normal result falls through to the kernel. const double r = support::helpers::internal_ldexp(pow_int_detail::reciprocal(sp.mantissa), static_cast(-sp.exp2)); - if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) { return r; } - } - else + if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) + { + return r; + } + } else { const double r = pow_int_detail::reconstruct_scaled(sp.mantissa, sp.exp2, support::fenv::get_rounding_mode()); if (const FPBits_t r_bits(r); r_bits.is_finite() && !r_bits.is_zero()) @@ -916,13 +1007,18 @@ namespace ccm::gen::impl const FPBits_t exp_abs_bits(exp); const std::uint64_t exp_a = exp_abs_bits.abs().uintval(); constexpr std::uint64_t kHugeExponentThreshold = 0x43d7'4910'd52d'3052ULL; - if (exp_a > kHugeExponentThreshold) { exp = exp_abs_bits.sign().is_neg() ? -0x1.0p100 : 0x1.0p100; } + if (exp_a > kHugeExponentThreshold) + { + exp = exp_abs_bits.sign().is_neg() ? -0x1.0p100 : 0x1.0p100; + } return pow_kernel(base, exp, sign); } } // namespace internal::impl constexpr double pow_impl(double base, double exp) noexcept - { return internal::impl::pow_impl(base, exp); } + { + return internal::impl::pow_impl(base, exp); + } } // namespace ccm::gen::impl diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp index c7f1b718..e9cf3cb8 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/pow_simd_impl.hpp @@ -31,16 +31,14 @@ namespace ccm::gen::impl { // Vector double-double pair. Each helper below replays the matching ccm::types // primitive lane for lane, so results are bit identical to the scalar kernel. - template - struct VDD + template struct VDD { pp::basic_simd hi; pp::basic_simd lo; }; // Dekker's FastTwoSum. Same assumption as types::exact_add: |a| >= |b| or a == 0. - template - CCM_ALWAYS_INLINE VDD v_exact_add(pp::basic_simd const & a, pp::basic_simd const & b) noexcept + template CCM_ALWAYS_INLINE VDD v_exact_add(pp::basic_simd const & a, pp::basic_simd const & b) noexcept { using DVec = pp::basic_simd; const DVec hi = a + b; @@ -49,16 +47,14 @@ namespace ccm::gen::impl } // types::add(DoubleDouble, DoubleDouble). Assumption: |a.hi| >= |b.hi|. - template - CCM_ALWAYS_INLINE VDD v_add(VDD const & a, VDD const & b) noexcept + template CCM_ALWAYS_INLINE VDD v_add(VDD const & a, VDD const & b) noexcept { const VDD r = v_exact_add(a.hi, b.hi); return v_exact_add(r.hi, r.lo + (a.lo + b.lo)); } // Veltkamp's splitting. - template - CCM_ALWAYS_INLINE VDD v_split(pp::basic_simd const & a) noexcept + template CCM_ALWAYS_INLINE VDD v_split(pp::basic_simd const & a) noexcept { using DVec = pp::basic_simd; const DVec c(0x1.0p27 + 1.0); @@ -69,8 +65,7 @@ namespace ccm::gen::impl } // Dekker's product, mirroring types::exact_mult. - template - CCM_ALWAYS_INLINE VDD v_exact_mult(pp::basic_simd const & a, pp::basic_simd const & b) noexcept + template CCM_ALWAYS_INLINE VDD v_exact_mult(pp::basic_simd const & a, pp::basic_simd const & b) noexcept { using DVec = pp::basic_simd; const VDD as = v_split(a); @@ -83,8 +78,7 @@ namespace ccm::gen::impl } // types::quick_mult(double, DoubleDouble). - template - CCM_ALWAYS_INLINE VDD v_quick_mult(pp::basic_simd const & a, VDD const & b) noexcept + template CCM_ALWAYS_INLINE VDD v_quick_mult(pp::basic_simd const & a, VDD const & b) noexcept { VDD r = v_exact_mult(a, b.hi); r.lo = pp::fma(a, b.lo, r.lo); @@ -92,8 +86,7 @@ namespace ccm::gen::impl } // types::quick_mult(DoubleDouble, DoubleDouble). - template - CCM_ALWAYS_INLINE VDD v_quick_mult(VDD const & a, VDD const & b) noexcept + template CCM_ALWAYS_INLINE VDD v_quick_mult(VDD const & a, VDD const & b) noexcept { using DVec = pp::basic_simd; VDD r = v_exact_mult(a.hi, b.hi); @@ -103,23 +96,23 @@ namespace ccm::gen::impl } // support::multiply_add, the polyeval step: add(z, quick_mult(x, y)). - template - CCM_ALWAYS_INLINE VDD v_multiply_add(VDD const & x, VDD const & y, VDD const & z) noexcept - { return v_add(z, v_quick_mult(x, y)); } + template CCM_ALWAYS_INLINE VDD v_multiply_add(VDD const & x, VDD const & y, VDD const & z) noexcept + { + return v_add(z, v_quick_mult(x, y)); + } - template - CCM_ALWAYS_INLINE pp::basic_simd v_biased_exponent(pp::basic_simd const & a) noexcept + template CCM_ALWAYS_INLINE pp::basic_simd v_biased_exponent(pp::basic_simd const & a) noexcept { using U64 = pp::basic_simd; return (pp::simd_bit_cast(a) >> U64(52)) & U64(0x7ff); } - template - CCM_ALWAYS_INLINE VDD v_select(pp::basic_simd_mask<8, Abi> const & mask, VDD const & a, VDD const & b) noexcept - { return { pp::simd_select(mask, a.hi, b.hi), pp::simd_select(mask, a.lo, b.lo) }; } + template CCM_ALWAYS_INLINE VDD v_select(pp::basic_simd_mask<8, Abi> const & mask, VDD const & a, VDD const & b) noexcept + { + return { pp::simd_select(mask, a.hi, b.hi), pp::simd_select(mask, a.lo, b.lo) }; + } - template - CCM_ALWAYS_INLINE VDD v_broadcast(types::DoubleDouble const & c) noexcept + template CCM_ALWAYS_INLINE VDD v_broadcast(types::DoubleDouble const & c) noexcept { using DVec = pp::basic_simd; return { DVec(c.hi), DVec(c.lo) }; @@ -127,8 +120,7 @@ namespace ccm::gen::impl } // namespace simd_detail // x^y for a vector of double precision lanes. - template - [[nodiscard]] inline pp::basic_simd pow_simd(pp::basic_simd x, pp::basic_simd y) noexcept + template [[nodiscard]] inline pp::basic_simd pow_simd(pp::basic_simd x, pp::basic_simd y) noexcept { using DVec = pp::basic_simd; using U64 = pp::basic_simd; @@ -142,10 +134,12 @@ namespace ccm::gen::impl if constexpr (!ccm::builtin::target_cpu_has_fma) { DVec out{}; - for (int i = 0; i < N; ++i) { out[i] = ccm::gen::impl::pow_impl(x[i], y[i]); } + for (int i = 0; i < N; ++i) + { + out[i] = ccm::gen::impl::pow_impl(x[i], y[i]); + } return out; - } - else + } else { namespace cst = ccm::support::constants; namespace sd = simd_detail; @@ -290,7 +284,10 @@ namespace ccm::gen::impl { for (int i = 0; i < N; ++i) { - if (needs_scalar[i]) { result[i] = ccm::gen::impl::pow_impl(x[i], y[i]); } + if (needs_scalar[i]) + { + result[i] = ccm::gen::impl::pow_impl(x[i], y[i]); + } } } return result; diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp index 7062a782..0801ce6a 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_impl.hpp @@ -68,7 +68,7 @@ namespace ccm::gen::impl return DoubleBits(a).get_biased_exponent() >= DoubleBits(b).get_biased_exponent(); } - constexpr double powf_double_double(int idx_x, double dx, double y6, double lo6_hi, const DoubleDouble& exp2_hi_mid) + constexpr double powf_double_double(int idx_x, double dx, double y6, double lo6_hi, const DoubleDouble & exp2_hi_mid) { using DoubleBits = support::fp::FPBits; @@ -152,20 +152,25 @@ namespace ccm::gen::impl if (CCM_UNLIKELY(((r_bits & 0xfff'ffff) == 0) && (r.lo != 0.0))) { const types::Sign hi_sign = DoubleBits(r.hi).sign(); - if (const types::Sign lo_sign = DoubleBits(r.lo).sign(); hi_sign == lo_sign) { ++r_bits; } - else if ((r_bits & DoubleBits::fraction_mask) > 0) { --r_bits; } + if (const types::Sign lo_sign = DoubleBits(r.lo).sign(); hi_sign == lo_sign) + { + ++r_bits; + } else if ((r_bits & DoubleBits::fraction_mask) > 0) + { + --r_bits; + } } return ccm::support::bit_cast(r_bits); } - constexpr std::optional handle_exceptional_cases(float& x, - float& y, - support::fp::FPBits& xbits, - support::fp::FPBits& ybits, - const std::uint32_t& x_u, - int& exponent, - std::uint64_t& sign) + constexpr std::optional handle_exceptional_cases(float & x, + float & y, + support::fp::FPBits & xbits, + support::fp::FPBits & ybits, + const std::uint32_t & x_u, + int & exponent, + std::uint64_t & sign) { using FloatBits = support::fp::FPBits; @@ -176,7 +181,10 @@ namespace ccm::gen::impl // The single precision number that is closest to 1 is (1 - 2^-24) if (CCM_UNLIKELY((y_abs & 0x0007'ffff) == 0 || y_abs > 0x4f170000)) { - if (y == 0.0F) { return 1.0F; } + if (y == 0.0F) + { + return 1.0F; + } switch (y_abs) // NOLINT(hicpp-multiway-paths-covered) { @@ -203,7 +211,10 @@ namespace ccm::gen::impl switch (y_u) { case 0x3f00'0000: // y = 0.5f - if (CCM_UNLIKELY(x == 0.0F || x_u == 0xff80'0000)) { return x * x; } + if (CCM_UNLIKELY(x == 0.0F || x_u == 0xff80'0000)) + { + return x * x; + } if (x < 0.0F) { support::fenv::set_errno_if_required(EDOM); @@ -228,7 +239,10 @@ namespace ccm::gen::impl { const auto x_d = static_cast(x); double result = x_d; - for (int i = 1; i < iter; ++i) { result *= x_d; } + for (int i = 1; i < iter; ++i) + { + result *= x_d; + } return static_cast(result); } } @@ -236,7 +250,10 @@ namespace ccm::gen::impl { if (y_abs > 0x7f80'0000) { - if (x_u == 0x3f80'0000) { return 1.0F; } + if (x_u == 0x3f80'0000) + { + return 1.0F; + } return y; } y = support::bit_cast((y_u & FloatBits::sign_mask) + 0x4f80'0000U); @@ -275,7 +292,10 @@ namespace ccm::gen::impl if (x_abs == 0x7f80'0000) // x is +/-inf { const bool out_is_neg = x_is_neg && is_odd_integer(FloatBits(y_u).get_val()); - if (y_u >= FloatBits::sign_mask) { return out_is_neg ? -0.0F : 0.0F; } + if (y_u >= FloatBits::sign_mask) + { + return out_is_neg ? -0.0F : 0.0F; + } return FloatBits::inf(out_is_neg ? types::Sign::NEG : types::Sign::POS).get_val(); } @@ -297,9 +317,11 @@ namespace ccm::gen::impl if (is_integer(y)) { x = -x; - if (is_odd_integer(y)) { sign = 0x8000'0000'0000'0000ULL; } // sign = -1.0 - } - else + if (is_odd_integer(y)) + { + sign = 0x8000'0000'0000'0000ULL; + } // sign = -1.0 + } else { support::fenv::set_errno_if_required(EDOM); support::fenv::raise_except_if_required(FE_INVALID); @@ -357,13 +379,11 @@ namespace ccm::gen::impl // Then m_x = (1 + dx) / r, and // log2(m_x) = log2( (1 + dx) / r ) // = log2(1 + dx) - log2(r). - const double dx = [&]() - { + const double dx = [&]() { if constexpr (ccm::builtin::target_cpu_has_fma) { return static_cast(support::multiply_add(m_x, support::constants::R.at(static_cast(idx_x)), -1.0F)); - } - else + } else { return support::multiply_add(static_cast(m_x), support::constants::RD.at(static_cast(idx_x)), -1.0); } @@ -476,8 +496,7 @@ namespace ccm::gen::impl { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); - } - else if (r_upper == 0.0F) + } else if (r_upper == 0.0F) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp index 62e3cbab..253821cb 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powf_simd_impl.hpp @@ -42,8 +42,7 @@ namespace ccm::gen::impl 0x1p0, 0x1.62e42fefa39efp-7, 0x1.ebfbdff82a23ap-15, 0x1.c6b08d7076268p-23, 0x1.3b2ad33f8b48bp-31, 0x1.5d870c4d84445p-40 }; - template - [[nodiscard]] CCM_ALWAYS_INLINE pp::basic_simd v_nearest_integer(pp::basic_simd const & x) noexcept + template [[nodiscard]] CCM_ALWAYS_INLINE pp::basic_simd v_nearest_integer(pp::basic_simd const & x) noexcept { using DVec = pp::basic_simd; const DVec off(0x1.0p52); @@ -56,8 +55,7 @@ namespace ccm::gen::impl } // namespace simd_detail // x^y for a vector of single precision lanes. - template - [[nodiscard]] inline pp::basic_simd powf_simd(pp::basic_simd x, pp::basic_simd y) noexcept + template [[nodiscard]] inline pp::basic_simd powf_simd(pp::basic_simd x, pp::basic_simd y) noexcept { using FVec = pp::basic_simd; using DVec = pp::basic_simd; @@ -72,10 +70,12 @@ namespace ccm::gen::impl if constexpr (!ccm::builtin::target_cpu_has_fma) { FVec out{}; - for (int i = 0; i < N; ++i) { out[i] = ccm::gen::impl::powf_impl(x[i], y[i]); } + for (int i = 0; i < N; ++i) + { + out[i] = ccm::gen::impl::powf_impl(x[i], y[i]); + } return out; - } - else + } else { namespace cst = ccm::support::constants; @@ -161,7 +161,10 @@ namespace ccm::gen::impl { for (int i = 0; i < N; ++i) { - if (needs_scalar[i]) { result[i] = ccm::gen::impl::powf_impl(x[i], y[i]); } + if (needs_scalar[i]) + { + result[i] = ccm::gen::impl::powf_impl(x[i], y[i]); + } } } diff --git a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp index 158087a4..52a12cfd 100644 --- a/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp +++ b/include/ccmath/internal/math/generic/func/power/pow_impl/powl_ld80_kernel.hpp @@ -108,7 +108,10 @@ namespace ccm::gen::internal::impl::bit80 const int e_x = static_cast(base_bits.get_biased_exponent()) - static_cast(FPBits_t::exponent_bias) + exponent_adjust; typename FPBits_t::storage_type sig = base_bits.get_mantissa(); - if (base_bits.get_implicit_bit()) { sig |= FPBits_t::EXPLICIT_BIT_MASK; } + if (base_bits.get_implicit_bit()) + { + sig |= FPBits_t::EXPLICIT_BIT_MASK; + } const unsigned idx_x = static_cast((sig & FPBits_t::fraction_mask) >> (FPBits_t::fraction_length - 7)); @@ -170,8 +173,7 @@ namespace ccm::gen::internal::impl::bit80 forced_overflow = true; break; } - } - else + } else { scale_exp512 -= 512; scaled_down = true; @@ -207,7 +209,10 @@ namespace ccm::gen::internal::impl::bit80 { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); - if (!support::is_constant_evaluated()) { return support::helpers::internal_ldexp(1.0L, kOverflowShift); } + if (!support::is_constant_evaluated()) + { + return support::helpers::internal_ldexp(1.0L, kOverflowShift); + } return FPBits_t::inf(types::Sign::POS).get_val(); } @@ -215,7 +220,10 @@ namespace ccm::gen::internal::impl::bit80 { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); - if (!support::is_constant_evaluated()) { return support::helpers::internal_ldexp(1.0L, kUnderflowShift); } + if (!support::is_constant_evaluated()) + { + return support::helpers::internal_ldexp(1.0L, kUnderflowShift); + } return FPBits_t::zero(types::Sign::POS).get_val(); } @@ -256,7 +264,10 @@ namespace ccm::gen::internal::impl::bit80 result += exp2_hm_hi; long double final = result; - if (scale_exp512 != 0) { final = support::helpers::internal_ldexp(final, scale_exp512); } + if (scale_exp512 != 0) + { + final = support::helpers::internal_ldexp(final, scale_exp512); + } FPBits_t final_bits(final); if (final_bits.sign().is_neg()) { @@ -265,24 +276,20 @@ namespace ccm::gen::internal::impl::bit80 support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); final = FPBits_t::inf(types::Sign::POS).get_val(); - } - else if (!final_bits.is_zero() && final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) + } else if (!final_bits.is_zero() && final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); final = 0.0L; - } - else + } else { final = final_bits.abs().get_val(); } - } - else if (scale_exp512 > 0 && final_bits.is_inf() && final_bits.is_pos()) + } else if (scale_exp512 > 0 && final_bits.is_inf() && final_bits.is_pos()) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_OVERFLOW); - } - else if (scaled_down && final_bits.is_finite() && !final_bits.is_zero() && final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) + } else if (scaled_down && final_bits.is_finite() && !final_bits.is_zero() && final_bits.abs().uintval() < FPBits_t::min_subnormal().uintval()) { support::fenv::set_errno_if_required(ERANGE); support::fenv::raise_except_if_required(FE_UNDERFLOW); @@ -295,7 +302,9 @@ namespace ccm::gen::internal::impl::bit80 } // namespace powl_ld80_detail constexpr long double powl_ld80_general_finite(long double base, long double exp) noexcept - { return powl_ld80_detail::powl_ld80_general_finite(base, exp); } + { + return powl_ld80_detail::powl_ld80_general_finite(base, exp); + } } // namespace ccm::gen::internal::impl::bit80 diff --git a/include/ccmath/internal/math/generic/func/power/powl_gen.hpp b/include/ccmath/internal/math/generic/func/power/powl_gen.hpp index ed22fb86..92eb47c5 100644 --- a/include/ccmath/internal/math/generic/func/power/powl_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/powl_gen.hpp @@ -34,31 +34,41 @@ namespace ccm::gen { using PowlFPBits_t = support::fp::FPBits; - template - constexpr bool storage_is_zero(Storage value) noexcept - { return value == Storage{}; } + template constexpr bool storage_is_zero(Storage value) noexcept + { + return value == Storage{}; + } - template - constexpr int storage_countr_zero(Storage value) noexcept + template constexpr int storage_countr_zero(Storage value) noexcept { - if (storage_is_zero(value)) { return std::numeric_limits::digits; } + if (storage_is_zero(value)) + { + return std::numeric_limits::digits; + } return support::countr_zero(value); } - template - constexpr bool storage_fits_shifted_int64(Storage unit, int scale) noexcept + template constexpr bool storage_fits_shifted_int64(Storage unit, int scale) noexcept { - if (scale < 0 || scale > 62) { return false; } - if (scale == 0) { return unit <= static_cast(std::numeric_limits::max()); } + if (scale < 0 || scale > 62) + { + return false; + } + if (scale == 0) + { + return unit <= static_cast(std::numeric_limits::max()); + } const Storage limit = static_cast(1) << static_cast(62 - scale); return unit <= limit; } - template - constexpr std::int64_t storage_to_int64_magnitude(Storage unit, int scale) noexcept + template constexpr std::int64_t storage_to_int64_magnitude(Storage unit, int scale) noexcept { - if (scale == 0) { return static_cast(unit); } + if (scale == 0) + { + return static_cast(unit); + } return static_cast(unit) << static_cast(scale); } @@ -66,17 +76,29 @@ namespace ccm::gen constexpr typename PowlFPBits_t::storage_type float80_unit_mantissa(const PowlFPBits_t & bits) noexcept { typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); - if (bits.get_implicit_bit()) { mantissa |= PowlFPBits_t::EXPLICIT_BIT_MASK; } + if (bits.get_implicit_bit()) + { + mantissa |= PowlFPBits_t::EXPLICIT_BIT_MASK; + } return mantissa; } constexpr bool is_integer_float80_bits(const PowlFPBits_t & bits) noexcept { - if (bits.is_nan() || bits.is_inf()) { return false; } - if (bits.is_zero()) { return true; } + if (bits.is_nan() || bits.is_inf()) + { + return false; + } + if (bits.is_zero()) + { + return true; + } const typename PowlFPBits_t::storage_type mantissa = float80_unit_mantissa(bits); - if (storage_is_zero(mantissa)) { return true; } + if (storage_is_zero(mantissa)) + { + return true; + } const int exponent = bits.get_explicit_exponent(); const int trailing_zeros = storage_countr_zero(mantissa); @@ -85,8 +107,14 @@ namespace ccm::gen constexpr bool is_odd_integer_float80_bits(const PowlFPBits_t & bits) noexcept { - if (!is_integer_float80_bits(bits)) { return false; } - if (bits.is_zero()) { return false; } + if (!is_integer_float80_bits(bits)) + { + return false; + } + if (bits.is_zero()) + { + return false; + } const typename PowlFPBits_t::storage_type mantissa = float80_unit_mantissa(bits); const int exponent = bits.get_explicit_exponent(); @@ -102,12 +130,21 @@ namespace ccm::gen #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 return is_integer_float80_bits(bits); #else - if (bits.is_nan() || bits.is_inf()) { return false; } - if (bits.is_zero()) { return true; } + if (bits.is_nan() || bits.is_inf()) + { + return false; + } + if (bits.is_zero()) + { + return true; + } const int exponent = bits.get_explicit_exponent(); const typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); - if (storage_is_zero(mantissa)) { return true; } + if (storage_is_zero(mantissa)) + { + return true; + } const int trailing_zeros = storage_countr_zero(mantissa); return exponent + trailing_zeros >= static_cast(PowlFPBits_t::fraction_length); @@ -118,11 +155,20 @@ namespace ccm::gen { #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 std::int64_t magnitude = 0; - if (try_extract_int64(bits, magnitude)) { return (magnitude & 1) != 0; } + if (try_extract_int64(bits, magnitude)) + { + return (magnitude & 1) != 0; + } return is_odd_integer_float80_bits(bits); #else - if (!is_integer(bits)) { return false; } - if (bits.is_zero()) { return false; } + if (!is_integer(bits)) + { + return false; + } + if (bits.is_zero()) + { + return false; + } const int exponent = bits.get_explicit_exponent(); const typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); @@ -135,18 +181,27 @@ namespace ccm::gen constexpr bool try_extract_int64(const PowlFPBits_t & bits, std::int64_t & out) noexcept { - if (bits.is_nan() || bits.is_inf()) { return false; } + if (bits.is_nan() || bits.is_inf()) + { + return false; + } if (bits.is_zero()) { out = 0; return true; } - if (!is_integer(bits)) { return false; } + if (!is_integer(bits)) + { + return false; + } const int exponent = bits.get_explicit_exponent(); #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 typename PowlFPBits_t::storage_type mantissa = bits.get_explicit_mantissa(); - if (bits.get_implicit_bit()) { mantissa |= PowlFPBits_t::EXPLICIT_BIT_MASK; } + if (bits.get_implicit_bit()) + { + mantissa |= PowlFPBits_t::EXPLICIT_BIT_MASK; + } const int trailing_zeros = storage_countr_zero(mantissa); const int scale = exponent + trailing_zeros - static_cast(PowlFPBits_t::fraction_length); #else @@ -154,26 +209,41 @@ namespace ccm::gen const int trailing_zeros = storage_countr_zero(mantissa); const int scale = exponent + trailing_zeros - static_cast(PowlFPBits_t::fraction_length); #endif - if (scale < 0) { return false; } + if (scale < 0) + { + return false; + } mantissa >>= static_cast(trailing_zeros); - if (!storage_fits_shifted_int64(mantissa, scale)) { return false; } + if (!storage_fits_shifted_int64(mantissa, scale)) + { + return false; + } const std::int64_t magnitude = storage_to_int64_magnitude(mantissa, scale); - if (magnitude > kBoundedExponentMax) { return false; } + if (magnitude > kBoundedExponentMax) + { + return false; + } out = bits.is_neg() ? -magnitude : magnitude; return true; } constexpr bool is_integer(long double val) noexcept - { return is_integer(PowlFPBits_t(val)); } + { + return is_integer(PowlFPBits_t(val)); + } constexpr bool is_odd_integer(long double val) noexcept - { return is_odd_integer(PowlFPBits_t(val)); } + { + return is_odd_integer(PowlFPBits_t(val)); + } constexpr bool try_extract_int64(long double val, std::int64_t & out) noexcept - { return try_extract_int64(PowlFPBits_t(val), out); } + { + return try_extract_int64(PowlFPBits_t(val), out); + } } // namespace powl_bits @@ -183,11 +253,15 @@ namespace ccm::gen using Sign = types::Sign; constexpr long double powl_unsupported_result() noexcept - { return std::numeric_limits::quiet_NaN(); } + { + return std::numeric_limits::quiet_NaN(); + } // Incomplete powl tiers use double pow_impl and cast back. Not native long double yet. constexpr long double powl_reduced_precision_double_fallback(long double base, long double exp) noexcept - { return static_cast(::ccm::gen::impl::pow_impl(static_cast(base), static_cast(exp))); } + { + return static_cast(::ccm::gen::impl::pow_impl(static_cast(base), static_cast(exp))); + } #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 // Double-double (compensated) arithmetic for integer powers. Naive exponentiation by @@ -205,15 +279,23 @@ namespace ccm::gen inline constexpr long double kSplitSafe = 0x1.0p16300L; constexpr long double abs_ld(long double x) noexcept - { return x < 0.0L ? -x : x; } + { + return x < 0.0L ? -x : x; + } constexpr Pair mul(const Pair & a, const Pair & b) noexcept { const long double hi = a.hi * b.hi; // Once the product overflows (or contaminates with NaN) the exact-mult split would // produce inf - inf = NaN, so propagate a clean infinity instead. - if (ccm::isinf(hi) || ccm::isnan(hi)) { return Pair{ hi, 0.0L }; } - if (abs_ld(a.hi) >= kSplitSafe || abs_ld(b.hi) >= kSplitSafe) { return Pair{ hi, 0.0L }; } + if (ccm::isinf(hi) || ccm::isnan(hi)) + { + return Pair{ hi, 0.0L }; + } + if (abs_ld(a.hi) >= kSplitSafe || abs_ld(b.hi) >= kSplitSafe) + { + return Pair{ hi, 0.0L }; + } Pair p = bit80::powl_ld80_detail::exact_mult(a.hi, b.hi); p.lo += a.hi * b.lo + a.lo * b.hi; return bit80::powl_ld80_detail::exact_add(p.hi, p.lo); @@ -226,9 +308,15 @@ namespace ccm::gen Pair factor{ base, 0.0L }; while (e > 0U) { - if ((e & 1U) != 0U) { result = mul(result, factor); } + if ((e & 1U) != 0U) + { + result = mul(result, factor); + } e >>= 1U; - if (e > 0U) { factor = mul(factor, factor); } + if (e > 0U) + { + factor = mul(factor, factor); + } } return result; } @@ -237,9 +325,15 @@ namespace ccm::gen constexpr long double reciprocal(const Pair & p) noexcept { const long double r0 = 1.0L / p.hi; - if (r0 == 0.0L || ccm::isinf(r0) || ccm::isnan(r0)) { return r0; } + if (r0 == 0.0L || ccm::isinf(r0) || ccm::isnan(r0)) + { + return r0; + } // p.hi near LDBL_MAX would overflow the split. r0 alone is already within an ULP. - if (abs_ld(p.hi) >= kSplitSafe) { return r0; } + if (abs_ld(p.hi) >= kSplitSafe) + { + return r0; + } const Pair pr = bit80::powl_ld80_detail::exact_mult(p.hi, r0); const long double res = ((1.0L - pr.hi) - pr.lo) - p.lo * r0; return r0 + r0 * res; @@ -249,23 +343,38 @@ namespace ccm::gen constexpr long double powl_bounded_integer(long double base, std::int64_t exp) noexcept // NOLINT(misc-no-recursion) { - if (exp == 0) { return 1.0L; } + if (exp == 0) + { + return 1.0L; + } #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 const std::uint64_t magnitude = exp < 0 ? static_cast(-(exp + 1)) + 1U : static_cast(exp); const powl_dd::Pair p = powl_dd::ipow(base, magnitude); - if (exp < 0) { return powl_dd::reciprocal(p); } + if (exp < 0) + { + return powl_dd::reciprocal(p); + } return p.hi + p.lo; #else - if (exp < 0) { return 1.0L / powl_bounded_integer(base, -exp); } + if (exp < 0) + { + return 1.0L / powl_bounded_integer(base, -exp); + } long double result = 1.0L; long double factor = base; std::uint64_t e = static_cast(exp); while (e > 0) { - if ((e & 1U) != 0U) { result *= factor; } - if (e > 1U) { factor *= factor; } + if ((e & 1U) != 0U) + { + result *= factor; + } + if (e > 1U) + { + factor *= factor; + } e >>= 1U; } return result; @@ -280,10 +389,19 @@ namespace ccm::gen { constexpr long double powl_calc_80bits(long double base, long double exp) noexcept { - if (exp == 0.0L) { return 1.0L; } - if (base == 1.0L) { return 1.0L; } + if (exp == 0.0L) + { + return 1.0L; + } + if (base == 1.0L) + { + return 1.0L; + } - if (ccm::isnan(base) || ccm::isnan(exp)) { return std::numeric_limits::quiet_NaN(); } + if (ccm::isnan(base) || ccm::isnan(exp)) + { + return std::numeric_limits::quiet_NaN(); + } if (support::is_constant_evaluated() && base > 0.0L && !ccm::isinf(base)) { @@ -320,9 +438,15 @@ namespace ccm::gen const long double base_abs = base_bits.abs().get_val(); const long double one = 1.0L; - if (base_abs == one) { return 1.0L; } + if (base_abs == one) + { + return 1.0L; + } - if (exp_bits.is_neg()) { return base_abs < one ? std::numeric_limits::infinity() : 0.0L; } + if (exp_bits.is_neg()) + { + return base_abs < one ? std::numeric_limits::infinity() : 0.0L; + } return base_abs < one ? 0.0L : std::numeric_limits::infinity(); } @@ -332,13 +456,22 @@ namespace ccm::gen if (base_bits.is_neg() && !powl_bits::is_integer(exp)) { // glibc libm: |−inf|^y for non-integer y is +inf when y > 0 and +0 when y < 0. - if (exp > 0.0L) { return PowlFPBits_t::inf(Sign::POS).get_val(); } - if (exp < 0.0L) { return 0.0L; } + if (exp > 0.0L) + { + return PowlFPBits_t::inf(Sign::POS).get_val(); + } + if (exp < 0.0L) + { + return 0.0L; + } return std::numeric_limits::quiet_NaN(); } const bool out_is_neg = base_bits.is_neg() && powl_bits::is_odd_integer(exp); - if (exp < 0.0L) { return out_is_neg ? -0.0L : 0.0L; } + if (exp < 0.0L) + { + return out_is_neg ? -0.0L : 0.0L; + } return PowlFPBits_t::inf(out_is_neg ? Sign::NEG : Sign::POS).get_val(); } @@ -355,7 +488,10 @@ namespace ccm::gen if (base_bits.is_neg()) { - if (powl_bits::is_odd_integer(exp)) { result_is_neg = true; } + if (powl_bits::is_odd_integer(exp)) + { + result_is_neg = true; + } working_base = base_bits.abs().get_val(); } @@ -369,17 +505,26 @@ namespace ccm::gen // which over/underflows through internal_ldexp and is correct in every mode. if (const PowlFPBits_t r_bits(result); r_bits.is_finite() && !r_bits.is_zero() && !r_bits.is_subnormal()) { - if (result_is_neg) { result = -result; } + if (result_is_neg) + { + result = -result; + } return result; } } const PowlFPBits_t exp_abs_bits(exp); const long double exp_abs = exp_abs_bits.abs().get_val(); - if (exp_abs > kPowlHugeExponentThreshold) { working_exp = exp_abs_bits.is_neg() ? -0x1.0p100L : 0x1.0p100L; } + if (exp_abs > kPowlHugeExponentThreshold) + { + working_exp = exp_abs_bits.is_neg() ? -0x1.0p100L : 0x1.0p100L; + } long double result = bit80::powl_ld80_general_finite(working_base, working_exp); - if (result_is_neg && result != 0.0L && !ccm::isinf(result) && !ccm::isnan(result)) { result = -result; } + if (result_is_neg && result != 0.0L && !ccm::isinf(result) && !ccm::isnan(result)) + { + result = -result; + } return result; } } // namespace bit80 @@ -387,7 +532,10 @@ namespace ccm::gen constexpr long double powl_impl_non_native(long double base, long double exp) noexcept { - if (!config::reduced_precision_powl_fallback_enabled()) { return powl_unsupported_result(); } + if (!config::reduced_precision_powl_fallback_enabled()) + { + return powl_unsupported_result(); + } return powl_reduced_precision_double_fallback(base, exp); } @@ -410,6 +558,8 @@ namespace ccm::gen // Dispatch for long double pow. Native precision only on binary80. See powl_policy.hpp. constexpr long double powl_gen(long double base, long double exp) noexcept - { return internal::impl::powl_impl(base, exp); } + { + return internal::impl::powl_impl(base, exp); + } } // namespace ccm::gen diff --git a/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp b/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp index c8422b55..d58197ac 100644 --- a/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp +++ b/include/ccmath/internal/math/generic/func/power/sqrt_gen.hpp @@ -22,25 +22,21 @@ namespace ccm::gen { namespace internal { - template - struct Is80BitLongDouble + template struct Is80BitLongDouble { static constexpr bool value = false; }; #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 - template <> - struct Is80BitLongDouble + template <> struct Is80BitLongDouble { static constexpr bool value = true; }; #endif - template - constexpr bool Is80BitLongDouble_v = Is80BitLongDouble::value; + template constexpr bool Is80BitLongDouble_v = Is80BitLongDouble::value; - template - constexpr void normalize(int& exponent, typename support::fp::FPBits::storage_type& mantissa) + template constexpr void normalize(int & exponent, typename support::fp::FPBits::storage_type & mantissa) { const int shift = support::countl_zero(mantissa) - ((8 * static_cast(sizeof(mantissa))) - 1 - support::fp::FPBits::fraction_length); exponent -= shift; @@ -48,12 +44,12 @@ namespace ccm::gen } #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 - template <> - constexpr void normalize(int& exponent, std::uint64_t& mantissa) - { normalize(exponent, mantissa); } + template <> constexpr void normalize(int & exponent, std::uint64_t & mantissa) + { + normalize(exponent, mantissa); + } #elif defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) - template <> - constexpr void normalize(int& exponent, support::fp::FPBits::storage_type& mantissa) + template <> constexpr void normalize(int & exponent, support::fp::FPBits::storage_type & mantissa) { const auto shift = static_cast(static_cast(support::countl_zero(static_cast(mantissa))) - (8 * sizeof(std::uint64_t) - 1 - support::fp::FPBits::fraction_length)); @@ -79,17 +75,28 @@ namespace ccm::gen const Bits bits(x); - if (bits == Bits::inf(types::Sign::POS) || bits.is_zero() || bits.is_nan()) { return x; } + if (bits == Bits::inf(types::Sign::POS) || bits.is_zero() || bits.is_nan()) + { + return x; + } constexpr auto nan_type = Bits::quiet_nan().get_val(); - if (bits.is_neg()) { return nan_type; } + if (bits.is_neg()) + { + return nan_type; + } int x_exp = bits.get_explicit_exponent(); storage_type x_mant = bits.get_mantissa(); // If we have denormal values, normalize it. - if (bits.get_implicit_bit()) { x_mant |= one; } - else if (bits.is_subnormal()) { normalize(x_exp, x_mant); } + if (bits.get_implicit_bit()) + { + x_mant |= one; + } else if (bits.is_subnormal()) + { + normalize(x_exp, x_mant); + } // Ensure that the exponent is even. if ((x_exp & 1) != 0) @@ -112,8 +119,7 @@ namespace ccm::gen y += current_bit; } } - } - else // If we are not in a constant evaluated context, we can vectorize the loop. + } else // If we are not in a constant evaluated context, we can vectorize the loop. { CCM_SIMD_VECTORIZE for (storage_type current_bit = one >> 1; current_bit != 0U; current_bit >>= 1) { @@ -145,10 +151,16 @@ namespace ccm::gen { case FE_TONEAREST: // Round to nearest, ties to even - if (round_bit && (lsb || (r != 0))) { ++y; } + if (round_bit && (lsb || (r != 0))) + { + ++y; + } break; case FE_UPWARD: - if (round_bit || (r != 0)) { ++y; } + if (round_bit || (r != 0)) + { + ++y; + } break; default: break; } @@ -164,8 +176,7 @@ namespace ccm::gen #endif } // namespace bit80 - template - static constexpr std::enable_if_t, T> sqrt_calc_bits(const support::fp::FPBits& bits) + template static constexpr std::enable_if_t, T> sqrt_calc_bits(const support::fp::FPBits & bits) { using FPBits_t = support::fp::FPBits; using storage_type = typename FPBits_t::storage_type; @@ -179,8 +190,7 @@ namespace ccm::gen { ++x_exp; // ensure that x_exp is the correct exponent of one bit. internal::normalize(x_exp, x_mant); - } - else + } else { x_mant |= one; } @@ -227,10 +237,16 @@ namespace ccm::gen { case FE_TONEAREST: // Round to nearest, ties to even - if (round_bit && (lsb || (r != 0))) { ++y; } + if (round_bit && (lsb || (r != 0))) + { + ++y; + } break; case FE_UPWARD: - if (round_bit || (r != 0)) { ++y; } + if (round_bit || (r != 0)) + { + ++y; + } break; default: break; } @@ -240,11 +256,12 @@ namespace ccm::gen // This calculates the square root of any IEEE-754 floating point number using the shift and add algorithm. // The function accounts for all rounding modes and special cases. - template - static constexpr std::enable_if_t, T> sqrt_impl(T x) // NOLINT + template static constexpr std::enable_if_t, T> sqrt_impl(T x) // NOLINT { - if constexpr (Is80BitLongDouble_v) { return bit80::sqrt_calc_80bits(x); } - else + if constexpr (Is80BitLongDouble_v) + { + return bit80::sqrt_calc_80bits(x); + } else { // IEEE floating points formats. using FPBits_t = support::fp::FPBits; @@ -252,12 +269,18 @@ namespace ccm::gen // lut_double_sqrt_first_256 // Handle special cases where the bits are +Inf, ±0, or ±NaN - if (CCM_UNLIKELY(bits == FPBits_t::inf(types::Sign::POS) || bits.is_zero() || bits.is_nan())) { return x; } + if (CCM_UNLIKELY(bits == FPBits_t::inf(types::Sign::POS) || bits.is_zero() || bits.is_nan())) + { + return x; + } constexpr auto flt_nan = FPBits_t::quiet_nan().get_val(); // Handle special cases where the bits are -Inf or -num - if (CCM_UNLIKELY(bits.is_neg())) { return -flt_nan; } + if (CCM_UNLIKELY(bits.is_neg())) + { + return -flt_nan; + } // If we didn't encounter any special cases, we can calculate the square root normally. return sqrt_calc_bits(bits); @@ -266,8 +289,9 @@ namespace ccm::gen } // namespace impl } // namespace internal - template , bool> = true> - constexpr T sqrt_gen(T num) - { return internal::impl::sqrt_impl(num); } + template , bool> = true> constexpr T sqrt_gen(T num) + { + return internal::impl::sqrt_impl(num); + } } // namespace ccm::gen diff --git a/include/ccmath/internal/math/generic/func/special/assoc_laguerre_gen.hpp b/include/ccmath/internal/math/generic/func/special/assoc_laguerre_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/assoc_laguerre_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/assoc_laguerre_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/assoc_legendre_gen.hpp b/include/ccmath/internal/math/generic/func/special/assoc_legendre_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/assoc_legendre_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/assoc_legendre_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/beta_gen.hpp b/include/ccmath/internal/math/generic/func/special/beta_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/beta_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/beta_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/comp_ellint_1_gen.hpp b/include/ccmath/internal/math/generic/func/special/comp_ellint_1_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/comp_ellint_1_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/comp_ellint_1_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/comp_ellint_2_gen.hpp b/include/ccmath/internal/math/generic/func/special/comp_ellint_2_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/comp_ellint_2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/comp_ellint_2_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/comp_ellint_3_gen.hpp b/include/ccmath/internal/math/generic/func/special/comp_ellint_3_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/comp_ellint_3_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/comp_ellint_3_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/cyl_bessel_i_gen.hpp b/include/ccmath/internal/math/generic/func/special/cyl_bessel_i_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/cyl_bessel_i_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/cyl_bessel_i_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/cyl_bessel_j_gen.hpp b/include/ccmath/internal/math/generic/func/special/cyl_bessel_j_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/cyl_bessel_j_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/cyl_bessel_j_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/cyl_bessel_k_gen.hpp b/include/ccmath/internal/math/generic/func/special/cyl_bessel_k_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/cyl_bessel_k_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/cyl_bessel_k_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/cyl_neumann_gen.hpp b/include/ccmath/internal/math/generic/func/special/cyl_neumann_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/cyl_neumann_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/cyl_neumann_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/ellint_1_gen.hpp b/include/ccmath/internal/math/generic/func/special/ellint_1_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/ellint_1_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/ellint_1_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/ellint_2_gen.hpp b/include/ccmath/internal/math/generic/func/special/ellint_2_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/ellint_2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/ellint_2_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/ellint_3_gen.hpp b/include/ccmath/internal/math/generic/func/special/ellint_3_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/ellint_3_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/ellint_3_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/expint_gen.hpp b/include/ccmath/internal/math/generic/func/special/expint_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/expint_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/expint_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/hermite_gen.hpp b/include/ccmath/internal/math/generic/func/special/hermite_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/hermite_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/hermite_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/laguerre_gen.hpp b/include/ccmath/internal/math/generic/func/special/laguerre_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/laguerre_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/laguerre_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/legendre_gen.hpp b/include/ccmath/internal/math/generic/func/special/legendre_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/legendre_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/legendre_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/riemann_zeta_gen.hpp b/include/ccmath/internal/math/generic/func/special/riemann_zeta_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/riemann_zeta_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/riemann_zeta_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/sph_bessel_gen.hpp b/include/ccmath/internal/math/generic/func/special/sph_bessel_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/sph_bessel_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/sph_bessel_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/sph_legendre_gen.hpp b/include/ccmath/internal/math/generic/func/special/sph_legendre_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/sph_legendre_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/sph_legendre_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/special/sph_neumann_gen.hpp b/include/ccmath/internal/math/generic/func/special/sph_neumann_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/special/sph_neumann_gen.hpp +++ b/include/ccmath/internal/math/generic/func/special/sph_neumann_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/trig/acos_gen.hpp b/include/ccmath/internal/math/generic/func/trig/acos_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/trig/acos_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/acos_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/trig/asin_gen.hpp b/include/ccmath/internal/math/generic/func/trig/asin_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/trig/asin_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/asin_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/trig/atan2_gen.hpp b/include/ccmath/internal/math/generic/func/trig/atan2_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/trig/atan2_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/atan2_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/trig/atan_gen.hpp b/include/ccmath/internal/math/generic/func/trig/atan_gen.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/internal/math/generic/func/trig/atan_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/atan_gen.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/internal/math/generic/func/trig/cos_gen.hpp b/include/ccmath/internal/math/generic/func/trig/cos_gen.hpp index cbcd091e..7adc76ef 100644 --- a/include/ccmath/internal/math/generic/func/trig/cos_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/cos_gen.hpp @@ -17,12 +17,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T cos_gen(T num) noexcept + template , bool> = true> constexpr T cos_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::cos_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::cos_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::cos_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::cos_double(num); + } else { return static_cast(ccm::internal::cos_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/trig/sin_gen.hpp b/include/ccmath/internal/math/generic/func/trig/sin_gen.hpp index 3ee01a15..d211cb2d 100644 --- a/include/ccmath/internal/math/generic/func/trig/sin_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/sin_gen.hpp @@ -17,12 +17,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T sin_gen(T num) noexcept + template , bool> = true> constexpr T sin_gen(T num) noexcept { - if constexpr (std::is_same_v) { return ccm::internal::sin_float(num); } - else if constexpr (std::is_same_v) { return ccm::internal::sin_double(num); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::sin_float(num); + } else if constexpr (std::is_same_v) + { + return ccm::internal::sin_double(num); + } else { return static_cast(ccm::internal::sin_double(static_cast(num))); } diff --git a/include/ccmath/internal/math/generic/func/trig/tan_gen.hpp b/include/ccmath/internal/math/generic/func/trig/tan_gen.hpp index 1116f788..54abf746 100644 --- a/include/ccmath/internal/math/generic/func/trig/tan_gen.hpp +++ b/include/ccmath/internal/math/generic/func/trig/tan_gen.hpp @@ -18,13 +18,15 @@ namespace ccm::gen { - template , bool> = true> - constexpr T tan_gen(T num) noexcept + template , bool> = true> constexpr T tan_gen(T num) noexcept { const T s = sin_gen(num); const T c = cos_gen(num); - if (c == static_cast(0)) { return std::numeric_limits::quiet_NaN(); } + if (c == static_cast(0)) + { + return std::numeric_limits::quiet_NaN(); + } return s / c; } diff --git a/include/ccmath/internal/math/runtime/func/basic/fabs_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fabs_rt.hpp index 9c361732..343bd544 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fabs_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fabs_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fabs_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T fabs_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_abs) { return ccm::builtin::abs_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_abs) + { + return ccm::builtin::abs_rt(num); + } else { return gen::abs(num); } diff --git a/include/ccmath/internal/math/runtime/func/basic/fdim_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fdim_rt.hpp index 9ba4d4b7..c3a82aa7 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fdim_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fdim_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fdim_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T fdim_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_fdim) { return ccm::builtin::fdim_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_fdim) + { + return ccm::builtin::fdim_rt(x, y); + } else { return gen::fdim(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/basic/fma_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fma_rt.hpp index 21aa9f08..7e0aa3e2 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fma_rt.hpp @@ -16,7 +16,8 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fma_rt(T x, T y, T z) noexcept - { return support::fp::dispatch_runtime_fma(x, y, z); } + template , bool> = true> [[nodiscard]] inline T fma_rt(T x, T y, T z) noexcept + { + return support::fp::dispatch_runtime_fma(x, y, z); + } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/basic/fmax_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmax_rt.hpp index 5c44695d..54fee35a 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmax_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmax_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fmax_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T fmax_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_fmax) { return ccm::builtin::fmax_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_fmax) + { + return ccm::builtin::fmax_rt(x, y); + } else { return gen::max(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/basic/fmin_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmin_rt.hpp index 4bad819f..aabc99e5 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmin_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmin_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fmin_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T fmin_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_fmin) { return ccm::builtin::fmin_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_fmin) + { + return ccm::builtin::fmin_rt(x, y); + } else { return gen::min(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp index ebbb7f3a..8c9a15d0 100644 --- a/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/fmod_rt.hpp @@ -19,18 +19,21 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T fmod_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T fmod_rt(T x, T y) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::fmod_call(x, y); #else - if constexpr (ccm::builtin::has_runtime_fmod) { return ccm::builtin::fmod_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_fmod) + { + return ccm::builtin::fmod_rt(x, y); + } else { // No builtin and no system math, so use the exact FPBits reduction. It is exact for every magnitude. - if constexpr (std::is_same_v) { return static_cast(ccm::internal::fmod(static_cast(x), static_cast(y))); } - else + if constexpr (std::is_same_v) + { + return static_cast(ccm::internal::fmod(static_cast(x), static_cast(y))); + } else { return ccm::internal::fmod(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp index c9747052..069bf6c5 100644 --- a/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/basic/remainder_rt.hpp @@ -19,14 +19,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T remainder_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T remainder_rt(T x, T y) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::remainder_call(x, y); #else - if constexpr (ccm::builtin::has_runtime_remainder) { return ccm::builtin::remainder_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_remainder) + { + return ccm::builtin::remainder_rt(x, y); + } else { // No builtin and no system math, so reuse the exact remquo reduction. It rounds the // quotient to nearest, ties-to-even, and covers the special cases, unlike the trunc diff --git a/include/ccmath/internal/math/runtime/func/detail/system_math.hpp b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp index ebdee180..043eef31 100644 --- a/include/ccmath/internal/math/runtime/func/detail/system_math.hpp +++ b/include/ccmath/internal/math/runtime/func/detail/system_math.hpp @@ -39,91 +39,100 @@ namespace ccm::rt::detail::sys { // Expensive transcendentals. The runtime path uses these only under FE_TONEAREST, since libm is // not correctly rounded in the directed modes (the kernel handles those, like the builtin path). - template - [[nodiscard]] inline T exp2_call(T x) + template [[nodiscard]] inline T exp2_call(T x) { - if constexpr (std::is_same_v) { return ::exp2f(x); } - else + if constexpr (std::is_same_v) + { + return ::exp2f(x); + } else { return ::exp2(static_cast(x)); } } - template - [[nodiscard]] inline T log_call(T x) + template [[nodiscard]] inline T log_call(T x) { - if constexpr (std::is_same_v) { return ::logf(x); } - else + if constexpr (std::is_same_v) + { + return ::logf(x); + } else { return ::log(static_cast(x)); } } - template - [[nodiscard]] inline T log2_call(T x) + template [[nodiscard]] inline T log2_call(T x) { - if constexpr (std::is_same_v) { return ::log2f(x); } - else + if constexpr (std::is_same_v) + { + return ::log2f(x); + } else { return ::log2(static_cast(x)); } } - template - [[nodiscard]] inline T log10_call(T x) + template [[nodiscard]] inline T log10_call(T x) { - if constexpr (std::is_same_v) { return ::log10f(x); } - else + if constexpr (std::is_same_v) + { + return ::log10f(x); + } else { return ::log10(static_cast(x)); } } - template - [[nodiscard]] inline T sin_call(T x) + template [[nodiscard]] inline T sin_call(T x) { - if constexpr (std::is_same_v) { return ::sinf(x); } - else + if constexpr (std::is_same_v) + { + return ::sinf(x); + } else { return ::sin(static_cast(x)); } } - template - [[nodiscard]] inline T cos_call(T x) + template [[nodiscard]] inline T cos_call(T x) { - if constexpr (std::is_same_v) { return ::cosf(x); } - else + if constexpr (std::is_same_v) + { + return ::cosf(x); + } else { return ::cos(static_cast(x)); } } - template - [[nodiscard]] inline T tan_call(T x) + template [[nodiscard]] inline T tan_call(T x) { - if constexpr (std::is_same_v) { return ::tanf(x); } - else + if constexpr (std::is_same_v) + { + return ::tanf(x); + } else { return ::tan(static_cast(x)); } } - template - [[nodiscard]] inline T gamma_call(T x) + template [[nodiscard]] inline T gamma_call(T x) { - if constexpr (std::is_same_v) { return ::tgammaf(x); } - else + if constexpr (std::is_same_v) + { + return ::tgammaf(x); + } else { return ::tgamma(static_cast(x)); } } - template - [[nodiscard]] inline T lgamma_call(T x) + template [[nodiscard]] inline T lgamma_call(T x) { - if constexpr (std::is_same_v) { return ::lgammaf(x); } - else + if constexpr (std::is_same_v) + { + return ::lgammaf(x); + } else { return ::lgamma(static_cast(x)); } @@ -132,21 +141,23 @@ namespace ccm::rt::detail::sys // fmod and remainder are exact in every rounding mode, so they are routed unconditionally. The // generic *_rt formula (x - trunc(x / y) * y) loses all precision for large quotients, and the // exact kernel cannot be reached here without pulling the nearest dispatch (and pp) back in. - template - [[nodiscard]] inline T fmod_call(T x, T y) + template [[nodiscard]] inline T fmod_call(T x, T y) { - if constexpr (std::is_same_v) { return ::fmodf(x, y); } - else + if constexpr (std::is_same_v) + { + return ::fmodf(x, y); + } else { return ::fmod(static_cast(x), static_cast(y)); } } - template - [[nodiscard]] inline T remainder_call(T x, T y) + template [[nodiscard]] inline T remainder_call(T x, T y) { - if constexpr (std::is_same_v) { return ::remainderf(x, y); } - else + if constexpr (std::is_same_v) + { + return ::remainderf(x, y); + } else { return ::remainder(static_cast(x), static_cast(y)); } diff --git a/include/ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp b/include/ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp index 8308f708..3d46700e 100644 --- a/include/ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp +++ b/include/ccmath/internal/math/runtime/func/detail/trunc_scalar.hpp @@ -17,20 +17,31 @@ namespace ccm::rt::detail { - template - [[nodiscard]] inline T trunc_scalar(T num) noexcept + template [[nodiscard]] inline T trunc_scalar(T num) noexcept { using FPBits_t = ccm::support::fp::FPBits; using Storage_t = typename FPBits_t::storage_type; FPBits_t bits(num); - if (CCM_UNLIKELY(bits.is_inf_or_nan())) { return num; } - if (CCM_UNLIKELY(num == static_cast(0))) { return num; } + if (CCM_UNLIKELY(bits.is_inf_or_nan())) + { + return num; + } + if (CCM_UNLIKELY(num == static_cast(0))) + { + return num; + } const int exponent = bits.get_exponent(); - if (exponent >= FPBits_t::fraction_length) { return num; } - if (exponent <= -1) { return FPBits_t::zero(bits.sign()).get_val(); } + if (exponent >= FPBits_t::fraction_length) + { + return num; + } + if (exponent <= -1) + { + return FPBits_t::zero(bits.sign()).get_val(); + } const int trimming_size = FPBits_t::fraction_length - exponent; const auto truncated_mantissa = static_cast((bits.get_mantissa() >> trimming_size) << trimming_size); @@ -38,23 +49,33 @@ namespace ccm::rt::detail return bits.get_val(); } - template - [[nodiscard]] inline T floor_scalar(T num) noexcept + template [[nodiscard]] inline T floor_scalar(T num) noexcept { - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } const T truncated = trunc_scalar(num); - if (truncated == num || num > static_cast(0)) { return truncated; } + if (truncated == num || num > static_cast(0)) + { + return truncated; + } return truncated - static_cast(1); } - template - [[nodiscard]] inline T ceil_scalar(T num) noexcept + template [[nodiscard]] inline T ceil_scalar(T num) noexcept { - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } const T truncated = trunc_scalar(num); - if (truncated == num || num < static_cast(0)) { return truncated; } + if (truncated == num || num < static_cast(0)) + { + return truncated; + } return truncated + static_cast(1); } } // namespace ccm::rt::detail diff --git a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp index 3383ff02..9d147e6b 100644 --- a/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/exp2_rt.hpp @@ -20,23 +20,27 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T exp2_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T exp2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::exp2_float, ccm::internal::exp2_double); }; #if defined(CCM_CONFIG_SYSTEM_MATH) // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return detail::sys::exp2_call(num); #else if constexpr (ccm::builtin::has_runtime_exp2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return ccm::builtin::exp2_rt(num); - } - else + } else { return simd_impl::unary_via_scalar_abi(num, scalar); } diff --git a/include/ccmath/internal/math/runtime/func/expo/exp_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/exp_rt.hpp index e05c17f4..344e629c 100644 --- a/include/ccmath/internal/math/runtime/func/expo/exp_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/exp_rt.hpp @@ -21,19 +21,21 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T exp_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T exp_rt(T num) noexcept { - const auto scalar = [](T value) - { return detail::dispatch_float_double(value, ccm::internal::impl::exp_float_impl, ccm::internal::impl::exp_double_impl); }; + const auto scalar = [](T value) { + return detail::dispatch_float_double(value, ccm::internal::impl::exp_float_impl, ccm::internal::impl::exp_double_impl); + }; if constexpr (ccm::builtin::has_runtime_exp) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return ccm::builtin::exp_rt(num); - } - else + } else { #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::exp(v); }, scalar); diff --git a/include/ccmath/internal/math/runtime/func/expo/expm1_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/expm1_rt.hpp index e0fbcf30..c7c3d731 100644 --- a/include/ccmath/internal/math/runtime/func/expo/expm1_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/expm1_rt.hpp @@ -18,14 +18,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T expm1_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T expm1_rt(T num) noexcept { // TODO(IanP): add the FE_TONEAREST guard the other expo runtime headers use once the generic // expm1 double kernel meets the accuracy contract. It is not accurate enough yet, so routing // directed rounding to it would regress expm1 below the libm builtin used here in every mode. - if constexpr (ccm::builtin::has_runtime_expm1) { return ccm::builtin::expm1_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_expm1) + { + return ccm::builtin::expm1_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::expm1_float, ccm::internal::expm1_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp index 6b6c0f1c..7ee08ce3 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log10_rt.hpp @@ -21,23 +21,27 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T log10_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T log10_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log10_float, ccm::internal::log10_double); }; #if defined(CCM_CONFIG_SYSTEM_MATH) // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return detail::sys::log10_call(num); #else if constexpr (ccm::builtin::has_runtime_log10) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return ccm::builtin::log10_rt(num); - } - else + } else { #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log10(v); }, scalar); diff --git a/include/ccmath/internal/math/runtime/func/expo/log1p_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log1p_rt.hpp index d2648e8d..7f5c5ca1 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log1p_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log1p_rt.hpp @@ -18,14 +18,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T log1p_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T log1p_rt(T num) noexcept { // TODO(IanP): add the FE_TONEAREST guard the other expo runtime headers use once the generic // log1p double kernel meets the accuracy contract. It is not accurate enough yet, so routing // directed rounding to it would regress log1p below the libm builtin used here in every mode. - if constexpr (ccm::builtin::has_runtime_log1p) { return ccm::builtin::log1p_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_log1p) + { + return ccm::builtin::log1p_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log1p_float, ccm::internal::log1p_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp index ffb236ec..ab85d79c 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log2_rt.hpp @@ -22,23 +22,27 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T log2_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T log2_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log2_float, ccm::internal::log2_double); }; #if defined(CCM_CONFIG_SYSTEM_MATH) // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return detail::sys::log2_call(num); #else if constexpr (ccm::builtin::has_runtime_log2) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return ccm::builtin::log2_rt(num); - } - else + } else { #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log2(v); }, scalar); diff --git a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp index eca1fd71..5cedbdaf 100644 --- a/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/expo/log_rt.hpp @@ -22,23 +22,27 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T log_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T log_rt(T num) noexcept { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::log_float, ccm::internal::log_double); }; #if defined(CCM_CONFIG_SYSTEM_MATH) // The system libm is not correctly rounded outside round to nearest, so use the generic kernel there. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return detail::sys::log_call(num); #else if constexpr (ccm::builtin::has_runtime_log) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return scalar(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return scalar(num); + } return ccm::builtin::log_rt(num); - } - else + } else { #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) return detail::unary_svml_or_impl(num, [](auto v) { return intrin::log(v); }, scalar); diff --git a/include/ccmath/internal/math/runtime/func/fmanip/copysign_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/copysign_rt.hpp index abe43164..cfc5fe8b 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/copysign_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/copysign_rt.hpp @@ -23,12 +23,14 @@ namespace ccm::rt { namespace detail { - template - [[nodiscard]] inline T copysign_scalar(T mag, T sgn) noexcept + template [[nodiscard]] inline T copysign_scalar(T mag, T sgn) noexcept { if (ccm::isnan(mag) || ccm::isnan(sgn)) { - if (ccm::signbit(sgn)) { return -std::numeric_limits::quiet_NaN(); } + if (ccm::signbit(sgn)) + { + return -std::numeric_limits::quiet_NaN(); + } return std::numeric_limits::quiet_NaN(); } @@ -37,11 +39,12 @@ namespace ccm::rt } } // namespace detail - template , bool> = true> - [[nodiscard]] inline T copysign_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T copysign_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_copysign) { return ccm::builtin::copysign_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_copysign) + { + return ccm::builtin::copysign_rt(x, y); + } else { return detail::copysign_scalar(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/frexp_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/frexp_rt.hpp index 23d29d5c..7dd24c74 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/frexp_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/frexp_rt.hpp @@ -16,7 +16,8 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T frexp_rt(T num, int & exp) noexcept - { return ccm::internal::impl::frexp_impl(num, exp); } + template , bool> = true> [[nodiscard]] inline T frexp_rt(T num, int & exp) noexcept + { + return ccm::internal::impl::frexp_impl(num, exp); + } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/fmanip/ilogb_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/ilogb_rt.hpp index 6976330f..b76fd6b7 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/ilogb_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/ilogb_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline int ilogb_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline int ilogb_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_ilogb) { return ccm::builtin::ilogb_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_ilogb) + { + return ccm::builtin::ilogb_rt(num); + } else { return ccm::internal::impl::ilogb_impl(num); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/ldexp_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/ldexp_rt.hpp index 2bf3ad7f..c903f2fb 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/ldexp_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/ldexp_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T ldexp_rt(T num, int exp) noexcept + template , bool> = true> [[nodiscard]] inline T ldexp_rt(T num, int exp) noexcept { - if constexpr (ccm::builtin::has_runtime_ldexp) { return ccm::builtin::ldexp_rt(num, exp); } - else + if constexpr (ccm::builtin::has_runtime_ldexp) + { + return ccm::builtin::ldexp_rt(num, exp); + } else { return ccm::support::helpers::internal_ldexp(num, exp); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/logb_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/logb_rt.hpp index 9c0c4a11..23a386f9 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/logb_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/logb_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T logb_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T logb_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_logb) { return ccm::builtin::logb_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_logb) + { + return ccm::builtin::logb_rt(num); + } else { return ccm::internal::impl::logb_impl(num); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/modf_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/modf_rt.hpp index 36ac05af..853505ea 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/modf_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/modf_rt.hpp @@ -16,7 +16,8 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T modf_rt(T num, T & iptr) noexcept - { return internal::impl::modf_impl(num, &iptr); } + template , bool> = true> [[nodiscard]] inline T modf_rt(T num, T & iptr) noexcept + { + return internal::impl::modf_impl(num, &iptr); + } } // namespace ccm::rt diff --git a/include/ccmath/internal/math/runtime/func/fmanip/nextafter_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/nextafter_rt.hpp index 81345585..e2cefd9d 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/nextafter_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/nextafter_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T nextafter_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T nextafter_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_nextafter) { return ccm::builtin::nextafter_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_nextafter) + { + return ccm::builtin::nextafter_rt(x, y); + } else { return gen::nextafter_gen(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/nexttoward_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/nexttoward_rt.hpp index 1193349f..bd72e68e 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/nexttoward_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/nexttoward_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T nexttoward_rt(T x, long double y) noexcept + template , bool> = true> [[nodiscard]] inline T nexttoward_rt(T x, long double y) noexcept { - if constexpr (ccm::builtin::has_runtime_nexttoward) { return ccm::builtin::nexttoward_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_nexttoward) + { + return ccm::builtin::nexttoward_rt(x, y); + } else { return gen::nextafter_gen(x, y); } diff --git a/include/ccmath/internal/math/runtime/func/fmanip/scalbn_rt.hpp b/include/ccmath/internal/math/runtime/func/fmanip/scalbn_rt.hpp index c1d3a226..67bd3a4c 100644 --- a/include/ccmath/internal/math/runtime/func/fmanip/scalbn_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/fmanip/scalbn_rt.hpp @@ -19,11 +19,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T scalbn_rt(T num, int exp) noexcept + template , bool> = true> [[nodiscard]] inline T scalbn_rt(T num, int exp) noexcept { - if constexpr (ccm::builtin::has_runtime_scalbn) { return ccm::builtin::scalbn_rt(num, exp); } - else + if constexpr (ccm::builtin::has_runtime_scalbn) + { + return ccm::builtin::scalbn_rt(num, exp); + } else { return detail::dispatch_float_double( num, [&](float value) { return internal::scalbn_float(value, exp); }, [&](double value) { return internal::scalbn_double(value, exp); }); diff --git a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp index 706b11a2..a68eb62c 100644 --- a/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/gamma_rt.hpp @@ -19,14 +19,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T gamma_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T gamma_rt(T num) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::gamma_call(num); #else - if constexpr (ccm::builtin::has_runtime_gamma) { return ccm::builtin::gamma_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_gamma) + { + return ccm::builtin::gamma_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::gamma_float, ccm::internal::gamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp index dedec1d2..f22cca51 100644 --- a/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/misc/lgamma_rt.hpp @@ -19,14 +19,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T lgamma_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T lgamma_rt(T num) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::lgamma_call(num); #else - if constexpr (ccm::builtin::has_runtime_lgamma) { return ccm::builtin::lgamma_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_lgamma) + { + return ccm::builtin::lgamma_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, ccm::internal::lgamma_float, ccm::internal::lgamma_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp index e197b625..6ea69a65 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/ceil_rt.hpp @@ -19,12 +19,13 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T ceil_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T ceil_rt(T num) noexcept { #ifndef CCMATH_COMPILER_GCC - if constexpr (ccm::builtin::has_runtime_ceil) { return ccm::builtin::ceil_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_ceil) + { + return ccm::builtin::ceil_rt(num); + } else #endif { return detail::ceil_scalar(num); diff --git a/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp index 079e7e4f..4f88bd4d 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/floor_rt.hpp @@ -19,12 +19,13 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T floor_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T floor_rt(T num) noexcept { #ifndef CCMATH_COMPILER_GCC - if constexpr (ccm::builtin::has_runtime_floor) { return ccm::builtin::floor_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_floor) + { + return ccm::builtin::floor_rt(num); + } else #endif { return detail::floor_scalar(num); diff --git a/include/ccmath/internal/math/runtime/func/nearest/nearbyint_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/nearbyint_rt.hpp index 1b8e26d1..0cb6cb81 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/nearbyint_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/nearbyint_rt.hpp @@ -19,11 +19,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T nearbyint_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T nearbyint_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_nearbyint) { return ccm::builtin::nearbyint_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_nearbyint) + { + return ccm::builtin::nearbyint_rt(num); + } else { return ccm::support::fp::directional_round(num, ccm::support::fenv::get_rounding_mode()); } diff --git a/include/ccmath/internal/math/runtime/func/nearest/rint_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/rint_rt.hpp index ff51eb5f..d3583d81 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/rint_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/rint_rt.hpp @@ -20,18 +20,21 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T rint_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T rint_rt(T num) noexcept { #ifdef CCMATH_COMPILER_GCC - if constexpr (ccm::builtin::has_runtime_rint && std::is_same_v) { return ccm::builtin::rint_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_rint && std::is_same_v) + { + return ccm::builtin::rint_rt(num); + } else { return ccm::support::fp::directional_round(num, ccm::support::fenv::get_rounding_mode()); } #else - if constexpr (ccm::builtin::has_runtime_rint) { return ccm::builtin::rint_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_rint) + { + return ccm::builtin::rint_rt(num); + } else { return ccm::support::fp::directional_round(num, ccm::support::fenv::get_rounding_mode()); } diff --git a/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp index 3830ab10..aa4add67 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/round_rt.hpp @@ -21,15 +21,19 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T round_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T round_rt(T num) noexcept { #ifndef CCMATH_COMPILER_GCC - if constexpr (ccm::builtin::has_runtime_round) { return ccm::builtin::round_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_round) + { + return ccm::builtin::round_rt(num); + } else #endif { - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } constexpr int round_mode = static_cast(ccm::support::fp::rounding_mode::eFE_TONEARESTFROMZERO); return ccm::support::fp::directional_round(num, round_mode); } diff --git a/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp b/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp index 4f4869c0..ffe68bb7 100644 --- a/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/nearest/trunc_rt.hpp @@ -19,12 +19,13 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T trunc_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T trunc_rt(T num) noexcept { #ifndef CCMATH_COMPILER_GCC - if constexpr (ccm::builtin::has_runtime_trunc) { return ccm::builtin::trunc_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_trunc) + { + return ccm::builtin::trunc_rt(num); + } else #endif { return detail::trunc_scalar(num); diff --git a/include/ccmath/internal/math/runtime/func/power/cbrt_rt.hpp b/include/ccmath/internal/math/runtime/func/power/cbrt_rt.hpp index 5538843f..8a1eec10 100644 --- a/include/ccmath/internal/math/runtime/func/power/cbrt_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/cbrt_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T cbrt_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T cbrt_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_cbrt) { return ccm::builtin::cbrt_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_cbrt) + { + return ccm::builtin::cbrt_rt(num); + } else { const auto scalar = [](T value) { return gen::cbrt_gen(value); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/power/hypot_rt.hpp b/include/ccmath/internal/math/runtime/func/power/hypot_rt.hpp index 54b1fc26..3914bf62 100644 --- a/include/ccmath/internal/math/runtime/func/power/hypot_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/hypot_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T hypot_rt(T x, T y) noexcept + template , bool> = true> [[nodiscard]] inline T hypot_rt(T x, T y) noexcept { - if constexpr (ccm::builtin::has_runtime_hypot) { return ccm::builtin::hypot_rt(x, y); } - else + if constexpr (ccm::builtin::has_runtime_hypot) + { + return ccm::builtin::hypot_rt(x, y); + } else { const auto scalar = [](T lhs, T rhs) { return gen::hypot_gen(lhs, rhs); }; return simd_impl::binary_via_scalar_abi(x, y, scalar); diff --git a/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp b/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp index 3215b924..0308b419 100644 --- a/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/pow_rt.hpp @@ -44,9 +44,13 @@ namespace ccm::rt::simd_impl // Single and double precision use the dedicated vectorized kernels, which are bit // identical to the scalar correctly rounded powf and pow. Other types use the // per-lane std::simd baseline for now. - if constexpr (std::is_same_v) { return gen::impl::powf_simd(base_v, exp_v)[0]; } - else if constexpr (std::is_same_v) { return gen::impl::pow_simd(base_v, exp_v)[0]; } - else + if constexpr (std::is_same_v) + { + return gen::impl::powf_simd(base_v, exp_v)[0]; + } else if constexpr (std::is_same_v) + { + return gen::impl::pow_simd(base_v, exp_v)[0]; + } else { return pp::pow(base_v, exp_v)[0]; } @@ -56,34 +60,42 @@ namespace ccm::rt::simd_impl namespace ccm::rt { - template , bool> = true> - T pow_rt(T base, T exp) + template , bool> = true> T pow_rt(T base, T exp) { #ifndef CCM_CONFIG_TEST_DISABLE_RUNTIME_BUILTIN_POW if constexpr (ccm::builtin::has_runtime_pow) { // The runtime builtin lowers to libm, which is not correctly rounded outside round to // nearest. Outside FE_TONEAREST use the correctly-rounded generic kernel instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return gen::pow_gen(base, exp); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return gen::pow_gen(base, exp); + } return ccm::builtin::pow_rt(base, exp); } #endif #ifdef CCMATH_HAS_SIMD // In the unlikely event, the rounding mode is not the default, use the runtime implementation instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return gen::pow_gen(base, exp); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return gen::pow_gen(base, exp); + } #if !defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) // If long double is different from double, use the generic implementation instead. - if constexpr (std::is_same_v || std::is_same_v) { return simd_impl::pow_simd_impl(base, exp); } - else + if constexpr (std::is_same_v || std::is_same_v) + { + return simd_impl::pow_simd_impl(base, exp); + } else { return gen::pow_gen(base, exp); } #else // If long double is the same as double, we can use the SIMD implementation instead. - if constexpr (std::is_same_v || std::is_same_v) { return simd_impl::pow_simd_impl(base, exp); } - else if constexpr (std::is_same_v) + if constexpr (std::is_same_v || std::is_same_v) + { + return simd_impl::pow_simd_impl(base, exp); + } else if constexpr (std::is_same_v) { return static_cast(simd_impl::pow_simd_impl(static_cast(base), static_cast(exp))); - } - else + } else { return ccm::gen::pow_gen(base, exp); } diff --git a/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp b/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp index 0cac56c4..80fb7db0 100644 --- a/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/power/sqrt_rt.hpp @@ -48,23 +48,34 @@ namespace ccm::rt::simd_impl namespace ccm::rt { - template , bool> = true> - T sqrt_rt(T num) + template , bool> = true> T sqrt_rt(T num) { - if constexpr (ccm::builtin::has_runtime_sqrt) { return ccm::builtin::sqrt_rt(num); } + if constexpr (ccm::builtin::has_runtime_sqrt) + { + return ccm::builtin::sqrt_rt(num); + } #ifdef CCMATH_HAS_SIMD // In the unlikely event, the rounding mode is not the default, use the runtime implementation instead. - if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) { return gen::sqrt_gen(num); } + if (CCM_UNLIKELY(ccm::support::fenv::get_rounding_mode() != FE_TONEAREST)) + { + return gen::sqrt_gen(num); + } #if !defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT64) // If long double is different from double, use the generic implementation instead. - if constexpr (std::is_same_v || std::is_same_v) { return simd_impl::sqrt_simd_impl(num); } - else + if constexpr (std::is_same_v || std::is_same_v) + { + return simd_impl::sqrt_simd_impl(num); + } else { return gen::sqrt_gen(num); } #else // If long double is the same as double we can use the SIMD implementation instead. - if constexpr (std::is_same_v || std::is_same_v) { return simd_impl::sqrt_simd_impl(num); } - else if constexpr (std::is_same_v) { return static_cast(simd_impl::sqrt_simd_impl(static_cast(num))); } - else + if constexpr (std::is_same_v || std::is_same_v) + { + return simd_impl::sqrt_simd_impl(num); + } else if constexpr (std::is_same_v) + { + return static_cast(simd_impl::sqrt_simd_impl(static_cast(num))); + } else { return ccm::gen::sqrt_gen(num); } diff --git a/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp b/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp index 99987d5c..456163d8 100644 --- a/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp +++ b/include/ccmath/internal/math/runtime/func/rt_dispatch.hpp @@ -20,24 +20,26 @@ namespace ccm::rt::detail { - template - [[nodiscard]] inline bool simd_runtime_ok() noexcept - { return ccm::support::fenv::get_rounding_mode() == FE_TONEAREST; } + template [[nodiscard]] inline bool simd_runtime_ok() noexcept + { + return ccm::support::fenv::get_rounding_mode() == FE_TONEAREST; + } #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT64 - template - inline constexpr bool simd_eligible_v = std::is_same_v || std::is_same_v || std::is_same_v; + template inline constexpr bool simd_eligible_v = std::is_same_v || std::is_same_v || std::is_same_v; #else - template - inline constexpr bool simd_eligible_v = std::is_same_v || std::is_same_v; + template inline constexpr bool simd_eligible_v = std::is_same_v || std::is_same_v; #endif - template - [[nodiscard]] inline auto dispatch_float_double(T value, FloatFn float_fn, DoubleFn double_fn) + template [[nodiscard]] inline auto dispatch_float_double(T value, FloatFn float_fn, DoubleFn double_fn) { - if constexpr (std::is_same_v) { return float_fn(value); } - else if constexpr (std::is_same_v) { return double_fn(value); } - else + if constexpr (std::is_same_v) + { + return float_fn(value); + } else if constexpr (std::is_same_v) + { + return double_fn(value); + } else { return static_cast(double_fn(static_cast(value))); } @@ -47,9 +49,13 @@ namespace ccm::rt::detail [[nodiscard]] inline auto dispatch_float_double(T value, FloatFn float_fn, DoubleFn double_fn, LongDoubleFn long_double_fn) -> std::enable_if_t, decltype(long_double_fn(value))> { - if constexpr (std::is_same_v) { return float_fn(value); } - else if constexpr (std::is_same_v) { return double_fn(value); } - else + if constexpr (std::is_same_v) + { + return float_fn(value); + } else if constexpr (std::is_same_v) + { + return double_fn(value); + } else { return long_double_fn(value); } @@ -59,82 +65,90 @@ namespace ccm::rt::detail namespace ccm::rt::simd_impl { #ifdef CCMATH_HAS_SIMD - template - [[nodiscard]] inline T unary(T value, SimdOp simd_op, ScalarFn scalar_fn) noexcept + template [[nodiscard]] inline T unary(T value, SimdOp simd_op, ScalarFn scalar_fn) noexcept { if constexpr (detail::simd_eligible_v) { - if (CCM_UNLIKELY(!detail::simd_runtime_ok())) { return scalar_fn(value); } + if (CCM_UNLIKELY(!detail::simd_runtime_ok())) + { + return scalar_fn(value); + } intrin::simd const input(value); return simd_op(input).convert(); - } - else + } else { return scalar_fn(value); } } - template - [[nodiscard]] inline T binary(T lhs, T rhs, SimdOp simd_op, ScalarFn scalar_fn) noexcept + template [[nodiscard]] inline T binary(T lhs, T rhs, SimdOp simd_op, ScalarFn scalar_fn) noexcept { if constexpr (detail::simd_eligible_v) { - if (CCM_UNLIKELY(!detail::simd_runtime_ok())) { return scalar_fn(lhs, rhs); } + if (CCM_UNLIKELY(!detail::simd_runtime_ok())) + { + return scalar_fn(lhs, rhs); + } intrin::simd const left(lhs); intrin::simd const right(rhs); return simd_op(left, right).convert(); - } - else + } else { return scalar_fn(lhs, rhs); } } - template - [[nodiscard]] inline T unary_via_scalar_abi(T value, ScalarFn scalar_fn) noexcept + template [[nodiscard]] inline T unary_via_scalar_abi(T value, ScalarFn scalar_fn) noexcept { if constexpr (detail::simd_eligible_v) { - if (CCM_UNLIKELY(!detail::simd_runtime_ok())) { return scalar_fn(value); } + if (CCM_UNLIKELY(!detail::simd_runtime_ok())) + { + return scalar_fn(value); + } intrin::simd const input(value); return intrin::map_scalar(input, scalar_fn).convert(); - } - else + } else { return scalar_fn(value); } } - template - [[nodiscard]] inline T binary_via_scalar_abi(T lhs, T rhs, ScalarFn scalar_fn) noexcept + template [[nodiscard]] inline T binary_via_scalar_abi(T lhs, T rhs, ScalarFn scalar_fn) noexcept { if constexpr (detail::simd_eligible_v) { - if (CCM_UNLIKELY(!detail::simd_runtime_ok())) { return scalar_fn(lhs, rhs); } + if (CCM_UNLIKELY(!detail::simd_runtime_ok())) + { + return scalar_fn(lhs, rhs); + } intrin::simd const left(lhs); intrin::simd const right(rhs); return intrin::map_scalar(left, right, scalar_fn).convert(); - } - else + } else { return scalar_fn(lhs, rhs); } } #else - template - [[nodiscard]] inline T unary(T value, SimdOp /*simd_op*/, ScalarFn scalar_fn) noexcept - { return scalar_fn(value); } + template [[nodiscard]] inline T unary(T value, SimdOp /*simd_op*/, ScalarFn scalar_fn) noexcept + { + return scalar_fn(value); + } - template - [[nodiscard]] inline T binary(T lhs, T rhs, SimdOp /*simd_op*/, ScalarFn scalar_fn) noexcept - { return scalar_fn(lhs, rhs); } + template [[nodiscard]] inline T binary(T lhs, T rhs, SimdOp /*simd_op*/, ScalarFn scalar_fn) noexcept + { + return scalar_fn(lhs, rhs); + } - template - [[nodiscard]] inline T unary_via_scalar_abi(T value, ScalarFn scalar_fn) noexcept - { return scalar_fn(value); } + template [[nodiscard]] inline T unary_via_scalar_abi(T value, ScalarFn scalar_fn) noexcept + { + return scalar_fn(value); + } - template - [[nodiscard]] inline T binary_via_scalar_abi(T lhs, T rhs, ScalarFn scalar_fn) noexcept - { return scalar_fn(lhs, rhs); } + template [[nodiscard]] inline T binary_via_scalar_abi(T lhs, T rhs, ScalarFn scalar_fn) noexcept + { + return scalar_fn(lhs, rhs); + } #endif } // namespace ccm::rt::simd_impl diff --git a/include/ccmath/internal/math/runtime/func/svml_dispatch.hpp b/include/ccmath/internal/math/runtime/func/svml_dispatch.hpp index 88238b47..c27aaf3f 100644 --- a/include/ccmath/internal/math/runtime/func/svml_dispatch.hpp +++ b/include/ccmath/internal/math/runtime/func/svml_dispatch.hpp @@ -15,18 +15,17 @@ namespace ccm::rt::detail { #ifdef CCMATH_HAS_SIMD_SVML - template - inline constexpr bool has_svml_unary_v = simd_eligible_v; + template inline constexpr bool has_svml_unary_v = simd_eligible_v; #else - template - inline constexpr bool has_svml_unary_v = false; + template inline constexpr bool has_svml_unary_v = false; #endif - template - [[nodiscard]] inline T unary_svml_or_impl(T value, SimdOp simd_op, ScalarFn scalar_fn) noexcept + template [[nodiscard]] inline T unary_svml_or_impl(T value, SimdOp simd_op, ScalarFn scalar_fn) noexcept { - if constexpr (has_svml_unary_v) { return simd_impl::unary(value, simd_op, scalar_fn); } - else + if constexpr (has_svml_unary_v) + { + return simd_impl::unary(value, simd_op, scalar_fn); + } else { return simd_impl::unary_via_scalar_abi(value, scalar_fn); } @@ -34,5 +33,7 @@ namespace ccm::rt::detail template [[nodiscard]] inline T unary_trig_svml_or_impl(T value, SimdOp simd_op, ScalarFn scalar_fn) noexcept - { return unary_svml_or_impl(value, simd_op, scalar_fn); } + { + return unary_svml_or_impl(value, simd_op, scalar_fn); + } } // namespace ccm::rt::detail diff --git a/include/ccmath/internal/math/runtime/func/trig/acos_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/acos_rt.hpp index aa821b41..6537d436 100644 --- a/include/ccmath/internal/math/runtime/func/trig/acos_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/acos_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T acos_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T acos_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_acos) { return ccm::builtin::acos_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_acos) + { + return ccm::builtin::acos_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, internal::impl::acos_float, internal::impl::acos_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/trig/asin_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/asin_rt.hpp index 5087d308..21db52c8 100644 --- a/include/ccmath/internal/math/runtime/func/trig/asin_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/asin_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T asin_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T asin_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_asin) { return ccm::builtin::asin_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_asin) + { + return ccm::builtin::asin_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, internal::impl::asin_float, internal::impl::asin_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/trig/atan2_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/atan2_rt.hpp index 1e6b83d6..5e34f4b7 100644 --- a/include/ccmath/internal/math/runtime/func/trig/atan2_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/atan2_rt.hpp @@ -18,14 +18,14 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T atan2_rt(T y, T x) noexcept + template , bool> = true> [[nodiscard]] inline T atan2_rt(T y, T x) noexcept { - if constexpr (ccm::builtin::has_runtime_atan2) { return ccm::builtin::atan2_rt(y, x); } - else + if constexpr (ccm::builtin::has_runtime_atan2) { - const auto scalar = [](T yv, T xv) - { + return ccm::builtin::atan2_rt(y, x); + } else + { + const auto scalar = [](T yv, T xv) { return detail::dispatch_float_double( yv, [&](float yf) { return internal::impl::atan2_float(yf, static_cast(xv)); }, diff --git a/include/ccmath/internal/math/runtime/func/trig/atan_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/atan_rt.hpp index a6f3666c..a1ce3349 100644 --- a/include/ccmath/internal/math/runtime/func/trig/atan_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/atan_rt.hpp @@ -18,11 +18,12 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T atan_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T atan_rt(T num) noexcept { - if constexpr (ccm::builtin::has_runtime_atan) { return ccm::builtin::atan_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_atan) + { + return ccm::builtin::atan_rt(num); + } else { const auto scalar = [](T value) { return detail::dispatch_float_double(value, internal::impl::atan_float, internal::impl::atan_double); }; return simd_impl::unary_via_scalar_abi(num, scalar); diff --git a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp index 98af643c..94399a6f 100644 --- a/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/cos_rt.hpp @@ -21,14 +21,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T cos_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T cos_rt(T num) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::cos_call(num); #else - if constexpr (ccm::builtin::has_runtime_cos) { return ccm::builtin::cos_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_cos) + { + return ccm::builtin::cos_rt(num); + } else { const auto scalar = [](T value) { return gen::cos_gen(value); }; #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) diff --git a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp index 93da86d2..3dc68df9 100644 --- a/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/sin_rt.hpp @@ -21,14 +21,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T sin_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T sin_rt(T num) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::sin_call(num); #else - if constexpr (ccm::builtin::has_runtime_sin) { return ccm::builtin::sin_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_sin) + { + return ccm::builtin::sin_rt(num); + } else { const auto scalar = [](T value) { return gen::sin_gen(value); }; #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) diff --git a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp index 2ea3dda4..ee87c13f 100644 --- a/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp +++ b/include/ccmath/internal/math/runtime/func/trig/tan_rt.hpp @@ -21,14 +21,15 @@ namespace ccm::rt { - template , bool> = true> - [[nodiscard]] inline T tan_rt(T num) noexcept + template , bool> = true> [[nodiscard]] inline T tan_rt(T num) noexcept { #if defined(CCM_CONFIG_SYSTEM_MATH) return detail::sys::tan_call(num); #else - if constexpr (ccm::builtin::has_runtime_tan) { return ccm::builtin::tan_rt(num); } - else + if constexpr (ccm::builtin::has_runtime_tan) + { + return ccm::builtin::tan_rt(num); + } else { const auto scalar = [](T value) { return gen::tan_gen(value); }; #if defined(CCMATH_HAS_SIMD) && defined(CCMATH_HAS_SIMD_SVML) && !defined(_MSC_VER) diff --git a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp index 36ae2953..d9a0033c 100644 --- a/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp +++ b/include/ccmath/internal/math/runtime/pp/assume_aligned.hpp @@ -11,8 +11,7 @@ namespace ccm::pp { - template - CCM_ALWAYS_INLINE void * assume_aligned(void * ptr) + template CCM_ALWAYS_INLINE void * assume_aligned(void * ptr) { static_assert(Alignment != 0 && (Alignment & (Alignment - 1)) == 0, "Alignment must be a nonzero power of 2"); #if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)) diff --git a/include/ccmath/internal/math/runtime/pp/conversion.hpp b/include/ccmath/internal/math/runtime/pp/conversion.hpp index bb510c69..e27a2085 100644 --- a/include/ccmath/internal/math/runtime/pp/conversion.hpp +++ b/include/ccmath/internal/math/runtime/pp/conversion.hpp @@ -22,14 +22,14 @@ namespace ccm::pp { // Value conversion of every lane to U, preserving lane count (and thus ABI). // e.g. static_simd_cast(basic_simd) truncates toward zero per lane. - template - CCM_ALWAYS_INLINE basic_simd static_simd_cast(basic_simd const & v) - { return basic_simd::from_member(SimdTraits::template convert(v.get())); } + template CCM_ALWAYS_INLINE basic_simd static_simd_cast(basic_simd const & v) + { + return basic_simd::from_member(SimdTraits::template convert(v.get())); + } // Bit-preserving reinterpret of each lane to a same-sized type U // (e.g. float <-> int32, double <-> int64). The bit pattern is unchanged. - template - CCM_ALWAYS_INLINE basic_simd simd_bit_cast(basic_simd const & v) + template CCM_ALWAYS_INLINE basic_simd simd_bit_cast(basic_simd const & v) { static_assert(sizeof(U) == sizeof(T), "simd_bit_cast requires equal element size"); return basic_simd::from_member(SimdTraits::template bitcast(v.get())); diff --git a/include/ccmath/internal/math/runtime/pp/declaration.hpp b/include/ccmath/internal/math/runtime/pp/declaration.hpp index b06a3b54..f015b3f9 100644 --- a/include/ccmath/internal/math/runtime/pp/declaration.hpp +++ b/include/ccmath/internal/math/runtime/pp/declaration.hpp @@ -41,38 +41,30 @@ namespace ccm::pp using SimdSizeType = int; // Signed integer type used to represent a mask lane of the given byte width. - template - struct mask_int_from; - template <> - struct mask_int_from<1> + template struct mask_int_from; + template <> struct mask_int_from<1> { using type = std::int8_t; }; - template <> - struct mask_int_from<2> + template <> struct mask_int_from<2> { using type = std::int16_t; }; - template <> - struct mask_int_from<4> + template <> struct mask_int_from<4> { using type = std::int32_t; }; - template <> - struct mask_int_from<8> + template <> struct mask_int_from<8> { using type = std::int64_t; }; - template - using mask_integer_from = typename mask_int_from::type; + template using mask_integer_from = typename mask_int_from::type; // A type is vectorizable when std::simd may form a data-parallel object of // it. long double is intentionally excluded: it has no vector_size form. - template - struct is_vectorizable : std::false_type - { - }; + template struct is_vectorizable : std::false_type + {}; // clang-format off template <> struct is_vectorizable : std::true_type {}; template <> struct is_vectorizable : std::true_type {}; @@ -92,38 +84,33 @@ namespace ccm::pp template <> struct is_vectorizable : std::true_type {}; // clang-format on #if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L - template <> - struct is_vectorizable : std::true_type - { - }; + template <> struct is_vectorizable : std::true_type + {}; #endif - template - inline constexpr bool is_vectorizable_v = is_vectorizable::value; + template inline constexpr bool is_vectorizable_v = is_vectorizable::value; constexpr bool is_pow2(SimdSizeType n) - { return n > 0 && (n & (n - 1)) == 0; } + { + return n > 0 && (n & (n - 1)) == 0; + } } // namespace detail // [simd.abi] ABI tags. ScalarAbi holds a single element. VecAbi holds N // lanes backed by a compiler vector extension (or an array fallback). struct ScalarAbi { - template - struct IsValid : std::true_type - { - }; + template struct IsValid : std::true_type + {}; static constexpr detail::SimdSizeType size = 1; }; - template - struct VecAbi + template struct VecAbi { template struct IsValid : std::bool_constant && N >= 2 && detail::is_pow2(N) && (N * static_cast(sizeof(T))) <= 64> - { - }; + {}; static constexpr detail::SimdSizeType size = N; }; @@ -151,42 +138,33 @@ namespace ccm::pp #endif } - template > - struct NativeAbiImpl + template > struct NativeAbiImpl { using type = ScalarAbi; }; - template - struct NativeAbiImpl + template struct NativeAbiImpl { static constexpr SimdSizeType bytes = native_simd_bytes(); static constexpr SimdSizeType lanes = bytes / static_cast(sizeof(T)); using type = std::conditional_t<(lanes >= 2 && VecAbi::template IsValid::value), VecAbi, ScalarAbi>; }; - template - using NativeAbi = typename NativeAbiImpl::type; + template using NativeAbi = typename NativeAbiImpl::type; // deduce_t selects the ABI for an N-wide simd alias. - template - struct DeduceAbi + template struct DeduceAbi { using type = std::conditional_t>; }; - template - using deduce_t = typename DeduceAbi::type; + template using deduce_t = typename DeduceAbi::type; } // namespace detail - template > - class basic_simd; + template > class basic_simd; - template >> - class basic_simd_mask; + template >> class basic_simd_mask; - template - class const_where_expression; + template class const_where_expression; - template - class where_expression; + template class where_expression; } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/flags.hpp b/include/ccmath/internal/math/runtime/pp/flags.hpp index 4f1311a2..ed6bdf2d 100644 --- a/include/ccmath/internal/math/runtime/pp/flags.hpp +++ b/include/ccmath/internal/math/runtime/pp/flags.hpp @@ -29,36 +29,26 @@ namespace ccm::pp namespace detail { struct flag_aligned - { - }; - template - struct flag_overaligned - { - }; + {}; + template struct flag_overaligned + {}; } // namespace detail - template - struct simd_flags; + template struct simd_flags; namespace detail { - template - struct overalign_value : std::integral_constant - { - }; - template - struct overalign_value> : std::integral_constant - { - }; + template struct overalign_value : std::integral_constant + {}; + template struct overalign_value> : std::integral_constant + {}; // Alignment a flag set promises for a load/store: // 0 -> no promise (treat as unaligned) // 1 -> the simd object's natural alignment // >1 -> an explicit overalignment N - template - struct flags_align_request : std::integral_constant - { - }; + template struct flags_align_request : std::integral_constant + {}; // NOLINTBEGIN(readability-avoid-nested-conditional-operator) template struct flags_align_request> @@ -66,14 +56,12 @@ namespace ccm::pp ((std::size_t{ 0 } + ... + overalign_value::value) != 0) ? (std::size_t{ 0 } + ... + overalign_value::value) : ((std::is_same_v || ...) ? std::size_t{ 1 } : std::size_t{ 0 })> - { - }; + {}; // NOLINTEND(readability-avoid-nested-conditional-operator) // Communicate a known alignment to the optimizer. The pointer value is // unchanged. The compiler may then use aligned moves / assume alignment. - template - CCM_ALWAYS_INLINE T const *assume_aligned_ptr(T const *p) + template CCM_ALWAYS_INLINE T const * assume_aligned_ptr(T const * p) { #if defined(__clang__) || defined(__GNUC__) return static_cast(__builtin_assume_aligned(p, A)); @@ -84,8 +72,7 @@ namespace ccm::pp return p; #endif } - template - CCM_ALWAYS_INLINE T *assume_aligned_ptr(T *p) + template CCM_ALWAYS_INLINE T * assume_aligned_ptr(T * p) { #if defined(__clang__) || defined(__GNUC__) return static_cast(__builtin_assume_aligned(p, A)); @@ -98,27 +85,19 @@ namespace ccm::pp } } // namespace detail - template - struct simd_flags - { - }; + template struct simd_flags + {}; - template - struct is_simd_flag_type : std::false_type - { - }; - template - struct is_simd_flag_type> : std::true_type - { - }; - template - inline constexpr bool is_simd_flag_type_v = is_simd_flag_type::value; + template struct is_simd_flag_type : std::false_type + {}; + template struct is_simd_flag_type> : std::true_type + {}; + template inline constexpr bool is_simd_flag_type_v = is_simd_flag_type::value; // [simd.flags] inline constexpr simd_flags<> simd_flag_default{}; inline constexpr simd_flags simd_flag_aligned{}; - template - inline constexpr simd_flags> simd_flag_overaligned{}; + template inline constexpr simd_flags> simd_flag_overaligned{}; // Parallelism TS spellings. using element_aligned_tag = simd_flags<>; diff --git a/include/ccmath/internal/math/runtime/pp/mask_reductions.hpp b/include/ccmath/internal/math/runtime/pp/mask_reductions.hpp index f1b3bc4a..36f20e9d 100644 --- a/include/ccmath/internal/math/runtime/pp/mask_reductions.hpp +++ b/include/ccmath/internal/math/runtime/pp/mask_reductions.hpp @@ -22,47 +22,63 @@ namespace ccm::pp { // [simd.mask.reductions] - template - CCM_ALWAYS_INLINE bool all_of(basic_simd_mask const & mask) - { return SimdTraits, Abi>::all_of(mask.get()); } - template - CCM_ALWAYS_INLINE bool any_of(basic_simd_mask const & mask) - { return SimdTraits, Abi>::any_of(mask.get()); } - template - CCM_ALWAYS_INLINE bool none_of(basic_simd_mask const & mask) - { return !SimdTraits, Abi>::any_of(mask.get()); } - template - CCM_ALWAYS_INLINE detail::SimdSizeType reduce_count(basic_simd_mask const & mask) - { return SimdTraits, Abi>::popcount(mask.get()); } + template CCM_ALWAYS_INLINE bool all_of(basic_simd_mask const & mask) + { + return SimdTraits, Abi>::all_of(mask.get()); + } + template CCM_ALWAYS_INLINE bool any_of(basic_simd_mask const & mask) + { + return SimdTraits, Abi>::any_of(mask.get()); + } + template CCM_ALWAYS_INLINE bool none_of(basic_simd_mask const & mask) + { + return !SimdTraits, Abi>::any_of(mask.get()); + } + template CCM_ALWAYS_INLINE detail::SimdSizeType reduce_count(basic_simd_mask const & mask) + { + return SimdTraits, Abi>::popcount(mask.get()); + } // Index of the first / last set lane. Precondition: any_of(mask) is true; // when no lane is set these return mask.size() as a defensive sentinel. - template - CCM_ALWAYS_INLINE detail::SimdSizeType reduce_min_index(basic_simd_mask const & mask) + template CCM_ALWAYS_INLINE detail::SimdSizeType reduce_min_index(basic_simd_mask const & mask) { for (detail::SimdSizeType i = 0; i < mask.size(); ++i) { - if (mask[i]) { return i; } + if (mask[i]) + { + return i; + } } return mask.size(); } - template - CCM_ALWAYS_INLINE detail::SimdSizeType reduce_max_index(basic_simd_mask const & mask) + template CCM_ALWAYS_INLINE detail::SimdSizeType reduce_max_index(basic_simd_mask const & mask) { for (detail::SimdSizeType i = mask.size() - 1; i >= 0; --i) { - if (mask[i]) { return i; } + if (mask[i]) + { + return i; + } } return mask.size(); } // Single-bool overloads (a bool models a 1-lane mask). CCM_ALWAYS_INLINE bool all_of(bool x) - { return x; } + { + return x; + } CCM_ALWAYS_INLINE bool any_of(bool x) - { return x; } + { + return x; + } CCM_ALWAYS_INLINE bool none_of(bool x) - { return !x; } + { + return !x; + } CCM_ALWAYS_INLINE detail::SimdSizeType reduce_count(bool x) - { return x ? 1 : 0; } + { + return x ? 1 : 0; + } } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp index 2c9a79df..689c2750 100644 --- a/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp +++ b/include/ccmath/internal/math/runtime/pp/msvc_intrin.hpp @@ -34,8 +34,7 @@ namespace ccm::pp { - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = float; using MaskInt = std::int32_t; @@ -45,62 +44,128 @@ namespace ccm::pp static constexpr std::size_t simd_align = 16; static constexpr std::size_t mask_align = 16; - CCM_ALWAYS_INLINE static SimdMember broadcast(float v) { return _mm_set1_ps(v); } - CCM_ALWAYS_INLINE static float get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(float v) + { + return _mm_set1_ps(v); + } + CCM_ALWAYS_INLINE static float get(SimdMember const & m, detail::SimdSizeType i) { std::array a; _mm_storeu_ps(a.data(), m); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, float v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, float v) { std::array a; _mm_storeu_ps(a.data(), m); a[i] = v; m = _mm_loadu_ps(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(float const *p) { return _mm_loadu_ps(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, float *p) { _mm_storeu_ps(p, m); } + CCM_ALWAYS_INLINE static SimdMember load(float const * p) + { + return _mm_loadu_ps(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, float * p) + { + _mm_storeu_ps(p, m); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm_add_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm_sub_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return _mm_mul_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return _mm_div_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm_xor_ps(a, _mm_set1_ps(-0.0F)); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm_add_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm_sub_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return _mm_mul_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return _mm_div_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm_xor_ps(a, _mm_set1_ps(-0.0F)); + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmpeq_ps(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmpneq_ps(a, b)); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmplt_ps(a, b)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmple_ps(a, b)); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmpgt_ps(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return _mm_castps_si128(_mm_cmpge_ps(a, b)); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmpeq_ps(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmpneq_ps(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmplt_ps(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmple_ps(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmpgt_ps(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return _mm_castps_si128(_mm_cmpge_ps(a, b)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi32(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm_set1_epi32(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { std::array a; _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); return a[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { std::array a; _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); a[i] = b ? -1 : 0; m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a.data())); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) == 0xF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm_movemask_ps(_mm_castsi128_ps(m)) == 0xF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); detail::SimdSizeType c = 0; - for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 4; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) @@ -109,16 +174,31 @@ namespace ccm::pp return _mm_or_ps(_mm_and_ps(mps, a), _mm_andnot_ps(mps, b)); } - CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { return _mm_sqrt_ps(v); } - CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) { return _mm_andnot_ps(_mm_set1_ps(-0.0F), v); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return _mm_min_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return _mm_max_ps(a, b); } + CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) + { + return _mm_sqrt_ps(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) + { + return _mm_andnot_ps(_mm_set1_ps(-0.0F), v); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return _mm_min_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return _mm_max_ps(a, b); + } #define CCM_PP_MSVC_F32_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ std::array a; \ _mm_storeu_ps(a.data(), v); \ - for (int i = 0; i < 4; ++i) { a[i] = detail::SFN(a[i]); } \ + for (int i = 0; i < 4; ++i) \ + { \ + a[i] = detail::SFN(a[i]); \ + } \ return _mm_loadu_ps(a.data()); \ } CCM_PP_MSVC_F32_ROUND(op_floor, s_floor) @@ -132,7 +212,10 @@ namespace ccm::pp _mm_storeu_ps(aa.data(), a); _mm_storeu_ps(bb.data(), b); _mm_storeu_ps(cc.data(), c); - for (int i = 0; i < 4; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } + for (int i = 0; i < 4; ++i) + { + aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); + } return _mm_loadu_ps(aa.data()); } CCM_ALWAYS_INLINE static float hadd(SimdMember v) @@ -146,7 +229,10 @@ namespace ccm::pp std::array a; _mm_storeu_ps(a.data(), v); float s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static float hmax(SimdMember v) @@ -154,21 +240,25 @@ namespace ccm::pp std::array a; _mm_storeu_ps(a.data(), v); float s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; _mm_storeu_ps(a.data(), v); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 4; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -176,8 +266,7 @@ namespace ccm::pp } }; - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = double; using MaskInt = std::int64_t; @@ -187,56 +276,119 @@ namespace ccm::pp static constexpr std::size_t simd_align = 16; static constexpr std::size_t mask_align = 16; - CCM_ALWAYS_INLINE static SimdMember broadcast(double v) { return _mm_set1_pd(v); } - CCM_ALWAYS_INLINE static double get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(double v) + { + return _mm_set1_pd(v); + } + CCM_ALWAYS_INLINE static double get(SimdMember const & m, detail::SimdSizeType i) { std::array a; _mm_storeu_pd(a.data(), m); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, double v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, double v) { std::array a; _mm_storeu_pd(a.data(), m); a[i] = v; m = _mm_loadu_pd(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(double const *p) { return _mm_loadu_pd(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, double *p) { _mm_storeu_pd(p, m); } + CCM_ALWAYS_INLINE static SimdMember load(double const * p) + { + return _mm_loadu_pd(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, double * p) + { + _mm_storeu_pd(p, m); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm_add_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm_sub_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return _mm_mul_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return _mm_div_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm_xor_pd(a, _mm_set1_pd(-0.0)); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm_add_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm_sub_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return _mm_mul_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return _mm_div_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm_xor_pd(a, _mm_set1_pd(-0.0)); + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmpeq_pd(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmpneq_pd(a, b)); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmplt_pd(a, b)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmple_pd(a, b)); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmpgt_pd(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return _mm_castpd_si128(_mm_cmpge_pd(a, b)); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmpeq_pd(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmpneq_pd(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmplt_pd(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmple_pd(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmpgt_pd(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return _mm_castpd_si128(_mm_cmpge_pd(a, b)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi64x(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm_set1_epi64x(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { std::array a; _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); return a[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { std::array a; _mm_storeu_si128(reinterpret_cast<__m128i *>(a.data()), m); a[i] = b ? -1 : 0; m = _mm_loadu_si128(reinterpret_cast<__m128i const *>(a.data())); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) == 0x3; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm_movemask_pd(_mm_castsi128_pd(m)) == 0x3; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; @@ -249,10 +401,22 @@ namespace ccm::pp return _mm_or_pd(_mm_and_pd(mpd, a), _mm_andnot_pd(mpd, b)); } - CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { return _mm_sqrt_pd(v); } - CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) { return _mm_andnot_pd(_mm_set1_pd(-0.0), v); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return _mm_min_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return _mm_max_pd(a, b); } + CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) + { + return _mm_sqrt_pd(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) + { + return _mm_andnot_pd(_mm_set1_pd(-0.0), v); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return _mm_min_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return _mm_max_pd(a, b); + } #define CCM_PP_MSVC_F64_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ @@ -296,17 +460,18 @@ namespace ccm::pp return a[0] > a[1] ? a[0] : a[1]; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; _mm_storeu_pd(a.data(), v); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 2; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 2; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -317,16 +482,17 @@ namespace ccm::pp namespace detail { // Shared spill helpers for the integer __m128i specializations. - template - CCM_ALWAYS_INLINE void si128_store(__m128i v, T *a) - { _mm_storeu_si128(reinterpret_cast<__m128i *>(a), v); } - template - CCM_ALWAYS_INLINE __m128i si128_load(T const *a) - { return _mm_loadu_si128(reinterpret_cast<__m128i const *>(a)); } + template CCM_ALWAYS_INLINE void si128_store(__m128i v, T * a) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(a), v); + } + template CCM_ALWAYS_INLINE __m128i si128_load(T const * a) + { + return _mm_loadu_si128(reinterpret_cast<__m128i const *>(a)); + } } // namespace detail - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = std::int32_t; using MaskInt = std::int32_t; @@ -336,53 +502,121 @@ namespace ccm::pp static constexpr std::size_t simd_align = 16; static constexpr std::size_t mask_align = 16; - CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) { return _mm_set1_epi32(v); } - CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) + { + return _mm_set1_epi32(v); + } + CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const & m, detail::SimdSizeType i) { std::array a; detail::si128_store(m, a.data()); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int32_t v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, std::int32_t v) { std::array a; detail::si128_store(m, a.data()); a[i] = v; m = detail::si128_load(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const *p) { return detail::si128_load(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int32_t *p) { detail::si128_store(m, p); } + CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const * p) + { + return detail::si128_load(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, std::int32_t * p) + { + detail::si128_store(m, p); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm_add_epi32(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm_sub_epi32(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm_sub_epi32(_mm_setzero_si128(), a); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm_add_epi32(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm_sub_epi32(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm_sub_epi32(_mm_setzero_si128(), a); + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm_cmpeq_epi32(a, b); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm_cmplt_epi32(a, b); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm_cmpgt_epi32(a, b); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return mnot(eq(a, b)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return mnot(gt(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return mnot(lt(a, b)); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm_cmpeq_epi32(a, b); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm_cmplt_epi32(a, b); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm_cmpgt_epi32(a, b); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return mnot(eq(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return mnot(gt(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return mnot(lt(a, b)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi32(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { return get(m, i) != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { set(m, i, b ? -1 : 0); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) == 0xF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm_set1_epi32(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return get(m, i) != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + set(m, i, b ? -1 : 0); + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm_movemask_ps(_mm_castsi128_ps(m)) == 0xF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm_movemask_ps(_mm_castsi128_ps(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; detail::si128_store(m, a.data()); detail::SimdSizeType c = 0; - for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 4; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) - { return _mm_or_si128(_mm_and_si128(m, a), _mm_andnot_si128(m, b)); } + { + return _mm_or_si128(_mm_and_si128(m, a), _mm_andnot_si128(m, b)); + } // Cold integer ops: spill, scalarize, reload. #define CCM_PP_MSVC_I32_BIN(NAME, OP) \ @@ -391,7 +625,10 @@ namespace ccm::pp std::array x, y; \ detail::si128_store(a, x.data()); \ detail::si128_store(b, y.data()); \ - for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ + for (int i = 0; i < 4; ++i) \ + { \ + x[i] = static_cast(x[i] OP y[i]); \ + } \ return detail::si128_load(x.data()); \ } CCM_PP_MSVC_I32_BIN(mul, *) @@ -400,12 +637,30 @@ namespace ccm::pp CCM_PP_MSVC_I32_BIN(shl, <<) CCM_PP_MSVC_I32_BIN(shr, >>) #undef CCM_PP_MSVC_I32_BIN - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return select(lt(a, b), a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return select(lt(a, b), a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return select(gt(a, b), a, b); + } CCM_ALWAYS_INLINE static std::int32_t hadd(SimdMember v) { std::array a; @@ -417,7 +672,10 @@ namespace ccm::pp std::array a; detail::si128_store(v, a.data()); std::int32_t s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static std::int32_t hmax(SimdMember v) @@ -425,21 +683,25 @@ namespace ccm::pp std::array a; detail::si128_store(v, a.data()); std::int32_t s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; detail::si128_store(v, a.data()); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 4; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -449,8 +711,7 @@ namespace ccm::pp // Mainly the mask type for double. SSE2 has few 64-bit integer ops, so most // arithmetic/compares spill. - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = std::int64_t; using MaskInt = std::int64_t; @@ -460,26 +721,44 @@ namespace ccm::pp static constexpr std::size_t simd_align = 16; static constexpr std::size_t mask_align = 16; - CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) { return _mm_set1_epi64x(v); } - CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) + { + return _mm_set1_epi64x(v); + } + CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const & m, detail::SimdSizeType i) { std::array a; detail::si128_store(m, a.data()); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int64_t v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, std::int64_t v) { std::array a; detail::si128_store(m, a.data()); a[i] = v; m = detail::si128_load(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const *p) { return detail::si128_load(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int64_t *p) { detail::si128_store(m, p); } + CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const * p) + { + return detail::si128_load(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, std::int64_t * p) + { + detail::si128_store(m, p); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm_add_epi64(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm_sub_epi64(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm_sub_epi64(_mm_setzero_si128(), a); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm_add_epi64(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm_sub_epi64(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm_sub_epi64(_mm_setzero_si128(), a); + } #define CCM_PP_MSVC_I64_CMP(NAME, OP) \ CCM_ALWAYS_INLINE static MaskMember NAME(SimdMember a, SimdMember b) \ @@ -499,15 +778,42 @@ namespace ccm::pp CCM_PP_MSVC_I64_CMP(ge, >=) #undef CCM_PP_MSVC_I64_CMP - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm_set1_epi64x(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { return get(m, i) != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { set(m, i, b ? -1 : 0); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) == 0x3; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm_set1_epi64x(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return get(m, i) != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + set(m, i, b ? -1 : 0); + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm_movemask_pd(_mm_castsi128_pd(m)) == 0x3; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm_movemask_pd(_mm_castsi128_pd(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; @@ -515,12 +821,26 @@ namespace ccm::pp return (a[0] != 0 ? 1 : 0) + (a[1] != 0 ? 1 : 0); } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) - { return _mm_or_si128(_mm_and_si128(m, a), _mm_andnot_si128(m, b)); } + { + return _mm_or_si128(_mm_and_si128(m, a), _mm_andnot_si128(m, b)); + } - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return _mm_and_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return _mm_or_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return _mm_xor_si128(a, b); } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return _mm_xor_si128(a, _mm_set1_epi32(-1)); } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return _mm_and_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return _mm_or_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return _mm_xor_si128(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return _mm_xor_si128(a, _mm_set1_epi32(-1)); + } #define CCM_PP_MSVC_I64_BIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ @@ -537,8 +857,14 @@ namespace ccm::pp CCM_PP_MSVC_I64_BIN(shl, <<) CCM_PP_MSVC_I64_BIN(shr, >>) #undef CCM_PP_MSVC_I64_BIN - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return select(lt(a, b), a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return select(lt(a, b), a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return select(gt(a, b), a, b); + } CCM_ALWAYS_INLINE static std::int64_t hadd(SimdMember v) { std::array a; @@ -558,17 +884,18 @@ namespace ccm::pp return a[0] > a[1] ? a[0] : a[1]; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; detail::si128_store(v, a.data()); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 2; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 2; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -583,23 +910,32 @@ namespace ccm::pp namespace detail { CCM_ALWAYS_INLINE __m256i avx_and(__m256i a, __m256i b) - { return _mm256_castps_si256(_mm256_and_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + { + return _mm256_castps_si256(_mm256_and_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); + } CCM_ALWAYS_INLINE __m256i avx_or(__m256i a, __m256i b) - { return _mm256_castps_si256(_mm256_or_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + { + return _mm256_castps_si256(_mm256_or_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); + } CCM_ALWAYS_INLINE __m256i avx_xor(__m256i a, __m256i b) - { return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); } + { + return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b))); + } CCM_ALWAYS_INLINE __m256i avx_not(__m256i a) - { return avx_xor(a, _mm256_set1_epi32(-1)); } - template - CCM_ALWAYS_INLINE void si256_store(__m256i v, T *a) - { _mm256_storeu_si256(reinterpret_cast<__m256i *>(a), v); } - template - CCM_ALWAYS_INLINE __m256i si256_load(T const *a) - { return _mm256_loadu_si256(reinterpret_cast<__m256i const *>(a)); } + { + return avx_xor(a, _mm256_set1_epi32(-1)); + } + template CCM_ALWAYS_INLINE void si256_store(__m256i v, T * a) + { + _mm256_storeu_si256(reinterpret_cast<__m256i *>(a), v); + } + template CCM_ALWAYS_INLINE __m256i si256_load(T const * a) + { + return _mm256_loadu_si256(reinterpret_cast<__m256i const *>(a)); + } } // namespace detail - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = float; using MaskInt = std::int32_t; @@ -609,76 +945,160 @@ namespace ccm::pp static constexpr std::size_t simd_align = 32; static constexpr std::size_t mask_align = 32; - CCM_ALWAYS_INLINE static SimdMember broadcast(float v) { return _mm256_set1_ps(v); } - CCM_ALWAYS_INLINE static float get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(float v) + { + return _mm256_set1_ps(v); + } + CCM_ALWAYS_INLINE static float get(SimdMember const & m, detail::SimdSizeType i) { std::array a; _mm256_storeu_ps(a.data(), m); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, float v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, float v) { std::array a; _mm256_storeu_ps(a.data(), m); a[i] = v; m = _mm256_loadu_ps(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(float const *p) { return _mm256_loadu_ps(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, float *p) { _mm256_storeu_ps(p, m); } + CCM_ALWAYS_INLINE static SimdMember load(float const * p) + { + return _mm256_loadu_ps(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, float * p) + { + _mm256_storeu_ps(p, m); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm256_add_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm256_sub_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return _mm256_mul_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return _mm256_div_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm256_xor_ps(a, _mm256_set1_ps(-0.0F)); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm256_add_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm256_sub_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return _mm256_mul_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return _mm256_div_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm256_xor_ps(a, _mm256_set1_ps(-0.0F)); + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_EQ_OQ)); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_NEQ_UQ)); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_LT_OQ)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_LE_OQ)); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_GT_OQ)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_GE_OQ)); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_EQ_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_NEQ_UQ)); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_LT_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_LE_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_GT_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return _mm256_castps_si256(_mm256_cmp_ps(a, b, _CMP_GE_OQ)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi32(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm256_set1_epi32(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { std::array a; detail::si256_store(m, a.data()); return a[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { std::array a; detail::si256_store(m, a.data()); a[i] = b ? -1 : 0; m = detail::si256_load(a.data()); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return detail::avx_not(a); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) == 0xFF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return detail::avx_not(a); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm256_movemask_ps(_mm256_castsi256_ps(m)) == 0xFF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; - for (int i = 0; i < 8; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 8; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } - CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) { return _mm256_blendv_ps(b, a, _mm256_castsi256_ps(m)); } + CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) + { + return _mm256_blendv_ps(b, a, _mm256_castsi256_ps(m)); + } - CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { return _mm256_sqrt_ps(v); } - CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) { return _mm256_andnot_ps(_mm256_set1_ps(-0.0F), v); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return _mm256_min_ps(a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return _mm256_max_ps(a, b); } + CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) + { + return _mm256_sqrt_ps(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) + { + return _mm256_andnot_ps(_mm256_set1_ps(-0.0F), v); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return _mm256_min_ps(a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return _mm256_max_ps(a, b); + } #define CCM_PP_MSVC_F32X8_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ std::array a; \ _mm256_storeu_ps(a.data(), v); \ - for (int i = 0; i < 8; ++i) { a[i] = detail::SFN(a[i]); } \ + for (int i = 0; i < 8; ++i) \ + { \ + a[i] = detail::SFN(a[i]); \ + } \ return _mm256_loadu_ps(a.data()); \ } CCM_PP_MSVC_F32X8_ROUND(op_floor, s_floor) @@ -695,7 +1115,10 @@ namespace ccm::pp _mm256_storeu_ps(aa.data(), a); _mm256_storeu_ps(bb.data(), b); _mm256_storeu_ps(cc.data(), c); - for (int i = 0; i < 8; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } + for (int i = 0; i < 8; ++i) + { + aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); + } return _mm256_loadu_ps(aa.data()); #endif } @@ -704,7 +1127,10 @@ namespace ccm::pp std::array a; _mm256_storeu_ps(a.data(), v); float s = 0; - for (int i = 0; i < 8; ++i) { s += a[i]; } + for (int i = 0; i < 8; ++i) + { + s += a[i]; + } return s; } CCM_ALWAYS_INLINE static float hmin(SimdMember v) @@ -712,7 +1138,10 @@ namespace ccm::pp std::array a; _mm256_storeu_ps(a.data(), v); float s = a[0]; - for (int i = 1; i < 8; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 8; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static float hmax(SimdMember v) @@ -720,20 +1149,24 @@ namespace ccm::pp std::array a; _mm256_storeu_ps(a.data(), v); float s = a[0]; - for (int i = 1; i < 8; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 8; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; _mm256_storeu_ps(a.data(), v); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 8; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 8; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -741,8 +1174,7 @@ namespace ccm::pp } }; - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = double; using MaskInt = std::int64_t; @@ -752,76 +1184,160 @@ namespace ccm::pp static constexpr std::size_t simd_align = 32; static constexpr std::size_t mask_align = 32; - CCM_ALWAYS_INLINE static SimdMember broadcast(double v) { return _mm256_set1_pd(v); } - CCM_ALWAYS_INLINE static double get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(double v) + { + return _mm256_set1_pd(v); + } + CCM_ALWAYS_INLINE static double get(SimdMember const & m, detail::SimdSizeType i) { std::array a; _mm256_storeu_pd(a.data(), m); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, double v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, double v) { std::array a; _mm256_storeu_pd(a.data(), m); a[i] = v; m = _mm256_loadu_pd(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(double const *p) { return _mm256_loadu_pd(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, double *p) { _mm256_storeu_pd(p, m); } + CCM_ALWAYS_INLINE static SimdMember load(double const * p) + { + return _mm256_loadu_pd(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, double * p) + { + _mm256_storeu_pd(p, m); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm256_add_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm256_sub_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return _mm256_mul_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return _mm256_div_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm256_xor_pd(a, _mm256_set1_pd(-0.0)); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm256_add_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm256_sub_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return _mm256_mul_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return _mm256_div_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm256_xor_pd(a, _mm256_set1_pd(-0.0)); + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_EQ_OQ)); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_NEQ_UQ)); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_LT_OQ)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_LE_OQ)); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_GT_OQ)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_GE_OQ)); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_EQ_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_NEQ_UQ)); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_LT_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_LE_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_GT_OQ)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return _mm256_castpd_si256(_mm256_cmp_pd(a, b, _CMP_GE_OQ)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi64x(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm256_set1_epi64x(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { std::array a; detail::si256_store(m, a.data()); return a[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { std::array a; detail::si256_store(m, a.data()); a[i] = b ? -1 : 0; m = detail::si256_load(a.data()); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return detail::avx_not(a); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) == 0xF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return detail::avx_not(a); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm256_movemask_pd(_mm256_castsi256_pd(m)) == 0xF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; - for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 4; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } - CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) { return _mm256_blendv_pd(b, a, _mm256_castsi256_pd(m)); } + CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) + { + return _mm256_blendv_pd(b, a, _mm256_castsi256_pd(m)); + } - CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { return _mm256_sqrt_pd(v); } - CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) { return _mm256_andnot_pd(_mm256_set1_pd(-0.0), v); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return _mm256_min_pd(a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return _mm256_max_pd(a, b); } + CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) + { + return _mm256_sqrt_pd(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) + { + return _mm256_andnot_pd(_mm256_set1_pd(-0.0), v); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return _mm256_min_pd(a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return _mm256_max_pd(a, b); + } #define CCM_PP_MSVC_F64X4_ROUND(NAME, SFN) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember v) \ { \ std::array a; \ _mm256_storeu_pd(a.data(), v); \ - for (int i = 0; i < 4; ++i) { a[i] = detail::SFN(a[i]); } \ + for (int i = 0; i < 4; ++i) \ + { \ + a[i] = detail::SFN(a[i]); \ + } \ return _mm256_loadu_pd(a.data()); \ } CCM_PP_MSVC_F64X4_ROUND(op_floor, s_floor) @@ -838,7 +1354,10 @@ namespace ccm::pp _mm256_storeu_pd(aa.data(), a); _mm256_storeu_pd(bb.data(), b); _mm256_storeu_pd(cc.data(), c); - for (int i = 0; i < 4; ++i) { aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); } + for (int i = 0; i < 4; ++i) + { + aa[i] = detail::s_fma(aa[i], bb[i], cc[i]); + } return _mm256_loadu_pd(aa.data()); #endif } @@ -853,7 +1372,10 @@ namespace ccm::pp std::array a; _mm256_storeu_pd(a.data(), v); double s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static double hmax(SimdMember v) @@ -861,20 +1383,24 @@ namespace ccm::pp std::array a; _mm256_storeu_pd(a.data(), v); double s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; _mm256_storeu_pd(a.data(), v); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 4; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -882,8 +1408,7 @@ namespace ccm::pp } }; - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = std::int32_t; using MaskInt = std::int32_t; @@ -893,30 +1418,57 @@ namespace ccm::pp static constexpr std::size_t simd_align = 32; static constexpr std::size_t mask_align = 32; - CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) { return _mm256_set1_epi32(v); } - CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(std::int32_t v) + { + return _mm256_set1_epi32(v); + } + CCM_ALWAYS_INLINE static std::int32_t get(SimdMember const & m, detail::SimdSizeType i) { std::array a; detail::si256_store(m, a.data()); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int32_t v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, std::int32_t v) { std::array a; detail::si256_store(m, a.data()); a[i] = v; m = detail::si256_load(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const *p) { return detail::si256_load(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int32_t *p) { detail::si256_store(m, p); } + CCM_ALWAYS_INLINE static SimdMember load(std::int32_t const * p) + { + return detail::si256_load(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, std::int32_t * p) + { + detail::si256_store(m, p); + } #if CCMATH_SIMD_HAVE_AVX2 - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm256_add_epi32(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm256_sub_epi32(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm256_sub_epi32(_mm256_setzero_si256(), a); } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm256_cmpeq_epi32(a, b); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm256_cmpgt_epi32(a, b); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm256_cmpgt_epi32(b, a); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm256_add_epi32(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm256_sub_epi32(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm256_sub_epi32(_mm256_setzero_si256(), a); + } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm256_cmpeq_epi32(a, b); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm256_cmpgt_epi32(a, b); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm256_cmpgt_epi32(b, a); + } #else #define CCM_PP_I32X8_SPILL2(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ @@ -924,7 +1476,10 @@ namespace ccm::pp std::array x, y; \ detail::si256_store(a, x.data()); \ detail::si256_store(b, y.data()); \ - for (int i = 0; i < 8; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ + for (int i = 0; i < 8; ++i) \ + { \ + x[i] = static_cast(x[i] OP y[i]); \ + } \ return detail::si256_load(x.data()); \ } CCM_PP_I32X8_SPILL2(add, +) @@ -934,7 +1489,10 @@ namespace ccm::pp { std::array x; detail::si256_store(a, x.data()); - for (int i = 0; i < 8; ++i) { x[i] = static_cast(-x[i]); } + for (int i = 0; i < 8; ++i) + { + x[i] = static_cast(-x[i]); + } return detail::si256_load(x.data()); } #define CCM_PP_I32X8_SPILLCMP(NAME, OP) \ @@ -943,7 +1501,10 @@ namespace ccm::pp std::array x, y, r; \ detail::si256_store(a, x.data()); \ detail::si256_store(b, y.data()); \ - for (int i = 0; i < 8; ++i) { r[i] = (x[i] OP y[i]) ? -1 : 0; } \ + for (int i = 0; i < 8; ++i) \ + { \ + r[i] = (x[i] OP y[i]) ? -1 : 0; \ + } \ return detail::si256_load(r.data()); \ } CCM_PP_I32X8_SPILLCMP(eq, ==) @@ -951,41 +1512,97 @@ namespace ccm::pp CCM_PP_I32X8_SPILLCMP(lt, <) #undef CCM_PP_I32X8_SPILLCMP #endif - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return mnot(eq(a, b)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return mnot(gt(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return mnot(lt(a, b)); } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return mnot(eq(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return mnot(gt(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return mnot(lt(a, b)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi32(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { return get(m, i) != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { set(m, i, b ? -1 : 0); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return detail::avx_not(a); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) == 0xFF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm256_set1_epi32(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return get(m, i) != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + set(m, i, b ? -1 : 0); + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return detail::avx_not(a); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm256_movemask_ps(_mm256_castsi256_ps(m)) == 0xFF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm256_movemask_ps(_mm256_castsi256_ps(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; - for (int i = 0; i < 8; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 8; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) - { return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(b), _mm256_castsi256_ps(a), _mm256_castsi256_ps(m))); } + { + return _mm256_castps_si256(_mm256_blendv_ps(_mm256_castsi256_ps(b), _mm256_castsi256_ps(a), _mm256_castsi256_ps(m))); + } - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return detail::avx_not(a); } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return detail::avx_not(a); + } #define CCM_PP_I32X8_SPILLBIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ std::array x, y; \ detail::si256_store(a, x.data()); \ detail::si256_store(b, y.data()); \ - for (int i = 0; i < 8; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ + for (int i = 0; i < 8; ++i) \ + { \ + x[i] = static_cast(x[i] OP y[i]); \ + } \ return detail::si256_load(x.data()); \ } CCM_PP_I32X8_SPILLBIN(mul, *) @@ -994,14 +1611,23 @@ namespace ccm::pp CCM_PP_I32X8_SPILLBIN(shl, <<) CCM_PP_I32X8_SPILLBIN(shr, >>) #undef CCM_PP_I32X8_SPILLBIN - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return select(lt(a, b), a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return select(lt(a, b), a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return select(gt(a, b), a, b); + } CCM_ALWAYS_INLINE static std::int32_t hadd(SimdMember v) { std::array a; detail::si256_store(v, a.data()); std::int32_t s = 0; - for (int i = 0; i < 8; ++i) { s = static_cast(s + a[i]); } + for (int i = 0; i < 8; ++i) + { + s = static_cast(s + a[i]); + } return s; } CCM_ALWAYS_INLINE static std::int32_t hmin(SimdMember v) @@ -1009,7 +1635,10 @@ namespace ccm::pp std::array a; detail::si256_store(v, a.data()); std::int32_t s = a[0]; - for (int i = 1; i < 8; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 8; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static std::int32_t hmax(SimdMember v) @@ -1017,20 +1646,24 @@ namespace ccm::pp std::array a; detail::si256_store(v, a.data()); std::int32_t s = a[0]; - for (int i = 1; i < 8; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 8; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; detail::si256_store(v, a.data()); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 8; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 8; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); @@ -1038,8 +1671,7 @@ namespace ccm::pp } }; - template <> - struct SimdTraits> + template <> struct SimdTraits> { using value_type = std::int64_t; using MaskInt = std::int64_t; @@ -1049,37 +1681,67 @@ namespace ccm::pp static constexpr std::size_t simd_align = 32; static constexpr std::size_t mask_align = 32; - CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) { return _mm256_set1_epi64x(v); } - CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const &m, detail::SimdSizeType i) + CCM_ALWAYS_INLINE static SimdMember broadcast(std::int64_t v) + { + return _mm256_set1_epi64x(v); + } + CCM_ALWAYS_INLINE static std::int64_t get(SimdMember const & m, detail::SimdSizeType i) { std::array a; detail::si256_store(m, a.data()); return a[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember &m, detail::SimdSizeType i, std::int64_t v) + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, std::int64_t v) { std::array a; detail::si256_store(m, a.data()); a[i] = v; m = detail::si256_load(a.data()); } - CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const *p) { return detail::si256_load(p); } - CCM_ALWAYS_INLINE static void store(SimdMember const &m, std::int64_t *p) { detail::si256_store(m, p); } + CCM_ALWAYS_INLINE static SimdMember load(std::int64_t const * p) + { + return detail::si256_load(p); + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, std::int64_t * p) + { + detail::si256_store(m, p); + } #if CCMATH_SIMD_HAVE_AVX2 - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return _mm256_add_epi64(a, b); } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return _mm256_sub_epi64(a, b); } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return _mm256_sub_epi64(_mm256_setzero_si256(), a); } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return _mm256_cmpeq_epi64(a, b); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return _mm256_cmpgt_epi64(a, b); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return _mm256_cmpgt_epi64(b, a); } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return _mm256_add_epi64(a, b); + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return _mm256_sub_epi64(a, b); + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return _mm256_sub_epi64(_mm256_setzero_si256(), a); + } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return _mm256_cmpeq_epi64(a, b); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return _mm256_cmpgt_epi64(a, b); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return _mm256_cmpgt_epi64(b, a); + } #else CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { std::array x, y; detail::si256_store(a, x.data()); detail::si256_store(b, y.data()); - for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] + y[i]); } + for (int i = 0; i < 4; ++i) + { + x[i] = static_cast(x[i] + y[i]); + } return detail::si256_load(x.data()); } CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) @@ -1087,14 +1749,20 @@ namespace ccm::pp std::array x, y; detail::si256_store(a, x.data()); detail::si256_store(b, y.data()); - for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] - y[i]); } + for (int i = 0; i < 4; ++i) + { + x[i] = static_cast(x[i] - y[i]); + } return detail::si256_load(x.data()); } CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { std::array x; detail::si256_store(a, x.data()); - for (int i = 0; i < 4; ++i) { x[i] = static_cast(-x[i]); } + for (int i = 0; i < 4; ++i) + { + x[i] = static_cast(-x[i]); + } return detail::si256_load(x.data()); } #define CCM_PP_I64X4_SPILLCMP(NAME, OP) \ @@ -1103,7 +1771,10 @@ namespace ccm::pp std::array x, y, r; \ detail::si256_store(a, x.data()); \ detail::si256_store(b, y.data()); \ - for (int i = 0; i < 4; ++i) { r[i] = (x[i] OP y[i]) ? -1 : 0; } \ + for (int i = 0; i < 4; ++i) \ + { \ + r[i] = (x[i] OP y[i]) ? -1 : 0; \ + } \ return detail::si256_load(r.data()); \ } CCM_PP_I64X4_SPILLCMP(eq, ==) @@ -1111,41 +1782,97 @@ namespace ccm::pp CCM_PP_I64X4_SPILLCMP(lt, <) #undef CCM_PP_I64X4_SPILLCMP #endif - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return mnot(eq(a, b)); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return mnot(gt(a, b)); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return mnot(lt(a, b)); } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return mnot(eq(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return mnot(gt(a, b)); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return mnot(lt(a, b)); + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return _mm256_set1_epi64x(b ? -1 : 0); } - CCM_ALWAYS_INLINE static bool mget(MaskMember const &m, detail::SimdSizeType i) { return get(m, i) != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember &m, detail::SimdSizeType i, bool b) { set(m, i, b ? -1 : 0); } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return detail::avx_not(a); } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) == 0xF; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; } + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return _mm256_set1_epi64x(b ? -1 : 0); + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return get(m, i) != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + set(m, i, b ? -1 : 0); + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return detail::avx_not(a); + } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return _mm256_movemask_pd(_mm256_castsi256_pd(m)) == 0xF; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return _mm256_movemask_pd(_mm256_castsi256_pd(m)) != 0; + } CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { std::array a; detail::si256_store(m, a.data()); detail::SimdSizeType c = 0; - for (int i = 0; i < 4; ++i) { c += (a[i] != 0) ? 1 : 0; } + for (int i = 0; i < 4; ++i) + { + c += (a[i] != 0) ? 1 : 0; + } return c; } CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) - { return _mm256_castpd_si256(_mm256_blendv_pd(_mm256_castsi256_pd(b), _mm256_castsi256_pd(a), _mm256_castsi256_pd(m))); } + { + return _mm256_castpd_si256(_mm256_blendv_pd(_mm256_castsi256_pd(b), _mm256_castsi256_pd(a), _mm256_castsi256_pd(m))); + } - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return detail::avx_and(a, b); } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return detail::avx_or(a, b); } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return detail::avx_xor(a, b); } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return detail::avx_not(a); } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return detail::avx_and(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return detail::avx_or(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return detail::avx_xor(a, b); + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return detail::avx_not(a); + } #define CCM_PP_I64X4_SPILLBIN(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ { \ std::array x, y; \ detail::si256_store(a, x.data()); \ detail::si256_store(b, y.data()); \ - for (int i = 0; i < 4; ++i) { x[i] = static_cast(x[i] OP y[i]); } \ + for (int i = 0; i < 4; ++i) \ + { \ + x[i] = static_cast(x[i] OP y[i]); \ + } \ return detail::si256_load(x.data()); \ } CCM_PP_I64X4_SPILLBIN(mul, *) @@ -1154,8 +1881,14 @@ namespace ccm::pp CCM_PP_I64X4_SPILLBIN(shl, <<) CCM_PP_I64X4_SPILLBIN(shr, >>) #undef CCM_PP_I64X4_SPILLBIN - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return select(lt(a, b), a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(gt(a, b), a, b); } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return select(lt(a, b), a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return select(gt(a, b), a, b); + } CCM_ALWAYS_INLINE static std::int64_t hadd(SimdMember v) { std::array a; @@ -1167,7 +1900,10 @@ namespace ccm::pp std::array a; detail::si256_store(v, a.data()); std::int64_t s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] < s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] < s ? a[i] : s; + } return s; } CCM_ALWAYS_INLINE static std::int64_t hmax(SimdMember v) @@ -1175,20 +1911,24 @@ namespace ccm::pp std::array a; detail::si256_store(v, a.data()); std::int64_t s = a[0]; - for (int i = 1; i < 4; ++i) { s = a[i] > s ? a[i] : s; } + for (int i = 1; i < 4; ++i) + { + s = a[i] > s ? a[i] : s; + } return s; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember convert(SimdMember v) { std::array a; detail::si256_store(v, a.data()); typename SimdTraits>::SimdMember r; - for (detail::SimdSizeType i = 0; i < 4; ++i) { SimdTraits>::set(r, i, static_cast(a[i])); } + for (detail::SimdSizeType i = 0; i < 4; ++i) + { + SimdTraits>::set(r, i, static_cast(a[i])); + } return r; } - template - CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static typename SimdTraits>::SimdMember bitcast(SimdMember v) { typename SimdTraits>::SimdMember r; std::memcpy(&r, &v, sizeof(r)); diff --git a/include/ccmath/internal/math/runtime/pp/pp.hpp b/include/ccmath/internal/math/runtime/pp/pp.hpp index f8667576..ab8b40af 100644 --- a/include/ccmath/internal/math/runtime/pp/pp.hpp +++ b/include/ccmath/internal/math/runtime/pp/pp.hpp @@ -34,16 +34,12 @@ namespace ccm::pp { // [simd.alias] Width-parametrized aliases over the deduced ABI. - template ::size()> - using simd = basic_simd>; + template ::size()> using simd = basic_simd>; - template ::size()> - using simd_mask = basic_simd_mask>; + template ::size()> using simd_mask = basic_simd_mask>; // Widest native register width for T. - template - using native_simd = basic_simd>; + template using native_simd = basic_simd>; - template - using native_simd_mask = basic_simd_mask>; + template using native_simd_mask = basic_simd_mask>; } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/reduce.hpp b/include/ccmath/internal/math/runtime/pp/reduce.hpp index fb5d51a7..b429b022 100644 --- a/include/ccmath/internal/math/runtime/pp/reduce.hpp +++ b/include/ccmath/internal/math/runtime/pp/reduce.hpp @@ -28,20 +28,22 @@ namespace ccm::pp namespace detail { // Neutral element for a masked min reduction (so masked-off lanes never win). - template - CCM_ALWAYS_INLINE T reduce_min_identity() + template CCM_ALWAYS_INLINE T reduce_min_identity() { - if constexpr (std::is_floating_point_v) { return std::numeric_limits::infinity(); } - else + if constexpr (std::is_floating_point_v) + { + return std::numeric_limits::infinity(); + } else { return std::numeric_limits::max(); } } - template - CCM_ALWAYS_INLINE T reduce_max_identity() + template CCM_ALWAYS_INLINE T reduce_max_identity() { - if constexpr (std::is_floating_point_v) { return -std::numeric_limits::infinity(); } - else + if constexpr (std::is_floating_point_v) + { + return -std::numeric_limits::infinity(); + } else { return std::numeric_limits::lowest(); } @@ -49,25 +51,30 @@ namespace ccm::pp } // namespace detail // Sum of all lanes. - template - CCM_ALWAYS_INLINE T reduce(basic_simd const & v) - { return SimdTraits::hadd(v.get()); } + template CCM_ALWAYS_INLINE T reduce(basic_simd const & v) + { + return SimdTraits::hadd(v.get()); + } // Reduction with an arbitrary associative binary operation. - template - CCM_ALWAYS_INLINE T reduce(basic_simd const & v, BinaryOp op) + template CCM_ALWAYS_INLINE T reduce(basic_simd const & v, BinaryOp op) { T acc = v[0]; - for (detail::SimdSizeType i = 1; i < v.size(); ++i) { acc = op(acc, v[i]); } + for (detail::SimdSizeType i = 1; i < v.size(); ++i) + { + acc = op(acc, v[i]); + } return acc; } - template - CCM_ALWAYS_INLINE T reduce_min(basic_simd const & v) - { return SimdTraits::hmin(v.get()); } - template - CCM_ALWAYS_INLINE T reduce_max(basic_simd const & v) - { return SimdTraits::hmax(v.get()); } + template CCM_ALWAYS_INLINE T reduce_min(basic_simd const & v) + { + return SimdTraits::hmin(v.get()); + } + template CCM_ALWAYS_INLINE T reduce_max(basic_simd const & v) + { + return SimdTraits::hmax(v.get()); + } // Masked reductions: masked-off lanes contribute the identity element. template > @@ -75,13 +82,18 @@ namespace ccm::pp { basic_simd const sel = simd_select(m, v, basic_simd(identity)); T acc = sel[0]; - for (detail::SimdSizeType i = 1; i < sel.size(); ++i) { acc = op(acc, sel[i]); } + for (detail::SimdSizeType i = 1; i < sel.size(); ++i) + { + acc = op(acc, sel[i]); + } return acc; } - template - CCM_ALWAYS_INLINE T reduce_min(basic_simd const & v, basic_simd_mask const & m) - { return reduce_min(simd_select(m, v, basic_simd(detail::reduce_min_identity()))); } - template - CCM_ALWAYS_INLINE T reduce_max(basic_simd const & v, basic_simd_mask const & m) - { return reduce_max(simd_select(m, v, basic_simd(detail::reduce_max_identity()))); } + template CCM_ALWAYS_INLINE T reduce_min(basic_simd const & v, basic_simd_mask const & m) + { + return reduce_min(simd_select(m, v, basic_simd(detail::reduce_min_identity()))); + } + template CCM_ALWAYS_INLINE T reduce_max(basic_simd const & v, basic_simd_mask const & m) + { + return reduce_max(simd_select(m, v, basic_simd(detail::reduce_max_identity()))); + } } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/reference.hpp b/include/ccmath/internal/math/runtime/pp/reference.hpp index da903b46..d60510cf 100644 --- a/include/ccmath/internal/math/runtime/pp/reference.hpp +++ b/include/ccmath/internal/math/runtime/pp/reference.hpp @@ -20,21 +20,23 @@ namespace ccm::pp::detail { - template - class SimdReference // NOLINT(cppcoreguidelines-special-member-functions) + template class SimdReference // NOLINT(cppcoreguidelines-special-member-functions) { using Traits = SimdTraits; - typename Traits::SimdMember *data_; + typename Traits::SimdMember * data_; SimdSizeType index_; public: - CCM_ALWAYS_INLINE SimdReference(typename Traits::SimdMember *data, SimdSizeType index) : data_(data), index_(index) {} + CCM_ALWAYS_INLINE SimdReference(typename Traits::SimdMember * data, SimdSizeType index) : data_(data), index_(index) {} SimdReference(SimdReference const &) = default; - CCM_ALWAYS_INLINE operator T() const { return Traits::get(*data_, index_); } // NOLINT(google-explicit-constructor) + CCM_ALWAYS_INLINE operator T() const + { + return Traits::get(*data_, index_); + } // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE SimdReference &operator=(T value) + CCM_ALWAYS_INLINE SimdReference & operator=(T value) { Traits::set(*data_, index_, value); return *this; diff --git a/include/ccmath/internal/math/runtime/pp/scalar.hpp b/include/ccmath/internal/math/runtime/pp/scalar.hpp index b009834f..92c11bcc 100644 --- a/include/ccmath/internal/math/runtime/pp/scalar.hpp +++ b/include/ccmath/internal/math/runtime/pp/scalar.hpp @@ -22,8 +22,7 @@ namespace ccm::pp { - template - struct SimdTraits + template struct SimdTraits { using value_type = T; using SimdMember = T; @@ -32,54 +31,156 @@ namespace ccm::pp static constexpr std::size_t simd_align = alignof(T); static constexpr std::size_t mask_align = alignof(bool); - CCM_ALWAYS_INLINE static SimdMember broadcast(T v) { return v; } - CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType /*i*/) { return m; } - CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType /*i*/, T v) { m = v; } - CCM_ALWAYS_INLINE static SimdMember load(T const * p) { return *p; } - CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) { *p = m; } + CCM_ALWAYS_INLINE static SimdMember broadcast(T v) + { + return v; + } + CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType /*i*/) + { + return m; + } + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType /*i*/, T v) + { + m = v; + } + CCM_ALWAYS_INLINE static SimdMember load(T const * p) + { + return *p; + } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) + { + *p = m; + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return a + b; } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return a - b; } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return a * b; } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return a / b; } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return -a; } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return a + b; + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return a - b; + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return a * b; + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return a / b; + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return -a; + } - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return a == b; } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return a != b; } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return a < b; } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return a <= b; } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return a > b; } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return a >= b; } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return a == b; + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return a != b; + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return a < b; + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return a <= b; + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return a > b; + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return a >= b; + } - CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { return b; } - CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType /*i*/) { return m; } - CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType /*i*/, bool b) { m = b; } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return a && b; } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return a || b; } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return a != b; } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return !a; } + CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) + { + return b; + } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType /*i*/) + { + return m; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType /*i*/, bool b) + { + m = b; + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return a && b; + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return a || b; + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return a != b; + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return !a; + } - CCM_ALWAYS_INLINE static bool all_of(MaskMember m) { return m; } - CCM_ALWAYS_INLINE static bool any_of(MaskMember m) { return m; } - CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) { return m ? 1 : 0; } + CCM_ALWAYS_INLINE static bool all_of(MaskMember m) + { + return m; + } + CCM_ALWAYS_INLINE static bool any_of(MaskMember m) + { + return m; + } + CCM_ALWAYS_INLINE static detail::SimdSizeType popcount(MaskMember m) + { + return m ? 1 : 0; + } - CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) { return m ? a : b; } + CCM_ALWAYS_INLINE static SimdMember select(MaskMember m, SimdMember a, SimdMember b) + { + return m ? a : b; + } // Integer-only operations (instantiated only when used, i.e. for integral T). - CCM_ALWAYS_INLINE static SimdMember mod(SimdMember a, SimdMember b) { return a % b; } - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return a & b; } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return a | b; } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return a ^ b; } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return static_cast(~a); } - CCM_ALWAYS_INLINE static SimdMember shl(SimdMember a, SimdMember b) { return static_cast(a << b); } - CCM_ALWAYS_INLINE static SimdMember shr(SimdMember a, SimdMember b) { return static_cast(a >> b); } + CCM_ALWAYS_INLINE static SimdMember mod(SimdMember a, SimdMember b) + { + return a % b; + } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return a & b; + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return a | b; + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return a ^ b; + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return static_cast(~a); + } + CCM_ALWAYS_INLINE static SimdMember shl(SimdMember a, SimdMember b) + { + return static_cast(a << b); + } + CCM_ALWAYS_INLINE static SimdMember shr(SimdMember a, SimdMember b) + { + return static_cast(a >> b); + } // Lane-type conversion and bit reinterpret. - template - CCM_ALWAYS_INLINE static U convert(SimdMember v) - { return static_cast(v); } - template - CCM_ALWAYS_INLINE static U bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static U convert(SimdMember v) + { + return static_cast(v); + } + template CCM_ALWAYS_INLINE static U bitcast(SimdMember v) { U out; std::memcpy(&out, &v, sizeof(U)); @@ -87,19 +188,55 @@ namespace ccm::pp } // Math primitives. - CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) { return detail::s_sqrt(v); } - CCM_ALWAYS_INLINE static SimdMember op_floor(SimdMember v) { return detail::s_floor(v); } - CCM_ALWAYS_INLINE static SimdMember op_ceil(SimdMember v) { return detail::s_ceil(v); } - CCM_ALWAYS_INLINE static SimdMember op_trunc(SimdMember v) { return detail::s_trunc(v); } - CCM_ALWAYS_INLINE static SimdMember op_round(SimdMember v) { return detail::s_round(v); } - CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) { return detail::s_fabs(v); } - CCM_ALWAYS_INLINE static SimdMember op_fma(SimdMember a, SimdMember b, SimdMember c) { return detail::s_fma(a, b, c); } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return a < b ? a : b; } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return a < b ? b : a; } + CCM_ALWAYS_INLINE static SimdMember op_sqrt(SimdMember v) + { + return detail::s_sqrt(v); + } + CCM_ALWAYS_INLINE static SimdMember op_floor(SimdMember v) + { + return detail::s_floor(v); + } + CCM_ALWAYS_INLINE static SimdMember op_ceil(SimdMember v) + { + return detail::s_ceil(v); + } + CCM_ALWAYS_INLINE static SimdMember op_trunc(SimdMember v) + { + return detail::s_trunc(v); + } + CCM_ALWAYS_INLINE static SimdMember op_round(SimdMember v) + { + return detail::s_round(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fabs(SimdMember v) + { + return detail::s_fabs(v); + } + CCM_ALWAYS_INLINE static SimdMember op_fma(SimdMember a, SimdMember b, SimdMember c) + { + return detail::s_fma(a, b, c); + } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return a < b ? a : b; + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return a < b ? b : a; + } // Horizontal reductions (a single lane reduces to itself). - CCM_ALWAYS_INLINE static T hadd(SimdMember v) { return v; } - CCM_ALWAYS_INLINE static T hmin(SimdMember v) { return v; } - CCM_ALWAYS_INLINE static T hmax(SimdMember v) { return v; } + CCM_ALWAYS_INLINE static T hadd(SimdMember v) + { + return v; + } + CCM_ALWAYS_INLINE static T hmin(SimdMember v) + { + return v; + } + CCM_ALWAYS_INLINE static T hmax(SimdMember v) + { + return v; + } }; } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/simd.hpp b/include/ccmath/internal/math/runtime/pp/simd.hpp index 0b70eae0..30533c32 100644 --- a/include/ccmath/internal/math/runtime/pp/simd.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd.hpp @@ -26,8 +26,7 @@ namespace ccm::pp { // [simd.class] A data-parallel value of N lanes of T. C++26 names ported to // C++17. Storage and operations come from the per-ABI SimdTraits backend. - template - class basic_simd + template class basic_simd { using Traits = SimdTraits; using member_type = typename Traits::SimdMember; @@ -39,7 +38,10 @@ namespace ccm::pp using abi_type = Abi; using mask_type = basic_simd_mask; - [[nodiscard]] static constexpr detail::SimdSizeType size() { return Traits::size; } + [[nodiscard]] static constexpr detail::SimdSizeType size() + { + return Traits::size; + } basic_simd() = default; @@ -51,7 +53,7 @@ namespace ccm::pp std::enable_if_t && std::is_convertible_v()(std::integral_constant{})), T>, int> = 0> - CCM_ALWAYS_INLINE explicit basic_simd(G &&gen) // NOLINT(cppcoreguidelines-missing-std-forward) + CCM_ALWAYS_INLINE explicit basic_simd(G && gen) // NOLINT(cppcoreguidelines-missing-std-forward) { detail::unroll_ic([&](auto i) { Traits::set(data_, i, static_cast(gen(i))); }); } @@ -60,137 +62,201 @@ namespace ccm::pp // static_cast. Explicit (we do not model the standard's value-preserving // implicit-conversion rules). template ::size() == size()) && !std::is_same_v, basic_simd>, int> = 0> - CCM_ALWAYS_INLINE explicit basic_simd(basic_simd const &o) + CCM_ALWAYS_INLINE explicit basic_simd(basic_simd const & o) { - for (detail::SimdSizeType i = 0; i < size(); ++i) { (*this)[i] = static_cast(o[i]); } + for (detail::SimdSizeType i = 0; i < size(); ++i) + { + (*this)[i] = static_cast(o[i]); + } } // Load from contiguous memory. template , std::enable_if_t, int> = 0> - CCM_ALWAYS_INLINE basic_simd(T const *ptr, Flags flags = {}) // NOLINT(google-explicit-constructor) - { copy_from(ptr, flags); } + CCM_ALWAYS_INLINE basic_simd(T const * ptr, Flags flags = {}) // NOLINT(google-explicit-constructor) + { + copy_from(ptr, flags); + } - template > - CCM_ALWAYS_INLINE void copy_from(T const *ptr, Flags /*flags*/ = {}) + template > CCM_ALWAYS_INLINE void copy_from(T const * ptr, Flags /*flags*/ = {}) { constexpr std::size_t req = detail::flags_align_request::value; - if constexpr (req != 0) { ptr = detail::assume_aligned_ptr<(req == 1 ? Traits::simd_align : req)>(ptr); } + if constexpr (req != 0) + { + ptr = detail::assume_aligned_ptr<(req == 1 ? Traits::simd_align : req)>(ptr); + } data_ = Traits::load(ptr); } - template > - CCM_ALWAYS_INLINE void copy_to(T *ptr, Flags /*flags*/ = {}) const + template > CCM_ALWAYS_INLINE void copy_to(T * ptr, Flags /*flags*/ = {}) const { constexpr std::size_t req = detail::flags_align_request::value; - if constexpr (req != 0) { ptr = detail::assume_aligned_ptr<(req == 1 ? Traits::simd_align : req)>(ptr); } + if constexpr (req != 0) + { + ptr = detail::assume_aligned_ptr<(req == 1 ? Traits::simd_align : req)>(ptr); + } Traits::store(data_, ptr); } // Element access. Const returns by value (C++26). The mutable overload // returns a lane proxy (TS-style extension). - CCM_ALWAYS_INLINE value_type operator[](detail::SimdSizeType i) const { return Traits::get(data_, i); } - CCM_ALWAYS_INLINE detail::SimdReference operator[](detail::SimdSizeType i) { return { &data_, i }; } + CCM_ALWAYS_INLINE value_type operator[](detail::SimdSizeType i) const + { + return Traits::get(data_, i); + } + CCM_ALWAYS_INLINE detail::SimdReference operator[](detail::SimdSizeType i) + { + return { &data_, i }; + } - CCM_ALWAYS_INLINE static basic_simd from_member(member_type const &m) + CCM_ALWAYS_INLINE static basic_simd from_member(member_type const & m) { basic_simd r; r.data_ = m; return r; } - [[nodiscard]] CCM_ALWAYS_INLINE member_type const &get() const { return data_; } - CCM_ALWAYS_INLINE member_type &get() { return data_; } + [[nodiscard]] CCM_ALWAYS_INLINE member_type const & get() const + { + return data_; + } + CCM_ALWAYS_INLINE member_type & get() + { + return data_; + } - CCM_ALWAYS_INLINE friend basic_simd operator+(basic_simd const &a, basic_simd const &b) { return from_member(Traits::add(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend basic_simd operator-(basic_simd const &a, basic_simd const &b) { return from_member(Traits::sub(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend basic_simd operator*(basic_simd const &a, basic_simd const &b) { return from_member(Traits::mul(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend basic_simd operator/(basic_simd const &a, basic_simd const &b) { return from_member(Traits::div(a.data_, b.data_)); } - CCM_ALWAYS_INLINE basic_simd operator-() const { return from_member(Traits::negate(data_)); } - CCM_ALWAYS_INLINE basic_simd operator+() const { return *this; } + CCM_ALWAYS_INLINE friend basic_simd operator+(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::add(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend basic_simd operator-(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::sub(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend basic_simd operator*(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::mul(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend basic_simd operator/(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::div(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE basic_simd operator-() const + { + return from_member(Traits::negate(data_)); + } + CCM_ALWAYS_INLINE basic_simd operator+() const + { + return *this; + } - CCM_ALWAYS_INLINE basic_simd &operator+=(basic_simd const &o) + CCM_ALWAYS_INLINE basic_simd & operator+=(basic_simd const & o) { data_ = Traits::add(data_, o.data_); return *this; } - CCM_ALWAYS_INLINE basic_simd &operator-=(basic_simd const &o) + CCM_ALWAYS_INLINE basic_simd & operator-=(basic_simd const & o) { data_ = Traits::sub(data_, o.data_); return *this; } - CCM_ALWAYS_INLINE basic_simd &operator*=(basic_simd const &o) + CCM_ALWAYS_INLINE basic_simd & operator*=(basic_simd const & o) { data_ = Traits::mul(data_, o.data_); return *this; } - CCM_ALWAYS_INLINE basic_simd &operator/=(basic_simd const &o) + CCM_ALWAYS_INLINE basic_simd & operator/=(basic_simd const & o) { data_ = Traits::div(data_, o.data_); return *this; } template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator%(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::mod(a.data_, b.data_)); } - template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator&(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::band(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator%(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::mod(a.data_, b.data_)); + } template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator|(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::bor(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator&(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::band(a.data_, b.data_)); + } template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator^(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::bxor(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator|(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::bor(a.data_, b.data_)); + } template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator<<(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::shl(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator^(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::bxor(a.data_, b.data_)); + } template , int> = 0> - CCM_ALWAYS_INLINE friend basic_simd operator>>(basic_simd const &a, basic_simd const &b) - { return from_member(Traits::shr(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator<<(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::shl(a.data_, b.data_)); + } template , int> = 0> - CCM_ALWAYS_INLINE basic_simd operator~() const - { return from_member(Traits::bnot(data_)); } + CCM_ALWAYS_INLINE friend basic_simd operator>>(basic_simd const & a, basic_simd const & b) + { + return from_member(Traits::shr(a.data_, b.data_)); + } + template , int> = 0> CCM_ALWAYS_INLINE basic_simd operator~() const + { + return from_member(Traits::bnot(data_)); + } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator%=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator%=(basic_simd const & o) { data_ = Traits::mod(data_, o.data_); return *this; } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator&=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator&=(basic_simd const & o) { data_ = Traits::band(data_, o.data_); return *this; } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator|=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator|=(basic_simd const & o) { data_ = Traits::bor(data_, o.data_); return *this; } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator^=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator^=(basic_simd const & o) { data_ = Traits::bxor(data_, o.data_); return *this; } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator<<=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator<<=(basic_simd const & o) { data_ = Traits::shl(data_, o.data_); return *this; } - template , int> = 0> - CCM_ALWAYS_INLINE basic_simd &operator>>=(basic_simd const &o) + template , int> = 0> CCM_ALWAYS_INLINE basic_simd & operator>>=(basic_simd const & o) { data_ = Traits::shr(data_, o.data_); return *this; } - CCM_ALWAYS_INLINE friend mask_type operator==(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::eq(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend mask_type operator!=(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::ne(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend mask_type operator<(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::lt(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend mask_type operator<=(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::le(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend mask_type operator>(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::gt(a.data_, b.data_)); } - CCM_ALWAYS_INLINE friend mask_type operator>=(basic_simd const &a, basic_simd const &b) { return mask_type::from_member(Traits::ge(a.data_, b.data_)); } + CCM_ALWAYS_INLINE friend mask_type operator==(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::eq(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend mask_type operator!=(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::ne(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend mask_type operator<(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::lt(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend mask_type operator<=(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::le(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend mask_type operator>(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::gt(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE friend mask_type operator>=(basic_simd const & a, basic_simd const & b) + { + return mask_type::from_member(Traits::ge(a.data_, b.data_)); + } }; } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/simd_cat.hpp b/include/ccmath/internal/math/runtime/pp/simd_cat.hpp index 16d92e2e..b5367506 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_cat.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_cat.hpp @@ -27,15 +27,17 @@ namespace ccm::pp { template - CCM_ALWAYS_INLINE basic_simd::size() + ...)>> simd_cat(basic_simd const &...xs) + CCM_ALWAYS_INLINE basic_simd::size() + ...)>> simd_cat(basic_simd const &... xs) { using R = basic_simd::size() + ...)>>; R r; detail::SimdSizeType off = 0; ( - [&] - { - for (detail::SimdSizeType i = 0; i < xs.size(); ++i) { r[off + i] = xs[i]; } + [&] { + for (detail::SimdSizeType i = 0; i < xs.size(); ++i) + { + r[off + i] = xs[i]; + } off += xs.size(); }(), ...); @@ -43,14 +45,17 @@ namespace ccm::pp } template , int> = 0> - CCM_ALWAYS_INLINE std::array(basic_simd::size() / V::size())> simd_split(basic_simd const &v) + CCM_ALWAYS_INLINE std::array(basic_simd::size() / V::size())> simd_split(basic_simd const & v) { constexpr detail::SimdSizeType parts = basic_simd::size() / V::size(); std::array(parts)> out; detail::SimdSizeType off = 0; for (detail::SimdSizeType p = 0; p < parts; ++p) { - for (detail::SimdSizeType i = 0; i < V::size(); ++i) { out[static_cast(p)][i] = v[off + i]; } + for (detail::SimdSizeType i = 0; i < V::size(); ++i) + { + out[static_cast(p)][i] = v[off + i]; + } off += V::size(); } return out; @@ -58,14 +63,13 @@ namespace ccm::pp template CCM_ALWAYS_INLINE basic_simd_mask, (basic_simd_mask::size() + ...)>> - simd_cat(basic_simd_mask const &...xs) + simd_cat(basic_simd_mask const &... xs) { using R = basic_simd_mask, (basic_simd_mask::size() + ...)>>; unsigned long long bits = 0; detail::SimdSizeType off = 0; ( - [&] - { + [&] { bits |= (xs.to_ullong() << off); off += xs.size(); }(), @@ -74,7 +78,7 @@ namespace ccm::pp } template , int> = 0> - CCM_ALWAYS_INLINE std::array(basic_simd_mask::size() / MaskV::size())> simd_split(basic_simd_mask const &m) + CCM_ALWAYS_INLINE std::array(basic_simd_mask::size() / MaskV::size())> simd_split(basic_simd_mask const & m) { constexpr detail::SimdSizeType parts = basic_simd_mask::size() / MaskV::size(); std::array(parts)> out; diff --git a/include/ccmath/internal/math/runtime/pp/simd_config.hpp b/include/ccmath/internal/math/runtime/pp/simd_config.hpp index a6498f78..a535c057 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_config.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_config.hpp @@ -314,7 +314,9 @@ namespace ccm::pp::config::detail template CCM_SIMD_ENFORCED_ALWAYS_INLINE constexpr void simd_int_pack(std::integer_sequence /*unused*/, F && code) // NOLINT(cppcoreguidelines-missing-std-forward) - { code(std::integer_sequence{}); } + { + code(std::integer_sequence{}); + } } // namespace ccm::pp::config::detail #define CCMATH_SIMD_INT_PACK(N, CODE) ccm::pp::config::detail::simd_int_pack(std::make_integer_sequence{}, CODE) diff --git a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp index 00f2c01c..3b715790 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_mask.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_mask.hpp @@ -29,8 +29,7 @@ namespace ccm::pp // [simd.mask] A data-parallel boolean. Parametrized by lane byte width to // match the C++26 basic_simd_mask, internally backed by the signed integer of // that width through the shared SimdTraits. - template - class basic_simd_mask + template class basic_simd_mask { using IntT = detail::mask_integer_from; using Traits = SimdTraits; @@ -42,7 +41,10 @@ namespace ccm::pp using value_type = bool; using abi_type = Abi; - [[nodiscard]] static constexpr detail::SimdSizeType size() { return Traits::size; } + [[nodiscard]] static constexpr detail::SimdSizeType size() + { + return Traits::size; + } basic_simd_mask() = default; CCM_ALWAYS_INLINE explicit basic_simd_mask(bool value) : data_(Traits::mbroadcast(value)) {} @@ -53,9 +55,15 @@ namespace ccm::pp r.data_ = m; return r; } - [[nodiscard]] CCM_ALWAYS_INLINE member_type const & get() const { return data_; } + [[nodiscard]] CCM_ALWAYS_INLINE member_type const & get() const + { + return data_; + } - CCM_ALWAYS_INLINE bool operator[](detail::SimdSizeType i) const { return Traits::mget(data_, i); } + CCM_ALWAYS_INLINE bool operator[](detail::SimdSizeType i) const + { + return Traits::mget(data_, i); + } // Bit serialization: lane i maps to bit i (lane 0 is the least significant). [[nodiscard]] CCM_ALWAYS_INLINE unsigned long long to_ullong() const @@ -63,38 +71,66 @@ namespace ccm::pp unsigned long long bits = 0; for (detail::SimdSizeType i = 0; i < size(); ++i) { - if (Traits::mget(data_, i)) { bits |= (1ULL << i); } + if (Traits::mget(data_, i)) + { + bits |= (1ULL << i); + } } return bits; } #if !defined(CCM_CONFIG_FREESTANDING) [[nodiscard]] CCM_ALWAYS_INLINE std::bitset(SimdTraits, Abi>::size)> to_bitset() const - { return std::bitset(size())>(to_ullong()); } + { + return std::bitset(size())>(to_ullong()); + } #endif CCM_ALWAYS_INLINE static basic_simd_mask from_bits(unsigned long long bits) { basic_simd_mask r; - for (detail::SimdSizeType i = 0; i < size(); ++i) { Traits::mset(r.data_, i, ((bits >> i) & 1ULL) != 0); } + for (detail::SimdSizeType i = 0; i < size(); ++i) + { + Traits::mset(r.data_, i, ((bits >> i) & 1ULL) != 0); + } return r; } // Bitwise / logical combinators. CCM_ALWAYS_INLINE friend basic_simd_mask operator&(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mand(a.data_, b.data_)); } + { + return from_member(Traits::mand(a.data_, b.data_)); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator|(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mor(a.data_, b.data_)); } + { + return from_member(Traits::mor(a.data_, b.data_)); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator^(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mxor(a.data_, b.data_)); } + { + return from_member(Traits::mxor(a.data_, b.data_)); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator&&(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mand(a.data_, b.data_)); } + { + return from_member(Traits::mand(a.data_, b.data_)); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator||(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mor(a.data_, b.data_)); } - CCM_ALWAYS_INLINE basic_simd_mask operator!() const { return from_member(Traits::mnot(data_)); } - CCM_ALWAYS_INLINE basic_simd_mask operator~() const { return from_member(Traits::mnot(data_)); } + { + return from_member(Traits::mor(a.data_, b.data_)); + } + CCM_ALWAYS_INLINE basic_simd_mask operator!() const + { + return from_member(Traits::mnot(data_)); + } + CCM_ALWAYS_INLINE basic_simd_mask operator~() const + { + return from_member(Traits::mnot(data_)); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator==(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mnot(Traits::mxor(a.data_, b.data_))); } + { + return from_member(Traits::mnot(Traits::mxor(a.data_, b.data_))); + } CCM_ALWAYS_INLINE friend basic_simd_mask operator!=(basic_simd_mask const & a, basic_simd_mask const & b) - { return from_member(Traits::mxor(a.data_, b.data_)); } + { + return from_member(Traits::mxor(a.data_, b.data_)); + } }; } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/simd_math.hpp b/include/ccmath/internal/math/runtime/pp/simd_math.hpp index 6f99e753..20fe6eb8 100644 --- a/include/ccmath/internal/math/runtime/pp/simd_math.hpp +++ b/include/ccmath/internal/math/runtime/pp/simd_math.hpp @@ -30,18 +30,23 @@ namespace ccm::pp { namespace detail { - template - CCM_ALWAYS_INLINE basic_simd map1(basic_simd const & v, F f) + template CCM_ALWAYS_INLINE basic_simd map1(basic_simd const & v, F f) { basic_simd r; - for (SimdSizeType i = 0; i < v.size(); ++i) { r[i] = f(v[i]); } + for (SimdSizeType i = 0; i < v.size(); ++i) + { + r[i] = f(v[i]); + } return r; } template CCM_ALWAYS_INLINE basic_simd map2(basic_simd const & a, basic_simd const & b, F f) { basic_simd r; - for (SimdSizeType i = 0; i < a.size(); ++i) { r[i] = f(a[i], b[i]); } + for (SimdSizeType i = 0; i < a.size(); ++i) + { + r[i] = f(a[i], b[i]); + } return r; } } // namespace detail @@ -49,7 +54,9 @@ namespace ccm::pp #define CCM_PP_MATH1(NAME, OP) \ template , int> = 0> \ CCM_ALWAYS_INLINE basic_simd NAME(basic_simd const & v) \ - { return basic_simd::from_member(SimdTraits::OP(v.get())); } + { \ + return basic_simd::from_member(SimdTraits::OP(v.get())); \ + } CCM_PP_MATH1(sqrt, op_sqrt) CCM_PP_MATH1(floor, op_floor) CCM_PP_MATH1(ceil, op_ceil) @@ -60,19 +67,25 @@ namespace ccm::pp template , int> = 0> CCM_ALWAYS_INLINE basic_simd fma(basic_simd const & a, basic_simd const & b, basic_simd const & c) - { return basic_simd::from_member(SimdTraits::op_fma(a.get(), b.get(), c.get())); } + { + return basic_simd::from_member(SimdTraits::op_fma(a.get(), b.get(), c.get())); + } - template - CCM_ALWAYS_INLINE basic_simd min(basic_simd const & a, basic_simd const & b) - { return basic_simd::from_member(SimdTraits::op_min(a.get(), b.get())); } - template - CCM_ALWAYS_INLINE basic_simd max(basic_simd const & a, basic_simd const & b) - { return basic_simd::from_member(SimdTraits::op_max(a.get(), b.get())); } + template CCM_ALWAYS_INLINE basic_simd min(basic_simd const & a, basic_simd const & b) + { + return basic_simd::from_member(SimdTraits::op_min(a.get(), b.get())); + } + template CCM_ALWAYS_INLINE basic_simd max(basic_simd const & a, basic_simd const & b) + { + return basic_simd::from_member(SimdTraits::op_max(a.get(), b.get())); + } // abs: floating point reuses the fabs primitive. Integral negates negatives. template , int> = 0> CCM_ALWAYS_INLINE basic_simd abs(basic_simd const & v) - { return basic_simd::from_member(SimdTraits::op_fabs(v.get())); } + { + return basic_simd::from_member(SimdTraits::op_fabs(v.get())); + } template && std::is_signed_v, int> = 0> CCM_ALWAYS_INLINE basic_simd abs(basic_simd const & v) { diff --git a/include/ccmath/internal/math/runtime/pp/traits.hpp b/include/ccmath/internal/math/runtime/pp/traits.hpp index 4ab9bb2f..781c3b21 100644 --- a/include/ccmath/internal/math/runtime/pp/traits.hpp +++ b/include/ccmath/internal/math/runtime/pp/traits.hpp @@ -18,86 +18,57 @@ namespace ccm::pp { // [simd.traits] - template - struct is_abi_tag : std::false_type - { - }; - template <> - struct is_abi_tag : std::true_type - { - }; - template - struct is_abi_tag> : std::true_type - { - }; - template - inline constexpr bool is_abi_tag_v = is_abi_tag::value; + template struct is_abi_tag : std::false_type + {}; + template <> struct is_abi_tag : std::true_type + {}; + template struct is_abi_tag> : std::true_type + {}; + template inline constexpr bool is_abi_tag_v = is_abi_tag::value; - template - struct is_simd : std::false_type - { - }; - template - struct is_simd> : std::true_type - { - }; - template - inline constexpr bool is_simd_v = is_simd::value; + template struct is_simd : std::false_type + {}; + template struct is_simd> : std::true_type + {}; + template inline constexpr bool is_simd_v = is_simd::value; - template - struct is_simd_mask : std::false_type - { - }; - template - struct is_simd_mask> : std::true_type - { - }; - template - inline constexpr bool is_simd_mask_v = is_simd_mask::value; + template struct is_simd_mask : std::false_type + {}; + template struct is_simd_mask> : std::true_type + {}; + template inline constexpr bool is_simd_mask_v = is_simd_mask::value; - template > - struct simd_size : std::integral_constant - { - }; - template > - inline constexpr detail::SimdSizeType simd_size_v = simd_size::value; + template > struct simd_size : std::integral_constant + {}; + template > inline constexpr detail::SimdSizeType simd_size_v = simd_size::value; // resize_simd_t: same element type as V, but N lanes. - template - struct resize_simd; - template - struct resize_simd> + template struct resize_simd; + template struct resize_simd> { using type = basic_simd>; }; - template - struct resize_simd> + template struct resize_simd> { using type = basic_simd_mask, N>>; }; - template - using resize_simd_t = typename resize_simd::type; + template using resize_simd_t = typename resize_simd::type; // rebind_simd_t: same lane count as V, but element type U. The ABI tag // is element-agnostic, so the tag is preserved. - template - struct rebind_simd; - template - struct rebind_simd> + template struct rebind_simd; + template struct rebind_simd> { using type = basic_simd; }; - template - struct rebind_simd> + template struct rebind_simd> { using type = basic_simd_mask; }; - template - using rebind_simd_t = typename rebind_simd::type; + template using rebind_simd_t = typename rebind_simd::type; namespace detail { - template - using is_vectorizable_t = is_vectorizable; + template using is_vectorizable_t = is_vectorizable; } // namespace detail } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/pp/utility.hpp b/include/ccmath/internal/math/runtime/pp/utility.hpp index cc364743..fa19df39 100644 --- a/include/ccmath/internal/math/runtime/pp/utility.hpp +++ b/include/ccmath/internal/math/runtime/pp/utility.hpp @@ -21,8 +21,7 @@ namespace ccm::pp // Per-(T, Abi) storage and operation set. Specialized by the scalar and // vec_ext backends. This is the libc++ __simd_storage + __simd_operations + // __mask_operations rolled into one trait, keyed on the public ABI tag. - template - struct SimdTraits; + template struct SimdTraits; namespace detail { @@ -30,21 +29,27 @@ namespace ccm::pp // receives a plain index so it can be used to subscript a builtin vector. template CCM_ALWAYS_INLINE constexpr void unroll_impl(F && f, std::index_sequence /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) - { (f(static_cast(Is)), ...); } + { + (f(static_cast(Is)), ...); + } - template - CCM_ALWAYS_INLINE constexpr void unroll(F && f) - { unroll_impl(std::forward(f), std::make_index_sequence(N)>{}); } + template CCM_ALWAYS_INLINE constexpr void unroll(F && f) + { + unroll_impl(std::forward(f), std::make_index_sequence(N)>{}); + } // As unroll, but passes a std::integral_constant so the callback can use // the index in a constant-expression context (used by the generator ctor). template CCM_ALWAYS_INLINE constexpr void unroll_ic_impl(F && f, std::index_sequence /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward) - { (f(std::integral_constant(Is)>{}), ...); } + { + (f(std::integral_constant(Is)>{}), ...); + } - template - CCM_ALWAYS_INLINE constexpr void unroll_ic(F && f) - { unroll_ic_impl(std::forward(f), std::make_index_sequence(N)>{}); } + template CCM_ALWAYS_INLINE constexpr void unroll_ic(F && f) + { + unroll_ic_impl(std::forward(f), std::make_index_sequence(N)>{}); + } // Per-lane scalar math primitives for the scalar backend and the vec_ext // portable fallback. GNU/Clang use foldable, errno-free compiler builtins @@ -55,38 +60,38 @@ namespace ccm::pp #define CCM_PP_F(BASE) __builtin_##BASE##f #define CCM_PP_D(BASE) __builtin_##BASE #else - extern "C" - { - float sqrtf(float); - double sqrt(double); - float floorf(float); - double floor(double); - float ceilf(float); - double ceil(double); - float truncf(float); - double trunc(double); - float roundf(float); - double round(double); - double fabs(double); - float expf(float); - double exp(double); - float logf(float); - double log(double); - float fmaf(float, float, float); - double fma(double, double, double); - float powf(float, float); - double pow(double, double); + extern "C" { + float sqrtf(float); + double sqrt(double); + float floorf(float); + double floor(double); + float ceilf(float); + double ceil(double); + float truncf(float); + double trunc(double); + float roundf(float); + double round(double); + double fabs(double); + float expf(float); + double exp(double); + float logf(float); + double log(double); + float fmaf(float, float, float); + double fma(double, double, double); + float powf(float, float); + double pow(double, double); } #define CCM_PP_F(BASE) BASE##f #define CCM_PP_D(BASE) BASE #endif #define CCM_PP_S_UNARY(NAME, BASE) \ - template \ - CCM_ALWAYS_INLINE T NAME(T x) \ + template CCM_ALWAYS_INLINE T NAME(T x) \ { \ - if constexpr (std::is_same_v) { return CCM_PP_F(BASE)(x); } \ - else \ + if constexpr (std::is_same_v) \ + { \ + return CCM_PP_F(BASE)(x); \ + } else \ { \ return CCM_PP_D(BASE)(x); \ } \ @@ -100,20 +105,22 @@ namespace ccm::pp CCM_PP_S_UNARY(s_log, log) #undef CCM_PP_S_UNARY - template - CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) + template CCM_ALWAYS_INLINE T s_fma(T a, T b, T c) { - if constexpr (std::is_same_v) { return CCM_PP_F(fma)(a, b, c); } - else + if constexpr (std::is_same_v) + { + return CCM_PP_F(fma)(a, b, c); + } else { return CCM_PP_D(fma)(a, b, c); } } - template - CCM_ALWAYS_INLINE T s_pow(T a, T b) + template CCM_ALWAYS_INLINE T s_pow(T a, T b) { - if constexpr (std::is_same_v) { return CCM_PP_F(pow)(a, b); } - else + if constexpr (std::is_same_v) + { + return CCM_PP_F(pow)(a, b); + } else { return CCM_PP_D(pow)(a, b); } @@ -125,18 +132,21 @@ namespace ccm::pp // MSVC does not always provide a linkable fabsf, so on the portable path the // float case routes through the double fabs, which is exact because fabs only // clears the sign bit. - template - CCM_ALWAYS_INLINE T s_fabs(T x) + template CCM_ALWAYS_INLINE T s_fabs(T x) { #if (defined(__GNUC__) || defined(__clang__)) && !defined(CCM_PP_FORCE_PORTABLE) - if constexpr (std::is_same_v) { return __builtin_fabsf(x); } - else + if constexpr (std::is_same_v) + { + return __builtin_fabsf(x); + } else { return __builtin_fabs(x); } #else - if constexpr (std::is_same_v) { return static_cast(fabs(static_cast(x))); } - else + if constexpr (std::is_same_v) + { + return static_cast(fabs(static_cast(x))); + } else { return fabs(x); } diff --git a/include/ccmath/internal/math/runtime/pp/vec_ext.hpp b/include/ccmath/internal/math/runtime/pp/vec_ext.hpp index 1fe43c44..72de825b 100644 --- a/include/ccmath/internal/math/runtime/pp/vec_ext.hpp +++ b/include/ccmath/internal/math/runtime/pp/vec_ext.hpp @@ -67,17 +67,14 @@ namespace ccm::pp namespace detail { // A native vector of M elements of type U. - template - struct vec_builtin + template struct vec_builtin { typedef U type __attribute__((__vector_size__(sizeof(U) * M))); }; - template - using vec_builtin_t = typename vec_builtin::type; + template using vec_builtin_t = typename vec_builtin::type; } // namespace detail - template - struct SimdTraits> + template struct SimdTraits> { using value_type = T; using MaskInt = detail::mask_integer_from; @@ -93,8 +90,14 @@ namespace ccm::pp detail::unroll([&](auto i) { r[i] = v; }); return r; } - CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType i) { return m[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, T v) { m[i] = v; } + CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType i) + { + return m[i]; + } + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, T v) + { + m[i] = v; + } // A single (unaligned) vector move rather than a per-lane loop. CCM_ALWAYS_INLINE static SimdMember load(T const * p) { @@ -102,23 +105,59 @@ namespace ccm::pp std::memcpy(&r, p, sizeof(r)); return r; } - CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) { std::memcpy(p, &m, sizeof(m)); } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) + { + std::memcpy(p, &m, sizeof(m)); + } - CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) { return a + b; } - CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) { return a - b; } - CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) { return a * b; } - CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) { return a / b; } - CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) { return -a; } + CCM_ALWAYS_INLINE static SimdMember add(SimdMember a, SimdMember b) + { + return a + b; + } + CCM_ALWAYS_INLINE static SimdMember sub(SimdMember a, SimdMember b) + { + return a - b; + } + CCM_ALWAYS_INLINE static SimdMember mul(SimdMember a, SimdMember b) + { + return a * b; + } + CCM_ALWAYS_INLINE static SimdMember div(SimdMember a, SimdMember b) + { + return a / b; + } + CCM_ALWAYS_INLINE static SimdMember negate(SimdMember a) + { + return -a; + } // A same-size vector comparison yields a vector of the matching signed // integer with all-ones / zero lanes. The cast reinterprets it as our // canonical MaskMember type. - CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) { return reinterpret_mask(a == b); } - CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) { return reinterpret_mask(a != b); } - CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) { return reinterpret_mask(a < b); } - CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) { return reinterpret_mask(a <= b); } - CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) { return reinterpret_mask(a > b); } - CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) { return reinterpret_mask(a >= b); } + CCM_ALWAYS_INLINE static MaskMember eq(SimdMember a, SimdMember b) + { + return reinterpret_mask(a == b); + } + CCM_ALWAYS_INLINE static MaskMember ne(SimdMember a, SimdMember b) + { + return reinterpret_mask(a != b); + } + CCM_ALWAYS_INLINE static MaskMember lt(SimdMember a, SimdMember b) + { + return reinterpret_mask(a < b); + } + CCM_ALWAYS_INLINE static MaskMember le(SimdMember a, SimdMember b) + { + return reinterpret_mask(a <= b); + } + CCM_ALWAYS_INLINE static MaskMember gt(SimdMember a, SimdMember b) + { + return reinterpret_mask(a > b); + } + CCM_ALWAYS_INLINE static MaskMember ge(SimdMember a, SimdMember b) + { + return reinterpret_mask(a >= b); + } CCM_ALWAYS_INLINE static MaskMember mbroadcast(bool b) { @@ -127,12 +166,30 @@ namespace ccm::pp detail::unroll([&](auto i) { r[i] = v; }); return r; } - CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { return m[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { m[i] = b ? static_cast(-1) : MaskInt{ 0 }; } - CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { return a & b; } - CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) { return a | b; } - CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) { return a ^ b; } - CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) { return ~a; } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return m[i] != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + m[i] = b ? static_cast(-1) : MaskInt{ 0 }; + } + CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) + { + return a & b; + } + CCM_ALWAYS_INLINE static MaskMember mor(MaskMember a, MaskMember b) + { + return a | b; + } + CCM_ALWAYS_INLINE static MaskMember mxor(MaskMember a, MaskMember b) + { + return a ^ b; + } + CCM_ALWAYS_INLINE static MaskMember mnot(MaskMember a) + { + return ~a; + } // All lanes are 0 or -1, so a horizontal AND/OR collapses to a movemask test // rather than a per-lane scalar chain. @@ -175,17 +232,37 @@ namespace ccm::pp } // Integer-only operations (instantiated only when used, i.e. for integral T). - CCM_ALWAYS_INLINE static SimdMember mod(SimdMember a, SimdMember b) { return a % b; } - CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) { return a & b; } - CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) { return a | b; } - CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) { return a ^ b; } - CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) { return ~a; } - CCM_ALWAYS_INLINE static SimdMember shl(SimdMember a, SimdMember b) { return a << b; } - CCM_ALWAYS_INLINE static SimdMember shr(SimdMember a, SimdMember b) { return a >> b; } + CCM_ALWAYS_INLINE static SimdMember mod(SimdMember a, SimdMember b) + { + return a % b; + } + CCM_ALWAYS_INLINE static SimdMember band(SimdMember a, SimdMember b) + { + return a & b; + } + CCM_ALWAYS_INLINE static SimdMember bor(SimdMember a, SimdMember b) + { + return a | b; + } + CCM_ALWAYS_INLINE static SimdMember bxor(SimdMember a, SimdMember b) + { + return a ^ b; + } + CCM_ALWAYS_INLINE static SimdMember bnot(SimdMember a) + { + return ~a; + } + CCM_ALWAYS_INLINE static SimdMember shl(SimdMember a, SimdMember b) + { + return a << b; + } + CCM_ALWAYS_INLINE static SimdMember shr(SimdMember a, SimdMember b) + { + return a >> b; + } // Lane-type conversion (value) and bit reinterpret (same total size). - template - CCM_ALWAYS_INLINE static detail::vec_builtin_t convert(SimdMember v) + template CCM_ALWAYS_INLINE static detail::vec_builtin_t convert(SimdMember v) { #if defined(__has_builtin) && __has_builtin(__builtin_convertvector) return __builtin_convertvector(v, detail::vec_builtin_t); @@ -195,9 +272,10 @@ namespace ccm::pp return r; #endif } - template - CCM_ALWAYS_INLINE static detail::vec_builtin_t bitcast(SimdMember v) - { return reinterpret_cast>(v); } + template CCM_ALWAYS_INLINE static detail::vec_builtin_t bitcast(SimdMember v) + { + return reinterpret_cast>(v); + } // Math primitives. Clang lowers these to packed instructions. GCC takes the // per-lane fallback. @@ -271,8 +349,14 @@ namespace ccm::pp return r; #endif } - CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) { return select(lt(a, b), a, b); } - CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) { return select(lt(b, a), a, b); } + CCM_ALWAYS_INLINE static SimdMember op_min(SimdMember a, SimdMember b) + { + return select(lt(a, b), a, b); + } + CCM_ALWAYS_INLINE static SimdMember op_max(SimdMember a, SimdMember b) + { + return select(lt(b, a), a, b); + } // Horizontal reductions. CCM_ALWAYS_INLINE static T hadd(SimdMember v) @@ -280,8 +364,10 @@ namespace ccm::pp // __builtin_reduce_add is integer-only (float add is not associative); // floats use the per-lane fold. #if CCM_PP_HAS_RED(add) - if constexpr (std::is_integral::value) { return __builtin_reduce_add(v); } - else + if constexpr (std::is_integral::value) + { + return __builtin_reduce_add(v); + } else #endif { T s = T(0); @@ -312,13 +398,13 @@ namespace ccm::pp private: // Reinterpret an arbitrary same-size integer comparison vector as MaskMember. - template - CCM_ALWAYS_INLINE static MaskMember reinterpret_mask(Cmp c) - { return reinterpret_cast(c); } + template CCM_ALWAYS_INLINE static MaskMember reinterpret_mask(Cmp c) + { + return reinterpret_cast(c); + } }; #else // CCM_PP_HAS_VECTOR_EXT == 0 : portable array fallback - template - struct SimdTraits> + template struct SimdTraits> { using value_type = T; using MaskInt = detail::mask_integer_from; @@ -334,15 +420,24 @@ namespace ccm::pp detail::unroll([&](auto i) { r[i] = v; }); return r; } - CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType i) { return m[i]; } - CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, T v) { m[i] = v; } + CCM_ALWAYS_INLINE static T get(SimdMember const & m, detail::SimdSizeType i) + { + return m[i]; + } + CCM_ALWAYS_INLINE static void set(SimdMember & m, detail::SimdSizeType i, T v) + { + m[i] = v; + } CCM_ALWAYS_INLINE static SimdMember load(T const * p) { SimdMember r; std::memcpy(r.data(), p, sizeof(r)); return r; } - CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) { std::memcpy(p, m.data(), sizeof(m)); } + CCM_ALWAYS_INLINE static void store(SimdMember const & m, T * p) + { + std::memcpy(p, m.data(), sizeof(m)); + } #define CCM_PP_ARRAY_BINOP(NAME, OP) \ CCM_ALWAYS_INLINE static SimdMember NAME(SimdMember a, SimdMember b) \ @@ -385,8 +480,14 @@ namespace ccm::pp detail::unroll([&](auto i) { r[i] = v; }); return r; } - CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) { return m[i] != 0; } - CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) { m[i] = b ? static_cast(-1) : MaskInt{ 0 }; } + CCM_ALWAYS_INLINE static bool mget(MaskMember const & m, detail::SimdSizeType i) + { + return m[i] != 0; + } + CCM_ALWAYS_INLINE static void mset(MaskMember & m, detail::SimdSizeType i, bool b) + { + m[i] = b ? static_cast(-1) : MaskInt{ 0 }; + } CCM_ALWAYS_INLINE static MaskMember mand(MaskMember a, MaskMember b) { MaskMember r; @@ -459,15 +560,13 @@ namespace ccm::pp return r; } - template - CCM_ALWAYS_INLINE static std::array(N)> convert(SimdMember v) + template CCM_ALWAYS_INLINE static std::array(N)> convert(SimdMember v) { std::array(N)> r; detail::unroll([&](auto i) { r[i] = static_cast(v[i]); }); return r; } - template - CCM_ALWAYS_INLINE static std::array(N)> bitcast(SimdMember v) + template CCM_ALWAYS_INLINE static std::array(N)> bitcast(SimdMember v) { std::array(N)> r; std::memcpy(r.data(), v.data(), sizeof(r)); diff --git a/include/ccmath/internal/math/runtime/pp/where.hpp b/include/ccmath/internal/math/runtime/pp/where.hpp index 4f35a21d..75cef8a7 100644 --- a/include/ccmath/internal/math/runtime/pp/where.hpp +++ b/include/ccmath/internal/math/runtime/pp/where.hpp @@ -24,81 +24,109 @@ namespace ccm::pp { // Lane-wise blend: pick a where mask is set, else b. template - CCM_ALWAYS_INLINE basic_simd simd_select(basic_simd_mask const& mask, basic_simd const& a, basic_simd const& b) - { return basic_simd::from_member(SimdTraits::select(mask.get(), a.get(), b.get())); } + CCM_ALWAYS_INLINE basic_simd simd_select(basic_simd_mask const & mask, basic_simd const & a, basic_simd const & b) + { + return basic_simd::from_member(SimdTraits::select(mask.get(), a.get(), b.get())); + } // [simd.whereexpr] Masked, write-only view onto a simd object. - template - class where_expression + template class where_expression { MaskType mask_; - SimdType* data_; + SimdType * data_; public: using value_type = typename SimdType::value_type; - CCM_ALWAYS_INLINE where_expression(MaskType const& mask, SimdType& data) : mask_(mask), data_(&data) {} + CCM_ALWAYS_INLINE where_expression(MaskType const & mask, SimdType & data) : mask_(mask), data_(&data) {} // NOLINTBEGIN(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - CCM_ALWAYS_INLINE void operator=(SimdType const& rhs) { *data_ = simd_select(mask_, rhs, *data_); } - CCM_ALWAYS_INLINE void operator=(value_type rhs) { *data_ = simd_select(mask_, SimdType(rhs), *data_); } + CCM_ALWAYS_INLINE void operator=(SimdType const & rhs) + { + *data_ = simd_select(mask_, rhs, *data_); + } + CCM_ALWAYS_INLINE void operator=(value_type rhs) + { + *data_ = simd_select(mask_, SimdType(rhs), *data_); + } // NOLINTEND(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - CCM_ALWAYS_INLINE void operator+=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ + rhs, *data_); } - CCM_ALWAYS_INLINE void operator-=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ - rhs, *data_); } - CCM_ALWAYS_INLINE void operator*=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ * rhs, *data_); } - CCM_ALWAYS_INLINE void operator/=(SimdType const& rhs) { *data_ = simd_select(mask_, *data_ / rhs, *data_); } + CCM_ALWAYS_INLINE void operator+=(SimdType const & rhs) + { + *data_ = simd_select(mask_, *data_ + rhs, *data_); + } + CCM_ALWAYS_INLINE void operator-=(SimdType const & rhs) + { + *data_ = simd_select(mask_, *data_ - rhs, *data_); + } + CCM_ALWAYS_INLINE void operator*=(SimdType const & rhs) + { + *data_ = simd_select(mask_, *data_ * rhs, *data_); + } + CCM_ALWAYS_INLINE void operator/=(SimdType const & rhs) + { + *data_ = simd_select(mask_, *data_ / rhs, *data_); + } // Masked load: read only the selected lanes from memory, leave the rest. - template > - CCM_ALWAYS_INLINE void copy_from(value_type const* ptr, Flags /*flags*/ = {}) + template > CCM_ALWAYS_INLINE void copy_from(value_type const * ptr, Flags /*flags*/ = {}) { for (detail::SimdSizeType i = 0; i < SimdType::size(); ++i) { - if (mask_[i]) { (*data_)[i] = ptr[i]; } + if (mask_[i]) + { + (*data_)[i] = ptr[i]; + } } } // Masked store: write only the selected lanes to memory, leave the rest. - template > - CCM_ALWAYS_INLINE void copy_to(value_type* ptr, Flags /*flags*/ = {}) const + template > CCM_ALWAYS_INLINE void copy_to(value_type * ptr, Flags /*flags*/ = {}) const { for (detail::SimdSizeType i = 0; i < SimdType::size(); ++i) { - if (mask_[i]) { ptr[i] = (*data_)[i]; } + if (mask_[i]) + { + ptr[i] = (*data_)[i]; + } } } }; // [simd.whereexpr] Masked, read-only view onto a const simd object. - template - class const_where_expression + template class const_where_expression { MaskType mask_; - SimdType const* data_; + SimdType const * data_; public: using value_type = typename SimdType::value_type; - CCM_ALWAYS_INLINE const_where_expression(MaskType const& mask, SimdType const& data) : mask_(mask), data_(&data) {} + CCM_ALWAYS_INLINE const_where_expression(MaskType const & mask, SimdType const & data) : mask_(mask), data_(&data) {} // Masked store: write only the selected lanes to memory. - template > - CCM_ALWAYS_INLINE void copy_to(value_type* ptr, Flags /*flags*/ = {}) const + template > CCM_ALWAYS_INLINE void copy_to(value_type * ptr, Flags /*flags*/ = {}) const { for (detail::SimdSizeType i = 0; i < SimdType::size(); ++i) { - if (mask_[i]) { ptr[i] = (*data_)[i]; } + if (mask_[i]) + { + ptr[i] = (*data_)[i]; + } } } }; template - CCM_ALWAYS_INLINE where_expression, basic_simd> where(basic_simd_mask const& mask, - basic_simd& value) - { return { mask, value }; } + CCM_ALWAYS_INLINE where_expression, basic_simd> where(basic_simd_mask const & mask, + basic_simd & value) + { + return { mask, value }; + } template - CCM_ALWAYS_INLINE const_where_expression, basic_simd> where(basic_simd_mask const& mask, - basic_simd const& value) - { return { mask, value }; } + CCM_ALWAYS_INLINE const_where_expression, basic_simd> where(basic_simd_mask const & mask, + basic_simd const & value) + { + return { mask, value }; + } } // namespace ccm::pp diff --git a/include/ccmath/internal/math/runtime/simd/common.hpp b/include/ccmath/internal/math/runtime/simd/common.hpp index fba9ba5f..c370dcbc 100644 --- a/include/ccmath/internal/math/runtime/simd/common.hpp +++ b/include/ccmath/internal/math/runtime/simd/common.hpp @@ -16,117 +16,138 @@ namespace ccm::intrin { - template - struct simd; + template struct simd; - template - struct simd_mask; + template struct simd_mask; struct element_aligned_tag - { - }; + {}; - template - CCM_GPU_HOST_DEVICE constexpr T const& choose(bool a, T const& b, T const& c) - { return a ? b : c; } // NOLINT(bugprone-return-const-ref-from-parameter) callers pass lvalues + template CCM_GPU_HOST_DEVICE constexpr T const & choose(bool a, T const & b, T const & c) + { + return a ? b : c; + } // NOLINT(bugprone-return-const-ref-from-parameter) callers pass lvalues - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd& operator+=(simd& a, simd const& b) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator+=(simd & a, simd const & b) { a = a + b; return a; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd& operator-=(simd& a, simd const& b) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator-=(simd & a, simd const & b) { a = a - b; return a; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd& operator*=(simd& a, simd const& b) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator*=(simd & a, simd const & b) { a = a * b; return a; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd& operator/=(simd& a, simd const& b) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator/=(simd & a, simd const & b) { a = a / b; return a; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(T const& a, simd const& b) - { return simd(a) + b; } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(T const & a, simd const & b) + { + return simd(a) + b; + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(simd const& a, T const& b) - { return a + simd(b); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(simd const & a, T const & b) + { + return a + simd(b); + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(T const& a, simd const& b) - { return simd(a) - b; } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(T const & a, simd const & b) + { + return simd(a) - b; + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(simd const& a, T const& b) - { return a - simd(b); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(simd const & a, T const & b) + { + return a - simd(b); + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(T const& a, simd const& b) - { return simd(a) * b; } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(T const & a, simd const & b) + { + return simd(a) * b; + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(simd const& a, T const& b) - { return a * simd(b); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(simd const & a, T const & b) + { + return a * simd(b); + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(T const& a, simd const& b) - { return simd(a) / b; } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(T const & a, simd const & b) + { + return simd(a) / b; + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(simd const& a, T const& b) - { return a / simd(b); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(simd const & a, T const & b) + { + return a / simd(b); + } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(bool a) - { return a; } + { + return a; + } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(bool a) - { return a; } + { + return a; + } - template - struct simd_storage + template struct simd_storage { using value_type = T; using simd_type = simd; CCM_ALWAYS_INLINE simd_storage() = default; - static constexpr int size() { return simd::size(); } - CCM_ALWAYS_INLINE explicit simd_storage(simd const& value) { value.copy_to(m_value, element_aligned_tag()); } + static constexpr int size() + { + return simd::size(); + } + CCM_ALWAYS_INLINE explicit simd_storage(simd const & value) + { + value.copy_to(m_value, element_aligned_tag()); + } CCM_ALWAYS_INLINE explicit simd_storage(T value) : simd_storage(simd(value)) {} - CCM_ALWAYS_INLINE simd_storage& operator=(simd const& value) + CCM_ALWAYS_INLINE simd_storage & operator=(simd const & value) { value.copy_to(m_value, element_aligned_tag()); return *this; } - CCM_ALWAYS_INLINE T const* data() const { return m_value.data(); } - CCM_ALWAYS_INLINE T* data() { return m_value; } - CCM_ALWAYS_INLINE T const& operator[](int i) const { return m_value[i]; } - CCM_ALWAYS_INLINE T& operator[](int i) { return m_value[i]; } + CCM_ALWAYS_INLINE T const * data() const + { + return m_value.data(); + } + CCM_ALWAYS_INLINE T * data() + { + return m_value; + } + CCM_ALWAYS_INLINE T const & operator[](int i) const + { + return m_value[i]; + } + CCM_ALWAYS_INLINE T & operator[](int i) + { + return m_value[i]; + } private: std::array::size()> m_value; }; - template - struct simd_size + template struct simd_size { static constexpr int value = 1; }; - template - struct simd_size> + template struct simd_size> { static constexpr int value = simd::size(); }; diff --git a/include/ccmath/internal/math/runtime/simd/func/impl/neon/pow.hpp b/include/ccmath/internal/math/runtime/simd/func/impl/neon/pow.hpp index bbee0f4b..4cbb325a 100644 --- a/include/ccmath/internal/math/runtime/simd/func/impl/neon/pow.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/impl/neon/pow.hpp @@ -18,10 +18,14 @@ namespace ccm::intrin { CCM_ALWAYS_INLINE simd pow(simd const & a, simd const & b) - { return simd(gen::pow_gen(a.convert(), b.convert())); } + { + return simd(gen::pow_gen(a.convert(), b.convert())); + } CCM_ALWAYS_INLINE simd pow(simd const & a, simd const & b) - { return simd(gen::pow_gen(a.convert(), b.convert())); } + { + return simd(gen::pow_gen(a.convert(), b.convert())); + } } // namespace ccm::intrin #endif #endif diff --git a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/map.hpp b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/map.hpp index 48084b39..e19d16be 100644 --- a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/map.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/map.hpp @@ -14,11 +14,13 @@ namespace ccm::intrin { - template - CCM_ALWAYS_INLINE simd map_scalar(simd const & input, Fn fn) - { return simd(fn(input.convert())); } + template CCM_ALWAYS_INLINE simd map_scalar(simd const & input, Fn fn) + { + return simd(fn(input.convert())); + } - template - CCM_ALWAYS_INLINE simd map_scalar(simd const & lhs, simd const & rhs, Fn fn) - { return simd(fn(lhs.convert(), rhs.convert())); } + template CCM_ALWAYS_INLINE simd map_scalar(simd const & lhs, simd const & rhs, Fn fn) + { + return simd(fn(lhs.convert(), rhs.convert())); + } } // namespace ccm::intrin diff --git a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/pow.hpp b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/pow.hpp index 629ea220..85aad677 100644 --- a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/pow.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/pow.hpp @@ -15,7 +15,8 @@ namespace ccm::intrin { - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd pow(simd const & a, simd const & b) - { return simd(gen::pow_gen(a.get(), b.get())); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd pow(simd const & a, simd const & b) + { + return simd(gen::pow_gen(a.get(), b.get())); + } } // namespace ccm::intrin diff --git a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/sqrt.hpp b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/sqrt.hpp index 55340f00..a130a6dd 100644 --- a/include/ccmath/internal/math/runtime/simd/func/impl/scalar/sqrt.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/impl/scalar/sqrt.hpp @@ -16,12 +16,12 @@ namespace ccm::intrin { - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd sqrt(simd const & a) - { return simd(ccm::gen::sqrt_gen(a.get())); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd sqrt(simd const & a) + { + return simd(ccm::gen::sqrt_gen(a.get())); + } - template - CCM_ALWAYS_INLINE std::enable_if_t, simd> sqrt(simd const & a) + template CCM_ALWAYS_INLINE std::enable_if_t, simd> sqrt(simd const & a) { return map_scalar(a, [](T value) { return ccm::gen::sqrt_gen(value); }); } diff --git a/include/ccmath/internal/math/runtime/simd/func/impl/vector_size/sqrt.hpp b/include/ccmath/internal/math/runtime/simd/func/impl/vector_size/sqrt.hpp index 5e383e96..1ecbc1ed 100644 --- a/include/ccmath/internal/math/runtime/simd/func/impl/vector_size/sqrt.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/impl/vector_size/sqrt.hpp @@ -19,8 +19,7 @@ namespace ccm::intrin { - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> sqrt(simd> const & a) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> sqrt(simd> const & a) { return map_scalar(a, [](T value) { return ccm::gen::sqrt_gen(value); }); } diff --git a/include/ccmath/internal/math/runtime/simd/func/svml_ops.hpp b/include/ccmath/internal/math/runtime/simd/func/svml_ops.hpp index ea4226df..3b784b88 100644 --- a/include/ccmath/internal/math/runtime/simd/func/svml_ops.hpp +++ b/include/ccmath/internal/math/runtime/simd/func/svml_ops.hpp @@ -21,46 +21,74 @@ namespace ccm::intrin { CCM_ALWAYS_INLINE simd sin(simd const & a) - { return simd(_mm_sin_ps(a.get())); } + { + return simd(_mm_sin_ps(a.get())); + } CCM_ALWAYS_INLINE simd sin(simd const & a) - { return simd(_mm_sin_pd(a.get())); } + { + return simd(_mm_sin_pd(a.get())); + } CCM_ALWAYS_INLINE simd cos(simd const & a) - { return simd(_mm_cos_ps(a.get())); } + { + return simd(_mm_cos_ps(a.get())); + } CCM_ALWAYS_INLINE simd cos(simd const & a) - { return simd(_mm_cos_pd(a.get())); } + { + return simd(_mm_cos_pd(a.get())); + } CCM_ALWAYS_INLINE simd tan(simd const & a) - { return simd(_mm_tan_ps(a.get())); } + { + return simd(_mm_tan_ps(a.get())); + } CCM_ALWAYS_INLINE simd tan(simd const & a) - { return simd(_mm_tan_pd(a.get())); } + { + return simd(_mm_tan_pd(a.get())); + } CCM_ALWAYS_INLINE simd exp(simd const & a) - { return simd(_mm_exp_ps(a.get())); } + { + return simd(_mm_exp_ps(a.get())); + } CCM_ALWAYS_INLINE simd exp(simd const & a) - { return simd(_mm_exp_pd(a.get())); } + { + return simd(_mm_exp_pd(a.get())); + } CCM_ALWAYS_INLINE simd log(simd const & a) - { return simd(_mm_log_ps(a.get())); } + { + return simd(_mm_log_ps(a.get())); + } CCM_ALWAYS_INLINE simd log(simd const & a) - { return simd(_mm_log_pd(a.get())); } + { + return simd(_mm_log_pd(a.get())); + } CCM_ALWAYS_INLINE simd log10(simd const & a) - { return simd(_mm_log10_ps(a.get())); } + { + return simd(_mm_log10_ps(a.get())); + } CCM_ALWAYS_INLINE simd log10(simd const & a) - { return simd(_mm_log10_pd(a.get())); } + { + return simd(_mm_log10_pd(a.get())); + } CCM_ALWAYS_INLINE simd log2(simd const & a) - { return simd(_mm_log2_ps(a.get())); } + { + return simd(_mm_log2_ps(a.get())); + } CCM_ALWAYS_INLINE simd log2(simd const & a) - { return simd(_mm_log2_pd(a.get())); } + { + return simd(_mm_log2_pd(a.get())); + } } // namespace ccm::intrin #endif @@ -68,46 +96,74 @@ namespace ccm::intrin namespace ccm::intrin { CCM_ALWAYS_INLINE simd sin(simd const & a) - { return simd(_mm256_sin_ps(a.get())); } + { + return simd(_mm256_sin_ps(a.get())); + } CCM_ALWAYS_INLINE simd sin(simd const & a) - { return simd(_mm256_sin_pd(a.get())); } + { + return simd(_mm256_sin_pd(a.get())); + } CCM_ALWAYS_INLINE simd cos(simd const & a) - { return simd(_mm256_cos_ps(a.get())); } + { + return simd(_mm256_cos_ps(a.get())); + } CCM_ALWAYS_INLINE simd cos(simd const & a) - { return simd(_mm256_cos_pd(a.get())); } + { + return simd(_mm256_cos_pd(a.get())); + } CCM_ALWAYS_INLINE simd tan(simd const & a) - { return simd(_mm256_tan_ps(a.get())); } + { + return simd(_mm256_tan_ps(a.get())); + } CCM_ALWAYS_INLINE simd tan(simd const & a) - { return simd(_mm256_tan_pd(a.get())); } + { + return simd(_mm256_tan_pd(a.get())); + } CCM_ALWAYS_INLINE simd exp(simd const & a) - { return simd(_mm256_exp_ps(a.get())); } + { + return simd(_mm256_exp_ps(a.get())); + } CCM_ALWAYS_INLINE simd exp(simd const & a) - { return simd(_mm256_exp_pd(a.get())); } + { + return simd(_mm256_exp_pd(a.get())); + } CCM_ALWAYS_INLINE simd log(simd const & a) - { return simd(_mm256_log_ps(a.get())); } + { + return simd(_mm256_log_ps(a.get())); + } CCM_ALWAYS_INLINE simd log(simd const & a) - { return simd(_mm256_log_pd(a.get())); } + { + return simd(_mm256_log_pd(a.get())); + } CCM_ALWAYS_INLINE simd log10(simd const & a) - { return simd(_mm256_log10_ps(a.get())); } + { + return simd(_mm256_log10_ps(a.get())); + } CCM_ALWAYS_INLINE simd log10(simd const & a) - { return simd(_mm256_log10_pd(a.get())); } + { + return simd(_mm256_log10_pd(a.get())); + } CCM_ALWAYS_INLINE simd log2(simd const & a) - { return simd(_mm256_log2_ps(a.get())); } + { + return simd(_mm256_log2_ps(a.get())); + } CCM_ALWAYS_INLINE simd log2(simd const & a) - { return simd(_mm256_log2_pd(a.get())); } + { + return simd(_mm256_log2_pd(a.get())); + } } // namespace ccm::intrin #endif #endif diff --git a/include/ccmath/internal/math/runtime/simd/instructions/avx2.hpp b/include/ccmath/internal/math/runtime/simd/instructions/avx2.hpp index 76d59934..fa9b10a9 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/avx2.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/avx2.hpp @@ -23,13 +23,11 @@ namespace ccm::intrin { struct avx2 - { - }; + {}; } // namespace abi - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; @@ -37,25 +35,43 @@ namespace ccm::intrin using abi_type = abi::avx2; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE simd_mask(bool value) : m_value(_mm256_castsi256_ps(_mm256_set1_epi32(-static_cast(value)))) {} // NOLINT - static constexpr int size() { return 8; } - CCM_ALWAYS_INLINE constexpr simd_mask(__m256 const &value_in) : m_value(value_in) {} // NOLINT - [[nodiscard]] constexpr __m256 get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return { _mm256_or_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return { _mm256_and_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd_mask operator!() const { return { _mm256_andnot_ps(m_value, simd_mask(true).get()) }; } + static constexpr int size() + { + return 8; + } + CCM_ALWAYS_INLINE constexpr simd_mask(__m256 const & value_in) : m_value(value_in) {} // NOLINT + [[nodiscard]] constexpr __m256 get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return { _mm256_or_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return { _mm256_and_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return { _mm256_andnot_ps(m_value, simd_mask(true).get()) }; + } private: __m256 m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm256_testc_ps(a.get(), simd_mask(true).get()) != 0; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm256_testc_ps(a.get(), simd_mask(true).get()) != 0; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm256_testc_ps(simd_mask(false).get(), a.get()) == 0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm256_testc_ps(simd_mask(false).get(), a.get()) == 0; + } - template <> - struct simd + template <> struct simd { using value_type = float; @@ -63,115 +79,208 @@ namespace ccm::intrin using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - static constexpr int size() { return 8; } + static constexpr int size() + { + return 8; + } CCM_ALWAYS_INLINE simd(float value) : m_value(_mm256_set1_ps(value)) {} // NOLINT CCM_ALWAYS_INLINE simd(float a, float b, float c, float d, float e, float f, float g, float h) : m_value(_mm256_setr_ps(a, b, c, d, e, f, g, h)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } // NOLINT - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } // NOLINT + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(float const *ptr, Flags /*flags*/) : m_value(_mm256_loadu_ps(ptr)) + template CCM_ALWAYS_INLINE simd(float const * ptr, Flags /*flags*/) : m_value(_mm256_loadu_ps(ptr)) {} + CCM_ALWAYS_INLINE simd(float const * ptr, int stride) + : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride], ptr[4 * stride], ptr[5 * stride], ptr[6 * stride], ptr[7 * stride]) // NOLINT + {} + CCM_ALWAYS_INLINE constexpr simd(__m256 const & value_in) : m_value(value_in) {} // NOLINT + CCM_ALWAYS_INLINE simd operator*(simd const & other) const { + return { _mm256_mul_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd(float const *ptr, int stride) - : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride], ptr[4 * stride], ptr[5 * stride], ptr[6 * stride], ptr[7 * stride]) // NOLINT + CCM_ALWAYS_INLINE simd operator/(simd const & other) const { + return { _mm256_div_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm256_add_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm256_sub_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const + { + return { _mm256_sub_ps(_mm256_set1_ps(0.0F), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(float const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm256_loadu_ps(ptr); + } + CCM_ALWAYS_INLINE void copy_to(float * ptr, element_aligned_tag /*unused*/) const + { + _mm256_storeu_ps(ptr, m_value); + } + [[nodiscard]] constexpr __m256 get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE float convert() const + { + return _mm256_cvtss_f32(m_value); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return { _mm256_cmp_ps(m_value, other.m_value, _CMP_LT_OS) }; + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return { _mm256_cmp_ps(m_value, other.m_value, _CMP_EQ_OS) }; } - CCM_ALWAYS_INLINE constexpr simd(__m256 const &value_in) : m_value(value_in) {} // NOLINT - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm256_mul_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm256_div_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm256_add_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm256_sub_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const { return { _mm256_sub_ps(_mm256_set1_ps(0.0F), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(float const *ptr, element_aligned_tag /*unused*/) { m_value = _mm256_loadu_ps(ptr); } - CCM_ALWAYS_INLINE void copy_to(float *ptr, element_aligned_tag /*unused*/) const { _mm256_storeu_ps(ptr, m_value); } - [[nodiscard]] constexpr __m256 get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE float convert() const { return _mm256_cvtss_f32(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const { return { _mm256_cmp_ps(m_value, other.m_value, _CMP_LT_OS) }; } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const { return { _mm256_cmp_ps(m_value, other.m_value, _CMP_EQ_OS) }; } private: __m256 m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) - { return { _mm256_blendv_ps(c.get(), b.get(), a.get()) }; } + CCM_ALWAYS_INLINE simd choose(simd_mask const & a, simd const & b, simd const & c) + { + return { _mm256_blendv_ps(c.get(), b.get(), a.get()) }; + } - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::avx2; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE simd_mask(bool value) : m_value(_mm256_castsi256_pd(_mm256_set1_epi64x(-static_cast(value)))) {} // NOLINT - CCM_ALWAYS_INLINE static constexpr int size() { return 4; } - CCM_ALWAYS_INLINE constexpr simd_mask(__m256d const &value_in) : m_value(value_in) {} // NOLINT - [[nodiscard]] constexpr __m256d get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return { _mm256_or_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return { _mm256_and_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd_mask operator!() const { return { _mm256_andnot_pd(m_value, simd_mask(true).get()) }; } + CCM_ALWAYS_INLINE static constexpr int size() + { + return 4; + } + CCM_ALWAYS_INLINE constexpr simd_mask(__m256d const & value_in) : m_value(value_in) {} // NOLINT + [[nodiscard]] constexpr __m256d get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return { _mm256_or_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return { _mm256_and_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return { _mm256_andnot_pd(m_value, simd_mask(true).get()) }; + } private: __m256d m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm256_testc_pd(a.get(), simd_mask(true).get()) != 0; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm256_testc_pd(a.get(), simd_mask(true).get()) != 0; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm256_testc_pd(simd_mask(false).get(), a.get()) == 0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm256_testc_pd(simd_mask(false).get(), a.get()) == 0; + } - template <> - struct simd // NOLINT + template <> struct simd // NOLINT { public: - using value_type = double; - using abi_type = abi::avx2; - using mask_type = simd_mask; - using storage_type = simd_storage; - CCM_ALWAYS_INLINE simd() = default; - CCM_ALWAYS_INLINE simd(simd const &) = default; - CCM_ALWAYS_INLINE simd(simd &&) = default; - CCM_ALWAYS_INLINE simd &operator=(simd const &) = default; - CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; - CCM_ALWAYS_INLINE static constexpr int size() { return 4; } + using value_type = double; + using abi_type = abi::avx2; + using mask_type = simd_mask; + using storage_type = simd_storage; + CCM_ALWAYS_INLINE simd() = default; + CCM_ALWAYS_INLINE simd(simd const &) = default; + CCM_ALWAYS_INLINE simd(simd &&) = default; + CCM_ALWAYS_INLINE simd & operator=(simd const &) = default; + CCM_ALWAYS_INLINE simd & operator=(simd &&) = default; + CCM_ALWAYS_INLINE static constexpr int size() + { + return 4; + } CCM_ALWAYS_INLINE simd(double value) : m_value(_mm256_set1_pd(value)) {} // NOLINT CCM_ALWAYS_INLINE simd(double a, double b, double c, double d) : m_value(_mm256_setr_pd(a, b, c, d)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } // NOLINT - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } // NOLINT + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(double const *ptr, Flags flags) : m_value(_mm256_loadu_pd(ptr)) + template CCM_ALWAYS_INLINE simd(double const * ptr, Flags flags) : m_value(_mm256_loadu_pd(ptr)) {} + CCM_ALWAYS_INLINE simd(double const * ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} // NOLINT + CCM_ALWAYS_INLINE constexpr simd(__m256d const & value_in) : m_value(value_in) {} // NOLINT + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return { _mm256_mul_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const { + return { _mm256_div_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm256_add_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm256_sub_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const + { + return { _mm256_sub_pd(_mm256_set1_pd(0.0), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(double const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm256_loadu_pd(ptr); + } + CCM_ALWAYS_INLINE void copy_to(double * ptr, element_aligned_tag /*unused*/) const + { + _mm256_storeu_pd(ptr, m_value); + } + [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m256d get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE double convert() const + { + return _mm256_cvtsd_f64(m_value); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return { _mm256_cmp_pd(m_value, other.m_value, _CMP_LT_OS) }; + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return { _mm256_cmp_pd(m_value, other.m_value, _CMP_EQ_OS) }; } - CCM_ALWAYS_INLINE simd(double const *ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} // NOLINT - CCM_ALWAYS_INLINE constexpr simd(__m256d const &value_in) : m_value(value_in) {} // NOLINT - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm256_mul_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm256_div_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm256_add_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm256_sub_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const { return { _mm256_sub_pd(_mm256_set1_pd(0.0), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(double const *ptr, element_aligned_tag /*unused*/) { m_value = _mm256_loadu_pd(ptr); } - CCM_ALWAYS_INLINE void copy_to(double *ptr, element_aligned_tag /*unused*/) const { _mm256_storeu_pd(ptr, m_value); } - [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m256d get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE double convert() const { return _mm256_cvtsd_f64(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const { return { _mm256_cmp_pd(m_value, other.m_value, _CMP_LT_OS) }; } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const { return { _mm256_cmp_pd(m_value, other.m_value, _CMP_EQ_OS) }; } private: __m256d m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) - { return { _mm256_blendv_pd(c.get(), b.get(), a.get()) }; } + CCM_ALWAYS_INLINE simd + choose(simd_mask const & a, simd const & b, simd const & c) + { + return { _mm256_blendv_pd(c.get(), b.get(), a.get()) }; + } } // namespace ccm::intrin #endif // CCMATH_HAS_SIMD_AVX2 diff --git a/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp b/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp index 2df8b22b..e764ba8e 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/neon.hpp @@ -22,13 +22,11 @@ namespace ccm::intrin namespace abi { class neon - { - }; + {}; } // namespace abi - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; @@ -36,141 +34,257 @@ namespace ccm::intrin using abi_type = abi::neon; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE simd_mask(bool value) : m_value(vreinterpretq_u32_s32(vdupq_n_s32(-int(value)))) {} - static constexpr int size() { return 4; } - CCM_ALWAYS_INLINE constexpr simd_mask(uint32x4_t const &value_in) : m_value(value_in) {} - [[nodiscard]] CCM_ALWAYS_INLINE constexpr uint32x4_t get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(vorrq_u32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(vandq_u32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(vmvnq_u32(m_value)); } + static constexpr int size() + { + return 4; + } + CCM_ALWAYS_INLINE constexpr simd_mask(uint32x4_t const & value_in) : m_value(value_in) {} + [[nodiscard]] CCM_ALWAYS_INLINE constexpr uint32x4_t get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(vorrq_u32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(vandq_u32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(vmvnq_u32(m_value)); + } private: uint32x4_t m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return vminvq_u32(a.get()) == static_cast(-1); } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return vminvq_u32(a.get()) == static_cast(-1); + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return vmaxvq_u32(a.get()) == static_cast(-1); } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return vmaxvq_u32(a.get()) == static_cast(-1); + } - template <> - struct simd + template <> struct simd { using value_type = float; using abi_type = abi::neon; using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - static constexpr int size() { return 4; } + static constexpr int size() + { + return 4; + } CCM_ALWAYS_INLINE simd(float value) : m_value(vdupq_n_f32(value)) {} CCM_ALWAYS_INLINE simd(float a, float b, float c, float d) { std::array values = { a, b, c, d }; m_value = vld1q_f32(values.data()); } - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(float const *ptr, Flags flags) - { copy_from(ptr, flags); } - CCM_ALWAYS_INLINE simd(float const *ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} - CCM_ALWAYS_INLINE constexpr simd(float32x4_t const &value_in) : m_value(value_in) {} - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return simd(vmulq_f32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return simd(vdivq_f32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return simd(vaddq_f32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return simd(vsubq_f32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator-() const { return simd(vnegq_f32(m_value)); } - CCM_ALWAYS_INLINE void copy_from(float const *ptr, element_aligned_tag) { m_value = vld1q_f32(ptr); } - CCM_ALWAYS_INLINE void copy_to(float *ptr, element_aligned_tag) const { vst1q_f32(ptr, m_value); } - [[nodiscard]] CCM_ALWAYS_INLINE constexpr float32x4_t get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE float convert() const { return vgetq_lane_f32(m_value, 0); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(vcltq_f32(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(vceqq_f32(m_value, other.m_value)); } + template CCM_ALWAYS_INLINE simd(float const * ptr, Flags flags) + { + copy_from(ptr, flags); + } + CCM_ALWAYS_INLINE simd(float const * ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} + CCM_ALWAYS_INLINE constexpr simd(float32x4_t const & value_in) : m_value(value_in) {} + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return simd(vmulq_f32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return simd(vdivq_f32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return simd(vaddq_f32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return simd(vsubq_f32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator-() const + { + return simd(vnegq_f32(m_value)); + } + CCM_ALWAYS_INLINE void copy_from(float const * ptr, element_aligned_tag) + { + m_value = vld1q_f32(ptr); + } + CCM_ALWAYS_INLINE void copy_to(float * ptr, element_aligned_tag) const + { + vst1q_f32(ptr, m_value); + } + [[nodiscard]] CCM_ALWAYS_INLINE constexpr float32x4_t get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE float convert() const + { + return vgetq_lane_f32(m_value, 0); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(vcltq_f32(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(vceqq_f32(m_value, other.m_value)); + } private: float32x4_t m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) - { return simd(vreinterpretq_f32_u32(vbslq_u32(a.get(), vreinterpretq_u32_f32(b.get()), vreinterpretq_u32_f32(c.get())))); } + CCM_ALWAYS_INLINE simd choose(simd_mask const & a, simd const & b, simd const & c) + { + return simd(vreinterpretq_f32_u32(vbslq_u32(a.get(), vreinterpretq_u32_f32(b.get()), vreinterpretq_u32_f32(c.get())))); + } - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::neon; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE simd_mask(bool value) : m_value(vreinterpretq_u64_s64(vdupq_n_s64(-std::int64_t(value)))) {} - static constexpr int size() { return 4; } - CCM_ALWAYS_INLINE constexpr simd_mask(uint64x2_t const &value_in) : m_value(value_in) {} - [[nodiscard]] CCM_ALWAYS_INLINE constexpr uint64x2_t get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(vorrq_u64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(vandq_u64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(vreinterpretq_u64_u32(vmvnq_u32(vreinterpretq_u32_u64(m_value)))); } + static constexpr int size() + { + return 4; + } + CCM_ALWAYS_INLINE constexpr simd_mask(uint64x2_t const & value_in) : m_value(value_in) {} + [[nodiscard]] CCM_ALWAYS_INLINE constexpr uint64x2_t get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(vorrq_u64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(vandq_u64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(vreinterpretq_u64_u32(vmvnq_u32(vreinterpretq_u32_u64(m_value)))); + } private: uint64x2_t m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return all_of(simd_mask(vreinterpretq_u32_u64(a.get()))); } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return all_of(simd_mask(vreinterpretq_u32_u64(a.get()))); + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return any_of(simd_mask(vreinterpretq_u32_u64(a.get()))); } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return any_of(simd_mask(vreinterpretq_u32_u64(a.get()))); + } - template <> - struct simd + template <> struct simd { - using value_type = double; - using abi_type = abi::neon; - using mask_type = simd_mask; - using storage_type = simd_storage; - CCM_ALWAYS_INLINE simd() = default; - CCM_ALWAYS_INLINE simd(simd const &) = default; - CCM_ALWAYS_INLINE simd(simd &&) = default; - CCM_ALWAYS_INLINE simd &operator=(simd const &) = default; - CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; - static constexpr int size() { return 2; } + using value_type = double; + using abi_type = abi::neon; + using mask_type = simd_mask; + using storage_type = simd_storage; + CCM_ALWAYS_INLINE simd() = default; + CCM_ALWAYS_INLINE simd(simd const &) = default; + CCM_ALWAYS_INLINE simd(simd &&) = default; + CCM_ALWAYS_INLINE simd & operator=(simd const &) = default; + CCM_ALWAYS_INLINE simd & operator=(simd &&) = default; + static constexpr int size() + { + return 2; + } CCM_ALWAYS_INLINE simd(double value) : m_value(vdupq_n_f64(value)) {} CCM_ALWAYS_INLINE simd(double a, double b) : m_value(vsetq_lane_f64(b, vsetq_lane_f64(a, vdupq_n_f64(0), 0), 1)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(double const *ptr, Flags flags) - { copy_from(ptr, flags); } - CCM_ALWAYS_INLINE simd(double const *ptr, int stride) : simd(ptr[0], ptr[stride]) {} - CCM_ALWAYS_INLINE constexpr simd(float64x2_t const &value_in) : m_value(value_in) {} - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return simd(vmulq_f64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return simd(vdivq_f64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return simd(vaddq_f64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return simd(vsubq_f64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd operator-() const { return simd(vnegq_f64(m_value)); } - CCM_ALWAYS_INLINE void copy_from(double const *ptr, element_aligned_tag) { m_value = vld1q_f64(ptr); } - CCM_ALWAYS_INLINE void copy_to(double *ptr, element_aligned_tag) const { vst1q_f64(ptr, m_value); } - [[nodiscard]] CCM_ALWAYS_INLINE constexpr float64x2_t get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE double convert() const { return vgetq_lane_f64(m_value, 0); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(vcltq_f64(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(vceqq_f64(m_value, other.m_value)); } + template CCM_ALWAYS_INLINE simd(double const * ptr, Flags flags) + { + copy_from(ptr, flags); + } + CCM_ALWAYS_INLINE simd(double const * ptr, int stride) : simd(ptr[0], ptr[stride]) {} + CCM_ALWAYS_INLINE constexpr simd(float64x2_t const & value_in) : m_value(value_in) {} + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return simd(vmulq_f64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return simd(vdivq_f64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return simd(vaddq_f64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return simd(vsubq_f64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd operator-() const + { + return simd(vnegq_f64(m_value)); + } + CCM_ALWAYS_INLINE void copy_from(double const * ptr, element_aligned_tag) + { + m_value = vld1q_f64(ptr); + } + CCM_ALWAYS_INLINE void copy_to(double * ptr, element_aligned_tag) const + { + vst1q_f64(ptr, m_value); + } + [[nodiscard]] CCM_ALWAYS_INLINE constexpr float64x2_t get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE double convert() const + { + return vgetq_lane_f64(m_value, 0); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(vcltq_f64(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(vceqq_f64(m_value, other.m_value)); + } private: float64x2_t m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) - { return simd(vreinterpretq_f64_u64(vbslq_u64(a.get(), vreinterpretq_u64_f64(b.get()), vreinterpretq_u64_f64(c.get())))); } + CCM_ALWAYS_INLINE simd + choose(simd_mask const & a, simd const & b, simd const & c) + { + return simd(vreinterpretq_f64_u64(vbslq_u64(a.get(), vreinterpretq_u64_f64(b.get()), vreinterpretq_u64_f64(c.get())))); + } } // namespace ccm::intrin #endif // CCMATH_HAS_SIMD_NEON #endif // CCMATH_HAS_SIMD diff --git a/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp b/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp index 4ca531a2..9dca3c5b 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/scalar.hpp @@ -17,30 +17,42 @@ namespace ccm::intrin namespace abi { struct scalar - { - }; + {}; } // namespace abi - template - struct simd_mask + template struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::scalar; CCM_ALWAYS_INLINE simd_mask() = default; - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE static constexpr int size() { return 1; } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE static constexpr int size() + { + return 1; + } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE explicit simd_mask(bool value) : m_value(value) {} - [[nodiscard]] CCM_GPU_HOST_DEVICE constexpr bool get() const { return m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator||(simd_mask const &other) const { return m_value || other.m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator&&(simd_mask const &other) const { return m_value && other.m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator!() const { return !m_value; } + [[nodiscard]] CCM_GPU_HOST_DEVICE constexpr bool get() const + { + return m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator||(simd_mask const & other) const + { + return m_value || other.m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator&&(simd_mask const & other) const + { + return m_value && other.m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator!() const + { + return !m_value; + } private: bool m_value; }; - template - struct simd_storage + template struct simd_storage { private: using Abi = abi::scalar; @@ -49,72 +61,129 @@ namespace ccm::intrin using value_type = T; using simd_type = simd; CCM_ALWAYS_INLINE simd_storage() = default; - static constexpr int size() { return simd::size(); } - CCM_ALWAYS_INLINE explicit CCM_GPU_HOST_DEVICE simd_storage(simd const &value) CCM_GPU_HOST_DEVICE - { value.copy_to(&m_value, element_aligned_tag()); } + static constexpr int size() + { + return simd::size(); + } + CCM_ALWAYS_INLINE explicit CCM_GPU_HOST_DEVICE simd_storage(simd const & value) CCM_GPU_HOST_DEVICE + { + value.copy_to(&m_value, element_aligned_tag()); + } CCM_ALWAYS_INLINE explicit CCM_GPU_HOST_DEVICE simd_storage(T value) : simd_storage(simd(value)) {} - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_storage &operator=(simd const &value) + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_storage & operator=(simd const & value) { value.copy_to(&m_value, element_aligned_tag()); return *this; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T const *data() const { return &m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T *data() { return &m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T const &operator[](int /*unused*/) const { return m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T &operator[](int /*unused*/) { return m_value; } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T const * data() const + { + return &m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T * data() + { + return &m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T const & operator[](int /*unused*/) const + { + return m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T & operator[](int /*unused*/) + { + return m_value; + } private: T m_value; }; - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask const &a) - { return a.get(); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask const & a) + { + return a.get(); + } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask const &a) - { return a.get(); } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask const & a) + { + return a.get(); + } template // NOLINTNEXTLINE struct simd { - using value_type = T; - using abi_type = abi::scalar; - using mask_type = simd_mask; - using storage_type = simd_storage; - CCM_ALWAYS_INLINE simd() = default; - CCM_ALWAYS_INLINE simd(simd const &) = default; - CCM_ALWAYS_INLINE simd(simd &&) = default; - CCM_ALWAYS_INLINE simd &operator=(simd const &) = default; - CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE static constexpr int size() { return 1; } + using value_type = T; + using abi_type = abi::scalar; + using mask_type = simd_mask; + using storage_type = simd_storage; + CCM_ALWAYS_INLINE simd() = default; + CCM_ALWAYS_INLINE simd(simd const &) = default; + CCM_ALWAYS_INLINE simd(simd &&) = default; + CCM_ALWAYS_INLINE simd & operator=(simd const &) = default; + CCM_ALWAYS_INLINE simd & operator=(simd &&) = default; + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE static constexpr int size() + { + return 1; + } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T value) : m_value(value) {} // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(storage_type const &value) // NOLINT(google-explicit-constructor) - { copy_from(value.data(), element_aligned_tag()); } // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(storage_type const & value) // NOLINT(google-explicit-constructor) + { + copy_from(value.data(), element_aligned_tag()); + } // NOLINT(google-explicit-constructor) + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T const *ptr, Flags flags) - { copy_from(ptr, flags); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T const *ptr, int /*stride*/) : m_value(ptr[0]) {} // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(simd const &other) const { return simd(m_value * other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(simd const &other) const { return simd(m_value / other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(simd const &other) const { return simd(m_value + other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(simd const &other) const { return simd(m_value - other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const { return simd(-m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE void copy_from(T const *ptr, element_aligned_tag /*unused*/) { m_value = *ptr; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE void copy_to(T *ptr, element_aligned_tag /*unused*/) const { *ptr = m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE constexpr T get() const { return m_value; } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T convert() const { return m_value; } + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T const * ptr, Flags flags) + { + copy_from(ptr, flags); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd(T const * ptr, int /*stride*/) + : m_value(ptr[0]) {} // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator*(simd const & other) const + { + return simd(m_value * other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator/(simd const & other) const + { + return simd(m_value / other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator+(simd const & other) const + { + return simd(m_value + other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-(simd const & other) const + { + return simd(m_value - other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const + { + return simd(-m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE void copy_from(T const * ptr, element_aligned_tag /*unused*/) + { + m_value = *ptr; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE void copy_to(T * ptr, element_aligned_tag /*unused*/) const + { + *ptr = m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE constexpr T get() const + { + return m_value; + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE T convert() const + { + return m_value; + } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator<(simd const &other) const - { return simd_mask(m_value < other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator==(simd const &other) const - { return simd_mask(m_value == other.m_value); } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator<(simd const & other) const + { + return simd_mask(m_value < other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd_mask operator==(simd const & other) const + { + return simd_mask(m_value == other.m_value); + } private: T m_value; @@ -122,6 +191,8 @@ namespace ccm::intrin template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd - choose(simd_mask const &a, simd const &b, simd const &c) - { return simd(choose(a.get(), b.get(), c.get())); } + choose(simd_mask const & a, simd const & b, simd const & c) + { + return simd(choose(a.get(), b.get(), c.get())); + } } // namespace ccm::intrin diff --git a/include/ccmath/internal/math/runtime/simd/instructions/sse2.hpp b/include/ccmath/internal/math/runtime/simd/instructions/sse2.hpp index ecc67876..4063b063 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/sse2.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/sse2.hpp @@ -26,156 +26,257 @@ namespace ccm::intrin namespace abi { struct sse2 - { - }; + {}; } // namespace abi - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::sse2; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(_mm_castsi128_ps(_mm_set1_epi32(-static_cast(value)))) {} - static constexpr int size() { return 4; } - constexpr explicit simd_mask(__m128 const &value_in) : m_value(value_in) {} - [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(_mm_or_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(_mm_and_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(_mm_andnot_ps(m_value, simd_mask(true).get())); } + static constexpr int size() + { + return 4; + } + constexpr explicit simd_mask(__m128 const & value_in) : m_value(value_in) {} + [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(_mm_or_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(_mm_and_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(_mm_andnot_ps(m_value, simd_mask(true).get())); + } private: __m128 m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm_movemask_ps(a.get()) == 0xF; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm_movemask_ps(a.get()) == 0xF; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm_movemask_ps(a.get()) != 0x0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm_movemask_ps(a.get()) != 0x0; + } - template <> - struct simd + template <> struct simd { using value_type = float; using abi_type = abi::sse2; using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - static constexpr int size() { return 4; } + static constexpr int size() + { + return 4; + } CCM_ALWAYS_INLINE simd(float value) : m_value(_mm_set1_ps(value)) {} // NOLINT(google-explicit-constructor) CCM_ALWAYS_INLINE simd(float a, float b, float c, float d) : m_value(_mm_setr_ps(a, b, c, d)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } // NOLINT - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } // NOLINT + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(float const *ptr, Flags /*flags*/) : m_value(_mm_loadu_ps(ptr)) - { - } - CCM_ALWAYS_INLINE simd(float const *ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} // NOLINT - CCM_ALWAYS_INLINE constexpr simd(__m128 const &value_in) : m_value(value_in) {} // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm_mul_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm_div_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm_add_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm_sub_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-() const { return { _mm_sub_ps(_mm_set1_ps(0.0F), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(float const *ptr, element_aligned_tag /*unused*/) { m_value = _mm_loadu_ps(ptr); } - CCM_ALWAYS_INLINE void copy_to(float *ptr, element_aligned_tag /*unused*/) const { _mm_storeu_ps(ptr, m_value); } - [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE float convert() const { return _mm_cvtss_f32(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(_mm_cmplt_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(_mm_cmpeq_ps(m_value, other.m_value)); } + template CCM_ALWAYS_INLINE simd(float const * ptr, Flags /*flags*/) : m_value(_mm_loadu_ps(ptr)) {} + CCM_ALWAYS_INLINE simd(float const * ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} // NOLINT + CCM_ALWAYS_INLINE constexpr simd(__m128 const & value_in) : m_value(value_in) {} // NOLINT(google-explicit-constructor) + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return { _mm_mul_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return { _mm_div_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm_add_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm_sub_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-() const + { + return { _mm_sub_ps(_mm_set1_ps(0.0F), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(float const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm_loadu_ps(ptr); + } + CCM_ALWAYS_INLINE void copy_to(float * ptr, element_aligned_tag /*unused*/) const + { + _mm_storeu_ps(ptr, m_value); + } + [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE float convert() const + { + return _mm_cvtss_f32(m_value); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(_mm_cmplt_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(_mm_cmpeq_ps(m_value, other.m_value)); + } private: __m128 m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) + CCM_ALWAYS_INLINE simd choose(simd_mask const & a, simd const & b, simd const & c) { // NOLINTNEXTLINE(modernize-return-braced-init-list) return simd(_mm_add_ps(_mm_and_ps(a.get(), b.get()), _mm_andnot_ps(a.get(), c.get()))); } - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::sse2; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(_mm_castsi128_pd(_mm_set1_epi64x(-static_cast(value)))) {} - static constexpr int size() { return 4; } - constexpr explicit simd_mask(__m128d const &value_in) : m_value(value_in) {} - [[nodiscard]] constexpr __m128d get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(_mm_or_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(_mm_and_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(_mm_andnot_pd(m_value, simd_mask(true).get())); } + static constexpr int size() + { + return 4; + } + constexpr explicit simd_mask(__m128d const & value_in) : m_value(value_in) {} + [[nodiscard]] constexpr __m128d get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(_mm_or_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(_mm_and_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(_mm_andnot_pd(m_value, simd_mask(true).get())); + } private: __m128d m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm_movemask_pd(a.get()) == 0x3; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm_movemask_pd(a.get()) == 0x3; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm_movemask_pd(a.get()) != 0x0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm_movemask_pd(a.get()) != 0x0; + } - template <> - struct simd // NOLINT + template <> struct simd // NOLINT { - using value_type = double; - using abi_type = abi::sse2; - using mask_type = simd_mask; - using storage_type = simd_storage; - CCM_ALWAYS_INLINE simd() = default; - CCM_ALWAYS_INLINE simd(simd const &) = default; - CCM_ALWAYS_INLINE simd(simd &&) = default; - CCM_ALWAYS_INLINE simd &operator=(simd const &) = default; - CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; - static constexpr int size() { return 2; } + using value_type = double; + using abi_type = abi::sse2; + using mask_type = simd_mask; + using storage_type = simd_storage; + CCM_ALWAYS_INLINE simd() = default; + CCM_ALWAYS_INLINE simd(simd const &) = default; + CCM_ALWAYS_INLINE simd(simd &&) = default; + CCM_ALWAYS_INLINE simd & operator=(simd const &) = default; + CCM_ALWAYS_INLINE simd & operator=(simd &&) = default; + static constexpr int size() + { + return 2; + } CCM_ALWAYS_INLINE simd(double value) : m_value(_mm_set1_pd(value)) {} // NOLINT(google-explicit-constructor) CCM_ALWAYS_INLINE simd(double a, double b) : m_value(_mm_setr_pd(a, b)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) - { copy_from(value.data(), element_aligned_tag()); } // NOLINT(google-explicit-constructor, cppcoreguidelines-pro-type-member-init) - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } // NOLINT(google-explicit-constructor, cppcoreguidelines-pro-type-member-init) + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(double const *ptr, Flags /*flags*/) : m_value(_mm_loadu_pd(ptr)) + template CCM_ALWAYS_INLINE simd(double const * ptr, Flags /*flags*/) : m_value(_mm_loadu_pd(ptr)) {} + CCM_ALWAYS_INLINE simd(double const * ptr, int stride) : simd(ptr[0], ptr[stride]) {} // NOLINT + CCM_ALWAYS_INLINE constexpr simd(__m128d const & value_in) : m_value(value_in) {} // NOLINT(google-explicit-constructor) + CCM_ALWAYS_INLINE simd operator*(simd const & other) const { + return { _mm_mul_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return { _mm_div_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm_add_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm_sub_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-() const + { + return { _mm_sub_pd(_mm_set1_pd(0.0), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(double const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm_loadu_pd(ptr); + } + CCM_ALWAYS_INLINE void copy_to(double * ptr, element_aligned_tag /*unused*/) const + { + _mm_storeu_pd(ptr, m_value); + } + [[nodiscard]] constexpr __m128d get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE double convert() const + { + return _mm_cvtsd_f64(m_value); } - CCM_ALWAYS_INLINE simd(double const *ptr, int stride) : simd(ptr[0], ptr[stride]) {} // NOLINT - CCM_ALWAYS_INLINE constexpr simd(__m128d const &value_in) : m_value(value_in) {} // NOLINT(google-explicit-constructor) - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm_mul_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm_div_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm_add_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm_sub_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-() const { return { _mm_sub_pd(_mm_set1_pd(0.0), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(double const *ptr, element_aligned_tag /*unused*/) { m_value = _mm_loadu_pd(ptr); } - CCM_ALWAYS_INLINE void copy_to(double *ptr, element_aligned_tag /*unused*/) const { _mm_storeu_pd(ptr, m_value); } - [[nodiscard]] constexpr __m128d get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE double convert() const { return _mm_cvtsd_f64(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(_mm_cmplt_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(_mm_cmpeq_pd(m_value, other.m_value)); } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(_mm_cmplt_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(_mm_cmpeq_pd(m_value, other.m_value)); + } private: __m128d m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) + CCM_ALWAYS_INLINE simd + choose(simd_mask const & a, simd const & b, simd const & c) { // NOLINTNEXTLINE(modernize-return-braced-init-list) return simd(_mm_add_pd(_mm_and_pd(a.get(), b.get()), _mm_andnot_pd(a.get(), c.get()))); diff --git a/include/ccmath/internal/math/runtime/simd/instructions/sse4.hpp b/include/ccmath/internal/math/runtime/simd/instructions/sse4.hpp index 8fd7123f..6a0c635f 100644 --- a/include/ccmath/internal/math/runtime/simd/instructions/sse4.hpp +++ b/include/ccmath/internal/math/runtime/simd/instructions/sse4.hpp @@ -27,155 +27,257 @@ namespace ccm::intrin namespace abi { struct sse4 - { - }; + {}; } // namespace abi - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::sse4; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(_mm_castsi128_ps(_mm_set1_epi32(-static_cast(value)))) {} - static constexpr int size() { return 4; } - constexpr explicit simd_mask(__m128 const &value_in) : m_value(value_in) {} - [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(_mm_or_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(_mm_and_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(_mm_andnot_ps(m_value, simd_mask(true).get())); } + static constexpr int size() + { + return 4; + } + constexpr explicit simd_mask(__m128 const & value_in) : m_value(value_in) {} + [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(_mm_or_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(_mm_and_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(_mm_andnot_ps(m_value, simd_mask(true).get())); + } private: __m128 m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm_movemask_ps(a.get()) == 0xF; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm_movemask_ps(a.get()) == 0xF; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm_movemask_ps(a.get()) != 0x0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm_movemask_ps(a.get()) != 0x0; + } - template <> - struct simd + template <> struct simd { using value_type = float; using abi_type = abi::sse4; using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - static constexpr int size() { return 4; } + static constexpr int size() + { + return 4; + } CCM_ALWAYS_INLINE simd(float value) : m_value(_mm_set1_ps(value)) {} CCM_ALWAYS_INLINE simd(float a, float b, float c, float d) : m_value(_mm_setr_ps(a, b, c, d)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(float const *ptr, Flags /*flags*/) : m_value(_mm_loadu_ps(ptr)) - { - } - CCM_ALWAYS_INLINE simd(float const *ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} - CCM_ALWAYS_INLINE constexpr simd(__m128 const &value_in) : m_value(value_in) {} - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm_mul_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm_div_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm_add_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm_sub_ps(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-() const { return { _mm_sub_ps(_mm_set1_ps(0.0F), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(float const *ptr, element_aligned_tag /*unused*/) { m_value = _mm_loadu_ps(ptr); } - CCM_ALWAYS_INLINE void copy_to(float *ptr, element_aligned_tag /*unused*/) const { _mm_storeu_ps(ptr, m_value); } - [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE float convert() const { return _mm_cvtss_f32(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(_mm_cmplt_ps(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(_mm_cmpeq_ps(m_value, other.m_value)); } + template CCM_ALWAYS_INLINE simd(float const * ptr, Flags /*flags*/) : m_value(_mm_loadu_ps(ptr)) {} + CCM_ALWAYS_INLINE simd(float const * ptr, int stride) : simd(ptr[0], ptr[stride], ptr[2 * stride], ptr[3 * stride]) {} + CCM_ALWAYS_INLINE constexpr simd(__m128 const & value_in) : m_value(value_in) {} + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return { _mm_mul_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return { _mm_div_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm_add_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm_sub_ps(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-() const + { + return { _mm_sub_ps(_mm_set1_ps(0.0F), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(float const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm_loadu_ps(ptr); + } + CCM_ALWAYS_INLINE void copy_to(float * ptr, element_aligned_tag /*unused*/) const + { + _mm_storeu_ps(ptr, m_value); + } + [[nodiscard]] CCM_ALWAYS_INLINE constexpr __m128 get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE float convert() const + { + return _mm_cvtss_f32(m_value); + } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(_mm_cmplt_ps(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(_mm_cmpeq_ps(m_value, other.m_value)); + } private: __m128 m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) + CCM_ALWAYS_INLINE simd choose(simd_mask const & a, simd const & b, simd const & c) { // NOLINTNEXTLINE(modernize-return-braced-init-list) return simd(_mm_add_ps(_mm_and_ps(a.get(), b.get()), _mm_andnot_ps(a.get(), c.get()))); } - template <> - struct simd_mask + template <> struct simd_mask { using value_type = bool; using simd_type = simd; using abi_type = abi::sse4; CCM_ALWAYS_INLINE simd_mask() = default; CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(_mm_castsi128_pd(_mm_set1_epi64x(-static_cast(value)))) {} - static constexpr int size() { return 4; } - constexpr explicit simd_mask(__m128d const &value_in) : m_value(value_in) {} - [[nodiscard]] constexpr __m128d get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(_mm_or_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(_mm_and_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(_mm_andnot_pd(m_value, simd_mask(true).get())); } + static constexpr int size() + { + return 4; + } + constexpr explicit simd_mask(__m128d const & value_in) : m_value(value_in) {} + [[nodiscard]] constexpr __m128d get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(_mm_or_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(_mm_and_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(_mm_andnot_pd(m_value, simd_mask(true).get())); + } private: __m128d m_value; }; - CCM_ALWAYS_INLINE bool all_of(simd_mask const &a) - { return _mm_movemask_pd(a.get()) == 0x3; } + CCM_ALWAYS_INLINE bool all_of(simd_mask const & a) + { + return _mm_movemask_pd(a.get()) == 0x3; + } - CCM_ALWAYS_INLINE bool any_of(simd_mask const &a) - { return _mm_movemask_pd(a.get()) != 0x0; } + CCM_ALWAYS_INLINE bool any_of(simd_mask const & a) + { + return _mm_movemask_pd(a.get()) != 0x0; + } - template <> - struct simd + template <> struct simd { - using value_type = double; - using abi_type = abi::sse4; - using mask_type = simd_mask; - using storage_type = simd_storage; - CCM_ALWAYS_INLINE simd() = default; - CCM_ALWAYS_INLINE simd(simd const &) = default; - CCM_ALWAYS_INLINE simd(simd &&) = default; - CCM_ALWAYS_INLINE simd &operator=(simd const &) = default; - CCM_ALWAYS_INLINE simd &operator=(simd &&) = default; - static constexpr int size() { return 2; } + using value_type = double; + using abi_type = abi::sse4; + using mask_type = simd_mask; + using storage_type = simd_storage; + CCM_ALWAYS_INLINE simd() = default; + CCM_ALWAYS_INLINE simd(simd const &) = default; + CCM_ALWAYS_INLINE simd(simd &&) = default; + CCM_ALWAYS_INLINE simd & operator=(simd const &) = default; + CCM_ALWAYS_INLINE simd & operator=(simd &&) = default; + static constexpr int size() + { + return 2; + } CCM_ALWAYS_INLINE simd(double value) : m_value(_mm_set1_pd(value)) {} CCM_ALWAYS_INLINE simd(double a, double b) : m_value(_mm_setr_pd(a, b)) {} - CCM_ALWAYS_INLINE simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + CCM_ALWAYS_INLINE simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(double const *ptr, Flags /*flags*/) : m_value(_mm_loadu_pd(ptr)) + template CCM_ALWAYS_INLINE simd(double const * ptr, Flags /*flags*/) : m_value(_mm_loadu_pd(ptr)) {} + CCM_ALWAYS_INLINE simd(double const * ptr, int stride) : simd(ptr[0], ptr[stride]) {} + CCM_ALWAYS_INLINE constexpr simd(__m128d const & value_in) : m_value(value_in) {} + CCM_ALWAYS_INLINE simd operator*(simd const & other) const { + return { _mm_mul_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return { _mm_div_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return { _mm_add_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return { _mm_sub_pd(m_value, other.m_value) }; + } + CCM_ALWAYS_INLINE simd operator-() const + { + return { _mm_sub_pd(_mm_set1_pd(0.0), m_value) }; + } + CCM_ALWAYS_INLINE void copy_from(double const * ptr, element_aligned_tag /*unused*/) + { + m_value = _mm_loadu_pd(ptr); + } + CCM_ALWAYS_INLINE void copy_to(double * ptr, element_aligned_tag /*unused*/) const + { + _mm_storeu_pd(ptr, m_value); + } + [[nodiscard]] constexpr __m128d get() const + { + return m_value; + } + [[nodiscard]] CCM_ALWAYS_INLINE double convert() const + { + return _mm_cvtsd_f64(m_value); } - CCM_ALWAYS_INLINE simd(double const *ptr, int stride) : simd(ptr[0], ptr[stride]) {} - CCM_ALWAYS_INLINE constexpr simd(__m128d const &value_in) : m_value(value_in) {} - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return { _mm_mul_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return { _mm_div_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return { _mm_add_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return { _mm_sub_pd(m_value, other.m_value) }; } - CCM_ALWAYS_INLINE simd operator-() const { return { _mm_sub_pd(_mm_set1_pd(0.0), m_value) }; } - CCM_ALWAYS_INLINE void copy_from(double const *ptr, element_aligned_tag /*unused*/) { m_value = _mm_loadu_pd(ptr); } - CCM_ALWAYS_INLINE void copy_to(double *ptr, element_aligned_tag /*unused*/) const { _mm_storeu_pd(ptr, m_value); } - [[nodiscard]] constexpr __m128d get() const { return m_value; } - [[nodiscard]] CCM_ALWAYS_INLINE double convert() const { return _mm_cvtsd_f64(m_value); } - CCM_ALWAYS_INLINE simd_mask operator<(simd const &other) const - { return simd_mask(_mm_cmplt_pd(m_value, other.m_value)); } - CCM_ALWAYS_INLINE simd_mask operator==(simd const &other) const - { return simd_mask(_mm_cmpeq_pd(m_value, other.m_value)); } + CCM_ALWAYS_INLINE simd_mask operator<(simd const & other) const + { + return simd_mask(_mm_cmplt_pd(m_value, other.m_value)); + } + CCM_ALWAYS_INLINE simd_mask operator==(simd const & other) const + { + return simd_mask(_mm_cmpeq_pd(m_value, other.m_value)); + } private: __m128d m_value; }; - CCM_ALWAYS_INLINE simd choose(simd_mask const &a, simd const &b, simd const &c) + CCM_ALWAYS_INLINE simd + choose(simd_mask const & a, simd const & b, simd const & c) { // NOLINTNEXTLINE(modernize-return-braced-init-list) return simd(_mm_add_pd(_mm_and_pd(a.get(), b.get()), _mm_andnot_pd(a.get(), c.get()))); diff --git a/include/ccmath/internal/math/runtime/simd/pack.hpp b/include/ccmath/internal/math/runtime/simd/pack.hpp index c49c7e72..1727bec1 100644 --- a/include/ccmath/internal/math/runtime/simd/pack.hpp +++ b/include/ccmath/internal/math/runtime/simd/pack.hpp @@ -19,44 +19,59 @@ namespace ccm::intrin namespace abi { - template - struct pack; + template struct pack; } // namespace abi - template - struct simd_mask> + template struct simd_mask> { using value_type = bool; using simd_type = simd>; using abi_type = abi::pack; CCM_ALWAYS_INLINE simd_mask() = default; - [[nodiscard]] static constexpr int size() { return N; } + [[nodiscard]] static constexpr int size() + { + return N; + } CCM_ALWAYS_INLINE explicit simd_mask(bool value) { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { m_value[i] = value; } + { + m_value[i] = value; + } + } + constexpr bool operator[](int i) const + { + return m_value[i]; + } + CCM_ALWAYS_INLINE int & operator[](int i) + { + return m_value[i]; } - constexpr bool operator[](int i) const { return m_value[i]; } - CCM_ALWAYS_INLINE int & operator[](int i) { return m_value[i]; } CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = m_value[i] || other.m_value[i]; } + { + result.m_value[i] = m_value[i] || other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = m_value[i] && other.m_value[i]; } + { + result.m_value[i] = m_value[i] && other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd_mask operator!() const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = !m_value[i]; } + { + result.m_value[i] = !m_value[i]; + } return result; } @@ -64,40 +79,56 @@ namespace ccm::intrin std::array(N)> m_value; }; - template - struct simd_mask> + template struct simd_mask> { using value_type = bool; using simd_type = simd>; using abi_type = abi::pack; CCM_ALWAYS_INLINE simd_mask() = default; - [[nodiscard]] static constexpr int size() { return N; } + [[nodiscard]] static constexpr int size() + { + return N; + } CCM_ALWAYS_INLINE explicit simd_mask(bool value) { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { m_value[i] = value; } + { + m_value[i] = value; + } + } + constexpr bool operator[](int i) const + { + return m_value[i]; + } + CCM_ALWAYS_INLINE std::int64_t & operator[](int i) + { + return m_value[i]; } - constexpr bool operator[](int i) const { return m_value[i]; } - CCM_ALWAYS_INLINE std::int64_t & operator[](int i) { return m_value[i]; } CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = m_value[i] || other.m_value[i]; } + { + result.m_value[i] = m_value[i] || other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = m_value[i] && other.m_value[i]; } + { + result.m_value[i] = m_value[i] && other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd_mask operator!() const { simd_mask result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result.m_value[i] = !m_value[i]; } + { + result.m_value[i] = !m_value[i]; + } return result; } @@ -105,26 +136,27 @@ namespace ccm::intrin std::array(N)> m_value; }; - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask> const & a) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask> const & a) { bool result = true; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result = result && a[i]; } + { + result = result && a[i]; + } return result; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask> const & a) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask> const & a) { bool result = false; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result = result || a[i]; } + { + result = result || a[i]; + } return result; } - template - struct simd> + template struct simd> { using value_type = T; @@ -132,54 +164,73 @@ namespace ccm::intrin using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - [[nodiscard]] static constexpr int size() { return N; } + [[nodiscard]] static constexpr int size() + { + return N; + } CCM_ALWAYS_INLINE explicit simd(T value) { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { m_value[i] = value; } + { + m_value[i] = value; + } + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE explicit simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE explicit simd(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(T const * ptr, Flags flags) - { copy_from(ptr, flags); } + template CCM_ALWAYS_INLINE simd(T const * ptr, Flags flags) + { + copy_from(ptr, flags); + } CCM_ALWAYS_INLINE simd operator*(simd const & other) const { simd result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] * other.m_value[i]; } + { + result[i] = m_value[i] * other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd operator/(simd const & other) const { simd result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] / other.m_value[i]; } + { + result[i] = m_value[i] / other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd operator+(simd const & other) const { simd result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] + other.m_value[i]; } + { + result[i] = m_value[i] + other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd operator-(simd const & other) const { simd result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] - other.m_value[i]; } + { + result[i] = m_value[i] - other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const { simd result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = -m_value[i]; } + { + result[i] = -m_value[i]; + } return result; } CCM_ALWAYS_INLINE void copy_from(T const * ptr, element_aligned_tag /*unused*/) @@ -196,20 +247,30 @@ namespace ccm::intrin ptr[i] = m_value[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } } - CCM_ALWAYS_INLINE constexpr T operator[](int i) const { return m_value[i]; } - CCM_ALWAYS_INLINE T & operator[](int i) { return m_value[i]; } + CCM_ALWAYS_INLINE constexpr T operator[](int i) const + { + return m_value[i]; + } + CCM_ALWAYS_INLINE T & operator[](int i) + { + return m_value[i]; + } CCM_ALWAYS_INLINE simd_mask> operator<(simd const & other) const { simd_mask> result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] < other.m_value[i]; } + { + result[i] = m_value[i] < other.m_value[i]; + } return result; } CCM_ALWAYS_INLINE simd_mask> operator==(simd const & other) const { simd_mask> result; CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) - { result[i] = m_value[i] == other.m_value[i]; } + { + result[i] = m_value[i] == other.m_value[i]; + } return result; } @@ -223,7 +284,9 @@ namespace ccm::intrin { simd> result; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result[i] = a[i] ? b[i] : c[i]; } + { + result[i] = a[i] ? b[i] : c[i]; + } return result; } diff --git a/include/ccmath/internal/math/runtime/simd/simd.hpp b/include/ccmath/internal/math/runtime/simd/simd.hpp index 54ab3e01..cd224484 100644 --- a/include/ccmath/internal/math/runtime/simd/simd.hpp +++ b/include/ccmath/internal/math/runtime/simd/simd.hpp @@ -61,6 +61,5 @@ namespace ccm::intrin #endif } // namespace abi - template - using native_simd = simd; + template using native_simd = simd; } // namespace ccm::intrin diff --git a/include/ccmath/internal/math/runtime/simd/vector_size.hpp b/include/ccmath/internal/math/runtime/simd/vector_size.hpp index 78838819..f64ca010 100644 --- a/include/ccmath/internal/math/runtime/simd/vector_size.hpp +++ b/include/ccmath/internal/math/runtime/simd/vector_size.hpp @@ -24,15 +24,12 @@ namespace ccm::intrin namespace abi { - template - struct vector_size - { - }; + template struct vector_size + {}; } // namespace abi - template - struct simd_mask> + template struct simd_mask> { private: using native_type = @@ -43,21 +40,38 @@ namespace ccm::intrin using simd_type = simd>; using abi_type = abi::vector_size; CCM_ALWAYS_INLINE simd_mask() = default; - static constexpr int size() { return N / sizeof(T); } + static constexpr int size() + { + return N / sizeof(T); + } CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(static_cast(value)) {} CCM_ALWAYS_INLINE explicit simd_mask(native_type value) : m_value(value) {} - CCM_ALWAYS_INLINE int operator[](int i) { return reinterpret_cast(&m_value)[i]; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - [[nodiscard]] CCM_ALWAYS_INLINE native_type const &get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(m_value || other.m_value); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(m_value && other.m_value); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(!m_value); } + CCM_ALWAYS_INLINE int operator[](int i) + { + return reinterpret_cast(&m_value)[i]; + } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + [[nodiscard]] CCM_ALWAYS_INLINE native_type const & get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(m_value || other.m_value); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(m_value && other.m_value); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(!m_value); + } private: native_type m_value; }; - template - struct simd_mask> + template struct simd_mask> { private: using native_type = __attribute__((__vector_size__(N * sizeof(long long)))) long long __attribute__((vector_size(N))); @@ -67,40 +81,58 @@ namespace ccm::intrin using simd_type = simd>; using abi_type = abi::vector_size; CCM_ALWAYS_INLINE simd_mask() = default; - static constexpr int size() { return N / sizeof(long long); } + static constexpr int size() + { + return N / sizeof(long long); + } CCM_ALWAYS_INLINE explicit simd_mask(bool value) : m_value(static_cast(value)) {} CCM_ALWAYS_INLINE explicit simd_mask(native_type value) : m_value(value) {} CCM_ALWAYS_INLINE long long operator[](int i) - { return reinterpret_cast(&m_value)[i]; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - [[nodiscard]] CCM_ALWAYS_INLINE native_type const &get() const { return m_value; } - CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const &other) const { return simd_mask(m_value || other.m_value); } - CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const &other) const { return simd_mask(m_value && other.m_value); } - CCM_ALWAYS_INLINE simd_mask operator!() const { return simd_mask(!m_value); } + { + return reinterpret_cast(&m_value)[i]; + } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + [[nodiscard]] CCM_ALWAYS_INLINE native_type const & get() const + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask operator||(simd_mask const & other) const + { + return simd_mask(m_value || other.m_value); + } + CCM_ALWAYS_INLINE simd_mask operator&&(simd_mask const & other) const + { + return simd_mask(m_value && other.m_value); + } + CCM_ALWAYS_INLINE simd_mask operator!() const + { + return simd_mask(!m_value); + } private: native_type m_value; }; - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask> const &a) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool all_of(simd_mask> const & a) { bool result = true; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result = result && a.get()[i]; } + { + result = result && a.get()[i]; + } return result; } - template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask> const &a) + template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE bool any_of(simd_mask> const & a) { bool result = false; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result = result || a.get()[i]; } + { + result = result || a.get()[i]; + } return result; } - template - struct simd> + template struct simd> { private: using native_type = __attribute__((__vector_size__(N * sizeof(T)))) T __attribute__((vector_size(N))); @@ -111,48 +143,89 @@ namespace ccm::intrin using mask_type = simd_mask; using storage_type = simd_storage; CCM_ALWAYS_INLINE simd() = default; - static constexpr int size() { return N / sizeof(T); } + static constexpr int size() + { + return N / sizeof(T); + } CCM_ALWAYS_INLINE explicit simd(T value) { - for (int i = 0; i < size(); i++) { reinterpret_cast(&m_value)[i] = value; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + for (int i = 0; i < size(); i++) + { + reinterpret_cast(&m_value)[i] = value; + } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } - explicit CCM_ALWAYS_INLINE simd(const native_type &value) : m_value(value) {} - CCM_ALWAYS_INLINE explicit simd(storage_type const &value) { copy_from(value.data(), element_aligned_tag()); } - CCM_ALWAYS_INLINE simd &operator=(storage_type const &value) + explicit CCM_ALWAYS_INLINE simd(const native_type & value) : m_value(value) {} + CCM_ALWAYS_INLINE explicit simd(storage_type const & value) + { + copy_from(value.data(), element_aligned_tag()); + } + CCM_ALWAYS_INLINE simd & operator=(storage_type const & value) { copy_from(value.data(), element_aligned_tag()); return *this; } - template - CCM_ALWAYS_INLINE simd(T const *ptr, Flags flags) - { copy_from(ptr, flags); } - CCM_ALWAYS_INLINE simd operator*(simd const &other) const { return simd(m_value * other.m_value); } - CCM_ALWAYS_INLINE simd operator/(simd const &other) const { return simd(m_value / other.m_value); } - CCM_ALWAYS_INLINE simd operator+(simd const &other) const { return simd(m_value + other.m_value); } - CCM_ALWAYS_INLINE simd operator+=(simd const &other) const { return m_value += other.m_value; } - CCM_ALWAYS_INLINE simd operator-(simd const &other) const { return simd(m_value - other.m_value); } - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const { return simd(-m_value); } - CCM_ALWAYS_INLINE void copy_from(T const *ptr, element_aligned_tag /*unused*/) + template CCM_ALWAYS_INLINE simd(T const * ptr, Flags flags) + { + copy_from(ptr, flags); + } + CCM_ALWAYS_INLINE simd operator*(simd const & other) const + { + return simd(m_value * other.m_value); + } + CCM_ALWAYS_INLINE simd operator/(simd const & other) const + { + return simd(m_value / other.m_value); + } + CCM_ALWAYS_INLINE simd operator+(simd const & other) const + { + return simd(m_value + other.m_value); + } + CCM_ALWAYS_INLINE simd operator+=(simd const & other) const + { + return m_value += other.m_value; + } + CCM_ALWAYS_INLINE simd operator-(simd const & other) const + { + return simd(m_value - other.m_value); + } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd operator-() const + { + return simd(-m_value); + } + CCM_ALWAYS_INLINE void copy_from(T const * ptr, element_aligned_tag /*unused*/) { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) { reinterpret_cast(&m_value)[i] = ptr[i]; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } } - CCM_ALWAYS_INLINE void copy_to(T *ptr, element_aligned_tag /*unused*/) const + CCM_ALWAYS_INLINE void copy_to(T * ptr, element_aligned_tag /*unused*/) const { CCM_SIMD_VECTORIZE for (int i = 0; i < size(); ++i) { ptr[i] = reinterpret_cast(&m_value)[i]; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } } - CCM_ALWAYS_INLINE constexpr T operator[](int i) const { return m_value[i]; } - [[nodiscard]] CCM_ALWAYS_INLINE native_type const &get() const { return m_value; } - CCM_ALWAYS_INLINE native_type &get() { return m_value; } - CCM_ALWAYS_INLINE simd_mask> operator<(simd const &other) const - { return simd_mask>(m_value < other.m_value); } - CCM_ALWAYS_INLINE simd_mask> operator==(simd const &other) const - { return simd_mask>(m_value == other.m_value); } + CCM_ALWAYS_INLINE constexpr T operator[](int i) const + { + return m_value[i]; + } + [[nodiscard]] CCM_ALWAYS_INLINE native_type const & get() const + { + return m_value; + } + CCM_ALWAYS_INLINE native_type & get() + { + return m_value; + } + CCM_ALWAYS_INLINE simd_mask> operator<(simd const & other) const + { + return simd_mask>(m_value < other.m_value); + } + CCM_ALWAYS_INLINE simd_mask> operator==(simd const & other) const + { + return simd_mask>(m_value == other.m_value); + } private: native_type m_value; @@ -160,21 +233,27 @@ namespace ccm::intrin template CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> - choose(simd_mask> const &a, simd> const &b, simd> const &c) + choose(simd_mask> const & a, simd> const & b, simd> const & c) { simd> result; CCM_SIMD_VECTORIZE for (int i = 0; i < a.size(); ++i) - { result.get()[i] = a.get()[i] ? b.get()[i] : c.get()[i]; } + { + result.get()[i] = a.get()[i] ? b.get()[i] : c.get()[i]; + } return result; } template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> max(simd> const &a, simd> const &b) - { return choose(b < a, a, b); } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> max(simd> const & a, simd> const & b) + { + return choose(b < a, a, b); + } template - CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> min(simd> const &a, simd> const &b) - { return choose(a < b, a, b); } + CCM_ALWAYS_INLINE CCM_GPU_HOST_DEVICE simd> min(simd> const & a, simd> const & b) + { + return choose(a < b, a, b); + } } // namespace ccm::intrin diff --git a/include/ccmath/internal/predef/assume.hpp b/include/ccmath/internal/predef/assume.hpp index 6db2aec8..f57adc86 100644 --- a/include/ccmath/internal/predef/assume.hpp +++ b/include/ccmath/internal/predef/assume.hpp @@ -24,10 +24,16 @@ // The code will work fine, so ignore the warning it may emit. #ifdef __clang__ #define CCM_ASSUME(...) \ - do { __builtin_assume(__VA_ARGS__); } while (0) + do \ + { \ + __builtin_assume(__VA_ARGS__); \ + } while (0) #elif defined(_MSC_VER) #define CCM_ASSUME(...) \ - do { __assume(__VA_ARGS__); } while (0) + do \ + { \ + __assume(__VA_ARGS__); \ + } while (0) #elif defined(__GNUC__) #if __GNUC__ >= 13 #define CCM_ASSUME(...) __attribute__((__assume__(__VA_ARGS__))) diff --git a/include/ccmath/internal/predef/expects_bool_condition.hpp b/include/ccmath/internal/predef/expects_bool_condition.hpp index a7faec69..2008df3e 100644 --- a/include/ccmath/internal/predef/expects_bool_condition.hpp +++ b/include/ccmath/internal/predef/expects_bool_condition.hpp @@ -17,8 +17,7 @@ namespace ccm::predef::internal /** * @brief Helper function to prevent the user from accidentally passing an integer to an expect function. */ - template , bool> = true> - constexpr bool expects_bool_condition(T value, [[maybe_unused]] bool expected) + template , bool> = true> constexpr bool expects_bool_condition(T value, [[maybe_unused]] bool expected) { #if defined(__GNUC__) || defined(__clang__) return __builtin_expect(value, expected); diff --git a/include/ccmath/internal/support/always_false.hpp b/include/ccmath/internal/support/always_false.hpp index 8fa53871..4974ab57 100644 --- a/include/ccmath/internal/support/always_false.hpp +++ b/include/ccmath/internal/support/always_false.hpp @@ -16,6 +16,5 @@ namespace ccm::support // https://artificial-mind.net/blog/2020/10/03/always-false // Not required in GCC 13.1+ and Clang 17.0.1+ but applied anyway for wider compatibility. // See: https://cplusplus.github.io/CWG/issues/2518.html - template - inline constexpr bool always_false = false; + template inline constexpr bool always_false = false; } // namespace ccm::support diff --git a/include/ccmath/internal/support/bits.hpp b/include/ccmath/internal/support/bits.hpp index 80b97083..070144e1 100644 --- a/include/ccmath/internal/support/bits.hpp +++ b/include/ccmath/internal/support/bits.hpp @@ -40,12 +40,14 @@ namespace ccm::support { namespace detail { - template - constexpr To bit_cast_float80_constexpr(const From & from) noexcept + template constexpr To bit_cast_float80_constexpr(const From & from) noexcept { // Rebuilding a long double from its integer storage is safe in a constant expression: every // bit of the result is sourced from the determinate integer, so the intrinsic handles it. - if constexpr (std::is_same_v) { return __builtin_bit_cast(To, from); } // NOLINT(bugprone-branch-clone) + if constexpr (std::is_same_v) + { + return __builtin_bit_cast(To, from); + } // NOLINT(bugprone-branch-clone) #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT80 else { @@ -62,7 +64,10 @@ namespace ccm::support constexpr std::size_t used_bytes = 10; // 1 sign + 15 exponent + 64 significand bits, low end first To to{}; - for (std::size_t i = 0; i < used_bytes; ++i) { to |= static_cast(buffer.bytes[i]) << (i * CHAR_BIT); } + for (std::size_t i = 0; i < used_bytes; ++i) + { + to |= static_cast(buffer.bytes[i]) << (i * CHAR_BIT); + } return to; } #else @@ -85,7 +90,10 @@ namespace ccm::support { if constexpr ((std::is_same_v || std::is_same_v) && sizeof(To) == sizeof(From)) { - if (is_constant_evaluated()) { return detail::bit_cast_float80_constexpr(from); } + if (is_constant_evaluated()) + { + return detail::bit_cast_float80_constexpr(from); + } } } #endif @@ -95,7 +103,9 @@ namespace ccm::support template && traits::ccm_is_unsigned_v && !traits::is_char_v && !std::is_same_v, bool> = true> constexpr bool has_single_bit(T x) noexcept - { return x && !(x & (x - 1)); } + { + return x && !(x & (x - 1)); + } // TODO: Consolidate top_bits helpers and retire duplicate bit-grab paths. @@ -106,8 +116,7 @@ namespace ccm::support if constexpr (std::is_same_v) { return static_cast(bit_cast(x) >> (std::numeric_limits::digits - TopBitsToTake)); - } - else + } else { return bit_cast(x) >> (std::numeric_limits::digits - TopBitsToTake); } @@ -119,57 +128,86 @@ namespace ccm::support * @return */ constexpr std::uint32_t top16_bits_of_double(double x) noexcept - { return static_cast(bit_cast(x) >> 48); } + { + return static_cast(bit_cast(x) >> 48); + } constexpr std::uint32_t top12_bits_of_double(double x) noexcept - { return static_cast(bit_cast(x) >> 52); } + { + return static_cast(bit_cast(x) >> 52); + } constexpr std::uint32_t top12_bits_of_float(float x) noexcept - { return bit_cast(x) >> 20; } + { + return bit_cast(x) >> 20; + } constexpr std::uint64_t double_to_uint64(double x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr std::int64_t double_to_int64(double x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr double uint64_to_double(std::uint64_t x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr double int64_to_double(std::int64_t x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr std::uint32_t float_to_uint32(float x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr std::int32_t float_to_int32(float x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr float uint32_to_float(std::uint32_t x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } constexpr float int32_to_float(std::int32_t x) noexcept - { return bit_cast(x); } + { + return bit_cast(x); + } /** * @brief Rotates unsigned integer bits to the right. * https://en.cppreference.com/w/cpp/numeric/rotr */ - template , bool> = true> - constexpr T rotr(T t, int cnt) noexcept + template , bool> = true> constexpr T rotr(T t, int cnt) noexcept { #if defined(_MSC_VER) && !defined(__clang__) // Allow for the use of compiler intrinsics if we are not being evaluated at compile time in msvc. if (!is_constant_evaluated()) { // These func are not constexpr in msvc. - if constexpr (std::is_same_v) { return _rotr(t, cnt); } - else if constexpr (std::is_same_v) { return _rotr64(t, cnt); } + if constexpr (std::is_same_v) + { + return _rotr(t, cnt); + } else if constexpr (std::is_same_v) + { + return _rotr64(t, cnt); + } } #endif const unsigned int dig = std::numeric_limits::digits; - if ((static_cast(cnt) % dig) == 0) { return t; } + if ((static_cast(cnt) % dig) == 0) + { + return t; + } if (cnt < 0) { @@ -185,16 +223,20 @@ namespace ccm::support * @brief Rotates unsigned integer bits to the left. * https://en.cppreference.com/w/cpp/numeric/rotl */ - template , bool> = true> - constexpr T rotl(T t, int cnt) noexcept + template , bool> = true> constexpr T rotl(T t, int cnt) noexcept { #if defined(_MSC_VER) && !defined(__clang__) // Allow for the use of compiler intrinsics if we are not being evaluated at compile time in msvc. if (!is_constant_evaluated()) { // These func are not constexpr in msvc. - if constexpr (std::is_same_v) { return _rotl(t, cnt); } - else if constexpr (std::is_same_v) { return _rotl64(t, cnt); } + if constexpr (std::is_same_v) + { + return _rotl(t, cnt); + } else if constexpr (std::is_same_v) + { + return _rotl64(t, cnt); + } } #endif return rotr(t, -cnt); @@ -203,8 +245,7 @@ namespace ccm::support // Macro to allow simplified creation of specializations // NOLINTBEGIN(bugprone-macro-parentheses) #define INTERNAL_CCM_ADD_CTZ_SPECIALIZATION(FUNC, TYPE, BUILTIN) \ - template <> \ - [[nodiscard]] constexpr int FUNC(TYPE value) \ + template <> [[nodiscard]] constexpr int FUNC(TYPE value) \ { \ static_assert(ccm::support::traits::ccm_is_unsigned_v); \ return value == 0 ? std::numeric_limits::digits : BUILTIN(value); \ @@ -216,8 +257,7 @@ namespace ccm::support * @brief Returns the number of consecutive 0 bits in the value of x, starting from the least significant bit ("right"). * https://en.cppreference.com/w/cpp/numeric/countr_zero */ - template - [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(T value) { return __builtin_ctzg(value, std::numeric_limits::digits); // NOLINT } @@ -226,11 +266,16 @@ namespace ccm::support * @brief Returns the number of consecutive 0 bits in the value of x, starting from the least significant bit ("right"). * https://en.cppreference.com/w/cpp/numeric/countr_zero */ - template - [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(T value) { - if (value == 0) { return std::numeric_limits::digits; } - if (value & 0x1) { return 0; } + if (value == 0) + { + return std::numeric_limits::digits; + } + if (value & 0x1) + { + return 0; + } // Bisection method unsigned zero_bits = 0; unsigned shift = std::numeric_limits::digits >> 1; @@ -263,16 +308,17 @@ namespace ccm::support #endif // CCM_HAS_BUILTIN(__builtin_ctzll) #if CCM_HAS_BUILTIN(__builtin_clzg) - template - [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(T value) { return __builtin_clzg(value, std::numeric_limits::digits); // NOLINT } #else // !CCM_HAS_BUILTIN(__builtin_clzg) - template - [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(T value) { - if (value == 0) { return std::numeric_limits::digits; } + if (value == 0) + { + return std::numeric_limits::digits; + } // Bisection method unsigned zero_bits = 0; if (is_constant_evaluated()) @@ -280,20 +326,23 @@ namespace ccm::support for (unsigned shift = std::numeric_limits::digits >> 1; shift; shift >>= 1) { T tmp = value >> shift; - if (tmp) { value = tmp; } - else + if (tmp) + { + value = tmp; + } else { zero_bits |= shift; } } - } - else + } else { CCM_SIMD_VECTORIZE for (unsigned shift = std::numeric_limits::digits >> 1; shift; shift >>= 1) { T tmp = value >> shift; - if (tmp) { value = tmp; } - else + if (tmp) + { + value = tmp; + } else { zero_bits |= shift; } @@ -318,17 +367,21 @@ namespace ccm::support #undef INTERNAL_CCM_ADD_CTZ_SPECIALIZATION - template - [[nodiscard]] constexpr std::enable_if_t, int> countr_one(T value) - { return support::countr_zero(~value); } + template [[nodiscard]] constexpr std::enable_if_t, int> countr_one(T value) + { + return support::countr_zero(~value); + } template , bool> = true> [[nodiscard]] constexpr std::enable_if_t, int> countl_one(T value) - { return value != std::numeric_limits::max() ? countl_zero(static_cast(~value)) : std::numeric_limits::digits; } + { + return value != std::numeric_limits::max() ? countl_zero(static_cast(~value)) : std::numeric_limits::digits; + } - template - [[nodiscard]] constexpr std::enable_if_t, int> bit_width(T value) - { return std::numeric_limits::digits - countl_zero(value); } + template [[nodiscard]] constexpr std::enable_if_t, int> bit_width(T value) + { + return std::numeric_limits::digits - countl_zero(value); + } /** * @brief Returns the smallest power of 2 that is greater than or equal to x. @@ -339,13 +392,18 @@ namespace ccm::support T> bit_ceil(T x) noexcept { - if (x < 2) { return 1; } + if (x < 2) + { + return 1; + } // Assume we got a good input const unsigned n = static_cast(std::numeric_limits::digits - countl_zero(static_cast(x - 1U))); - if constexpr (sizeof(T) >= sizeof(unsigned)) { return T{ 1 } << n; } - else + if constexpr (sizeof(T) >= sizeof(unsigned)) + { + return T{ 1 } << n; + } else { const unsigned extra = std::numeric_limits::digits - std::numeric_limits::digits; const unsigned retVal = 1U << (n + extra); @@ -354,28 +412,31 @@ namespace ccm::support } #if CCM_HAS_BUILTIN(__builtin_popcountg) - template - [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) { return __builtin_popcountg(value); // NOLINT } #else // !CCM_HAS_BUILTIN(__builtin_popcountg) - template - [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) { int count = 0; if (is_constant_evaluated()) { for (int i = 0; i != std::numeric_limits::digits; ++i) { - if ((value >> i) & 0x1) { ++count; } + if ((value >> i) & 0x1) + { + ++count; + } } - } - else + } else { CCM_SIMD_VECTORIZE for (int i = 0; i != std::numeric_limits::digits; ++i) { - if ((value >> i) & 0x1) { ++count; } + if ((value >> i) & 0x1) + { + ++count; + } } } return count; @@ -402,8 +463,7 @@ namespace ccm::support // Macro to allow simplified creation of specializations // NOLINTBEGIN(bugprone-macro-parentheses) #define INTERNAL_CCM_ADD_POPCOUNT_SPECIALIZATION(FUNC, TYPE, BUILTIN) \ - template <> \ - [[nodiscard]] constexpr int FUNC(TYPE value) \ + template <> [[nodiscard]] constexpr int FUNC(TYPE value) \ { \ static_assert(ccm::support::traits::ccm_is_unsigned_v); \ return BUILTIN(value); \ @@ -444,8 +504,7 @@ namespace ccm::support n = (n & 0x33333333) + (n >> 2) & 0x33333333; n = (n + (n >> 4)) & 0x0F0F0F0F; return (n * 0x01010101) >> 24; - } - else // 16 bit int + } else // 16 bit int { n = n - (n >> 1) & 0x5555; n = (n & 0x3333) + (n >> 2) & 0x3333; @@ -467,8 +526,7 @@ namespace ccm::support n = (n & 0x33333333) + (n >> 2) & 0x33333333; n = (n + (n >> 4)) & 0x0F0F0F0F; return (n * 0x01010101) >> 24; - } - else // 64-bit long + } else // 64-bit long { n = n - ((n >> 1) & 0x5555555555555555); n = (n & 0x3333333333333333) + ((n >> 2) & 0x3333333333333333); diff --git a/include/ccmath/internal/support/ctz.hpp b/include/ccmath/internal/support/ctz.hpp index 20001a96..41aa075d 100644 --- a/include/ccmath/internal/support/ctz.hpp +++ b/include/ccmath/internal/support/ctz.hpp @@ -24,8 +24,14 @@ namespace ccm::support template && std::is_unsigned_v && !std::is_same_v, bool> = true> constexpr int generic_ctz(T value) noexcept { - if (value == 0) { return std::numeric_limits::digits; } - if (value & 0x1) { return 0; } + if (value == 0) + { + return std::numeric_limits::digits; + } + if (value & 0x1) + { + return 0; + } // Bisection method unsigned zero_bits = 0; unsigned shift = std::numeric_limits::digits >> 1; @@ -54,8 +60,7 @@ namespace ccm::support #endif } - template <> - constexpr int ctz(unsigned short value) noexcept + template <> constexpr int ctz(unsigned short value) noexcept { #if CCM_HAS_BUILTIN(__builtin_ctzs) return __builtin_ctzs(value); @@ -64,8 +69,7 @@ namespace ccm::support #endif } - template <> - constexpr int ctz(unsigned int value) noexcept + template <> constexpr int ctz(unsigned int value) noexcept { #if CCM_HAS_BUILTIN(__builtin_ctz) return __builtin_ctz(value); @@ -74,8 +78,7 @@ namespace ccm::support #endif } - template <> - constexpr int ctz(unsigned long value) noexcept + template <> constexpr int ctz(unsigned long value) noexcept { #if CCM_HAS_BUILTIN(__builtin_ctzl) return __builtin_ctzl(value); @@ -84,8 +87,7 @@ namespace ccm::support #endif } - template <> - constexpr int ctz(unsigned long long value) noexcept + template <> constexpr int ctz(unsigned long long value) noexcept { #if CCM_HAS_BUILTIN(__builtin_ctzll) return __builtin_ctzll(value); diff --git a/include/ccmath/internal/support/fenv/fenv_support.hpp b/include/ccmath/internal/support/fenv/fenv_support.hpp index 8aee39e8..6d16a2f2 100644 --- a/include/ccmath/internal/support/fenv/fenv_support.hpp +++ b/include/ccmath/internal/support/fenv/fenv_support.hpp @@ -28,7 +28,7 @@ CCM_DISABLE_MSVC_WARNING(4702) // 4702: unreachable code #if !defined(__FAST_MATH__) && !defined(CCM_CONFIG_DISABLE_ERRNO) #if defined(_GNU_SOURCE) || defined(__GLIBC__) || defined(__ANDROID__) -extern "C" int *__errno_location(void) __attribute__((__nothrow__, __const__)); +extern "C" int * __errno_location(void) __attribute__((__nothrow__, __const__)); #endif #endif @@ -38,11 +38,11 @@ namespace ccm::support::fenv::detail { #if !defined(__FAST_MATH__) && !defined(CCM_CONFIG_DISABLE_ERRNO) #if defined(_GNU_SOURCE) || defined(__GLIBC__) || defined(__ANDROID__) - volatile int *const loc = __errno_location(); - *loc = err; + volatile int * const loc = __errno_location(); + *loc = err; #else - volatile int *const loc = &errno; - *loc = err; + volatile int * const loc = &errno; + *loc = err; #endif #if defined(__GNUC__) || defined(__clang__) __asm__ __volatile__("" : : "r"(loc) : "memory"); // NOLINT(hicpp-no-assembler) @@ -70,7 +70,9 @@ namespace ccm::support::fenv // Helper function to convert the enum class to an integer to enable bitwise operations. constexpr int get_mode(ccm_math_err_mode mode) - { return static_cast(mode); } + { + return static_cast(mode); + } constexpr int ccm_math_err_handling() { @@ -96,11 +98,17 @@ namespace ccm::support::fenv // ReSharper disable once CppDFAConstantFunctionResult constexpr int set_except_if_required(const int excepts) { - if (is_constant_evaluated()) { return 0; } // We cannot raise fenv exceptions in a constexpr context. So we return. + if (is_constant_evaluated()) + { + return 0; + } // We cannot raise fenv exceptions in a constexpr context. So we return. if constexpr (is_errno_enabled()) { - if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return host::set_except(excepts); } + if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) + { + return host::set_except(excepts); + } } // ReSharper disable once CppDFAUnreachableCode // This is unreachable code if the above constexpr if statement is true which is desired. return 0; @@ -108,10 +116,16 @@ namespace ccm::support::fenv constexpr int raise_except_if_required(const int excepts) { - if (is_constant_evaluated()) { return 0; } // We cannot raise fenv exceptions in a constexpr context. So we return. + if (is_constant_evaluated()) + { + return 0; + } // We cannot raise fenv exceptions in a constexpr context. So we return. if constexpr (is_errno_enabled()) { - if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { return host::raise_except(excepts); } + if constexpr ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) + { + return host::raise_except(excepts); + } } // ReSharper disable once CppDFAUnreachableCode // This is unreachable code if the above constexpr if statement is true which is desired. return 0; @@ -119,9 +133,18 @@ namespace ccm::support::fenv constexpr void set_errno_if_required(const int err) { - if (is_constant_evaluated()) { return; } - if constexpr (!is_errno_enabled()) { return; } - if ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrno)) != 0) { detail::write_errno(err); } + if (is_constant_evaluated()) + { + return; + } + if constexpr (!is_errno_enabled()) + { + return; + } + if ((ccm_math_err_handling() & get_mode(ccm_math_err_mode::eErrno)) != 0) + { + detail::write_errno(err); + } } } // namespace ccm::support::fenv diff --git a/include/ccmath/internal/support/fenv/host_fenv.hpp b/include/ccmath/internal/support/fenv/host_fenv.hpp index f618ffab..3040075b 100644 --- a/include/ccmath/internal/support/fenv/host_fenv.hpp +++ b/include/ccmath/internal/support/fenv/host_fenv.hpp @@ -113,25 +113,39 @@ namespace ccm::support::fenv::host using fenv_t = CCM_FENV_HOST_QUAL ::fenv_t; inline int get_round() - { return CCM_FENV_HOST_QUAL ::fegetround(); } + { + return CCM_FENV_HOST_QUAL ::fegetround(); + } inline int set_round(const int rounding_mode) - { return CCM_FENV_HOST_QUAL ::fesetround(rounding_mode); } + { + return CCM_FENV_HOST_QUAL ::fesetround(rounding_mode); + } inline int raise_except(const int err_code) - { return CCM_FENV_HOST_QUAL ::feraiseexcept(err_code); } + { + return CCM_FENV_HOST_QUAL ::feraiseexcept(err_code); + } inline int clear_except(const int err_code) - { return CCM_FENV_HOST_QUAL ::feclearexcept(err_code); } + { + return CCM_FENV_HOST_QUAL ::feclearexcept(err_code); + } inline int test_except(const int err_code) - { return CCM_FENV_HOST_QUAL ::fetestexcept(err_code); } + { + return CCM_FENV_HOST_QUAL ::fetestexcept(err_code); + } inline int get_env(fenv_t * envp) - { return CCM_FENV_HOST_QUAL ::fegetenv(envp); } + { + return CCM_FENV_HOST_QUAL ::fegetenv(envp); + } inline int set_env(const fenv_t * envp) - { return CCM_FENV_HOST_QUAL ::fesetenv(envp); } + { + return CCM_FENV_HOST_QUAL ::fesetenv(envp); + } #undef CCM_FENV_HOST_QUAL @@ -179,37 +193,59 @@ namespace ccm::support::fenv::host using fenv_t = int; inline int get_round() - { return FE_TONEAREST; } + { + return FE_TONEAREST; + } inline int set_round([[maybe_unused]] int rounding_mode) - { return 0; } + { + return 0; + } inline int raise_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } inline int clear_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } inline int test_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } inline int get_env([[maybe_unused]] fenv_t * envp) - { return 0; } + { + return 0; + } inline int set_env([[maybe_unused]] const fenv_t * envp) - { return 0; } + { + return 0; + } inline int set_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } inline int get_except() - { return 0; } + { + return 0; + } inline int enable_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } inline int disable_except([[maybe_unused]] int err_code) - { return 0; } + { + return 0; + } #endif } // namespace ccm::support::fenv::host diff --git a/include/ccmath/internal/support/fenv/rounding_mode.hpp b/include/ccmath/internal/support/fenv/rounding_mode.hpp index d817212e..163956b4 100644 --- a/include/ccmath/internal/support/fenv/rounding_mode.hpp +++ b/include/ccmath/internal/support/fenv/rounding_mode.hpp @@ -30,24 +30,36 @@ namespace ccm::support::fenv * @return CCM_CONSTEXPR_ROUNDING_MODE, which defaults to FE_TONEAREST. */ constexpr int constant_eval_rounding_mode() noexcept - { return CCM_CONSTEXPR_ROUNDING_MODE; } + { + return CCM_CONSTEXPR_ROUNDING_MODE; + } namespace internal { inline bool rt_rounding_mode_is_round_up() - { return host::get_round() == FE_UPWARD; } + { + return host::get_round() == FE_UPWARD; + } inline bool rt_rounding_mode_is_round_down() - { return host::get_round() == FE_DOWNWARD; } + { + return host::get_round() == FE_DOWNWARD; + } inline bool rt_rounding_mode_is_round_to_nearest() - { return host::get_round() == FE_TONEAREST; } + { + return host::get_round() == FE_TONEAREST; + } inline bool rt_rounding_mode_is_round_to_zero() - { return host::get_round() == FE_TOWARDZERO; } + { + return host::get_round() == FE_TOWARDZERO; + } inline int rt_get_rounding_mode() - { return host::get_round(); } + { + return host::get_round(); + } } // namespace internal /** @@ -56,7 +68,10 @@ namespace ccm::support::fenv */ constexpr bool rounding_mode_is_round_up() { - if (is_constant_evaluated()) { return constant_eval_rounding_mode() == FE_UPWARD; } + if (is_constant_evaluated()) + { + return constant_eval_rounding_mode() == FE_UPWARD; + } return internal::rt_rounding_mode_is_round_up(); } @@ -66,7 +81,10 @@ namespace ccm::support::fenv */ constexpr bool rounding_mode_is_round_down() { - if (is_constant_evaluated()) { return constant_eval_rounding_mode() == FE_DOWNWARD; } + if (is_constant_evaluated()) + { + return constant_eval_rounding_mode() == FE_DOWNWARD; + } return internal::rt_rounding_mode_is_round_down(); } @@ -76,7 +94,10 @@ namespace ccm::support::fenv */ constexpr bool rounding_mode_is_round_to_nearest() { - if (is_constant_evaluated()) { return constant_eval_rounding_mode() == FE_TONEAREST; } + if (is_constant_evaluated()) + { + return constant_eval_rounding_mode() == FE_TONEAREST; + } return internal::rt_rounding_mode_is_round_to_nearest(); } @@ -86,7 +107,10 @@ namespace ccm::support::fenv */ constexpr bool rounding_mode_is_round_to_zero() { - if (is_constant_evaluated()) { return constant_eval_rounding_mode() == FE_TOWARDZERO; } + if (is_constant_evaluated()) + { + return constant_eval_rounding_mode() == FE_TOWARDZERO; + } return internal::rt_rounding_mode_is_round_to_zero(); } @@ -96,7 +120,10 @@ namespace ccm::support::fenv */ constexpr int get_rounding_mode() { - if (is_constant_evaluated()) { return constant_eval_rounding_mode(); } + if (is_constant_evaluated()) + { + return constant_eval_rounding_mode(); + } return internal::rt_get_rounding_mode(); } } // namespace ccm::support::fenv diff --git a/include/ccmath/internal/support/floating_point_traits.hpp b/include/ccmath/internal/support/floating_point_traits.hpp index 0942abb9..8e31f572 100644 --- a/include/ccmath/internal/support/floating_point_traits.hpp +++ b/include/ccmath/internal/support/floating_point_traits.hpp @@ -19,11 +19,9 @@ namespace ccm::support { - template - struct floating_point_traits; + template struct floating_point_traits; - template <> - struct floating_point_traits + template <> struct floating_point_traits { using upgraded_floating_type = double; @@ -53,8 +51,7 @@ namespace ccm::support static constexpr float max_safe_integer = 0x1p+24F; // 16777216.0 (2^24) }; - template <> - struct floating_point_traits + template <> struct floating_point_traits { #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT128 using upgraded_floating_type = long double; @@ -93,8 +90,7 @@ namespace ccm::support }; #ifdef CCM_TYPES_LONG_DOUBLE_IS_FLOAT128 - template <> - struct floating_point_traits + template <> struct floating_point_traits { using upgraded_floating_type = ccm::types::DyadicFloat<256>; @@ -122,8 +118,7 @@ namespace ccm::support static constexpr long double max_safe_integer = 0x1p+112; // 5192296858534827628530496329220096.0L (2^112) }; #elif defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) - template <> - struct floating_point_traits + template <> struct floating_point_traits { using upgraded_floating_type = ccm::types::DyadicFloat<256>; @@ -153,8 +148,7 @@ namespace ccm::support }; #else // long double is the same as double - template <> - struct floating_point_traits + template <> struct floating_point_traits { using upgraded_floating_type = ccm::types::DyadicFloat<128>; @@ -185,23 +179,18 @@ namespace ccm::support }; #endif - template - using float_bits_t = typename floating_point_traits::uint_type; + template using float_bits_t = typename floating_point_traits::uint_type; - template - using float_signed_bits_t = typename floating_point_traits::int_type; + template using float_signed_bits_t = typename floating_point_traits::int_type; - template - using float_upgraded_t = typename floating_point_traits::upgraded_floating_type; + template using float_upgraded_t = typename floating_point_traits::upgraded_floating_type; - template - inline constexpr typename floating_point_traits::uint_type sign_mask_v = floating_point_traits::shifted_sign_mask; + template inline constexpr typename floating_point_traits::uint_type sign_mask_v = floating_point_traits::shifted_sign_mask; // TODO(IanP): Possible remove these func from floating_point_traits as they are more so there own things and not really traits. // All func below that use bit_cast have to use the __builtin_bit_cast as bit_cast itself includes floating_point_traits.hpp - template , int> = 0> - [[nodiscard]] constexpr T floating_point_abs_bits(const T & x) noexcept + template , int> = 0> [[nodiscard]] constexpr T floating_point_abs_bits(const T & x) noexcept { using traits = floating_point_traits; using uint_type = typename traits::uint_type; @@ -209,8 +198,7 @@ namespace ccm::support return bits & ~traits::shifted_sign_mask; } - template , bool> = true> - constexpr float_signed_bits_t get_exponent_of_floating_point(T x) noexcept + template , bool> = true> constexpr float_signed_bits_t get_exponent_of_floating_point(T x) noexcept { const auto bits = __builtin_bit_cast(ccm::support::float_bits_t, x); @@ -219,8 +207,7 @@ namespace ccm::support return static_cast>(masked_exponent) - floating_point_traits::exponent_bias; } - template , bool> = true> - constexpr T set_exponent_of_floating_point(T x, int exp) noexcept + template , bool> = true> constexpr T set_exponent_of_floating_point(T x, int exp) noexcept { const auto bit_casted = __builtin_bit_cast(ccm::support::float_bits_t, x); const auto inverted_exponent_mask = ~floating_point_traits::shifted_exponent_mask; diff --git a/include/ccmath/internal/support/fp/bit_mask_traits.hpp b/include/ccmath/internal/support/fp/bit_mask_traits.hpp index 744d33cd..f578dbff 100644 --- a/include/ccmath/internal/support/fp/bit_mask_traits.hpp +++ b/include/ccmath/internal/support/fp/bit_mask_traits.hpp @@ -19,8 +19,7 @@ namespace ccm::support { - template , bool> = true> - struct bitmask_traits + template , bool> = true> struct bitmask_traits { // If we've not specialized for the provided type then assume double. using MaskType = std::uint64_t; @@ -41,8 +40,7 @@ namespace ccm::support static constexpr MaskType neg_nan = 0xFFF8000000000000; }; - template <> - struct bitmask_traits + template <> struct bitmask_traits { using MaskType = std::uint32_t; @@ -62,8 +60,7 @@ namespace ccm::support static constexpr MaskType neg_nan = 0xFFC00000; }; - template <> - struct bitmask_traits + template <> struct bitmask_traits { using MaskType = std::uint64_t; @@ -89,8 +86,7 @@ namespace ccm::support // Values from this table: // https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format/ - template <> - struct bitmask_traits + template <> struct bitmask_traits { using MaskType = ccm::types::uint128_t; @@ -110,8 +106,7 @@ namespace ccm::support static constexpr MaskType neg_nan = 0xFFFF8000000000000000000000000000_u128; }; #elif defined(CCM_TYPES_LONG_DOUBLE_IS_FLOAT80) - template <> - struct bitmask_traits + template <> struct bitmask_traits { using MaskType = ccm::types::uint128_t; @@ -131,8 +126,7 @@ namespace ccm::support static constexpr MaskType neg_nan = 0xFFFF8000000000000000000000000000_u128; }; #else // long double is the same as double - template <> - struct bitmask_traits + template <> struct bitmask_traits { using MaskType = std::uint64_t; diff --git a/include/ccmath/internal/support/fp/cast.hpp b/include/ccmath/internal/support/fp/cast.hpp index 4a4bca55..f8c0acfa 100644 --- a/include/ccmath/internal/support/fp/cast.hpp +++ b/include/ccmath/internal/support/fp/cast.hpp @@ -29,9 +29,13 @@ namespace ccm::support::fp template [[nodiscard]] constexpr std::enable_if_t && traits::ccm_is_floating_point_v, OutType> cast(InType x) { - if constexpr (std::is_same_v) { return x; } - else if constexpr (sizeof(OutType) >= sizeof(InType)) { return static_cast(x); } - else + if constexpr (std::is_same_v) + { + return x; + } else if constexpr (sizeof(OutType) >= sizeof(InType)) + { + return static_cast(x); + } else { using InFPBits = FPBits; using InStorage = typename InFPBits::storage_type; @@ -48,11 +52,17 @@ namespace ccm::support::fp } InStorage x_mant = x_bits.get_mantissa(); - if (InFPBits::fraction_length > OutFPBits::fraction_length) { x_mant >>= InFPBits::fraction_length - OutFPBits::fraction_length; } + if (InFPBits::fraction_length > OutFPBits::fraction_length) + { + x_mant >>= InFPBits::fraction_length - OutFPBits::fraction_length; + } return OutFPBits::quiet_nan(x_bits.sign(), static_cast(x_mant)).get_val(); } - if (x_bits.is_inf()) { return OutFPBits::inf(x_bits.sign()).get_val(); } + if (x_bits.is_inf()) + { + return OutFPBits::inf(x_bits.sign()).get_val(); + } constexpr std::size_t max_fraction_len = InFPBits::fraction_length > OutFPBits::fraction_length ? InFPBits::fraction_length : OutFPBits::fraction_length; diff --git a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp index b2fcd046..9f8fdde1 100644 --- a/include/ccmath/internal/support/fp/directional_rounding_utils.hpp +++ b/include/ccmath/internal/support/fp/directional_rounding_utils.hpp @@ -20,10 +20,12 @@ namespace ccm::support::fp { /// Exact zero with sign per the current rounding mode (downward yields −0). - template - constexpr std::enable_if_t, T> signed_zero_for_current_mode() noexcept + template constexpr std::enable_if_t, T> signed_zero_for_current_mode() noexcept { - if (ccm::support::fenv::get_rounding_mode() == FE_DOWNWARD) { return T(-0.0); } + if (ccm::support::fenv::get_rounding_mode() == FE_DOWNWARD) + { + return T(-0.0); + } return T(0.0); } @@ -46,13 +48,19 @@ namespace ccm::support::fp FPBits_t const bits(val); - if (bits.is_inf_or_nan() || bits.is_zero()) { return val; } + if (bits.is_inf_or_nan() || bits.is_zero()) + { + return val; + } bool const is_neg = bits.is_neg(); int const exponent = bits.get_exponent(); // If our exponent is greater than the most negative possible mantissa, then x is in fact already an integral. - if (exponent >= static_cast(FPBits_t::fraction_length)) { return val; } + if (exponent >= static_cast(FPBits_t::fraction_length)) + { + return val; + } auto provided_round_mode = static_cast(desired_rounding_mode); @@ -60,8 +68,8 @@ namespace ccm::support::fp { switch (provided_round_mode) { - case rounding_mode::eFE_DOWNWARD: return is_neg ? T(-1.0) : T(0.0); - case rounding_mode::eFE_UPWARD: return is_neg ? T(-0.0) : T(1.0); + case rounding_mode::eFE_DOWNWARD : return is_neg ? T(-1.0) : T(0.0); + case rounding_mode::eFE_UPWARD : return is_neg ? T(-0.0) : T(1.0); case rounding_mode::eFE_TOWARDZERO: return is_neg ? T(-0.0) : T(0.0); case rounding_mode::eFE_TONEARESTFROMZERO: if (exponent < -1) @@ -87,7 +95,10 @@ namespace ccm::support::fp T truncated_value = new_bits.get_val(); // If the truncated value is the same as the original value, then the value is already integral. - if (truncated_value == val) { return val; } + if (truncated_value == val) + { + return val; + } // Bits below the rounding point and the half-way bit for tie handling. Storage_t const trimmed_value = bits.get_mantissa() & ((Storage_t(1) << trimming_length) - 1); @@ -99,22 +110,32 @@ namespace ccm::support::fp switch (provided_round_mode) { - case rounding_mode::eFE_DOWNWARD: return is_neg ? truncated_value - T(1.0) : truncated_value; - case rounding_mode::eFE_UPWARD: return is_neg ? truncated_value : truncated_value + T(1.0); + case rounding_mode::eFE_DOWNWARD : return is_neg ? truncated_value - T(1.0) : truncated_value; + case rounding_mode::eFE_UPWARD : return is_neg ? truncated_value : truncated_value + T(1.0); case rounding_mode::eFE_TOWARDZERO: return truncated_value; case rounding_mode::eFE_TONEARESTFROMZERO: - if (trimmed_value >= half_value) { return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); } + if (trimmed_value >= half_value) + { + return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); + } return truncated_value; case rounding_mode::eFE_TONEAREST: [[fallthrough]]; // The Default case is round to the nearest default: - if (trimmed_value > half_value) { return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); } - else if (trimmed_value == half_value) + if (trimmed_value > half_value) + { + return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); + } else if (trimmed_value == half_value) { - if (exponent == 0) { return is_neg ? T(-2.0) : T(2.0); } - if (truncated_value_is_odd) { return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); } + if (exponent == 0) + { + return is_neg ? T(-2.0) : T(2.0); + } + if (truncated_value_is_odd) + { + return is_neg ? truncated_value - T(1.0) : truncated_value + T(1.0); + } return truncated_value; - } - else + } else { return truncated_value; } @@ -122,16 +143,14 @@ namespace ccm::support::fp } // Helper func to set results for exceptional cases. - template - constexpr T round_result_slightly_down(T value_rn) + template constexpr T round_result_slightly_down(T value_rn) { volatile T tmp = value_rn; tmp -= FPBits::min_normal().get_val(); return tmp; } - template - constexpr T round_result_slightly_up(T value_rn) + template constexpr T round_result_slightly_up(T value_rn) { volatile T tmp = value_rn; tmp += FPBits::min_normal().get_val(); diff --git a/include/ccmath/internal/support/fp/except_value_utils.hpp b/include/ccmath/internal/support/fp/except_value_utils.hpp index 62fb846f..176db3a8 100644 --- a/include/ccmath/internal/support/fp/except_value_utils.hpp +++ b/include/ccmath/internal/support/fp/except_value_utils.hpp @@ -27,8 +27,7 @@ namespace ccm::support::fp { - template - struct ExceptValues + template struct ExceptValues { static_assert(traits::ccm_is_floating_point_v, "ExceptValues requires a floating-point type."); @@ -54,10 +53,10 @@ namespace ccm::support::fp StorageType out_bits = values[i].rnd_towardzero_result; switch (fenv::get_rounding_mode()) { - case FE_UPWARD: out_bits += values[i].rnd_upward_offset; break; - case FE_DOWNWARD: out_bits += values[i].rnd_downward_offset; break; + case FE_UPWARD : out_bits += values[i].rnd_upward_offset; break; + case FE_DOWNWARD : out_bits += values[i].rnd_downward_offset; break; case FE_TONEAREST: out_bits += values[i].rnd_tonearest_offset; break; - default: break; + default : break; } return FPBits(out_bits).get_val(); } @@ -75,25 +74,32 @@ namespace ccm::support::fp switch (fenv::get_rounding_mode()) { case FE_UPWARD: - if (sign) { out_bits += values[i].rnd_downward_offset; } - else + if (sign) + { + out_bits += values[i].rnd_downward_offset; + } else { out_bits += values[i].rnd_upward_offset; } break; case FE_DOWNWARD: - if (sign) { out_bits += values[i].rnd_upward_offset; } - else + if (sign) + { + out_bits += values[i].rnd_upward_offset; + } else { out_bits += values[i].rnd_downward_offset; } break; case FE_TONEAREST: out_bits += values[i].rnd_tonearest_offset; break; - default: break; + default : break; } T result = FPBits(out_bits).get_val(); - if (sign) { result = -result; } + if (sign) + { + result = -result; + } return result; } } @@ -101,20 +107,24 @@ namespace ccm::support::fp } }; - template - [[nodiscard]] constexpr T round_result_slightly_down(T value_rn) + template [[nodiscard]] constexpr T round_result_slightly_down(T value_rn) { - if (support::is_constant_evaluated()) { return value_rn; } + if (support::is_constant_evaluated()) + { + return value_rn; + } volatile T tmp = value_rn; tmp -= FPBits::min_normal().get_val(); return tmp; } - template - [[nodiscard]] constexpr T round_result_slightly_up(T value_rn) + template [[nodiscard]] constexpr T round_result_slightly_up(T value_rn) { - if (support::is_constant_evaluated()) { return value_rn; } + if (support::is_constant_evaluated()) + { + return value_rn; + } volatile T tmp = value_rn; tmp += FPBits::min_normal().get_val(); diff --git a/include/ccmath/internal/support/fp/fma.hpp b/include/ccmath/internal/support/fp/fma.hpp index ddb935b4..a7753591 100644 --- a/include/ccmath/internal/support/fp/fma.hpp +++ b/include/ccmath/internal/support/fp/fma.hpp @@ -31,60 +31,76 @@ namespace ccm::support::fp { namespace fma_internal { - template - [[nodiscard]] constexpr bool int_is_zero(const Int& value) + template [[nodiscard]] constexpr bool int_is_zero(const Int & value) { - if constexpr (std::is_integral_v) { return value == Int(0); } - else + if constexpr (std::is_integral_v) + { + return value == Int(0); + } else { return value.is_zero(); } } - template - [[nodiscard]] constexpr int int_bit_width(const Int& value) + template [[nodiscard]] constexpr int int_bit_width(const Int & value) { - if (int_is_zero(value)) { return 0; } + if (int_is_zero(value)) + { + return 0; + } return support::bit_width(value); } - template - [[nodiscard]] constexpr bool int_get_bit(const Int& value, int index) + template [[nodiscard]] constexpr bool int_get_bit(const Int & value, int index) { - if (index < 0) { return false; } - if constexpr (std::is_integral_v) { return ((value >> index) & Int(1)) != Int(0); } - else + if (index < 0) + { + return false; + } + if constexpr (std::is_integral_v) + { + return ((value >> index) & Int(1)) != Int(0); + } else { return value.get_bit(static_cast(index)); } } - template - [[nodiscard]] constexpr bool lower_bits_nonzero(const Int& value, int bit_count) + template [[nodiscard]] constexpr bool lower_bits_nonzero(const Int & value, int bit_count) { - if (bit_count <= 0) { return false; } + if (bit_count <= 0) + { + return false; + } constexpr int DIGITS = std::numeric_limits::digits; - if (bit_count >= DIGITS) { return !int_is_zero(value); } + if (bit_count >= DIGITS) + { + return !int_is_zero(value); + } if constexpr (std::is_integral_v) { const Int mask = (Int{ 1 } << bit_count) - Int{ 1 }; return (value & mask) != Int{ 0 }; - } - else + } else { for (int bit = 0; bit < bit_count; ++bit) { - if (value.get_bit(static_cast(bit))) { return true; } + if (value.get_bit(static_cast(bit))) + { + return true; + } } return false; } } - template - [[nodiscard]] constexpr bool shift_right_with_sticky(int shift_length, Int& value) + template [[nodiscard]] constexpr bool shift_right_with_sticky(int shift_length, Int & value) { - if (shift_length <= 0 || int_is_zero(value)) { return false; } + if (shift_length <= 0 || int_is_zero(value)) + { + return false; + } constexpr int DIGITS = std::numeric_limits::digits; if (shift_length >= DIGITS) @@ -98,11 +114,9 @@ namespace ccm::support::fp return sticky_bits; } - template - struct fixed_fma_traits; + template struct fixed_fma_traits; - template <> - struct fixed_fma_traits + template <> struct fixed_fma_traits { using product_int = std::uint64_t; using acc_int = types::UInt<128>; @@ -110,8 +124,7 @@ namespace ccm::support::fp static constexpr int accumulator_bits = 96; }; - template <> - struct fixed_fma_traits + template <> struct fixed_fma_traits { using product_int = types::UInt<128>; using acc_int = types::UInt<256>; @@ -119,11 +132,9 @@ namespace ccm::support::fp static constexpr int accumulator_bits = 212; }; - template - inline constexpr bool has_fixed_fma_kernel_v = std::is_same_v || std::is_same_v; + template inline constexpr bool has_fixed_fma_kernel_v = std::is_same_v || std::is_same_v; - template - struct decoded_operand + template struct decoded_operand { using storage_type = typename FPBits::storage_type; @@ -134,8 +145,7 @@ namespace ccm::support::fp int top_exponent = 0; }; - template - [[nodiscard]] constexpr decoded_operand decode_finite_nonzero(const FPBits& bits) + template [[nodiscard]] constexpr decoded_operand decode_finite_nonzero(const FPBits & bits) { decoded_operand decoded{}; decoded.sign = bits.sign(); @@ -146,21 +156,24 @@ namespace ccm::support::fp return decoded; } - template - [[nodiscard]] constexpr std::enable_if_t, T> wide_fma(T x, T y, T z); + template [[nodiscard]] constexpr std::enable_if_t, T> wide_fma(T x, T y, T z); - template - [[nodiscard]] constexpr T propagate_quiet_nan(const FPBits& bits) - { return FPBits::quiet_nan(bits.sign(), bits.get_mantissa()).get_val(); } + template [[nodiscard]] constexpr T propagate_quiet_nan(const FPBits & bits) + { + return FPBits::quiet_nan(bits.sign(), bits.get_mantissa()).get_val(); + } template - [[nodiscard]] constexpr bool special_case_fma(const FPBits& x_bits, const FPBits& y_bits, const FPBits& z_bits, T& result) + [[nodiscard]] constexpr bool special_case_fma(const FPBits & x_bits, const FPBits & y_bits, const FPBits & z_bits, T & result) { using Sign = types::Sign; if (CCM_UNLIKELY(x_bits.is_nan() || y_bits.is_nan() || z_bits.is_nan())) { - if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan() || z_bits.is_signaling_nan()) { fenv::raise_except_if_required(FE_INVALID); } + if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan() || z_bits.is_signaling_nan()) + { + fenv::raise_except_if_required(FE_INVALID); + } // When the addend is a quiet NaN, glibc std::fma does not raise FE_INVALID for an // inf*0 product. Apple libm does. Match the platform libm observable behavior. @@ -170,7 +183,10 @@ namespace ccm::support::fp if (CCM_UNLIKELY(!z_bits.is_quiet_nan() && ((x_bits.is_zero() && y_bits.is_inf()) || (x_bits.is_inf() && y_bits.is_zero())))) #endif { - if (!is_constant_evaluated()) { fenv::set_errno_if_required(EDOM); } + if (!is_constant_evaluated()) + { + fenv::set_errno_if_required(EDOM); + } fenv::raise_except_if_required(FE_INVALID); } @@ -198,7 +214,10 @@ namespace ccm::support::fp { if (x_bits.is_zero() || y_bits.is_zero()) { - if (!is_constant_evaluated()) { fenv::set_errno_if_required(EDOM); } + if (!is_constant_evaluated()) + { + fenv::set_errno_if_required(EDOM); + } fenv::raise_except_if_required(FE_INVALID); result = FPBits::quiet_nan().get_val(); return true; @@ -207,7 +226,10 @@ namespace ccm::support::fp const Sign product_sign = (x_bits.sign() == y_bits.sign()) ? Sign::POS : Sign::NEG; if (z_bits.is_inf() && z_bits.sign() != product_sign) { - if (!is_constant_evaluated()) { fenv::set_errno_if_required(EDOM); } + if (!is_constant_evaluated()) + { + fenv::set_errno_if_required(EDOM); + } fenv::raise_except_if_required(FE_INVALID); result = FPBits::quiet_nan().get_val(); return true; @@ -229,10 +251,12 @@ namespace ccm::support::fp if (z_bits.is_zero()) { Sign zero_sign = product_sign; - if (product_sign != z_bits.sign()) { zero_sign = (fenv::get_rounding_mode() == FE_DOWNWARD) ? Sign::NEG : Sign::POS; } + if (product_sign != z_bits.sign()) + { + zero_sign = (fenv::get_rounding_mode() == FE_DOWNWARD) ? Sign::NEG : Sign::POS; + } result = FPBits::zero(zero_sign).get_val(); - } - else + } else { result = z_bits.get_val(); } @@ -248,7 +272,10 @@ namespace ccm::support::fp using OutFPBits = FPBits; using StorageType = typename OutFPBits::storage_type; - if (int_is_zero(mantissa)) { return OutFPBits::zero(sign).get_val(); } + if (int_is_zero(mantissa)) + { + return OutFPBits::zero(sign).get_val(); + } constexpr int FRACTION_BITS = static_cast(OutFPBits::fraction_length); constexpr int PRECISION_BITS = FRACTION_BITS + 1; @@ -270,13 +297,19 @@ namespace ccm::support::fp switch (fenv::get_rounding_mode()) { - case FE_TONEAREST: return OutFPBits::inf(sign).get_val(); + case FE_TONEAREST : return OutFPBits::inf(sign).get_val(); case FE_TOWARDZERO: return OutFPBits::max_normal(sign).get_val(); case FE_DOWNWARD: - if (sign.is_pos()) { return OutFPBits::max_normal(types::Sign::POS).get_val(); } + if (sign.is_pos()) + { + return OutFPBits::max_normal(types::Sign::POS).get_val(); + } return OutFPBits::inf(types::Sign::NEG).get_val(); case FE_UPWARD: - if (sign.is_neg()) { return OutFPBits::max_normal(types::Sign::NEG).get_val(); } + if (sign.is_neg()) + { + return OutFPBits::max_normal(types::Sign::NEG).get_val(); + } return OutFPBits::inf(types::Sign::POS).get_val(); default: support::unreachable(); } @@ -294,19 +327,19 @@ namespace ccm::support::fp round_bit = int_get_bit(mantissa, NORMAL_SHIFT - 1); sticky_bit |= lower_bits_nonzero(mantissa, NORMAL_SHIFT - 1); out_mantissa = static_cast(mantissa >> static_cast(NORMAL_SHIFT)); - } - else + } else { underflow = true; const int subnormal_shift = MIN_SUBNORMAL_LS - base_exponent; - if (subnormal_shift > ActiveBits) { sticky_bit = true; } - else if (subnormal_shift == ActiveBits) + if (subnormal_shift > ActiveBits) + { + sticky_bit = true; + } else if (subnormal_shift == ActiveBits) { round_bit = int_get_bit(mantissa, ActiveBits - 1); sticky_bit |= lower_bits_nonzero(mantissa, ActiveBits - 1); - } - else + } else { round_bit = int_get_bit(mantissa, subnormal_shift - 1); sticky_bit |= lower_bits_nonzero(mantissa, subnormal_shift - 1); @@ -320,13 +353,22 @@ namespace ccm::support::fp switch (fenv::get_rounding_mode()) { case FE_TONEAREST: - if (round_bit && (lsb || sticky_bit)) { ++result; } + if (round_bit && (lsb || sticky_bit)) + { + ++result; + } break; case FE_DOWNWARD: - if (sign.is_neg() && (round_bit || sticky_bit)) { ++result; } + if (sign.is_neg() && (round_bit || sticky_bit)) + { + ++result; + } break; case FE_UPWARD: - if (sign.is_pos() && (round_bit || sticky_bit)) { ++result; } + if (sign.is_pos() && (round_bit || sticky_bit)) + { + ++result; + } break; default: break; } @@ -338,12 +380,17 @@ namespace ccm::support::fp int excepts = FE_INEXACT; if (OutFPBits(result).is_inf()) { - if (!is_constant_evaluated()) { fenv::set_errno_if_required(ERANGE); } + if (!is_constant_evaluated()) + { + fenv::set_errno_if_required(ERANGE); + } excepts |= FE_OVERFLOW; - } - else if (underflow) + } else if (underflow) { - if (!is_constant_evaluated()) { fenv::set_errno_if_required(ERANGE); } + if (!is_constant_evaluated()) + { + fenv::set_errno_if_required(ERANGE); + } excepts |= FE_UNDERFLOW; } fenv::raise_except_if_required(excepts); @@ -353,8 +400,7 @@ namespace ccm::support::fp return OutFPBits(result).get_val(); } - template - [[nodiscard]] constexpr std::enable_if_t, T> fixed_fma(T x, T y, T z) + template [[nodiscard]] constexpr std::enable_if_t, T> fixed_fma(T x, T y, T z) { using Traits = fixed_fma_traits; using FPBits = FPBits; @@ -368,7 +414,10 @@ namespace ccm::support::fp FPBits const z_bits(z); T special_result{}; - if (special_case_fma(x_bits, y_bits, z_bits, special_result)) { return special_result; } + if (special_case_fma(x_bits, y_bits, z_bits, special_result)) + { + return special_result; + } const auto x_value = decode_finite_nonzero(x_bits); const auto y_value = decode_finite_nonzero(y_bits); @@ -397,8 +446,7 @@ namespace ccm::support::fp { z_sticky = shift_right_with_sticky(product_top_exponent - z_value.top_exponent, z_acc); base_exponent = product_top_exponent - (Traits::accumulator_bits - 1); - } - else if (z_value.top_exponent > product_top_exponent) + } else if (z_value.top_exponent > product_top_exponent) { product_sticky = shift_right_with_sticky(z_value.top_exponent - product_top_exponent, product_acc); base_exponent = z_value.top_exponent - (Traits::accumulator_bits - 1); @@ -419,13 +467,19 @@ namespace ccm::support::fp result_mantissa >>= 1; ++base_exponent; } - } - else + } else { int compare = 0; - if (product_acc > z_acc) { compare = 1; } - else if (product_acc < z_acc) { compare = -1; } - else if (product_sticky != z_sticky) { compare = product_sticky ? 1 : -1; } + if (product_acc > z_acc) + { + compare = 1; + } else if (product_acc < z_acc) + { + compare = -1; + } else if (product_sticky != z_sticky) + { + compare = product_sticky ? 1 : -1; + } if (compare == 0) { @@ -440,22 +494,19 @@ namespace ccm::support::fp { result_mantissa = product_acc - z_acc - AccInt{ 1 }; sticky_tail = true; - } - else + } else { result_mantissa = product_acc - z_acc; sticky_tail = product_sticky; } - } - else + } else { result_sign = z_value.sign; if (product_sticky) { result_mantissa = z_acc - product_acc - AccInt{ 1 }; sticky_tail = true; - } - else + } else { result_mantissa = z_acc - product_acc; sticky_tail = z_sticky; @@ -484,8 +535,7 @@ namespace ccm::support::fp // Exact wide-integer FMA used for directed modes, constexpr policy modes, and every // binary64 software fallback. The product is formed exactly, the addend is aligned with a // sticky bit, and the result is rounded once through DyadicFloat::generic_as. - template - [[nodiscard]] constexpr std::enable_if_t, T> wide_fma(T x, T y, T z) + template [[nodiscard]] constexpr std::enable_if_t, T> wide_fma(T x, T y, T z) { using FPBits = FPBits; using StorageType = typename FPBits::storage_type; @@ -503,14 +553,29 @@ namespace ccm::support::fp if (CCM_UNLIKELY(x_bits.is_nan() || y_bits.is_nan() || z_bits.is_nan())) { - if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan() || z_bits.is_signaling_nan()) { fenv::raise_except_if_required(FE_INVALID); } - if (x_bits.is_quiet_nan()) { return FPBits::quiet_nan(x_bits.sign(), x_bits.get_mantissa()).get_val(); } - if (y_bits.is_quiet_nan()) { return FPBits::quiet_nan(y_bits.sign(), y_bits.get_mantissa()).get_val(); } - if (z_bits.is_quiet_nan()) { return FPBits::quiet_nan(z_bits.sign(), z_bits.get_mantissa()).get_val(); } + if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan() || z_bits.is_signaling_nan()) + { + fenv::raise_except_if_required(FE_INVALID); + } + if (x_bits.is_quiet_nan()) + { + return FPBits::quiet_nan(x_bits.sign(), x_bits.get_mantissa()).get_val(); + } + if (y_bits.is_quiet_nan()) + { + return FPBits::quiet_nan(y_bits.sign(), y_bits.get_mantissa()).get_val(); + } + if (z_bits.is_quiet_nan()) + { + return FPBits::quiet_nan(z_bits.sign(), z_bits.get_mantissa()).get_val(); + } return FPBits::quiet_nan().get_val(); } - if (CCM_UNLIKELY(x == T(0) || y == T(0) || z == T(0))) { return (x * y) + z; } + if (CCM_UNLIKELY(x == T(0) || y == T(0) || z == T(0))) + { + return (x * y) + z; + } int x_exp = 0; int y_exp = 0; @@ -548,7 +613,10 @@ namespace ccm::support::fp if (CCM_UNLIKELY(x_exp == FPBits::max_biased_exponent || y_exp == FPBits::max_biased_exponent || z_exp == FPBits::max_biased_exponent)) { - if (CCM_UNLIKELY(x_exp != FPBits::max_biased_exponent && y_exp != FPBits::max_biased_exponent && z_bits.is_inf())) { return z; } + if (CCM_UNLIKELY(x_exp != FPBits::max_biased_exponent && y_exp != FPBits::max_biased_exponent && z_bits.is_inf())) + { + return z; + } return (x * y) + z; } @@ -569,38 +637,49 @@ namespace ccm::support::fp { sticky_bits = shift_right_with_sticky(z_lsb_exp - prod_lsb_exp, prod_mant); prod_lsb_exp = z_lsb_exp; - } - else if (z_lsb_exp < prod_lsb_exp) + } else if (z_lsb_exp < prod_lsb_exp) { z_shifted = true; sticky_bits = shift_right_with_sticky(prod_lsb_exp - z_lsb_exp, z_mant); } - if (prod_sign == z_sign) { prod_mant += z_mant; } - else + if (prod_sign == z_sign) + { + prod_mant += z_mant; + } else { if (prod_mant >= z_mant) { - if (z_shifted && sticky_bits) { ++z_mant; } + if (z_shifted && sticky_bits) + { + ++z_mant; + } prod_mant -= z_mant; - } - else + } else { - if (!z_shifted && sticky_bits) { ++prod_mant; } + if (!z_shifted && sticky_bits) + { + ++prod_mant; + } prod_mant = z_mant - prod_mant; prod_sign = z_sign; } } - if (prod_mant == TmpResultType(0)) { prod_sign = fenv::get_rounding_mode() == FE_DOWNWARD ? Sign::NEG : Sign::POS; } + if (prod_mant == TmpResultType(0)) + { + prod_sign = fenv::get_rounding_mode() == FE_DOWNWARD ? Sign::NEG : Sign::POS; + } WideFloat result(prod_sign, prod_lsb_exp - FPBits::exponent_bias, prod_mant); - if (sticky_bits) { result.mantissa |= TmpResultType(1); } + if (sticky_bits) + { + result.mantissa |= TmpResultType(1); + } return result.template generic_as(); } - template - [[nodiscard]] constexpr float software_fmaf(float x, float y, float z) + template [[nodiscard]] constexpr float software_fmaf(float x, float y, float z) { // The fixed binary32 kernel forms the product exactly in a wide accumulator and rounds // once, so it is correctly rounded for every rounding mode. A "fast" double-precision @@ -615,46 +694,58 @@ namespace ccm::support::fp // libc's generic FMA (libc/src/__support/FPUtil/generic/FMA.h). This quiet variant is used by // internal error-free transforms and deliberately suppresses public FE_INEXACT / range flag // signaling while still producing the exact fused result under the active rounding mode. - template - [[nodiscard]] constexpr std::enable_if_t, T> generic_fma(T x, T y, T z) + template [[nodiscard]] constexpr std::enable_if_t, T> generic_fma(T x, T y, T z) { - if constexpr (std::is_same_v) { return fma_internal::software_fmaf(x, y, z); } - else if constexpr (std::is_same_v) { return fma_internal::fixed_fma(x, y, z); } - else + if constexpr (std::is_same_v) + { + return fma_internal::software_fmaf(x, y, z); + } else if constexpr (std::is_same_v) + { + return fma_internal::fixed_fma(x, y, z); + } else { return fma_internal::wide_fma(x, y, z); } } - template - [[nodiscard]] constexpr std::enable_if_t, T> public_fma(T x, T y, T z) + template [[nodiscard]] constexpr std::enable_if_t, T> public_fma(T x, T y, T z) { - if constexpr (std::is_same_v) { return fma_internal::software_fmaf(x, y, z); } - else if constexpr (std::is_same_v) { return fma_internal::fixed_fma(x, y, z); } - else + if constexpr (std::is_same_v) + { + return fma_internal::software_fmaf(x, y, z); + } else if constexpr (std::is_same_v) + { + return fma_internal::fixed_fma(x, y, z); + } else { return fma_internal::wide_fma(x, y, z); } } - template > - struct dispatch_fma_constexpr_builtin + template > struct dispatch_fma_constexpr_builtin { - static constexpr T tonearest(T /*unused*/, T /*unused*/, T /*unused*/) { return T{}; } + static constexpr T tonearest(T /*unused*/, T /*unused*/, T /*unused*/) + { + return T{}; + } }; - template - struct dispatch_fma_constexpr_builtin + template struct dispatch_fma_constexpr_builtin { - static constexpr T tonearest(T x, T y, T z) { return ccm::builtin::fma_ct(x, y, z); } + static constexpr T tonearest(T x, T y, T z) + { + return ccm::builtin::fma_ct(x, y, z); + } }; - template - [[nodiscard]] constexpr std::enable_if_t, T> dispatch_fma(T x, T y, T z) + template [[nodiscard]] constexpr std::enable_if_t, T> dispatch_fma(T x, T y, T z) { if constexpr (ccm::builtin::has_constexpr_fma) { - if (fenv::constant_eval_rounding_mode() == FE_TONEAREST) { return dispatch_fma_constexpr_builtin::tonearest(x, y, z); } + if (fenv::constant_eval_rounding_mode() == FE_TONEAREST) + { + return dispatch_fma_constexpr_builtin::tonearest(x, y, z); + } } if (!is_constant_evaluated()) @@ -665,20 +756,25 @@ namespace ccm::support::fp // yielding wrong directed-mode results. Fall back to the correctly-rounded software path. if constexpr (ccm::builtin::runtime_builtin_fma_trusted) { - if (fenv::get_rounding_mode() == FE_TONEAREST) { return builtin::fma_rt(x, y, z); } + if (fenv::get_rounding_mode() == FE_TONEAREST) + { + return builtin::fma_rt(x, y, z); + } } } return public_fma(x, y, z); } - template - [[nodiscard]] std::enable_if_t, T> dispatch_runtime_fma(T x, T y, T z) noexcept + template [[nodiscard]] std::enable_if_t, T> dispatch_runtime_fma(T x, T y, T z) noexcept { // See dispatch_fma: only trust the native FMA instruction under round-to-nearest. if constexpr (ccm::builtin::runtime_builtin_fma_trusted) { - if (fenv::get_rounding_mode() == FE_TONEAREST) { return builtin::fma_rt(x, y, z); } + if (fenv::get_rounding_mode() == FE_TONEAREST) + { + return builtin::fma_rt(x, y, z); + } } return public_fma(x, y, z); } diff --git a/include/ccmath/internal/support/fp/fp_bits.hpp b/include/ccmath/internal/support/fp/fp_bits.hpp index e705d479..d2931ebc 100644 --- a/include/ccmath/internal/support/fp/fp_bits.hpp +++ b/include/ccmath/internal/support/fp/fp_bits.hpp @@ -43,13 +43,10 @@ namespace ccm::support::fp /** * @brief Defines the layout of a floating point in memory and all of its parts along with the associated storage type. */ - template - struct FPLayout - { - }; + template struct FPLayout + {}; - template <> - struct FPLayout + template <> struct FPLayout { using storage_type = std::uint32_t; static constexpr std::int_fast32_t sign_length = 1; @@ -58,8 +55,7 @@ namespace ccm::support::fp static constexpr std::int_fast32_t fraction_length = significand_length; }; - template <> - struct FPLayout + template <> struct FPLayout { using storage_type = std::uint64_t; static constexpr std::int_fast32_t sign_length = 1; @@ -68,8 +64,7 @@ namespace ccm::support::fp static constexpr std::int_fast32_t fraction_length = significand_length; }; - template <> - struct FPLayout + template <> struct FPLayout { // Special case for 96-bit long double on x86 #if __SIZEOF_LONG_DOUBLE__ == 12 @@ -83,8 +78,7 @@ namespace ccm::support::fp static constexpr std::int_fast32_t fraction_length = significand_length - 1; }; - template <> - struct FPLayout + template <> struct FPLayout { using storage_type = types::uint128_t; static constexpr std::int_fast32_t sign_length = 1; @@ -93,8 +87,7 @@ namespace ccm::support::fp static constexpr std::int_fast32_t fraction_length = significand_length; }; - template - struct FPStorage : FPLayout + template struct FPStorage : FPLayout { using BASE = FPLayout; @@ -194,20 +187,31 @@ namespace ccm::support::fp * @brief A strongly typed integer that prevents mixing and matching integers with different semantics. * @tparam T The type of the integer. */ - template - struct TypedInt // NOLINT(cppcoreguidelines-special-member-functions) + template struct TypedInt // NOLINT(cppcoreguidelines-special-member-functions) { using value_type = T; constexpr explicit TypedInt(T value) : value(value) {} constexpr TypedInt(const TypedInt & value) = default; constexpr TypedInt & operator=(const TypedInt & value) = default; - constexpr explicit operator T() const { return value; } + constexpr explicit operator T() const + { + return value; + } - [[nodiscard]] constexpr storage_type to_storage_type() const { return storage_type(value); } + [[nodiscard]] constexpr storage_type to_storage_type() const + { + return storage_type(value); + } - friend constexpr bool operator==(TypedInt a, TypedInt b) { return a.value == b.value; } - friend constexpr bool operator!=(TypedInt a, TypedInt b) { return a.value != b.value; } + friend constexpr bool operator==(TypedInt a, TypedInt b) + { + return a.value == b.value; + } + friend constexpr bool operator!=(TypedInt a, TypedInt b) + { + return a.value != b.value; + } protected: T value; @@ -224,15 +228,30 @@ namespace ccm::support::fp using BASE = TypedInt; using BASE::BASE; - static constexpr auto subnormal() { return Exponent(-exponent_bias); } + static constexpr auto subnormal() + { + return Exponent(-exponent_bias); + } - static constexpr auto min() { return Exponent(1 - exponent_bias); } + static constexpr auto min() + { + return Exponent(1 - exponent_bias); + } - static constexpr auto zero() { return Exponent(0); } + static constexpr auto zero() + { + return Exponent(0); + } - static constexpr auto max() { return Exponent(exponent_bias); } + static constexpr auto max() + { + return Exponent(exponent_bias); + } - static constexpr auto inf() { return Exponent(exponent_bias + 1); } + static constexpr auto inf() + { + return Exponent(exponent_bias + 1); + } }; /** @@ -254,7 +273,10 @@ namespace ccm::support::fp * @brief Cast operator to convert from BiasedExponent to Exponent. */ // NOLINTNEXTLINE(google-explicit-constructor) - constexpr operator Exponent() const { return Exponent(static_cast(BASE::value) - exponent_bias); } + constexpr operator Exponent() const + { + return Exponent(static_cast(BASE::value) - exponent_bias); + } constexpr BiasedExponent & operator++() { @@ -288,31 +310,62 @@ namespace ccm::support::fp using BASE::BASE; friend constexpr Significand operator|(const Significand a, const Significand b) - { return Significand(storage_type(a.to_storage_type() | b.to_storage_type())); } + { + return Significand(storage_type(a.to_storage_type() | b.to_storage_type())); + } friend constexpr Significand operator^(const Significand a, const Significand b) - { return Significand(storage_type(a.to_storage_type() ^ b.to_storage_type())); } + { + return Significand(storage_type(a.to_storage_type() ^ b.to_storage_type())); + } - friend constexpr Significand operator>>(const Significand a, int shift) { return Significand(storage_type(a.to_storage_type() >> shift)); } + friend constexpr Significand operator>>(const Significand a, int shift) + { + return Significand(storage_type(a.to_storage_type() >> shift)); + } - static constexpr auto zero() { return Significand(storage_type(0)); } + static constexpr auto zero() + { + return Significand(storage_type(0)); + } - static constexpr auto lsb() { return Significand(storage_type(1)); } + static constexpr auto lsb() + { + return Significand(storage_type(1)); + } - static constexpr auto msb() { return Significand(storage_type(1) << (significand_length - 1)); } + static constexpr auto msb() + { + return Significand(storage_type(1) << (significand_length - 1)); + } - static constexpr auto bits_all_ones() { return Significand(significand_mask); } + static constexpr auto bits_all_ones() + { + return Significand(significand_mask); + } }; - static constexpr storage_type encode(BiasedExponent exp) { return (exp.to_storage_type() << significand_length) & exponent_mask; } + static constexpr storage_type encode(BiasedExponent exp) + { + return (exp.to_storage_type() << significand_length) & exponent_mask; + } - static constexpr storage_type encode(Significand value) { return value.to_storage_type() & significand_mask; } + static constexpr storage_type encode(Significand value) + { + return value.to_storage_type() & significand_mask; + } - static constexpr storage_type encode(BiasedExponent exp, Significand sig) { return encode(exp) | encode(sig); } + static constexpr storage_type encode(BiasedExponent exp, Significand sig) + { + return encode(exp) | encode(sig); + } static constexpr storage_type encode(types::Sign sign, BiasedExponent exp, Significand sig) { - if (sign.is_neg()) { return sign_mask | encode(exp, sig); } + if (sign.is_neg()) + { + return sign_mask | encode(exp, sig); + } return encode(exp, sig); } @@ -323,18 +376,32 @@ namespace ccm::support::fp /// Observer Functions - [[nodiscard]] constexpr storage_type exp_bits() const { return bits & exponent_mask; } + [[nodiscard]] constexpr storage_type exp_bits() const + { + return bits & exponent_mask; + } - [[nodiscard]] constexpr storage_type sig_bits() const { return bits & significand_mask; } + [[nodiscard]] constexpr storage_type sig_bits() const + { + return bits & significand_mask; + } - [[nodiscard]] constexpr storage_type exp_sig_bits() const { return bits & exponent_significand_mask; } + [[nodiscard]] constexpr storage_type exp_sig_bits() const + { + return bits & exponent_significand_mask; + } /// Parts of the floating point number [[nodiscard]] constexpr BiasedExponent biased_exponent() const - { return BiasedExponent(static_cast(exp_bits() >> significand_length)); } + { + return BiasedExponent(static_cast(exp_bits() >> significand_length)); + } - constexpr void set_biased_exponent(BiasedExponent biased) { bits = merge(bits, encode(biased), exponent_mask); } + constexpr void set_biased_exponent(BiasedExponent biased) + { + bits = merge(bits, encode(biased), exponent_mask); + } /** * @brief The bits of the floating point number represented as an unsigned integer. @@ -342,10 +409,16 @@ namespace ccm::support::fp storage_type bits{}; public: - [[nodiscard]] constexpr types::Sign sign() const { return (bits & sign_mask) ? types::Sign::NEG : types::Sign::POS; } + [[nodiscard]] constexpr types::Sign sign() const + { + return (bits & sign_mask) ? types::Sign::NEG : types::Sign::POS; + } constexpr void set_sign(types::Sign signVal) { - if (sign() != signVal) { bits ^= sign_mask; } + if (sign() != signVal) + { + bits ^= sign_mask; + } } }; @@ -354,8 +427,7 @@ namespace ccm::support::fp * * It facilitates the construction, modification, and observation of values manipulated as 'storage_type'. */ - template - struct FPRepSem : FPStorage + template struct FPRepSem : FPStorage { using BASE = FPStorage; using BASE::fraction_length; @@ -375,48 +447,99 @@ namespace ccm::support::fp public: /// Builder Functions - static constexpr RetT zero(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::subnormal(), Significand::zero())); } + static constexpr RetT zero(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::subnormal(), Significand::zero())); + } - static constexpr RetT one(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::zero(), Significand::zero())); } + static constexpr RetT one(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::zero(), Significand::zero())); + } - static constexpr RetT min_subnormal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::subnormal(), Significand::lsb())); } + static constexpr RetT min_subnormal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::subnormal(), Significand::lsb())); + } static constexpr RetT max_subnormal(types::Sign sign = types::Sign::POS) - { return RetT(encode(sign, Exponent::subnormal(), Significand::bits_all_ones())); } + { + return RetT(encode(sign, Exponent::subnormal(), Significand::bits_all_ones())); + } - static constexpr RetT min_normal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::min(), Significand::zero())); } + static constexpr RetT min_normal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::min(), Significand::zero())); + } - static constexpr RetT max_normal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::max(), Significand::bits_all_ones())); } + static constexpr RetT max_normal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::max(), Significand::bits_all_ones())); + } - static constexpr RetT inf(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::inf(), Significand::zero())); } + static constexpr RetT inf(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::inf(), Significand::zero())); + } static constexpr RetT signaling_nan(types::Sign sign = types::Sign::POS, storage_type v = 0) - { return RetT(encode(sign, Exponent::inf(), (v ? Significand(v) : (Significand::msb() >> 1)))); } + { + return RetT(encode(sign, Exponent::inf(), (v ? Significand(v) : (Significand::msb() >> 1)))); + } static constexpr RetT quiet_nan(types::Sign sign = types::Sign::POS, storage_type v = 0) - { return RetT(encode(sign, Exponent::inf(), Significand::msb() | Significand(v))); } + { + return RetT(encode(sign, Exponent::inf(), Significand::msb() | Significand(v))); + } /// Observer Functions - [[nodiscard]] constexpr bool is_zero() const { return exp_sig_bits() == 0; } + [[nodiscard]] constexpr bool is_zero() const + { + return exp_sig_bits() == 0; + } - [[nodiscard]] constexpr bool is_nan() const { return exp_sig_bits() > encode(Exponent::inf(), Significand::zero()); } + [[nodiscard]] constexpr bool is_nan() const + { + return exp_sig_bits() > encode(Exponent::inf(), Significand::zero()); + } - [[nodiscard]] constexpr bool is_quiet_nan() const { return exp_sig_bits() >= encode(Exponent::inf(), Significand::msb()); } + [[nodiscard]] constexpr bool is_quiet_nan() const + { + return exp_sig_bits() >= encode(Exponent::inf(), Significand::msb()); + } - [[nodiscard]] constexpr bool is_signaling_nan() const { return is_nan() && !is_quiet_nan(); } + [[nodiscard]] constexpr bool is_signaling_nan() const + { + return is_nan() && !is_quiet_nan(); + } - [[nodiscard]] constexpr bool is_inf() const { return exp_sig_bits() == encode(Exponent::inf(), Significand::zero()); } + [[nodiscard]] constexpr bool is_inf() const + { + return exp_sig_bits() == encode(Exponent::inf(), Significand::zero()); + } - [[nodiscard]] constexpr bool is_finite() const { return exp_bits() != encode(Exponent::inf()); } + [[nodiscard]] constexpr bool is_finite() const + { + return exp_bits() != encode(Exponent::inf()); + } - [[nodiscard]] constexpr bool is_subnormal() const { return exp_bits() == encode(Exponent::subnormal()); } + [[nodiscard]] constexpr bool is_subnormal() const + { + return exp_bits() == encode(Exponent::subnormal()); + } - [[nodiscard]] constexpr bool is_normal() const { return is_finite() && !is_subnormal(); } + [[nodiscard]] constexpr bool is_normal() const + { + return is_finite() && !is_subnormal(); + } constexpr RetT next_toward_inf() const { - if (is_finite()) { return RetT(bits + storage_type(1)); } + if (is_finite()) + { + return RetT(bits + storage_type(1)); + } return RetT(bits); } @@ -429,7 +552,10 @@ namespace ccm::support::fp */ constexpr storage_type get_explicit_mantissa() const { - if (is_subnormal()) { return sig_bits(); } + if (is_subnormal()) + { + return sig_bits(); + } return (storage_type(1) << BASE::significand_length) | sig_bits(); } }; @@ -437,8 +563,7 @@ namespace ccm::support::fp /** * @brief Template specialization for the X86 Extended Precision floating point type. */ - template - struct FPRepSem : FPStorage + template struct FPRepSem : FPStorage { using BASE = FPStorage; using BASE::fraction_length; @@ -466,20 +591,40 @@ namespace ccm::support::fp public: /// Builder Functions - static constexpr RetT zero(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::subnormal(), Significand::zero())); } + static constexpr RetT zero(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::subnormal(), Significand::zero())); + } - static constexpr RetT one(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::zero(), Significand::msb())); } + static constexpr RetT one(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::zero(), Significand::msb())); + } - static constexpr RetT min_subnormal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::subnormal(), Significand::lsb())); } + static constexpr RetT min_subnormal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::subnormal(), Significand::lsb())); + } static constexpr RetT max_subnormal(types::Sign sign = types::Sign::POS) - { return RetT(encode(sign, Exponent::subnormal(), Significand::bits_all_ones() ^ Significand::msb())); } + { + return RetT(encode(sign, Exponent::subnormal(), Significand::bits_all_ones() ^ Significand::msb())); + } - static constexpr RetT min_normal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::min(), Significand::msb())); } + static constexpr RetT min_normal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::min(), Significand::msb())); + } - static constexpr RetT max_normal(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::max(), Significand::bits_all_ones())); } + static constexpr RetT max_normal(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::max(), Significand::bits_all_ones())); + } - static constexpr RetT inf(types::Sign sign = types::Sign::POS) { return RetT(encode(sign, Exponent::inf(), Significand::msb())); } + static constexpr RetT inf(types::Sign sign = types::Sign::POS) + { + return RetT(encode(sign, Exponent::inf(), Significand::msb())); + } static constexpr RetT signaling_nan(types::Sign sign = types::Sign::POS, storage_type v = 0) { @@ -488,11 +633,16 @@ namespace ccm::support::fp } static constexpr RetT quiet_nan(types::Sign sign = types::Sign::POS, storage_type v = 0) - { return RetT(encode(sign, Exponent::inf(), Significand::msb() | (Significand::msb() >> 1) | Significand(v))); } + { + return RetT(encode(sign, Exponent::inf(), Significand::msb() | (Significand::msb() >> 1) | Significand(v))); + } /// Observer Functions - [[nodiscard]] constexpr bool is_zero() const { return exp_sig_bits() == 0; } + [[nodiscard]] constexpr bool is_zero() const + { + return exp_sig_bits() == 0; + } [[nodiscard]] constexpr bool is_nan() const { @@ -518,25 +668,48 @@ namespace ccm::support::fp * * This can be simplified to the following logic: */ - if (exp_bits() == encode(Exponent::inf())) { return !is_inf(); } - if (exp_bits() != encode(Exponent::subnormal())) { return (sig_bits() & encode(Significand::msb())) == 0; } + if (exp_bits() == encode(Exponent::inf())) + { + return !is_inf(); + } + if (exp_bits() != encode(Exponent::subnormal())) + { + return (sig_bits() & encode(Significand::msb())) == 0; + } return false; } [[nodiscard]] constexpr bool is_quiet_nan() const - { return exp_sig_bits() >= encode(Exponent::inf(), Significand::msb() | (Significand::msb() >> 1)); } + { + return exp_sig_bits() >= encode(Exponent::inf(), Significand::msb() | (Significand::msb() >> 1)); + } - [[nodiscard]] constexpr bool is_signaling_nan() const { return is_nan() && !is_quiet_nan(); } + [[nodiscard]] constexpr bool is_signaling_nan() const + { + return is_nan() && !is_quiet_nan(); + } - [[nodiscard]] constexpr bool is_inf() const { return exp_sig_bits() == encode(Exponent::inf(), Significand::msb()); } + [[nodiscard]] constexpr bool is_inf() const + { + return exp_sig_bits() == encode(Exponent::inf(), Significand::msb()); + } - [[nodiscard]] constexpr bool is_finite() const { return !is_inf() && !is_nan(); } + [[nodiscard]] constexpr bool is_finite() const + { + return !is_inf() && !is_nan(); + } - [[nodiscard]] constexpr bool is_subnormal() const { return exp_bits() == encode(Exponent::subnormal()); } + [[nodiscard]] constexpr bool is_subnormal() const + { + return exp_bits() == encode(Exponent::subnormal()); + } [[nodiscard]] constexpr bool is_normal() const { - if (const auto exp = exp_bits(); exp == encode(Exponent::subnormal()) || exp == encode(Exponent::inf())) { return false; } + if (const auto exp = exp_bits(); exp == encode(Exponent::subnormal()) || exp == encode(Exponent::inf())) + { + return false; + } return get_implicit_bit(); } @@ -544,15 +717,27 @@ namespace ccm::support::fp { if (is_finite()) { - if (exp_sig_bits() == max_normal().uintval()) { return inf(sign()); } - if (exp_sig_bits() == max_subnormal().uintval()) { return min_normal(sign()); } - if (sig_bits() == significand_mask) { return RetT(encode(sign(), ++biased_exponent(), Significand::zero())); } + if (exp_sig_bits() == max_normal().uintval()) + { + return inf(sign()); + } + if (exp_sig_bits() == max_subnormal().uintval()) + { + return min_normal(sign()); + } + if (sig_bits() == significand_mask) + { + return RetT(encode(sign(), ++biased_exponent(), Significand::zero())); + } return RetT(bits + static_cast(1)); } return RetT(bits); } - [[nodiscard]] constexpr storage_type get_explicit_mantissa() const { return sig_bits(); } + [[nodiscard]] constexpr storage_type get_explicit_mantissa() const + { + return sig_bits(); + } /** * @brief Checks if the implicit bit is set. @@ -563,7 +748,10 @@ namespace ccm::support::fp * * @attention This function is specific to FPRepSem. */ - [[nodiscard]] constexpr bool get_implicit_bit() const { return static_cast(bits & EXPLICIT_BIT_MASK); } + [[nodiscard]] constexpr bool get_implicit_bit() const + { + return static_cast(bits & EXPLICIT_BIT_MASK); + } /** * @brief Sets the implicit bit to the specified value. @@ -577,7 +765,10 @@ namespace ccm::support::fp */ constexpr void set_implicit_bit(bool implicitVal) { - if (get_implicit_bit() != implicitVal) { bits ^= EXPLICIT_BIT_MASK; } + if (get_implicit_bit() != implicitVal) + { + bits ^= EXPLICIT_BIT_MASK; + } } }; @@ -593,8 +784,7 @@ namespace ccm::support::fp * When the specific C++ floating point type is not a concern, 'FPRep' can be used by specifying the 'FPType' directly. * Example: FPRep::zero() // returns an FPRep<> */ - template - struct FPRepImpl : FPRepSem + template struct FPRepImpl : FPRepSem { using BASE = FPRepSem; using storage_type = typename BASE::storage_type; @@ -629,12 +819,24 @@ namespace ccm::support::fp /// Comparison Operators - constexpr friend bool operator==(FPRepImpl a, FPRepImpl b) { return a.uintval() == b.uintval(); } - constexpr friend bool operator!=(FPRepImpl a, FPRepImpl b) { return a.uintval() != b.uintval(); } + constexpr friend bool operator==(FPRepImpl a, FPRepImpl b) + { + return a.uintval() == b.uintval(); + } + constexpr friend bool operator!=(FPRepImpl a, FPRepImpl b) + { + return a.uintval() != b.uintval(); + } // Representation - constexpr storage_type uintval() const { return bits & fp_bits_mask; } - constexpr void set_uintval(storage_type value) { bits = (value & fp_bits_mask); } + constexpr storage_type uintval() const + { + return bits & fp_bits_mask; + } + constexpr void set_uintval(storage_type value) + { + bits = (value & fp_bits_mask); + } /// Builder Functions @@ -650,7 +852,10 @@ namespace ccm::support::fp /// Modifier Functions - constexpr RetT abs() const { return RetT(bits & BASE::exponent_significand_mask); } + constexpr RetT abs() const + { + return RetT(bits & BASE::exponent_significand_mask); + } /// Observer Functions @@ -666,19 +871,35 @@ namespace ccm::support::fp using BASE::next_toward_inf; using BASE::sign; - [[nodiscard]] constexpr bool is_inf_or_nan() const { return !is_finite(); } + [[nodiscard]] constexpr bool is_inf_or_nan() const + { + return !is_finite(); + } - [[nodiscard]] constexpr bool is_neg() const { return sign().is_neg(); } + [[nodiscard]] constexpr bool is_neg() const + { + return sign().is_neg(); + } - [[nodiscard]] constexpr bool is_pos() const { return sign().is_pos(); } + [[nodiscard]] constexpr bool is_pos() const + { + return sign().is_pos(); + } [[nodiscard]] constexpr std::uint16_t get_biased_exponent() const - { return static_cast(static_cast(BASE::biased_exponent())); } + { + return static_cast(static_cast(BASE::biased_exponent())); + } constexpr void set_biased_exponent(storage_type biased) - { BASE::set_biased_exponent(BiasedExponent(static_cast(static_cast(biased)))); } + { + BASE::set_biased_exponent(BiasedExponent(static_cast(static_cast(biased)))); + } - [[nodiscard]] constexpr int get_exponent() const { return static_cast(Exponent(BASE::biased_exponent())); } + [[nodiscard]] constexpr int get_exponent() const + { + return static_cast(Exponent(BASE::biased_exponent())); + } /** * @brief Retrieves the explicit exponent value, adjusted for subnormal and zero values. @@ -693,24 +914,39 @@ namespace ccm::support::fp [[nodiscard]] constexpr int get_explicit_exponent() const { Exponent exponent(BASE::biased_exponent()); - if (is_zero()) { exponent = Exponent::zero(); } - if (exponent == Exponent::subnormal()) { exponent = Exponent::min(); } + if (is_zero()) + { + exponent = Exponent::zero(); + } + if (exponent == Exponent::subnormal()) + { + exponent = Exponent::min(); + } return static_cast(exponent); } - constexpr storage_type get_mantissa() const { return bits & fraction_mask; } + constexpr storage_type get_mantissa() const + { + return bits & fraction_mask; + } /** * @brief Sets the mantissa value of the internal bits. * @param mantVal The mantissa value to set. */ - constexpr void set_mantissa(storage_type mantVal) { bits = BASE::merge(bits, mantVal, fraction_mask); } + constexpr void set_mantissa(storage_type mantVal) + { + bits = BASE::merge(bits, mantVal, fraction_mask); + } /** * @brief Sets the significand value of the internal bits. * @param sigVal The significand value to set. */ - constexpr void set_significand(storage_type sigVal) { bits = BASE::merge(bits, sigVal, significand_mask); } + constexpr void set_significand(storage_type sigVal) + { + bits = BASE::merge(bits, sigVal, significand_mask); + } /** * @brief Unsafe function that creates a floating point representation that simply packs the sign, biased exponent, and mantissa values without @@ -724,7 +960,9 @@ namespace ccm::support::fp * This function will not check for its validity and assumes the caller has set it correctly. */ static constexpr RetT create_value(types::Sign sign, storage_type biased_exp, storage_type mantissa) - { return RetT(encode(sign, BiasedExponent(static_cast(biased_exp)), Significand(mantissa))); } + { + return RetT(encode(sign, BiasedExponent(static_cast(biased_exp)), Significand(mantissa))); + } /** * @brief Converts an integer number and unbiased exponent to a proper float type. @@ -754,8 +992,7 @@ namespace ccm::support::fp // Mask will remove implicit number bit result.set_significand(number); result.set_biased_exponent(expo + 1); - } - else + } else { result.set_significand(number >> -expo); } @@ -775,7 +1012,10 @@ namespace ccm::support::fp using storage_type = typename BASE::storage_type; using BASE::BASE; - constexpr explicit operator storage_type() const { return BASE::uintval(); } + constexpr explicit operator storage_type() const + { + return BASE::uintval(); + } }; } // namespace internal @@ -785,19 +1025,30 @@ namespace ccm::support::fp * @tparam T The type to identify the FPType for. * @return The FPType corresponding to the C++ type T. */ - template - static constexpr FPType get_fp_type() + template static constexpr FPType get_fp_type() { using UnqualT = std::remove_cv_t; - if constexpr (std::is_same_v && FLT_MANT_DIG == 24) { return FPType::eBinary32; } - else if constexpr (std::is_same_v && DBL_MANT_DIG == 53) { return FPType::eBinary64; } - else if constexpr (std::is_same_v) + if constexpr (std::is_same_v && FLT_MANT_DIG == 24) { - if constexpr (LDBL_MANT_DIG == 53) { return FPType::eBinary64; } // long double is the same as double - else if constexpr (LDBL_MANT_DIG == 64) { return FPType::eBinary80; } // long double is 80-bits - else if constexpr (LDBL_MANT_DIG == 113) { return FPType::eBinary128; } // long double is 128-bits - } - else + return FPType::eBinary32; + } else if constexpr (std::is_same_v && DBL_MANT_DIG == 53) + { + return FPType::eBinary64; + } else if constexpr (std::is_same_v) + { + if constexpr (LDBL_MANT_DIG == 53) + { + return FPType::eBinary64; + } // long double is the same as double + else if constexpr (LDBL_MANT_DIG == 64) + { + return FPType::eBinary80; + } // long double is 80-bits + else if constexpr (LDBL_MANT_DIG == 113) + { + return FPType::eBinary128; + } // long double is 128-bits + } else { static_assert(support::always_false, "Unsupported type"); } @@ -808,8 +1059,7 @@ namespace ccm::support::fp * @brief A generic class to manipulate C++ floating point formats. * @tparam T The floating point type to manipulate. */ - template - struct FPBits final : internal::FPRepImpl(), FPBits> + template struct FPBits final : internal::FPRepImpl(), FPBits> { static_assert(support::traits::ccm_is_floating_point_v, "FPBits instantiated with invalid type."); using BASE = internal::FPRepImpl(), FPBits>; @@ -817,13 +1067,16 @@ namespace ccm::support::fp constexpr FPBits() = default; - template - constexpr explicit FPBits(XType x) + template constexpr explicit FPBits(XType x) { using UnQual = typename std::remove_cv_t; - if constexpr (std::is_same_v) { BASE::bits = support::bit_cast(x); } - else if constexpr (std::is_same_v) { BASE::bits = x; } - else + if constexpr (std::is_same_v) + { + BASE::bits = support::bit_cast(x); + } else if constexpr (std::is_same_v) + { + BASE::bits = x; + } else { // Prevent accidental type promotion or conversion form being able to happen. Exact types only. static_assert(support::always_false); @@ -834,6 +1087,9 @@ namespace ccm::support::fp * @brief Converts the stored bits to a floating-point value. * @return The floating-point value of type T. */ - constexpr T get_val() const { return support::bit_cast(BASE::bits); } + constexpr T get_val() const + { + return support::bit_cast(BASE::bits); + } }; } // namespace ccm::support::fp diff --git a/include/ccmath/internal/support/fp/nearest_integer.hpp b/include/ccmath/internal/support/fp/nearest_integer.hpp index 3718a657..370cb81b 100644 --- a/include/ccmath/internal/support/fp/nearest_integer.hpp +++ b/include/ccmath/internal/support/fp/nearest_integer.hpp @@ -36,8 +36,14 @@ namespace ccm::support::fp constexpr float round_threshold = 0.5F; // Threshold for correcting rounding modes at runtime const float diff = x - r; // Correct for non-default rounding modes at runtime - if (CCM_UNLIKELY(diff > round_threshold)) { return r + round_increment; } - if (CCM_UNLIKELY(diff < -round_threshold)) { return r - round_increment; } + if (CCM_UNLIKELY(diff > round_threshold)) + { + return r + round_increment; + } + if (CCM_UNLIKELY(diff < -round_threshold)) + { + return r - round_increment; + } } return r; } @@ -60,8 +66,14 @@ namespace ccm::support::fp constexpr double round_threshold = 0.5F; // Threshold for correcting rounding modes at runtime const double diff = x - r; // Correct for non-default rounding modes at runtime - if (CCM_UNLIKELY(diff > round_threshold)) { return r + round_increment; } - if (CCM_UNLIKELY(diff < -round_threshold)) { return r - round_increment; } + if (CCM_UNLIKELY(diff > round_threshold)) + { + return r + round_increment; + } + if (CCM_UNLIKELY(diff < -round_threshold)) + { + return r - round_increment; + } } return r; } diff --git a/include/ccmath/internal/support/fp/ops/add.hpp b/include/ccmath/internal/support/fp/ops/add.hpp index 5852ad86..1387f895 100644 --- a/include/ccmath/internal/support/fp/ops/add.hpp +++ b/include/ccmath/internal/support/fp/ops/add.hpp @@ -10,5 +10,7 @@ namespace ccm::support::fp::op template constexpr std::enable_if_t && std::is_floating_point_v && sizeof(OutType) <= sizeof(InType), OutType> add(InType x, InType y) - { return add_or_sub(x, y); } + { + return add_or_sub(x, y); + } } // namespace ccm::support::fp::op diff --git a/include/ccmath/internal/support/fp/ops/add_or_sub.hpp b/include/ccmath/internal/support/fp/ops/add_or_sub.hpp index 47350e7b..a2a83f7a 100644 --- a/include/ccmath/internal/support/fp/ops/add_or_sub.hpp +++ b/include/ccmath/internal/support/fp/ops/add_or_sub.hpp @@ -51,7 +51,10 @@ namespace ccm::support::fp::op { if (x_bits.is_nan() || y_bits.is_nan()) { - if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan()) { fenv::raise_except_if_required(FE_INVALID); } + if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan()) + { + fenv::raise_except_if_required(FE_INVALID); + } if (x_bits.is_quiet_nan()) { @@ -86,7 +89,10 @@ namespace ccm::support::fp::op return OutFPBits::inf(x_bits.sign()).get_val(); } - if (y_bits.is_inf()) { return OutFPBits::inf(y_bits.sign()).get_val(); } + if (y_bits.is_inf()) + { + return OutFPBits::inf(y_bits.sign()).get_val(); + } if (x_bits.is_zero()) { @@ -95,7 +101,7 @@ namespace ccm::support::fp::op switch (fenv::get_rounding_mode()) { case FE_DOWNWARD: return OutFPBits::zero(types::Sign::NEG).get_val(); - default: return OutFPBits::zero(types::Sign::POS).get_val(); + default : return OutFPBits::zero(types::Sign::POS).get_val(); } } @@ -103,13 +109,19 @@ namespace ccm::support::fp::op // immediately back to InType before negating it, resulting in double // rounding. volatile InType tmp = y; - if constexpr (IsSub) { tmp = -tmp; } + if constexpr (IsSub) + { + tmp = -tmp; + } return static_cast(tmp); } if (y_bits.is_zero()) { volatile InType tmp = y; - if constexpr (IsSub) { tmp = -tmp; } + if constexpr (IsSub) + { + tmp = -tmp; + } return static_cast(tmp); } } @@ -122,19 +134,29 @@ namespace ccm::support::fp::op switch (fenv::get_rounding_mode()) { case FE_DOWNWARD: return OutFPBits::zero(types::Sign::NEG).get_val(); - default: return OutFPBits::zero(types::Sign::POS).get_val(); + default : return OutFPBits::zero(types::Sign::POS).get_val(); } } types::Sign result_sign = types::Sign::POS; - if (x_abs > y_abs) { result_sign = x_bits.sign(); } // NOLINT(bugprone-branch-clone) + if (x_abs > y_abs) + { + result_sign = x_bits.sign(); + } // NOLINT(bugprone-branch-clone) else if (x_abs < y_abs) { - if (is_effectively_add) { result_sign = y_bits.sign(); } - else if (y_bits.is_pos()) { result_sign = types::Sign::NEG; } + if (is_effectively_add) + { + result_sign = y_bits.sign(); + } else if (y_bits.is_pos()) + { + result_sign = types::Sign::NEG; + } + } else if (is_effectively_add) + { + result_sign = x_bits.sign(); } - else if (is_effectively_add) { result_sign = x_bits.sign(); } InFPBits max_bits(ccm::gen::max(x_abs, y_abs)); InFPBits min_bits(ccm::gen::min(x_abs, y_abs)); @@ -145,15 +167,16 @@ namespace ccm::support::fp::op { // min_bits must be subnormal too. - if (is_effectively_add) { result_mant = max_bits.get_mantissa() + min_bits.get_mantissa(); } - else + if (is_effectively_add) + { + result_mant = max_bits.get_mantissa() + min_bits.get_mantissa(); + } else { result_mant = max_bits.get_mantissa() - min_bits.get_mantissa(); } result_mant <<= guard_bits_length; - } - else + } else { InStorageType max_mant = max_bits.get_explicit_mantissa() << guard_bits_length; InStorageType min_mant = min_bits.get_explicit_mantissa() << guard_bits_length; @@ -162,17 +185,23 @@ namespace ccm::support::fp::op InStorageType aligned_min_mant = min_mant >> ccm::gen::min(alignment, result_mantissa_length); bool aligned_min_mant_sticky = false; - if (alignment <= 3) { aligned_min_mant_sticky = false; } - else if (alignment <= InFPBits::fraction_length + 3) { aligned_min_mant_sticky = (min_mant << (InFPBits::storage_length - alignment)) != 0; } - else + if (alignment <= 3) + { + aligned_min_mant_sticky = false; + } else if (alignment <= InFPBits::fraction_length + 3) + { + aligned_min_mant_sticky = (min_mant << (InFPBits::storage_length - alignment)) != 0; + } else { aligned_min_mant_sticky = true; } InStorageType min_mant_sticky(static_cast(aligned_min_mant_sticky)); - if (is_effectively_add) { result_mant = max_mant + (aligned_min_mant | min_mant_sticky); } - else + if (is_effectively_add) + { + result_mant = max_mant + (aligned_min_mant | min_mant_sticky); + } else { result_mant = max_mant - (aligned_min_mant | min_mant_sticky); } diff --git a/include/ccmath/internal/support/fp/ops/mul.hpp b/include/ccmath/internal/support/fp/ops/mul.hpp index 10697f7f..90ad86bd 100644 --- a/include/ccmath/internal/support/fp/ops/mul.hpp +++ b/include/ccmath/internal/support/fp/ops/mul.hpp @@ -48,7 +48,10 @@ namespace ccm::support::fp::op { if (x_bits.is_nan() || y_bits.is_nan()) { - if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan()) { fenv::raise_except_if_required(FE_INVALID); } + if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan()) + { + fenv::raise_except_if_required(FE_INVALID); + } if (x_bits.is_quiet_nan()) { diff --git a/include/ccmath/internal/support/fp/ops/sub.hpp b/include/ccmath/internal/support/fp/ops/sub.hpp index 71c25d7e..d699cc3f 100644 --- a/include/ccmath/internal/support/fp/ops/sub.hpp +++ b/include/ccmath/internal/support/fp/ops/sub.hpp @@ -10,5 +10,7 @@ namespace ccm::support::fp::op template constexpr std::enable_if_t && std::is_floating_point_v && sizeof(OutType) <= sizeof(InType), OutType> sub(InType x, InType y) - { return add_or_sub(x, y); } + { + return add_or_sub(x, y); + } } // namespace ccm::support::fp::op diff --git a/include/ccmath/internal/support/helpers/digit_to_int.hpp b/include/ccmath/internal/support/helpers/digit_to_int.hpp index 86a902cd..97ebb434 100644 --- a/include/ccmath/internal/support/helpers/digit_to_int.hpp +++ b/include/ccmath/internal/support/helpers/digit_to_int.hpp @@ -14,9 +14,18 @@ namespace ccm::support::helpers { constexpr int digit_to_int(int c) { - if ('0' <= c && c <= '9') { return (c - '0'); } - if ('A' <= c && c <= 'F') { return (c - 'A' + 10); } - if ('a' <= c && c <= 'f') { return (c - 'a' + 10); } + if ('0' <= c && c <= '9') + { + return (c - '0'); + } + if ('A' <= c && c <= 'F') + { + return (c - 'A' + 10); + } + if ('a' <= c && c <= 'f') + { + return (c - 'a' + 10); + } return 0; } } // namespace ccm::support::helpers diff --git a/include/ccmath/internal/support/helpers/exp10.hpp b/include/ccmath/internal/support/helpers/exp10.hpp index 5ee1363e..d4f4fe2d 100644 --- a/include/ccmath/internal/support/helpers/exp10.hpp +++ b/include/ccmath/internal/support/helpers/exp10.hpp @@ -77,10 +77,14 @@ namespace ccm::support::helpers } constexpr double exp10_double_impl(double exp) noexcept - { return ccm::internal::exp2_double(exp * LOG2_10); } + { + return ccm::internal::exp2_double(exp * LOG2_10); + } constexpr double exp10_double_double_impl(double exp) noexcept - { return exp10_double_impl(exp); } + { + return exp10_double_impl(exp); + } constexpr float exp10_float_impl(float exp) noexcept { @@ -92,17 +96,26 @@ namespace ccm::support::helpers const double t = static_cast(exp) * LOG2_10; // Outside the float-finite band defer to the float kernel, which raises FE_OVERFLOW // / FE_UNDERFLOW and sets errno at the float boundary rather than the double one. - if (t >= 128.0 || t < -149.0) { return ccm::internal::exp2_float(static_cast(t)); } + if (t >= 128.0 || t < -149.0) + { + return ccm::internal::exp2_float(static_cast(t)); + } return static_cast(ccm::internal::exp2_double(t)); } } // namespace impl constexpr double exp10_double(double exp) noexcept - { return impl::exp10_double_impl(exp); } + { + return impl::exp10_double_impl(exp); + } constexpr double exp10_double_double(double exp) noexcept - { return impl::exp10_double_double_impl(exp); } + { + return impl::exp10_double_double_impl(exp); + } constexpr float exp10_float(float exp) noexcept - { return impl::exp10_float_impl(exp); } + { + return impl::exp10_float_impl(exp); + } } // namespace ccm::support::helpers diff --git a/include/ccmath/internal/support/helpers/exp_helpers.hpp b/include/ccmath/internal/support/helpers/exp_helpers.hpp index 4071e6e3..8b738c1b 100644 --- a/include/ccmath/internal/support/helpers/exp_helpers.hpp +++ b/include/ccmath/internal/support/helpers/exp_helpers.hpp @@ -20,16 +20,13 @@ namespace ccm::support::helpers { #if FLT_EVAL_METHOD == 1 // check if the type is the same as float - template - inline constexpr bool check_excess_precision = std::is_same_v; + template inline constexpr bool check_excess_precision = std::is_same_v; #else // check if the type is the same as float or double - template - inline constexpr bool check_excess_precision = ccm::support::or_v, std::is_same>; + template inline constexpr bool check_excess_precision = ccm::support::or_v, std::is_same>; #endif - template - constexpr T narrow_eval(T x) + template constexpr T narrow_eval(T x) { #if FLT_EVAL_METHOD == 0 return x; diff --git a/include/ccmath/internal/support/helpers/internal_ldexp.hpp b/include/ccmath/internal/support/helpers/internal_ldexp.hpp index ecb3f4bd..c9b3d152 100644 --- a/include/ccmath/internal/support/helpers/internal_ldexp.hpp +++ b/include/ccmath/internal/support/helpers/internal_ldexp.hpp @@ -24,11 +24,13 @@ namespace ccm::support::helpers { - template - constexpr std::enable_if_t && std::is_integral_v, T> internal_ldexp(T x, U exp) + template constexpr std::enable_if_t && std::is_integral_v, T> internal_ldexp(T x, U exp) { fp::FPBits const bits(x); - if (CCM_UNLIKELY((exp == 0) || bits.is_zero() || bits.is_inf_or_nan())) { return x; } + if (CCM_UNLIKELY((exp == 0) || bits.is_zero() || bits.is_inf_or_nan())) + { + return x; + } constexpr int exponent_limit = fp::FPBits::max_biased_exponent + fp::FPBits::fraction_length + 1; static_assert(exponent_limit <= INT_MAX && -exponent_limit >= INT_MIN); diff --git a/include/ccmath/internal/support/integer_literals.hpp b/include/ccmath/internal/support/integer_literals.hpp index eb95e4de..823c37e6 100644 --- a/include/ccmath/internal/support/integer_literals.hpp +++ b/include/ccmath/internal/support/integer_literals.hpp @@ -26,23 +26,30 @@ namespace ccm::support { constexpr std::uint8_t operator""_u8(unsigned long long value) - { return static_cast(value); } + { + return static_cast(value); + } constexpr std::uint16_t operator""_u16(unsigned long long value) - { return static_cast(value); } + { + return static_cast(value); + } constexpr std::uint32_t operator""_u32(unsigned long long value) - { return static_cast(value); } + { + return static_cast(value); + } constexpr std::uint64_t operator""_u64(unsigned long long value) - { return static_cast(value); } + { + return static_cast(value); + } namespace internal { // Creates a T by reading digits from an array. - template - constexpr T accumulate(int base, const uint8_t * digits, size_t size) + template constexpr T accumulate(int base, const uint8_t * digits, size_t size) { T value{}; for (size_t i = 0; i < size; ++i) @@ -54,8 +61,7 @@ namespace ccm::support } // A static buffer to hold the digits for a T. - template - struct DigitBuffer + template struct DigitBuffer { static_assert(base == 2 || base == 10 || base == 16); // One character provides log2(base) bits. @@ -64,9 +70,18 @@ namespace ccm::support // for buffer allocation. static constexpr std::size_t calculate_bits_per_digit() { - if constexpr (base == 2) { return 1; } - if constexpr (base == 10) { return 3; } - if constexpr (base == 16) { return 4; } + if constexpr (base == 2) + { + return 1; + } + if constexpr (base == 10) + { + return 3; + } + if constexpr (base == 16) + { + return 4; + } return 0; } @@ -79,7 +94,10 @@ namespace ccm::support constexpr explicit DigitBuffer(std::string_view str) { - for (char const ch : str) { push(ch); } + for (char const ch : str) + { + push(ch); + } } CCM_DISABLE_MSVC_WARNING(4127) // MSVC treats is_alpha as a constant expression. It is not. @@ -90,9 +108,15 @@ namespace ccm::support const auto to_lower = [](char ch) { return static_cast(std::byte(ch) | std::byte(32)); }; const auto is_digit = [](char ch) { return ch >= '0' && ch <= '9'; }; const auto is_alpha = [](char ch) { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'); }; - if (is_digit(c)) { return static_cast(c - '0'); } + if (is_digit(c)) + { + return static_cast(c - '0'); + } - if (base > 10 && is_alpha(c)) { return static_cast(to_lower(c) - 'a' + 10); } + if (base > 10 && is_alpha(c)) + { + return static_cast(to_lower(c) - 'a' + 10); + } return INVALID_DIGIT; } CCM_RESTORE_MSVC_WARNING() @@ -120,11 +144,9 @@ namespace ccm::support // Generic implementation for native types (including __uint128_t or ExtInt // where available). - template - struct Parser + template struct Parser { - template - static constexpr T parse(std::string_view str) + template static constexpr T parse(std::string_view str) { const DigitBuffer buffer(str); return accumulate(base, buffer.digits.data(), buffer.size); @@ -136,21 +158,18 @@ namespace ccm::support // binary and hexadecimal formats, we read digits by chunks of 64 bits and // produce the Big Int internal representation directly. For decimal numbers, we // go the slow path and use slower Big Int arithmetic. - template - struct Parser> + template struct Parser> { using UIntT = ccm::types::UInt; - template - static constexpr UIntT parse(std::string_view str) + template static constexpr UIntT parse(std::string_view str) { const DigitBuffer buffer(str); if constexpr (base == 10) { // Slow path, we sum and multiply BigInt for each digit. return accumulate(base, buffer.digits.data(), buffer.size); - } - else + } else { // Fast path, we consume blocks of WordType and create the BigInt's // internal representation directly. @@ -174,8 +193,7 @@ namespace ccm::support }; // Detects the base of the number and dispatches to the right implementation. - template - constexpr T parse_with_prefix_internal(std::string_view view) + template constexpr T parse_with_prefix_internal(std::string_view view) { using P = Parser; @@ -191,10 +209,12 @@ namespace ccm::support return P::template parse<10>(view.data()); // NOLINT(bugprone-suspicious-stringview-data-usage) } - template - constexpr T parse_with_prefix(const char * ptr) + template constexpr T parse_with_prefix(const char * ptr) { - if (ptr == nullptr) { return T(); } + if (ptr == nullptr) + { + return T(); + } return parse_with_prefix_internal(std::string_view(ptr)); } @@ -202,16 +222,21 @@ namespace ccm::support } // namespace internal constexpr ccm::types::UInt<96> operator""_u96(const char * x) - { return internal::parse_with_prefix>(x); } + { + return internal::parse_with_prefix>(x); + } constexpr ccm::types::uint128_t operator""_u128(const char * x) - { return internal::parse_with_prefix(x); } + { + return internal::parse_with_prefix(x); + } constexpr auto operator""_u256(const char * x) - { return internal::parse_with_prefix>(x); } + { + return internal::parse_with_prefix>(x); + } - template - constexpr T parse_bigint_internal(std::string_view view) + template constexpr T parse_bigint_internal(std::string_view view) { if (view[0] == '-' || view[0] == '+') { @@ -221,10 +246,12 @@ namespace ccm::support return internal::parse_with_prefix(view); } - template - constexpr T parse_bigint(const char * ptr) + template constexpr T parse_bigint(const char * ptr) { - if (ptr == nullptr) { return T(); } + if (ptr == nullptr) + { + return T(); + } return parse_bigint_internal(std::string_view(ptr)); } diff --git a/include/ccmath/internal/support/limits.hpp b/include/ccmath/internal/support/limits.hpp index 293d5eb9..07032a8f 100644 --- a/include/ccmath/internal/support/limits.hpp +++ b/include/ccmath/internal/support/limits.hpp @@ -28,16 +28,24 @@ namespace ccm::support round_toward_neg_infinity = 3 }; - template > - class numeric_limits + template > class numeric_limits { public: using type = T; static constexpr const bool is_specialized = std::numeric_limits::is_specialized; - [[nodiscard]] static constexpr type min() noexcept { return std::numeric_limits::min(); } - [[nodiscard]] static constexpr type max() noexcept { return std::numeric_limits::max(); } - [[nodiscard]] static constexpr type lowest() noexcept { return std::numeric_limits::lowest(); } + [[nodiscard]] static constexpr type min() noexcept + { + return std::numeric_limits::min(); + } + [[nodiscard]] static constexpr type max() noexcept + { + return std::numeric_limits::max(); + } + [[nodiscard]] static constexpr type lowest() noexcept + { + return std::numeric_limits::lowest(); + } static constexpr const int digits = std::numeric_limits::digits; static constexpr const int digits10 = std::numeric_limits::digits10; @@ -46,8 +54,14 @@ namespace ccm::support static constexpr const bool is_integer = std::numeric_limits::is_integer; static constexpr const bool is_exact = std::numeric_limits::is_exact; static constexpr const int radix = std::numeric_limits::radix; - [[nodiscard]] static constexpr type epsilon() noexcept { return std::numeric_limits::epsilon(); } - [[nodiscard]] static constexpr type round_error() noexcept { return std::numeric_limits::round_error(); } + [[nodiscard]] static constexpr type epsilon() noexcept + { + return std::numeric_limits::epsilon(); + } + [[nodiscard]] static constexpr type round_error() noexcept + { + return std::numeric_limits::round_error(); + } static constexpr const int min_exponent = std::numeric_limits::min_exponent; static constexpr const int min_exponent10 = std::numeric_limits::min_exponent10; @@ -57,10 +71,22 @@ namespace ccm::support static constexpr const bool has_infinity = std::numeric_limits::has_infinity; static constexpr const bool has_quiet_NaN = std::numeric_limits::has_quiet_NaN; static constexpr const bool has_signaling_NaN = std::numeric_limits::has_signaling_NaN; - [[nodiscard]] static constexpr type infinity() noexcept { return std::numeric_limits::infinity(); } - [[nodiscard]] static constexpr type quiet_NaN() noexcept { return std::numeric_limits::quiet_NaN(); } - [[nodiscard]] static constexpr type signaling_NaN() noexcept { return std::numeric_limits::signaling_NaN(); } - [[nodiscard]] static constexpr type denorm_min() noexcept { return std::numeric_limits::denorm_min(); } + [[nodiscard]] static constexpr type infinity() noexcept + { + return std::numeric_limits::infinity(); + } + [[nodiscard]] static constexpr type quiet_NaN() noexcept + { + return std::numeric_limits::quiet_NaN(); + } + [[nodiscard]] static constexpr type signaling_NaN() noexcept + { + return std::numeric_limits::signaling_NaN(); + } + [[nodiscard]] static constexpr type denorm_min() noexcept + { + return std::numeric_limits::denorm_min(); + } static constexpr const bool is_iec559 = std::numeric_limits::is_iec559; static constexpr const bool is_bounded = std::numeric_limits::is_bounded; diff --git a/include/ccmath/internal/support/math_support.hpp b/include/ccmath/internal/support/math_support.hpp index 2caf7147..96919374 100644 --- a/include/ccmath/internal/support/math_support.hpp +++ b/include/ccmath/internal/support/math_support.hpp @@ -26,8 +26,7 @@ namespace ccm::support { // Returns whether 'a + b' overflows, the result is stored in 'res'. - template - [[nodiscard]] constexpr bool add_overflow(T a, T b, T & res) + template [[nodiscard]] constexpr bool add_overflow(T a, T b, T & res) { #if CCM_HAS_BUILTIN(__builtin_add_overflow) return __builtin_add_overflow(a, b, &res); @@ -45,20 +44,21 @@ namespace ccm::support { res = static_cast(usum); return usum < ua; - } - else + } else { constexpr UnsignedT SIGN_BIT = UnsignedT(1) << (std::numeric_limits::digits - 1); const bool overflow = ((~(ua ^ ub) & (ua ^ usum)) & SIGN_BIT) != 0; - if (!overflow) { res = static_cast(usum); } + if (!overflow) + { + res = static_cast(usum); + } return overflow; } #endif } // Returns whether 'a - b' overflows, the result is stored in 'res'. - template - [[nodiscard]] constexpr bool sub_overflow(T a, T b, T & res) + template [[nodiscard]] constexpr bool sub_overflow(T a, T b, T & res) { #if CCM_HAS_BUILTIN(__builtin_sub_overflow) return __builtin_sub_overflow(a, b, &res); @@ -76,25 +76,29 @@ namespace ccm::support { res = static_cast(udiff); return ua < ub; - } - else + } else { constexpr UnsignedT SIGN_BIT = UnsignedT(1) << (std::numeric_limits::digits - 1); const bool overflow = (((ua ^ ub) & (ua ^ udiff)) & SIGN_BIT) != 0; - if (!overflow) { res = static_cast(udiff); } + if (!overflow) + { + res = static_cast(udiff); + } return overflow; } #endif } #define RETURN_IF(TYPE, BUILTIN) \ - if constexpr (std::is_same_v) { return BUILTIN(a, b, carry_in, &carry_out); } + if constexpr (std::is_same_v) \ + { \ + return BUILTIN(a, b, carry_in, &carry_out); \ + } // Returns the result of 'a + b' taking into account 'carry_in'. // The carry out is stored in carry_out when provided, dropped otherwise. // We keep the pass by pointer interface for consistency with the intrinsic. - template - [[nodiscard]] constexpr std::enable_if_t, T> add_with_carry(T a, T b, T carry_in, T & carry_out) + template [[nodiscard]] constexpr std::enable_if_t, T> add_with_carry(T a, T b, T carry_in, T & carry_out) { if constexpr (!is_constant_evaluated()) { @@ -120,8 +124,7 @@ namespace ccm::support // Returns the result of 'a - b' taking into account 'carry_in'. // The carry out is stored in carry_out when provided, dropped otherwise. // We keep the pass by pointer interface for consistency with the intrinsic. - template - [[nodiscard]] constexpr std::enable_if_t, T> sub_with_borrow(T a, T b, T carry_in, T & carry_out) + template [[nodiscard]] constexpr std::enable_if_t, T> sub_with_borrow(T a, T b, T carry_in, T & carry_out) { if (!is_constant_evaluated()) { @@ -154,13 +157,14 @@ namespace ccm::support CCM_DISABLE_CLANG_WARNING(-Wshift-count-overflow) // Disabled for same reasons stated above // Rightmost count bits set to 1. Unsigned T only. - template - static constexpr std::enable_if_t, T> mask_trailing_ones() + template static constexpr std::enable_if_t, T> mask_trailing_ones() { constexpr unsigned T_BITS = CHAR_BIT * sizeof(T); static_assert(count <= T_BITS && "Invalid bit index"); - if constexpr (count == 0) { return T(0); } - else + if constexpr (count == 0) + { + return T(0); + } else { return T(~T(0)) >> (T_BITS - count); } @@ -170,19 +174,22 @@ namespace ccm::support CCM_RESTORE_CLANG_WARNING() // Only unsigned types are allowed. - template - static constexpr std::enable_if_t, T> mask_leading_ones() - { return T(~mask_trailing_ones()); } + template static constexpr std::enable_if_t, T> mask_leading_ones() + { + return T(~mask_trailing_ones()); + } // Only unsigned types are allowed. - template - static constexpr std::enable_if_t, T> mask_trailing_zeros() - { return mask_leading_ones(); } + template static constexpr std::enable_if_t, T> mask_trailing_zeros() + { + return mask_leading_ones(); + } // Only unsigned types are allowed. - template - static constexpr std::enable_if_t, T> mask_leading_zeros() - { return mask_trailing_ones(); } + template static constexpr std::enable_if_t, T> mask_leading_zeros() + { + return mask_trailing_ones(); + } // TODO(IanP): Review whether these helpers should replace or merge with exact_add and split in double_double.hpp. /** @@ -206,8 +213,7 @@ namespace ccm::support * @param b */ - template , bool> = true> - static constexpr void fast_two_sum(T & hi, T & lo, T a, T b) + template , bool> = true> static constexpr void fast_two_sum(T & hi, T & lo, T a, T b) { hi = a + b; T const e = hi - a; // exact @@ -215,8 +221,7 @@ namespace ccm::support } /* Algorithm 2 from https://hal.science/hal-01351529 */ - template , bool> = true> - static constexpr void two_sum(T & s, T & t, T a, T b) + template , bool> = true> static constexpr void two_sum(T & s, T & t, T a, T b) { s = a + b; T const a_prime = s - b; diff --git a/include/ccmath/internal/support/meta_compare.hpp b/include/ccmath/internal/support/meta_compare.hpp index 565b3553..1dd754e6 100644 --- a/include/ccmath/internal/support/meta_compare.hpp +++ b/include/ccmath/internal/support/meta_compare.hpp @@ -18,43 +18,29 @@ namespace ccm::support namespace detail { // A variadic alias template that resolves to its first argument. - template - using first_t = T; + template using first_t = T; // These are deliberately not defined. - template - auto or_fn(int) -> first_t(Bn::value)>...>; + template auto or_fn(int) -> first_t(Bn::value)>...>; - template - auto or_fn(...) -> std::true_type; + template auto or_fn(...) -> std::true_type; - template - auto and_fn(int) -> first_t(Bn::value)>...>; + template auto and_fn(int) -> first_t(Bn::value)>...>; - template - auto and_fn(...) -> std::false_type; + template auto and_fn(...) -> std::false_type; } // namespace detail - template - struct or_ : decltype(detail::or_fn(0)) - { - }; + template struct or_ : decltype(detail::or_fn(0)) + {}; - template - struct and_ : decltype(detail::and_fn(0)) - { - }; + template struct and_ : decltype(detail::and_fn(0)) + {}; - template - struct not_ : std::bool_constant(Pp::value)> - { - }; + template struct not_ : std::bool_constant(Pp::value)> + {}; /// @endcond - template - inline constexpr bool or_v = or_::value; - template - inline constexpr bool and_v = and_::value; - template - inline constexpr bool not_v = not_::value; + template inline constexpr bool or_v = or_::value; + template inline constexpr bool and_v = and_::value; + template inline constexpr bool not_v = not_::value; } // namespace ccm::support diff --git a/include/ccmath/internal/support/multiply_add.hpp b/include/ccmath/internal/support/multiply_add.hpp index 633fbabb..e137be2c 100644 --- a/include/ccmath/internal/support/multiply_add.hpp +++ b/include/ccmath/internal/support/multiply_add.hpp @@ -19,8 +19,7 @@ namespace ccm::support { // Simple wrapper for fma - template > - struct multiply_add_builtin + template > struct multiply_add_builtin { static constexpr T call(T x, T y, T z) { @@ -38,26 +37,34 @@ namespace ccm::support } }; - template - struct multiply_add_builtin + template struct multiply_add_builtin { - static constexpr T call(T x, T y, T z) { return ccm::builtin::fma_ct(x, y, z); } + static constexpr T call(T x, T y, T z) + { + return ccm::builtin::fma_ct(x, y, z); + } }; - template - constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> multiply_add(const T &x, const T &y, const T &z) - { return (x * y) + z; } + template constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> multiply_add(const T & x, const T & y, const T & z) + { + return (x * y) + z; + } - template - constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> multiply_add(T x, T y, T z) - { return (x * y) + z; } + template constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> multiply_add(T x, T y, T z) + { + return (x * y) + z; + } #ifdef CCMATH_TARGET_CPU_HAS_FMA constexpr float multiply_add(float x, float y, float z) - { return multiply_add_builtin::call(x, y, z); } + { + return multiply_add_builtin::call(x, y, z); + } constexpr double multiply_add(double x, double y, double z) - { return multiply_add_builtin::call(x, y, z); } + { + return multiply_add_builtin::call(x, y, z); + } #endif } // namespace ccm::support diff --git a/include/ccmath/internal/support/poly_eval.hpp b/include/ccmath/internal/support/poly_eval.hpp index d818958d..b266d451 100644 --- a/include/ccmath/internal/support/poly_eval.hpp +++ b/include/ccmath/internal/support/poly_eval.hpp @@ -26,24 +26,27 @@ namespace ccm::support // polyeval( x, 4.0, 3.0, 2.0, 1.0 ) // Code borrowed from LLVM - template - constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> polyeval(const T & /*unused*/, const T &a0) - { return a0; } + template constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> polyeval(const T & /*unused*/, const T & a0) + { + return a0; + } - template - constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> polyeval(T /*unused*/, T a0) - { return a0; } + template constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> polyeval(T /*unused*/, T a0) + { + return a0; + } - template - constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> polyeval(const T &x, const T &a0, const Ts &...a) - { return multiply_add(x, polyeval(x, a...), a0); } + template constexpr std::enable_if_t<(sizeof(T) > sizeof(void *)), T> polyeval(const T & x, const T & a0, const Ts &... a) + { + return multiply_add(x, polyeval(x, a...), a0); + } - template - constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> polyeval(T x, T a0, Ts... a) - { return multiply_add(x, polyeval(x, a...), a0); } + template constexpr std::enable_if_t<(sizeof(T) <= sizeof(void *)), T> polyeval(T x, T a0, Ts... a) + { + return multiply_add(x, polyeval(x, a...), a0); + } struct fp_helpers - { - }; + {}; } // namespace ccm::support diff --git a/include/ccmath/internal/types/big_int.hpp b/include/ccmath/internal/types/big_int.hpp index 91ad22aa..a674cad5 100644 --- a/include/ccmath/internal/types/big_int.hpp +++ b/include/ccmath/internal/types/big_int.hpp @@ -47,44 +47,33 @@ namespace ccm::types * This struct provides a type alias for the half-width equivalent of the given unsigned integer type. * For example, std::uint32_t maps to std::uint16_t. */ - template - struct half_width; + template struct half_width; - template <> - struct half_width : support::traits::type_identity - { - }; + template <> struct half_width : support::traits::type_identity + {}; - template <> - struct half_width : support::traits::type_identity - { - }; + template <> struct half_width : support::traits::type_identity + {}; #ifdef CCM_TYPES_HAS_INT64 - template <> - struct half_width : support::traits::type_identity - { - }; + template <> struct half_width : support::traits::type_identity + {}; #ifdef CCM_TYPES_HAS_INT128 - template <> - struct half_width<__uint128_t> : support::traits::type_identity - { - }; + template <> struct half_width<__uint128_t> : support::traits::type_identity + {}; #endif #endif /** * @brief Alias for the half-width type of the given unsigned integer type. */ - template - using half_width_t = typename half_width::type; + template using half_width_t = typename half_width::type; /** * @brief An array of two elements for use in multiword operations. * * This struct provides a two-element array, useful for operations that require handling multiword data. */ - template - struct DoubleWide final : std::array + template struct DoubleWide final : std::array { using UP = std::array; using UP::UP; @@ -103,8 +92,7 @@ namespace ccm::types * @param value The unsigned integer value to be split. * @return A DoubleWide object containing the lower and upper half-width parts of the input value. */ - template - constexpr auto split(T value) + template constexpr auto split(T value) { static_assert(ccm::support::traits::ccm_is_unsigned_v); using half_type = half_width_t; @@ -117,9 +105,10 @@ namespace ccm::types * @param value The DoubleWide value to split. * @return The lower half of the DoubleWide value. */ - template - constexpr T lo(const DoubleWide& value) - { return value[0]; } + template constexpr T lo(const DoubleWide & value) + { + return value[0]; + } /** * @brief Returns the upper half of a DoubleWide value. @@ -127,9 +116,10 @@ namespace ccm::types * @param value The DoubleWide value to split. * @return The upper half of the DoubleWide value. */ - template - constexpr T hi(const DoubleWide& value) - { return value[1]; } + template constexpr T hi(const DoubleWide & value) + { + return value[1]; + } /** * @brief Returns the lower half of a unsigned value @@ -137,9 +127,10 @@ namespace ccm::types * @param value The unsigned integer value to be split. * @return The lower half of the input value. */ - template - constexpr half_width_t lo(T value) - { return lo(split(value)); } + template constexpr half_width_t lo(T value) + { + return lo(split(value)); + } /** * @brief Returns the upper half of a unsigned value @@ -147,9 +138,10 @@ namespace ccm::types * @param value The unsigned integer value to be split. * @return The upper half of the input value. */ - template - constexpr half_width_t hi(T value) - { return hi(split(value)); } + template constexpr half_width_t hi(T value) + { + return hi(split(value)); + } /** * @brief Multiplies two words and returns the result as a DoubleWide containing the product. @@ -168,14 +160,12 @@ namespace ccm::types * - For larger types, it uses a long multiplication approach, splitting the inputs into half words and performing * the multiplication in steps to avoid overflow, then combines the results. */ - template - constexpr DoubleWide mul2(word lhs, word rhs) + template constexpr DoubleWide mul2(word lhs, word rhs) { if constexpr (std::is_same_v) { return split(static_cast(lhs) * static_cast(rhs)); - } - else if constexpr (std::is_same_v) + } else if constexpr (std::is_same_v) { return split(static_cast(lhs) * static_cast(rhs)); } @@ -186,7 +176,10 @@ namespace ccm::types } #endif #ifdef CCM_TYPES_HAS_INT128 - else if constexpr (std::is_same_v) { return split<__uint128_t>(__uint128_t(lhs) * __uint128_t(rhs)); } + else if constexpr (std::is_same_v) + { + return split<__uint128_t>(__uint128_t(lhs) * __uint128_t(rhs)); + } #endif else { @@ -241,7 +234,7 @@ namespace ccm::types * @return The carry value after the operation completes. */ template - constexpr word inplace_binop(Function op_with_carry, std::array& dst, const std::array& rhs) + constexpr word inplace_binop(Function op_with_carry, std::array & dst, const std::array & rhs) { static_assert(N >= M); word carry_out = 0; @@ -253,7 +246,10 @@ namespace ccm::types dst[i] = op_with_carry(dst[i], rhs_value, carry_in, carry_out); // Stop early if 'rhs' is exhausted and no carry remains to be propagated. - if (!has_rhs_value && carry_out == 0) { break; } + if (!has_rhs_value && carry_out == 0) + { + break; + } } return carry_out; } @@ -271,9 +267,10 @@ namespace ccm::types * @param rhs The source array providing the values to add. * @return The carry value after the addition completes. */ - template - constexpr word add_with_carry(std::array& dst, const std::array& rhs) - { return inplace_binop(ccm::support::add_with_carry, dst, rhs); } + template constexpr word add_with_carry(std::array & dst, const std::array & rhs) + { + return inplace_binop(ccm::support::add_with_carry, dst, rhs); + } /** * @brief Performs in-place subtraction and returns the final borrow. @@ -288,9 +285,10 @@ namespace ccm::types * @param rhs The source array providing the values to subtract. * @return The borrow value after the subtraction completes. */ - template - constexpr word sub_with_borrow(std::array& dst, const std::array& rhs) - { return inplace_binop(ccm::support::sub_with_borrow, dst, rhs); } + template constexpr word sub_with_borrow(std::array & dst, const std::array & rhs) + { + return inplace_binop(ccm::support::sub_with_borrow, dst, rhs); + } /** * @brief Performs in-place multiply-add and returns the final carry. @@ -305,9 +303,10 @@ namespace ccm::types * @param c The multiplicand value. * @return The carry value after the multiply-add operation completes. */ - template - constexpr word mul_add_with_carry(std::array& dst, word b, word c) - { return add_with_carry(dst, mul2(b, c)); } + template constexpr word mul_add_with_carry(std::array & dst, word b, word c) + { + return add_with_carry(dst, mul2(b, c)); + } /** * @brief An array of two elements used as an accumulator in multiword computations. @@ -316,8 +315,7 @@ namespace ccm::types * * @tparam T The type of the elements in the array. */ - template - struct Accumulator final : std::array + template struct Accumulator final : std::array { using UP = std::array; @@ -347,13 +345,19 @@ namespace ccm::types * @brief Returns the sum (front value) of the accumulator. * @return The front value of the accumulator. */ - constexpr T sum() const { return UP::front(); } + constexpr T sum() const + { + return UP::front(); + } /** * @brief Returns the carry (back value) of the accumulator. * @return The back value of the accumulator. */ - constexpr T carry() const { return UP::back(); } + constexpr T carry() const + { + return UP::back(); + } }; /** @@ -368,11 +372,10 @@ namespace ccm::types * @param x The word to multiply each element of the 'dst' array by. * @return The carry value after the multiplication completes. */ - template - constexpr word scalar_multiply_with_carry(std::array& dst, word x) + template constexpr word scalar_multiply_with_carry(std::array & dst, word x) { Accumulator acc; - for (auto& val : dst) + for (auto & val : dst) { const word carry = mul_add_with_carry(acc, val, x); val = acc.advance(carry); @@ -398,7 +401,7 @@ namespace ccm::types * @return The carry value after the multiplication completes. */ template - constexpr word multiply_with_carry(std::array& dst, const std::array& lhs, const std::array& rhs) + constexpr word multiply_with_carry(std::array & dst, const std::array & lhs, const std::array & rhs) { static_assert(O >= M + N); Accumulator acc; @@ -407,7 +410,10 @@ namespace ccm::types const std::size_t lower_idx = i < N ? 0 : i - N + 1; const std::size_t upper_idx = i < M ? i : M - 1; word carry = 0; - for (std::size_t j = lower_idx; j <= upper_idx; ++j) { carry += mul_add_with_carry(acc, lhs[j], rhs[i - j]); } + for (std::size_t j = lower_idx; j <= upper_idx; ++j) + { + carry += mul_add_with_carry(acc, lhs[j], rhs[i - j]); + } dst[i] = acc.advance(carry); } return acc.carry(); @@ -425,20 +431,26 @@ namespace ccm::types * @param rhs The right-hand side operand array. */ template - constexpr void quick_mul_hi(std::array& dst, const std::array& lhs, const std::array& rhs) + constexpr void quick_mul_hi(std::array & dst, const std::array & lhs, const std::array & rhs) { Accumulator acc; word carry = 0; // Initial accumulation for elements at N - 1 in the full product. - for (std::size_t i = 0; i < N; ++i) { carry += mul_add_with_carry(acc, lhs[i], rhs[N - 1 - i]); } + for (std::size_t i = 0; i < N; ++i) + { + carry += mul_add_with_carry(acc, lhs[i], rhs[N - 1 - i]); + } // Accumulate and propagate carry for the remaining elements. for (std::size_t i = N; i < (2 * N) - 1; ++i) { acc.advance(carry); carry = 0; - for (std::size_t j = i - N + 1; j < N; ++j) { carry += mul_add_with_carry(acc, lhs[j], rhs[i - j]); } + for (std::size_t j = i - N + 1; j < N; ++j) + { + carry += mul_add_with_carry(acc, lhs[j], rhs[i - j]); + } dst[i - N] = acc.sum(); } dst.back() = acc.carry(); @@ -452,8 +464,7 @@ namespace ccm::types * @param array The array to check. * @return True if the value is negative, false otherwise. */ - template - constexpr bool is_negative(std::array& array) + template constexpr bool is_negative(std::array & array) { using signed_word = std::make_signed_t; return support::bit_cast(array.back()) < 0; @@ -491,28 +502,40 @@ namespace ccm::types { using type = std::conditional_t; auto tmp = ccm::support::bit_cast(array); - if constexpr (direction == Direction::eLEFT) { tmp <<= offset; } - else + if constexpr (direction == Direction::eLEFT) + { + tmp <<= offset; + } else { tmp >>= offset; } return ccm::support::bit_cast>(tmp); } #endif - if (CCM_UNLIKELY(offset == 0)) { return array; } - const bool is_neg = is_signed && is_negative(array); - constexpr auto at = [](std::size_t index) -> int + if (CCM_UNLIKELY(offset == 0)) { + return array; + } + const bool is_neg = is_signed && is_negative(array); + constexpr auto at = [](std::size_t index) -> int { // Reverse iteration when the direction is LEFT. - if constexpr (direction == Direction::eLEFT) { return static_cast(N) - static_cast(index) - 1; } + if constexpr (direction == Direction::eLEFT) + { + return static_cast(N) - static_cast(index) - 1; + } return static_cast(index); }; - const auto safe_get_at = [&](std::size_t index) -> word - { + const auto safe_get_at = [&](std::size_t index) -> word { // Return appropriate value for out-of-bound elements. const int i = at(index); - if (i < 0) { return 0; } - if (i >= static_cast(N)) { return is_neg ? static_cast(-1) : 0; } + if (i < 0) + { + return 0; + } + if (i >= static_cast(N)) + { + return is_neg ? static_cast(-1) : 0; + } return array[static_cast(i)]; }; const std::size_t index_offset = offset / WORD_BITS; @@ -525,10 +548,15 @@ namespace ccm::types { const word part1 = safe_get_at(index + index_offset); const word part2 = safe_get_at(index + index_offset + 1); - word& dst = out[static_cast(at(index))]; - if (bit_offset == 0) { dst = part1; } // No crosstalk between parts. - else if constexpr (direction == Direction::eLEFT) { dst = (part1 << bit_offset) | (part2 >> (WORD_BITS - bit_offset)); } - else + word & dst = out[static_cast(at(index))]; + if (bit_offset == 0) + { + dst = part1; + } // No crosstalk between parts. + else if constexpr (direction == Direction::eLEFT) + { + dst = (part1 << bit_offset) | (part2 >> (WORD_BITS - bit_offset)); + } else { dst = (part1 >> bit_offset) | (part2 << (WORD_BITS - bit_offset)); } @@ -537,8 +565,7 @@ namespace ccm::types } #define DECLARE_COUNTBIT(NAME, INDEX_EXPR) \ - template \ - constexpr int NAME(const std::array& val) \ + template constexpr int NAME(const std::array & val) \ { \ int bit_count = 0; \ for (std::size_t i = 0; i < N; ++i) \ @@ -557,8 +584,7 @@ namespace ccm::types DECLARE_COUNTBIT(countl_one, N - i - 1) // iterating backward } // namespace multiword - template - struct BigInt // NOLINT(cppcoreguidelines-special-member-functions) + template struct BigInt // NOLINT(cppcoreguidelines-special-member-functions) { private: static_assert(ccm::support::traits::ccm_is_integral_v && ccm::support::traits::ccm_is_unsigned_v, @@ -593,7 +619,7 @@ namespace ccm::types constexpr BigInt() = default; - constexpr BigInt(const BigInt& other) = default; + constexpr BigInt(const BigInt & other) = default; /** * @brief Constructs a BigInt from another BigInt with potentially different size and signedness. @@ -607,7 +633,7 @@ namespace ccm::types * @param other The source BigInt to construct from. */ template - constexpr BigInt(const BigInt& other) // NOLINT(google-explicit-constructor) + constexpr BigInt(const BigInt & other) // NOLINT(google-explicit-constructor) { using BigIntOther = BigInt; [[maybe_unused]] const bool should_sign_extend = Signed && other.is_neg(); @@ -630,8 +656,7 @@ namespace ccm::types } val[i] = cur_word; } - } - else + } else { std::size_t i = 0; WordType cur_word = 0; @@ -645,25 +670,31 @@ namespace ccm::types } } const WordType extension_bits = should_sign_extend ? std::numeric_limits::max() : std::numeric_limits::min(); - if ((i % WORD_SIZE_RATIO) != 0) { cur_word |= static_cast(extension_bits) << (BigIntOther::WORD_SIZE * (i % WORD_SIZE_RATIO)); } + if ((i % WORD_SIZE_RATIO) != 0) + { + cur_word |= static_cast(extension_bits) << (BigIntOther::WORD_SIZE * (i % WORD_SIZE_RATIO)); + } val[i / WORD_SIZE_RATIO] = cur_word; extend((i / WORD_SIZE_RATIO) + 1, should_sign_extend); } - } - else if constexpr (BigIntOther::WORD_SIZE == WORD_SIZE) + } else if constexpr (BigIntOther::WORD_SIZE == WORD_SIZE) { if constexpr (OtherBits >= Bits) { - for (std::size_t i = 0; i < WORD_COUNT; ++i) { val[i] = other[i]; } - } - else + for (std::size_t i = 0; i < WORD_COUNT; ++i) + { + val[i] = other[i]; + } + } else { std::size_t i = 0; - for (; i < BigIntOther::WORD_COUNT; ++i) { val[i] = other[i]; } + for (; i < BigIntOther::WORD_COUNT; ++i) + { + val[i] = other[i]; + } extend(i, should_sign_extend); } - } - else + } else { constexpr std::size_t WORD_SIZE_RATIO = BigIntOther::WORD_SIZE / WORD_SIZE; static_assert((BigIntOther::WORD_SIZE % WORD_SIZE) == 0, "Word types must be multiples of each other for correct conversion."); @@ -673,8 +704,7 @@ namespace ccm::types { val[i] = static_cast(other[i / WORD_SIZE_RATIO] >> ((i % WORD_SIZE_RATIO) * WORD_SIZE)); } - } - else + } else { std::size_t i = 0; for (; i < BigIntOther::WORD_COUNT; ++i) @@ -700,14 +730,17 @@ namespace ccm::types // intentionally using C-style arrays here. { static_assert(N == WORD_COUNT); - for (std::size_t i = 0; i < WORD_COUNT; ++i) { val[i] = nums[i]; } + for (std::size_t i = 0; i < WORD_COUNT; ++i) + { + val[i] = nums[i]; + } } /** * @brief Constructs a BigInt from a std::array of words. * @param words The input std::array of WordType values. */ - constexpr explicit BigInt(const std::array& words) : val(words) {} + constexpr explicit BigInt(const std::array & words) : val(words) {} /** * @brief Constructs a BigInt from an integral value. @@ -732,18 +765,19 @@ namespace ccm::types return; } val[i] = static_cast(v); - if constexpr (T_SIZE > WORD_SIZE) { v >>= WORD_SIZE; } - else + if constexpr (T_SIZE > WORD_SIZE) + { + v >>= WORD_SIZE; + } else { v = 0; } } } - constexpr BigInt& operator=(const BigInt& other) = default; + constexpr BigInt & operator=(const BigInt & other) = default; - template - constexpr BigInt& operator=(T v) + template constexpr BigInt & operator=(T v) { *this = BigInt(v); return *this; @@ -752,17 +786,26 @@ namespace ccm::types /** * @brief Creates a BigInt with the value zero. */ - static constexpr BigInt zero() { return BigInt(); } + static constexpr BigInt zero() + { + return BigInt(); + } /** * @brief Creates a BigInt with the value one. */ - static constexpr BigInt one() { return BigInt(1); } + static constexpr BigInt one() + { + return BigInt(1); + } /** * @brief Creates a BigInt with all bits set to one. */ - static constexpr BigInt all_ones() { return ~zero(); } + static constexpr BigInt all_ones() + { + return ~zero(); + } /** * @brief Creates a BigInt with the minimum value. @@ -770,7 +813,10 @@ namespace ccm::types static constexpr BigInt min() { BigInt out; // NOLINT(misc-const-correctness) - if constexpr (SIGNED) { out.set_msb(); } + if constexpr (SIGNED) + { + out.set_msb(); + } return out; } @@ -780,18 +826,26 @@ namespace ccm::types static constexpr BigInt max() { BigInt out = all_ones(); // NOLINT(misc-const-correctness) - if constexpr (SIGNED) { out.clear_msb(); } + if constexpr (SIGNED) + { + out.clear_msb(); + } return out; } /** * @brief Determines if the value of the BigInt is negative. */ - [[nodiscard]] constexpr bool is_neg() const { return SIGNED && get_msb(); } + [[nodiscard]] constexpr bool is_neg() const + { + return SIGNED && get_msb(); + } template constexpr explicit operator BigInt() const - { return BigInt(this); } + { + return BigInt(this); + } /** * @brief Converts the BigInt to another integral type. @@ -801,9 +855,10 @@ namespace ccm::types * @tparam T The target integral type. * @return The BigInt value converted to type T. */ - template - constexpr explicit operator T() const - { return to(); } + template constexpr explicit operator T() const + { + return to(); + } /** * @brief Converts the BigInt to another integral type. @@ -815,27 +870,35 @@ namespace ccm::types * * @note This function is only enabled for integral types excluding bool. */ - template - constexpr std::enable_if_t && !std::is_same_v, T> to() const + template constexpr std::enable_if_t && !std::is_same_v, T> to() const { constexpr std::size_t T_SIZE = sizeof(T) * CHAR_BIT; // Identify the size of the target type. auto lo = static_cast(val[0]); // Initialize the result with the first word of the BigInt. // If the target size is less than or equal to the word size, return the initial value. - if constexpr (T_SIZE <= WORD_SIZE) { return lo; } + if constexpr (T_SIZE <= WORD_SIZE) + { + return lo; + } // Calculate the maximum number of words to process. constexpr std::size_t MAX_COUNT = T_SIZE > Bits ? WORD_COUNT : T_SIZE / WORD_SIZE; // Combine the words into the result, shifting each word to its proper position. - for (std::size_t i = 1; i < MAX_COUNT; ++i) { lo += static_cast(val[i]) << (WORD_SIZE * i); } + for (std::size_t i = 1; i < MAX_COUNT; ++i) + { + lo += static_cast(val[i]) << (WORD_SIZE * i); + } // If the BigInt is signed and the target size is greater than the BigInt size, // sign-extend the result if the original value was negative. if constexpr (Signed && (T_SIZE > Bits)) { constexpr T MASK = (~T(0) << Bits); - if (is_neg()) { lo |= MASK; } + if (is_neg()) + { + lo |= MASK; + } } return lo; @@ -846,7 +909,10 @@ namespace ccm::types * * @return True if the BigInt is non-zero, false otherwise. */ - constexpr explicit operator bool() const { return !is_zero(); } + constexpr explicit operator bool() const + { + return !is_zero(); + } /** * @brief Checks if the BigInt is zero. @@ -857,7 +923,10 @@ namespace ccm::types { for (auto part : val) { - if (part != 0) { return false; } + if (part != 0) + { + return false; + } } return true; } @@ -867,9 +936,12 @@ namespace ccm::types * @param rhs The BigInt to add. * @return The carry value produced by the addition. */ - constexpr WordType add_overflow(const BigInt& rhs) { return multiword::add_with_carry(val, rhs.val); } + constexpr WordType add_overflow(const BigInt & rhs) + { + return multiword::add_with_carry(val, rhs.val); + } - constexpr BigInt operator+(const BigInt& other) const + constexpr BigInt operator+(const BigInt & other) const { BigInt result = *this; result.add_overflow(other); @@ -885,14 +957,14 @@ namespace ccm::types * @param other The BigInt to add. * @return The result of the addition. */ - constexpr BigInt operator+(BigInt&& other) const + constexpr BigInt operator+(BigInt && other) const { std::move(other); // We ignore the moved value here. other.add_overflow(*this); // We ignore the returned carry value here. return other; } - constexpr BigInt& operator+=(const BigInt& other) + constexpr BigInt & operator+=(const BigInt & other) { add_overflow(other); // We ignore the returned carry value here. return *this; @@ -903,23 +975,26 @@ namespace ccm::types * @param rhs The BigInt to subtract. * @return The carry value produced by the subtraction. */ - constexpr WordType sub_overflow(const BigInt& rhs) { return multiword::sub_with_borrow(val, rhs.val); } + constexpr WordType sub_overflow(const BigInt & rhs) + { + return multiword::sub_with_borrow(val, rhs.val); + } - constexpr BigInt operator-(const BigInt& other) const + constexpr BigInt operator-(const BigInt & other) const { BigInt result = *this; result.sub_overflow(other); // We ignore the returned carry value here. return result; } - constexpr BigInt operator-(BigInt&& other) const + constexpr BigInt operator-(BigInt && other) const { BigInt result = *this; result.sub_overflow(std::move(other)); // We ignore the returned carry value here. return result; } - constexpr BigInt& operator-=(const BigInt& other) + constexpr BigInt & operator-=(const BigInt & other) { if (sub_overflow(other)) // Perform subtraction, and if the returned carry is true, we have an overflow. { @@ -935,7 +1010,10 @@ namespace ccm::types * @param x The scalar to multiply with. * @return The carry value produced by the multiplication. */ - constexpr WordType mul(WordType x) { return multiword::scalar_multiply_with_carry(val, x); } + constexpr WordType mul(WordType x) + { + return multiword::scalar_multiply_with_carry(val, x); + } /** * @brief Multiplies this BigInt with another and returns the full product. @@ -943,8 +1021,7 @@ namespace ccm::types * @param other The BigInt to multiply with. * @return The full product as a new BigInt. */ - template - constexpr auto ful_mul(const BigInt& other) const + template constexpr auto ful_mul(const BigInt & other) const { BigInt result; multiword::multiply_with_carry(result.val, val, other.val); @@ -954,7 +1031,10 @@ namespace ccm::types /** * @brief Multiplies this BigInt with another and returns the full product tructated. */ - constexpr BigInt operator*(const BigInt& other) const { return BigInt(ful_mul(other)); } + constexpr BigInt operator*(const BigInt & other) const + { + return BigInt(ful_mul(other)); + } /** * @brief Approximates the high bits of the full product of two BigInts. @@ -981,7 +1061,7 @@ namespace ccm::types * @param other The BigInt to multiply with. * @return The approximate high bits of the product as a new BigInt. */ - [[nodiscard]] constexpr BigInt quick_mul_hi(const BigInt& other) const + [[nodiscard]] constexpr BigInt quick_mul_hi(const BigInt & other) const { BigInt result; multiword::quick_mul_hi(result.val, val, other.val); @@ -999,7 +1079,10 @@ namespace ccm::types BigInt cur_power = *this; while (expo > 0) { - if ((expo % 2) > 0) { result *= cur_power; } + if ((expo % 2) > 0) + { + result *= cur_power; + } expo >>= 1; cur_power *= cur_power; } @@ -1018,13 +1101,21 @@ namespace ccm::types * Reference used: * - https://stackoverflow.com/a/3602857 */ - constexpr std::optional div(const BigInt& divider) + constexpr std::optional div(const BigInt & divider) { - if (CCM_UNLIKELY(divider.is_zero())) { return std::nullopt; } - if (CCM_UNLIKELY(divider == BigInt::one())) { return BigInt::zero(); } + if (CCM_UNLIKELY(divider.is_zero())) + { + return std::nullopt; + } + if (CCM_UNLIKELY(divider == BigInt::one())) + { + return BigInt::zero(); + } Division result; - if constexpr (SIGNED) { result = divide_signed(*this, divider); } - else + if constexpr (SIGNED) + { + result = divide_signed(*this, divider); + } else { const auto unsigned_result = divide_unsigned(*this, divider); result.quotient = BigInt(unsigned_result.quotient); @@ -1051,7 +1142,10 @@ namespace ccm::types constexpr std::optional div_uint_half_times_pow_2(multiword::half_width_t x, size_t e) { BigInt remainder; - if (x == 0) { return std::nullopt; } + if (x == 0) + { + return std::nullopt; + } if (e >= Bits) { remainder = *this; @@ -1117,8 +1211,7 @@ namespace ccm::types q_tmp = rem / x_word; rem %= x_word; last_shift -= HALF_WORD_SIZE; - } - else + } else { d >>= HALF_WORD_SIZE; } // Use the upper half-word of the current chunk only. @@ -1139,81 +1232,94 @@ namespace ccm::types { // IF we have the remainder rem * 2^(lower - e), it might overflow to a higher word type chunk. // Due to this, we will adjust the remainder to defend against overflow. - if (pos < WORD_COUNT - 1) { remainder[pos + 1] = rem >> HALF_WORD_SIZE; } + if (pos < WORD_COUNT - 1) + { + remainder[pos + 1] = rem >> HALF_WORD_SIZE; + } remainder[pos] = (rem << HALF_WORD_SIZE) + (val[pos] & HALF_MASK); - } - else + } else { remainder[pos] = rem; } - } - else + } else { remainder[pos] = rem; } // Set the remaining lower bits of the remainder. - for (; pos > 0; --pos) { remainder[pos - 1] = val[pos - 1]; } + for (; pos > 0; --pos) + { + remainder[pos - 1] = val[pos - 1]; + } *this = quotient; return remainder; } - constexpr BigInt operator/(const BigInt& other) const + constexpr BigInt operator/(const BigInt & other) const { BigInt result(*this); result.div(other); return result; } - constexpr BigInt& operator/=(const BigInt& other) + constexpr BigInt & operator/=(const BigInt & other) { div(other); return *this; } - constexpr BigInt operator%(const BigInt& other) const + constexpr BigInt operator%(const BigInt & other) const { BigInt result(*this); return *result.div(other); } - constexpr BigInt& operator*=(const BigInt& other) + constexpr BigInt & operator*=(const BigInt & other) { *this = *this * other; return *this; } - constexpr BigInt& operator<<=(std::size_t s) + constexpr BigInt & operator<<=(std::size_t s) { val = multiword::shift(val, s); return *this; } - constexpr BigInt operator<<(std::size_t s) const { return BigInt(multiword::shift(val, s)); } + constexpr BigInt operator<<(std::size_t s) const + { + return BigInt(multiword::shift(val, s)); + } - template - constexpr BigInt operator<<(T s) const - { return BigInt(multiword::shift(val, s)); } + template constexpr BigInt operator<<(T s) const + { + return BigInt(multiword::shift(val, s)); + } - constexpr BigInt& operator>>=(std::size_t s) + constexpr BigInt & operator>>=(std::size_t s) { val = multiword::shift(val, s); return *this; } - constexpr BigInt operator>>(std::size_t s) const { return BigInt(multiword::shift(val, s)); } + constexpr BigInt operator>>(std::size_t s) const + { + return BigInt(multiword::shift(val, s)); + } #define DEFINE_BINOP(OP) \ - friend constexpr BigInt operator OP(const BigInt& lhs, const BigInt& rhs) \ + friend constexpr BigInt operator OP(const BigInt & lhs, const BigInt & rhs) \ { \ BigInt result; \ - for (std::size_t i = 0; i < WORD_COUNT; ++i) result[i] = lhs[i] OP rhs[i]; \ + for (std::size_t i = 0; i < WORD_COUNT; ++i) \ + result[i] = lhs[i] OP rhs[i]; \ return result; \ } \ - friend constexpr BigInt operator OP## = (BigInt & lhs, const BigInt& rhs) \ + friend constexpr BigInt operator OP## = (BigInt & lhs, const BigInt & rhs) \ { \ - for (std::size_t i = 0; i < WORD_COUNT; ++i) lhs[i] OP## = rhs[i]; \ + for (std::size_t i = 0; i < WORD_COUNT; ++i) \ + lhs[i] OP## = rhs[i]; \ return lhs; \ } @@ -1225,7 +1331,10 @@ namespace ccm::types constexpr BigInt operator~() const { BigInt result; - for (std::size_t i = 0; i < WORD_COUNT; ++i) { result[i] = ~val.at(i); } + for (std::size_t i = 0; i < WORD_COUNT; ++i) + { + result[i] = ~val.at(i); + } return result; } @@ -1236,35 +1345,49 @@ namespace ccm::types return result; } - friend constexpr bool operator==(const BigInt& lhs, const BigInt& rhs) + friend constexpr bool operator==(const BigInt & lhs, const BigInt & rhs) { for (std::size_t i = 0; i < WORD_COUNT; ++i) { - if (lhs.val[i] != rhs.val[i]) { return false; } + if (lhs.val[i] != rhs.val[i]) + { + return false; + } } return true; } - template - friend constexpr bool operator==(const BigInt& lhs, T rhs) - { return lhs == BigInt(rhs); } + template friend constexpr bool operator==(const BigInt & lhs, T rhs) + { + return lhs == BigInt(rhs); + } - friend constexpr bool operator!=(const BigInt& lhs, const BigInt& rhs) - { return !(lhs == rhs); } + friend constexpr bool operator!=(const BigInt & lhs, const BigInt & rhs) + { + return !(lhs == rhs); + } - friend constexpr bool operator>(const BigInt& lhs, const BigInt& rhs) - { return cmp(lhs, rhs) > 0; } + friend constexpr bool operator>(const BigInt & lhs, const BigInt & rhs) + { + return cmp(lhs, rhs) > 0; + } - friend constexpr bool operator>=(const BigInt& lhs, const BigInt& rhs) - { return cmp(lhs, rhs) >= 0; } + friend constexpr bool operator>=(const BigInt & lhs, const BigInt & rhs) + { + return cmp(lhs, rhs) >= 0; + } - friend constexpr bool operator<(const BigInt& lhs, const BigInt& rhs) - { return cmp(lhs, rhs) < 0; } + friend constexpr bool operator<(const BigInt & lhs, const BigInt & rhs) + { + return cmp(lhs, rhs) < 0; + } - friend constexpr bool operator<=(const BigInt& lhs, const BigInt& rhs) - { return cmp(lhs, rhs) <= 0; } + friend constexpr bool operator<=(const BigInt & lhs, const BigInt & rhs) + { + return cmp(lhs, rhs) <= 0; + } - constexpr BigInt& operator++() + constexpr BigInt & operator++() { increment(); return *this; @@ -1277,7 +1400,7 @@ namespace ccm::types return old_val; } - constexpr BigInt& operator--() + constexpr BigInt & operator--() { decrement(); return *this; @@ -1290,11 +1413,15 @@ namespace ccm::types return old_val; } - constexpr const WordType& operator[](std::size_t i) const - { return val[i]; } + constexpr const WordType & operator[](std::size_t i) const + { + return val[i]; + } - constexpr WordType& operator[](std::size_t i) - { return val.at(i); } + constexpr WordType & operator[](std::size_t i) + { + return val.at(i); + } [[nodiscard]] constexpr bool get_bit(std::size_t i) const { @@ -1309,28 +1436,39 @@ namespace ccm::types } private: - friend constexpr int cmp(const BigInt& lhs, const BigInt& rhs) + friend constexpr int cmp(const BigInt & lhs, const BigInt & rhs) { - constexpr auto compare = [](WordType a, WordType b) - { - if (a > b) { return a == b ? 0 : 1; } + constexpr auto compare = [](WordType a, WordType b) { + if (a > b) + { + return a == b ? 0 : 1; + } return a == b ? 0 : -1; }; if constexpr (Signed) { const bool lhs_is_neg = lhs.is_neg(); - if (const bool rhs_is_neg = rhs.is_neg(); lhs_is_neg != rhs_is_neg) { return rhs_is_neg ? 1 : -1; } + if (const bool rhs_is_neg = rhs.is_neg(); lhs_is_neg != rhs_is_neg) + { + return rhs_is_neg ? 1 : -1; + } } for (std::size_t i = WORD_COUNT; i-- > 0;) { - if (auto cmp = compare(lhs[i], rhs[i]); cmp != 0) { return cmp; } + if (auto cmp = compare(lhs[i], rhs[i]); cmp != 0) + { + return cmp; + } } return 0; } constexpr void bitwise_not() { - for (auto& part : val) { part = ~part; } + for (auto & part : val) + { + part = ~part; + } } constexpr void negate() @@ -1340,27 +1478,40 @@ namespace ccm::types } constexpr void increment() - { multiword::add_with_carry(val, std::array{ 1 }); } + { + multiword::add_with_carry(val, std::array{ 1 }); + } constexpr void decrement() - { multiword::sub_with_borrow(val, std::array{ 1 }); } + { + multiword::sub_with_borrow(val, std::array{ 1 }); + } constexpr void extend(std::size_t index, bool is_neg) { const WordType value = is_neg ? std::numeric_limits::max() : std::numeric_limits::min(); - for (std::size_t i = index; i < WORD_COUNT; ++i) { val[i] = value; } + for (std::size_t i = index; i < WORD_COUNT; ++i) + { + val[i] = value; + } } [[nodiscard]] constexpr bool get_msb() const - { return val.back() >> (WORD_SIZE - 1); } + { + return val.back() >> (WORD_SIZE - 1); + } constexpr void set_msb() - { val.back() |= support::mask_leading_ones(); } + { + val.back() |= support::mask_leading_ones(); + } constexpr void clear_msb() - { val.back() &= support::mask_trailing_ones(); } + { + val.back() &= support::mask_trailing_ones(); + } - constexpr static UnsignedDivision divide_unsigned(const unsigned_type& dividend, const unsigned_type& divider) + constexpr static UnsignedDivision divide_unsigned(const unsigned_type & dividend, const unsigned_type & divider) { unsigned_type remainder = dividend; unsigned_type quotient; @@ -1384,10 +1535,13 @@ namespace ccm::types return UnsignedDivision{ quotient, remainder }; } - constexpr static Division divide_signed(const BigInt& dividend, const BigInt& divider) + constexpr static Division divide_signed(const BigInt & dividend, const BigInt & divider) { // This is a special case for the minimum value, as it is not possible to negate the minimum value of a signed integer. - if (dividend == min() && divider == min()) { return Division{ one(), zero() }; } + if (dividend == min() && divider == min()) + { + return Division{ one(), zero() }; + } // Convert the dividend and divider to unsigned values. unsigned_type udividend(dividend); @@ -1396,8 +1550,14 @@ namespace ccm::types // If the dividend is negative, negate it. const bool dividend_is_neg = dividend.is_neg(); const bool divider_is_neg = divider.is_neg(); - if (dividend_is_neg) { udividend.negate(); } - if (divider_is_neg) { udivider.negate(); } + if (dividend_is_neg) + { + udividend.negate(); + } + if (divider_is_neg) + { + udivider.negate(); + } // Use the unsigned division function to calculate the quotient and remainder. const auto unsigned_result = divide_unsigned(udividend, udivider); @@ -1408,10 +1568,16 @@ namespace ccm::types result.remainder = signed_type(unsigned_result.remainder); // If the dividend and divisor have different signs, negate the quotient. - if (dividend_is_neg != divider_is_neg) { result.quotient.negate(); } + if (dividend_is_neg != divider_is_neg) + { + result.quotient.negate(); + } // If the dividend was negative, negate the remainder. - if (dividend_is_neg) { result.remainder.negate(); } + if (dividend_is_neg) + { + result.remainder.negate(); + } return result; } @@ -1434,35 +1600,25 @@ namespace ccm::types std::uint32_t #endif > - { - }; + {}; // In special cases where a size of 16 bits is requested, we use a 16-bit word type instead. - template <> - struct WordTypeSelector<16> : support::traits::type_identity - { - }; + template <> struct WordTypeSelector<16> : support::traits::type_identity + {}; // In special cases where a size of 32 or 96 bits is requested, we use a 32-bit word type instead. - template <> - struct WordTypeSelector<32> : support::traits::type_identity - { - }; + template <> struct WordTypeSelector<32> : support::traits::type_identity + {}; - template <> - struct WordTypeSelector<96> : support::traits::type_identity - { - }; + template <> struct WordTypeSelector<96> : support::traits::type_identity + {}; - template - using WordTypeSelectorT = typename WordTypeSelector::type; + template using WordTypeSelectorT = typename WordTypeSelector::type; } // namespace internal - template - using UInt = BigInt>; + template using UInt = BigInt>; - template - using Int = BigInt>; + template using Int = BigInt>; } // namespace ccm::types /// Specialization of std::numeric_limits for BigInt types. @@ -1473,8 +1629,14 @@ namespace std struct numeric_limits> { public: - static constexpr ccm::types::BigInt max() { return ccm::types::BigInt::max(); } - static constexpr ccm::types::BigInt min() { return ccm::types::BigInt::min(); } + static constexpr ccm::types::BigInt max() + { + return ccm::types::BigInt::max(); + } + static constexpr ccm::types::BigInt min() + { + return ccm::types::BigInt::min(); + } static constexpr int digits = static_cast(Bits - static_cast(Signed)); }; @@ -1483,80 +1645,56 @@ namespace std /// Special helper type traits for BigInt types. namespace ccm::types { - template - struct is_big_int : std::false_type - { - }; + template struct is_big_int : std::false_type + {}; - template - struct is_big_int> : std::true_type - { - }; + template struct is_big_int> : std::true_type + {}; - template - constexpr bool is_big_int_v = is_big_int::value; + template constexpr bool is_big_int_v = is_big_int::value; - template - struct is_integral_or_big_int : std::bool_constant<(ccm::support::traits::ccm_is_integral_v || is_big_int_v)> - { - }; + template struct is_integral_or_big_int : std::bool_constant<(ccm::support::traits::ccm_is_integral_v || is_big_int_v)> + {}; - template - constexpr bool is_integral_or_big_int_v = is_integral_or_big_int::value; + template constexpr bool is_integral_or_big_int_v = is_integral_or_big_int::value; - template - struct make_big_int_unsigned; + template struct make_big_int_unsigned; template struct make_big_int_unsigned> : support::traits::type_identity> - { - }; + {}; - template - using make_big_int_unsigned_t = typename make_big_int_unsigned::type; + template using make_big_int_unsigned_t = typename make_big_int_unsigned::type; - template - struct make_big_int_signed; + template struct make_big_int_signed; template struct make_big_int_signed> : support::traits::type_identity> - { - }; + {}; - template - using make_big_int_signed_t = typename make_big_int_signed::type; + template using make_big_int_signed_t = typename make_big_int_signed::type; - template - struct make_integral_or_big_int_unsigned; + template struct make_integral_or_big_int_unsigned; template struct make_integral_or_big_int_unsigned>> : support::traits::ccm_make_unsigned - { - }; + {}; - template - struct make_integral_or_big_int_unsigned>> : make_big_int_unsigned - { - }; + template struct make_integral_or_big_int_unsigned>> : make_big_int_unsigned + {}; - template - using make_integral_or_big_int_unsigned_t = typename make_integral_or_big_int_unsigned::type; + template using make_integral_or_big_int_unsigned_t = typename make_integral_or_big_int_unsigned::type; - template - struct make_integral_or_big_int_signed; + template struct make_integral_or_big_int_signed; template struct make_integral_or_big_int_signed>> : support::traits::ccm_make_signed - { - }; + {}; - template - struct make_integral_or_big_int_signed>> : make_big_int_signed - { - }; + template struct make_integral_or_big_int_signed>> : make_big_int_signed + {}; - template - using make_integral_or_big_int_signed_t = typename make_integral_or_big_int_signed::type; + template using make_integral_or_big_int_signed_t = typename make_integral_or_big_int_signed::type; } // namespace ccm::types /// Specialization of the bits.hpp header for BigInt types. @@ -1566,7 +1704,7 @@ namespace ccm::support template constexpr std:: enable_if_t<(sizeof(To) == sizeof(From)) && std::is_trivially_copyable_v && std::is_trivially_copyable_v && types::is_big_int::value, To> - bit_cast(const From& from) + bit_cast(const From & from) { To out{}; using Storage = decltype(out.val); @@ -1578,94 +1716,124 @@ namespace ccm::support constexpr std::enable_if_t) && std::is_trivially_constructible_v && std::is_trivially_copyable_v && std::is_trivially_copyable_v>, To> - bit_cast(const types::UInt& from) - { return support::bit_cast(from.val); } + bit_cast(const types::UInt & from) + { + return support::bit_cast(from.val); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) + template [[nodiscard]] constexpr std::enable_if_t, int> popcount(T value) { int bits = 0; for (auto word : value.val) { - if (word) { bits += popcount(word); } + if (word) + { + bits += popcount(word); + } } return bits; } - template - [[nodiscard]] constexpr std::enable_if_t, bool> has_single_bit(T value) + template [[nodiscard]] constexpr std::enable_if_t, bool> has_single_bit(T value) { int bits = 0; for (auto word : value.val) { - if (word == 0) { continue; } + if (word == 0) + { + continue; + } bits += popcount(word); - if (bits > 1) { return false; } + if (bits > 1) + { + return false; + } } return bits == 1; } - template - [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(const T& value) - { return types::multiword::countr_zero(value.val); } + template [[nodiscard]] constexpr std::enable_if_t, int> countr_zero(const T & value) + { + return types::multiword::countr_zero(value.val); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(const T& value) - { return types::multiword::countl_zero(value.val); } + template [[nodiscard]] constexpr std::enable_if_t, int> countl_zero(const T & value) + { + return types::multiword::countl_zero(value.val); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> countl_one(T value) - { return types::multiword::countl_one(value.val); } + template [[nodiscard]] constexpr std::enable_if_t, int> countl_one(T value) + { + return types::multiword::countl_one(value.val); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> countr_one(T value) - { return types::multiword::countr_one(value.val); } + template [[nodiscard]] constexpr std::enable_if_t, int> countr_one(T value) + { + return types::multiword::countr_one(value.val); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> bit_width(T value) - { return std::numeric_limits::digits - support::countl_zero(value); } + template [[nodiscard]] constexpr std::enable_if_t, int> bit_width(T value) + { + return std::numeric_limits::digits - support::countl_zero(value); + } - template - [[nodiscard]] constexpr std::enable_if_t, T> rotr(T value, int rotate); + template [[nodiscard]] constexpr std::enable_if_t, T> rotr(T value, int rotate); - template - [[nodiscard]] constexpr std::enable_if_t, T> rotl(T value, int rotate) + template [[nodiscard]] constexpr std::enable_if_t, T> rotl(T value, int rotate) { constexpr unsigned N = std::numeric_limits::digits; rotate = rotate % N; - if (!rotate) { return value; } - if (rotate < 0) { return support::rotr(value, -rotate); } + if (!rotate) + { + return value; + } + if (rotate < 0) + { + return support::rotr(value, -rotate); + } return (value << rotate) | (value >> (N - rotate)); } - template - [[nodiscard]] constexpr std::enable_if_t, T> rotr(T value, int rotate) + template [[nodiscard]] constexpr std::enable_if_t, T> rotr(T value, int rotate) { constexpr unsigned N = std::numeric_limits::digits; rotate = rotate % N; - if (!rotate) { return value; } - if (rotate < 0) { return support::rotl(value, -rotate); } + if (!rotate) + { + return value; + } + if (rotate < 0) + { + return support::rotl(value, -rotate); + } return (value >> rotate) | (value << (N - rotate)); } - template - constexpr std::enable_if_t, T> mask_trailing_ones() + template constexpr std::enable_if_t, T> mask_trailing_ones() { static_assert(!T::SIGNED && count <= T::BITS); - if constexpr (count == T::BITS) { return T::all_ones(); } + if constexpr (count == T::BITS) + { + return T::all_ones(); + } constexpr std::size_t QUOTIENT = count / T::WORD_SIZE; constexpr std::size_t REMAINDER = count % T::WORD_SIZE; constexpr auto ALL_ONES_WORD = std::numeric_limits::max(); T out; // zero initialized - for (std::size_t i = 0; i <= QUOTIENT; ++i) { out[i] = i < QUOTIENT ? ALL_ONES_WORD : mask_trailing_ones(); } + for (std::size_t i = 0; i <= QUOTIENT; ++i) + { + out[i] = i < QUOTIENT ? ALL_ONES_WORD : mask_trailing_ones(); + } return out; } - template - constexpr std::enable_if_t, T> mask_leading_ones() + template constexpr std::enable_if_t, T> mask_leading_ones() { static_assert(!T::SIGNED && count <= T::BITS); - if constexpr (count == T::BITS) { return T::all_ones(); } + if constexpr (count == T::BITS) + { + return T::all_ones(); + } constexpr std::size_t QUOTIENT = (T::BITS - count - 1U) / T::WORD_SIZE; constexpr std::size_t REMAINDER = count % T::WORD_SIZE; constexpr auto ALL_ONES_WORD = std::numeric_limits::max(); @@ -1677,33 +1845,40 @@ namespace ccm::support return out; } - template - constexpr std::enable_if_t, T> mask_trailing_zeros() - { return mask_leading_ones(); } + template constexpr std::enable_if_t, T> mask_trailing_zeros() + { + return mask_leading_ones(); + } - template - constexpr std::enable_if_t, T> mask_leading_zeros() - { return mask_trailing_ones(); } + template constexpr std::enable_if_t, T> mask_leading_zeros() + { + return mask_trailing_ones(); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> count_zeros(T value) - { return support::popcount(~value); } + template [[nodiscard]] constexpr std::enable_if_t, int> count_zeros(T value) + { + return support::popcount(~value); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> first_leading_zero(T value) - { return value == std::numeric_limits::max() ? 0 : support::countl_one(value) + 1; } + template [[nodiscard]] constexpr std::enable_if_t, int> first_leading_zero(T value) + { + return value == std::numeric_limits::max() ? 0 : support::countl_one(value) + 1; + } - template - [[nodiscard]] constexpr std::enable_if_t, int> first_leading_one(T value) - { return first_leading_zero(~value); } + template [[nodiscard]] constexpr std::enable_if_t, int> first_leading_one(T value) + { + return first_leading_zero(~value); + } - template - [[nodiscard]] constexpr std::enable_if_t, int> first_trailing_zero(T value) - { return value == std::numeric_limits::max() ? 0 : support::countr_zero(~value) + 1; } + template [[nodiscard]] constexpr std::enable_if_t, int> first_trailing_zero(T value) + { + return value == std::numeric_limits::max() ? 0 : support::countr_zero(~value) + 1; + } - template - [[nodiscard]] constexpr std::enable_if_t, int> first_trailing_one(T value) - { return value == std::numeric_limits::max() ? 0 : support::countr_zero(value) + 1; } + template [[nodiscard]] constexpr std::enable_if_t, int> first_trailing_one(T value) + { + return value == std::numeric_limits::max() ? 0 : support::countr_zero(value) + 1; + } } // namespace ccm::support #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/internal/types/double_double.hpp b/include/ccmath/internal/types/double_double.hpp index efc67319..3a08f657 100644 --- a/include/ccmath/internal/types/double_double.hpp +++ b/include/ccmath/internal/types/double_double.hpp @@ -27,7 +27,10 @@ namespace ccm // raising spurious public FE_INEXACT / range exceptions. constexpr double exact_fma(double x, double y, double z) noexcept // NOLINT(bugprone-exception-escape) { - if (support::is_constant_evaluated()) { return support::fp::generic_fma(x, y, z); } + if (support::is_constant_evaluated()) + { + return support::fp::generic_fma(x, y, z); + } #if CCM_HAS_BUILTIN(__builtin_fma) return __builtin_fma(x, y, z); #else @@ -115,6 +118,8 @@ namespace ccm template <> constexpr types::DoubleDouble multiply_add(const types::DoubleDouble & x, const types::DoubleDouble & y, const types::DoubleDouble & z) - { return add(z, quick_mult(x, y)); } + { + return add(z, quick_mult(x, y)); + } } // namespace support } // namespace ccm diff --git a/include/ccmath/internal/types/dyadic_float.hpp b/include/ccmath/internal/types/dyadic_float.hpp index 941dfa63..1e5d04b5 100644 --- a/include/ccmath/internal/types/dyadic_float.hpp +++ b/include/ccmath/internal/types/dyadic_float.hpp @@ -50,10 +50,12 @@ namespace ccm::types // Decide whether to round a UInt up, down, or not at all at a given bit position, // based on the current rounding mode. - template - constexpr int rounding_direction(const UInt& value, size_t rshift, Sign logical_sign) + template constexpr int rounding_direction(const UInt & value, size_t rshift, Sign logical_sign) { - if (rshift == 0 || (rshift < Bits && (value << (Bits - rshift)) == 0) || (rshift >= Bits && value == 0)) { return 0; } + if (rshift == 0 || (rshift < Bits && (value << (Bits - rshift)) == 0) || (rshift >= Bits && value == 0)) + { + return 0; + } switch (support::fenv::get_rounding_mode()) { @@ -65,9 +67,9 @@ namespace ccm::types } return -1; case FE_TOWARDZERO: return -1; - case FE_DOWNWARD: return logical_sign.is_neg() && (rshift < Bits && (value << (Bits - rshift)) != 0) ? +1 : -1; - case FE_UPWARD: return logical_sign.is_pos() && (rshift < Bits && (value << (Bits - rshift)) != 0) ? +1 : -1; - default: support::unreachable(); + case FE_DOWNWARD : return logical_sign.is_neg() && (rshift < Bits && (value << (Bits - rshift)) != 0) ? +1 : -1; + case FE_UPWARD : return logical_sign.is_pos() && (rshift < Bits && (value << (Bits - rshift)) != 0) ? +1 : -1; + default : support::unreachable(); } } @@ -100,8 +102,7 @@ namespace ccm::types * * @tparam Bits The length of the mantissa in bits. */ - template - struct DyadicFloat + template struct DyadicFloat { using mantissa_type = UInt; using MantissaType = mantissa_type; @@ -112,8 +113,7 @@ namespace ccm::types constexpr DyadicFloat() = default; - template , int> = 0> - constexpr DyadicFloat(T x) // NOLINT(google-explicit-constructor) + template , int> = 0> constexpr DyadicFloat(T x) // NOLINT(google-explicit-constructor) { static_assert(support::fp::FPBits::fraction_length < Bits); support::fp::FPBits x_bits(x); @@ -123,7 +123,10 @@ namespace ccm::types normalize(); } - constexpr DyadicFloat(Sign s, int e, const mantissa_type& m) : sign(s), exponent(e), mantissa(m) { normalize(); } + constexpr DyadicFloat(Sign s, int e, const mantissa_type & m) : sign(s), exponent(e), mantissa(m) + { + normalize(); + } /** * @brief Normalizes the mantissa, bringing the leading 1 bit to the most significant bit. @@ -133,7 +136,7 @@ namespace ccm::types * * @return A reference to the normalized DyadicFloat. */ - constexpr DyadicFloat& normalize() + constexpr DyadicFloat & normalize() { if (!mantissa.is_zero()) { @@ -153,14 +156,13 @@ namespace ccm::types * @param shift_length The number of bits to shift left. * @return A reference to the DyadicFloat after shifting. */ - constexpr DyadicFloat& shift_left(unsigned shift_length) + constexpr DyadicFloat & shift_left(unsigned shift_length) { if (shift_length < Bits) { exponent -= static_cast(shift_length); mantissa <<= shift_length; - } - else + } else { exponent = 0; mantissa = mantissa_type(0); @@ -177,14 +179,13 @@ namespace ccm::types * @param shift_length The number of bits to shift right. * @return A reference to the DyadicFloat after shifting. */ - constexpr DyadicFloat& shift_right(unsigned shift_length) + constexpr DyadicFloat & shift_right(unsigned shift_length) { if (shift_length < Bits) { exponent += static_cast(shift_length); mantissa >>= shift_length; - } - else + } else { exponent = 0; mantissa = mantissa_type(0); @@ -197,10 +198,13 @@ namespace ccm::types * * @return The unbiased exponent. */ - [[nodiscard]] constexpr int get_unbiased_exponent() const { return exponent + static_cast(Bits - 1); } + [[nodiscard]] constexpr int get_unbiased_exponent() const + { + return exponent + static_cast(Bits - 1); + } template - static constexpr DyadicFloat round(Sign result_sign, int result_exponent, const UInt& input_mantissa, size_t rshift) + static constexpr DyadicFloat round(Sign result_sign, int result_exponent, const UInt & input_mantissa, size_t rshift) { mantissa_type result_mantissa(input_mantissa >> rshift); if (rounding_direction(input_mantissa, rshift, result_sign) > 0) @@ -222,7 +226,10 @@ namespace ccm::types constexpr int EXTRA_FRACTION_LEN = static_cast(Bits - 1 - FPBits::fraction_length); - if (mantissa.is_zero()) { return FPBits::zero(sign).get_val(); } + if (mantissa.is_zero()) + { + return FPBits::zero(sign).get_val(); + } const int unbiased_exp = get_unbiased_exponent(); @@ -236,13 +243,19 @@ namespace ccm::types switch (support::fenv::get_rounding_mode()) { - case FE_TONEAREST: return FPBits::inf(sign).get_val(); + case FE_TONEAREST : return FPBits::inf(sign).get_val(); case FE_TOWARDZERO: return FPBits::max_normal(sign).get_val(); case FE_DOWNWARD: - if (sign.is_pos()) { return FPBits::max_normal(Sign::POS).get_val(); } + if (sign.is_pos()) + { + return FPBits::max_normal(Sign::POS).get_val(); + } return FPBits::inf(Sign::NEG).get_val(); case FE_UPWARD: - if (sign.is_neg()) { return FPBits::max_normal(Sign::NEG).get_val(); } + if (sign.is_neg()) + { + return FPBits::max_normal(Sign::NEG).get_val(); + } return FPBits::inf(Sign::POS).get_val(); default: support::unreachable(); } @@ -258,15 +271,13 @@ namespace ccm::types { sticky = true; underflow = true; - } - else if (unbiased_exp == -FPBits::exponent_bias - FPBits::fraction_length) + } else if (unbiased_exp == -FPBits::exponent_bias - FPBits::fraction_length) { round = true; underflow = true; const mantissa_type sticky_mask = (mantissa_type(1) << (Bits - 1)) - 1; sticky = (mantissa & sticky_mask) != 0; - } - else + } else { int extra_fraction_len = EXTRA_FRACTION_LEN; @@ -274,8 +285,7 @@ namespace ccm::types { underflow = true; extra_fraction_len += 1 - FPBits::exponent_bias - unbiased_exp; - } - else + } else { out_biased_exp = static_cast(unbiased_exp + FPBits::exponent_bias); } @@ -295,13 +305,22 @@ namespace ccm::types switch (support::fenv::get_rounding_mode()) { case FE_TONEAREST: - if (round && (lsb || sticky)) { ++result; } + if (round && (lsb || sticky)) + { + ++result; + } break; case FE_DOWNWARD: - if (sign.is_neg() && (round || sticky)) { ++result; } + if (sign.is_neg() && (round || sticky)) + { + ++result; + } break; case FE_UPWARD: - if (sign.is_pos() && (round || sticky)) { ++result; } + if (sign.is_pos() && (round || sticky)) + { + ++result; + } break; default: break; } @@ -315,8 +334,7 @@ namespace ccm::types { support::fenv::set_errno_if_required(ERANGE); excepts |= FE_OVERFLOW; - } - else if (underflow) + } else if (underflow) { support::fenv::set_errno_if_required(ERANGE); excepts |= FE_UNDERFLOW; @@ -357,7 +375,10 @@ namespace ccm::types typename = std::enable_if_t && (support::fp::FPBits::fraction_length < Bits), void>> constexpr T fast_as() const { - if (CCM_UNLIKELY(mantissa.is_zero())) { return support::fp::FPBits::zero(sign).get_val(); } + if (CCM_UNLIKELY(mantissa.is_zero())) + { + return support::fp::FPBits::zero(sign).get_val(); + } // Assume that it is normalized, and output is also normal. constexpr std::uint32_t precision = support::fp::FPBits::fraction_length + 1; @@ -379,7 +400,10 @@ namespace ccm::types // TODO(IanP): decide whether rounding the absolute value down to max_normal // should also raise FE_OVERFLOW and set ERANGE. The current behavior matches Clang. // TODO(IanP): check what MSVC does here and match it under MSVC. - if constexpr (ShouldSignalExceptions && support::fp::FPBits(r).is_inf()) { support::fenv::set_errno_if_required(ERANGE); } + if constexpr (ShouldSignalExceptions && support::fp::FPBits(r).is_inf()) + { + support::fenv::set_errno_if_required(ERANGE); + } return r; } @@ -483,7 +507,9 @@ namespace ccm::types bool ShouldSignalExceptions, typename = std::enable_if_t && (support::fp::FPBits::fraction_length < Bits), void>> constexpr T as() const - { return fast_as(); } + { + return fast_as(); + } /** * @brief Explicitly converts this DyadicFloat to the floating-point type \p T, @@ -500,55 +526,83 @@ namespace ccm::types */ template && (support::fp::FPBits::fraction_length < Bits), void>> explicit constexpr operator T() const - { return as(); } + { + return as(); + } constexpr mantissa_type as_mantissa_type() const { - if (mantissa.is_zero()) { return 0; } + if (mantissa.is_zero()) + { + return 0; + } mantissa_type new_mant = mantissa; - if (exponent > 0) { new_mant <<= exponent; } - else + if (exponent > 0) + { + new_mant <<= exponent; + } else { const std::size_t shift = static_cast(-static_cast(exponent)); new_mant >>= shift; } - if (sign.is_neg()) { new_mant = (~new_mant) + 1; } + if (sign.is_neg()) + { + new_mant = (~new_mant) + 1; + } return new_mant; } - constexpr mantissa_type as_mantissa_type_rounded(int* round_dir_out = nullptr) const + constexpr mantissa_type as_mantissa_type_rounded(int * round_dir_out = nullptr) const { int round_dir = 0; mantissa_type new_mant; - if (mantissa.is_zero()) { new_mant = 0; } - else + if (mantissa.is_zero()) + { + new_mant = 0; + } else { new_mant = mantissa; - if (exponent > 0) { new_mant <<= exponent; } - else if (exponent < 0) + if (exponent > 0) + { + new_mant <<= exponent; + } else if (exponent < 0) { const std::size_t shift = static_cast(-static_cast(exponent)); - if (shift >= Bits) { new_mant = 0; } - else + if (shift >= Bits) + { + new_mant = 0; + } else { new_mant >>= shift; } round_dir = rounding_direction(mantissa, shift, sign); - if (round_dir > 0) { ++new_mant; } + if (round_dir > 0) + { + ++new_mant; + } } - if (sign.is_neg()) { new_mant = (~new_mant) + 1; } + if (sign.is_neg()) + { + new_mant = (~new_mant) + 1; + } } - if (round_dir_out != nullptr) { *round_dir_out = round_dir; } + if (round_dir_out != nullptr) + { + *round_dir_out = round_dir; + } return new_mant; } - constexpr DyadicFloat operator-() const { return DyadicFloat(sign.is_neg() ? Sign::POS : Sign::NEG, exponent, mantissa); } + constexpr DyadicFloat operator-() const + { + return DyadicFloat(sign.is_neg() ? Sign::POS : Sign::NEG, exponent, mantissa); + } }; /** @@ -578,15 +632,25 @@ namespace ccm::types * @param b Second DyadicFloat operand. * @return A DyadicFloat representing the approximate sum of \p a and \p b. */ - template - constexpr DyadicFloat quick_add(DyadicFloat a, DyadicFloat b) + template constexpr DyadicFloat quick_add(DyadicFloat a, DyadicFloat b) { - if (CCM_UNLIKELY(a.mantissa.is_zero())) { return b; } - if (CCM_UNLIKELY(b.mantissa.is_zero())) { return a; } + if (CCM_UNLIKELY(a.mantissa.is_zero())) + { + return b; + } + if (CCM_UNLIKELY(b.mantissa.is_zero())) + { + return a; + } // Align exponents - if (a.exponent > b.exponent) { b.shift_right(static_cast(a.exponent - b.exponent)); } - else if (b.exponent > a.exponent) { a.shift_right(static_cast(b.exponent - a.exponent)); } + if (a.exponent > b.exponent) + { + b.shift_right(static_cast(a.exponent - b.exponent)); + } else if (b.exponent > a.exponent) + { + a.shift_right(static_cast(b.exponent - a.exponent)); + } DyadicFloat result; @@ -613,8 +677,7 @@ namespace ccm::types result.sign = a.sign; result.exponent = a.exponent; result.mantissa = a.mantissa - b.mantissa; - } - else + } else { result.sign = b.sign; result.exponent = b.exponent; @@ -648,8 +711,7 @@ namespace ccm::types * @param b Second dyadic float. * @return A new DyadicFloat representing the approximate product. */ - template - constexpr DyadicFloat quick_mul(DyadicFloat a, DyadicFloat b) + template constexpr DyadicFloat quick_mul(DyadicFloat a, DyadicFloat b) { DyadicFloat result; result.sign = (a.sign != b.sign) ? Sign::NEG : Sign::POS; @@ -664,8 +726,7 @@ namespace ccm::types { result.shift_left(1); } - } - else + } else { result.mantissa = static_cast::mantissa_type>(0); } @@ -684,9 +745,10 @@ namespace ccm::types * @param c The dyadic float to be added to the product of \p a and \p b. * @return A new DyadicFloat containing the result of \p c + (\p a * \p b). */ - template - constexpr DyadicFloat multiply_add(const DyadicFloat& a, const DyadicFloat& b, const DyadicFloat& c) - { return quick_add(c, quick_mul(a, b)); } + template constexpr DyadicFloat multiply_add(const DyadicFloat & a, const DyadicFloat & b, const DyadicFloat & c) + { + return quick_add(c, quick_mul(a, b)); + } /** * @brief Simple exponentiation implementation for printing or other use. @@ -701,15 +763,17 @@ namespace ccm::types * @param power The positive exponent. * @return A DyadicFloat representing \f$a^\text{power}\f$. */ - template - constexpr DyadicFloat pow_n(DyadicFloat a, uint32_t power) + template constexpr DyadicFloat pow_n(DyadicFloat a, uint32_t power) { DyadicFloat result = 1.0; DyadicFloat cur_power = a; while (power > 0) { - if ((power % 2) > 0) { result = quick_mul(result, cur_power); } + if ((power % 2) > 0) + { + result = quick_mul(result, cur_power); + } power = power >> 1; cur_power = quick_mul(cur_power, cur_power); } @@ -729,20 +793,19 @@ namespace ccm::types * \f$2^{-\text{pow\_2}}\f$. * @return The result of \f$a \times 2^{\text{pow\_2}}\f$. */ - template - constexpr DyadicFloat mul_pow_2(DyadicFloat a, int32_t pow_2) + template constexpr DyadicFloat mul_pow_2(DyadicFloat a, int32_t pow_2) { DyadicFloat result = a; result.exponent += pow_2; return result; } - template - constexpr DyadicFloat quick_sub(DyadicFloat a, DyadicFloat b) - { return quick_add(a, -b); } + template constexpr DyadicFloat quick_sub(DyadicFloat a, DyadicFloat b) + { + return quick_add(a, -b); + } - template - constexpr DyadicFloat rounded_mul(const DyadicFloat& a, const DyadicFloat& b) + template constexpr DyadicFloat rounded_mul(const DyadicFloat & a, const DyadicFloat & b) { using DblMant = UInt<(2 * Bits)>; const Sign result_sign = (a.sign != b.sign) ? Sign::NEG : Sign::POS; @@ -758,8 +821,7 @@ namespace ccm::types return DyadicFloat::round(result_sign, result_exponent, normalized_product, Bits); } - template - constexpr DyadicFloat approx_reciprocal(const DyadicFloat& a) + template constexpr DyadicFloat approx_reciprocal(const DyadicFloat & a) { DyadicFloat x(Sign::POS, -32 - a.exponent - static_cast(Bits), @@ -776,14 +838,16 @@ namespace ccm::types return x; } - template - constexpr DyadicFloat rounded_div(const DyadicFloat& af, const DyadicFloat& bf) + template constexpr DyadicFloat rounded_div(const DyadicFloat & af, const DyadicFloat & bf) { using WideMant = UInt<(Bits * 2) + 64>; constexpr std::size_t guard_bits = 64; const Sign result_sign = (af.sign != bf.sign) ? Sign::NEG : Sign::POS; - if (af.mantissa.is_zero()) { return DyadicFloat(result_sign, 0, typename DyadicFloat::mantissa_type(0)); } + if (af.mantissa.is_zero()) + { + return DyadicFloat(result_sign, 0, typename DyadicFloat::mantissa_type(0)); + } const bool quotient_in_unit_interval = af.mantissa < bf.mantissa; const int result_exponent = af.exponent - bf.exponent - static_cast(Bits) + (quotient_in_unit_interval ? 0 : 1); @@ -797,7 +861,10 @@ namespace ccm::types // Encode any truncated tail into the discarded region so DyadicFloat::round sees the // correct sticky state when it drops the guard bits. - if (!remainder.is_zero()) { quotient |= WideMant(1); } + if (!remainder.is_zero()) + { + quotient |= WideMant(1); + } return DyadicFloat::round(result_sign, result_exponent, quotient, guard_bits); } diff --git a/include/ccmath/internal/types/normalized_float.hpp b/include/ccmath/internal/types/normalized_float.hpp index 61df02b8..426c63f1 100644 --- a/include/ccmath/internal/types/normalized_float.hpp +++ b/include/ccmath/internal/types/normalized_float.hpp @@ -26,8 +26,7 @@ namespace ccm::types { // Normalized floating-point mantissa in the form (-1)^sign * 2^exponent * 1.fraction. // Zero, infinity, and NaN bit patterns are not represented here. - template - struct NormalizedFloat + template struct NormalizedFloat { static_assert(support::traits::ccm_is_floating_point_v, "NormalizedFloat requires a floating-point type."); @@ -43,27 +42,48 @@ namespace ccm::types constexpr NormalizedFloat(Sign s, std::int32_t e, StorageType m) : exponent(e), mantissa(m), sign(s) { - if (mantissa >= ONE) { return; } + if (mantissa >= ONE) + { + return; + } const unsigned normalization_shift = evaluate_normalization_shift(mantissa); mantissa <<= normalization_shift; exponent -= static_cast(normalization_shift); } - constexpr explicit NormalizedFloat(T x) { init_from_bits(FPBits(x)); } + constexpr explicit NormalizedFloat(T x) + { + init_from_bits(FPBits(x)); + } - constexpr explicit NormalizedFloat(FPBits bits) { init_from_bits(bits); } + constexpr explicit NormalizedFloat(FPBits bits) + { + init_from_bits(bits); + } [[nodiscard]] constexpr int cmp(const NormalizedFloat & other) const { const int result = sign.is_neg() ? -1 : 1; - if (sign != other.sign) { return result; } + if (sign != other.sign) + { + return result; + } - if (exponent > other.exponent) { return result; } + if (exponent > other.exponent) + { + return result; + } if (exponent == other.exponent) { - if (mantissa > other.mantissa) { return result; } - if (mantissa == other.mantissa) { return 0; } + if (mantissa > other.mantissa) + { + return result; + } + if (mantissa == other.mantissa) + { + return 0; + } return -result; } return -result; @@ -78,8 +98,10 @@ namespace ccm::types constexpr explicit operator T() const { - if constexpr (support::fp::get_fp_type() == support::fp::FPType::eBinary80) { return convert_float80(); } - else + if constexpr (support::fp::get_fp_type() == support::fp::FPType::eBinary80) + { + return convert_float80(); + } else { return convert_ieee(); } @@ -89,7 +111,8 @@ namespace ccm::types static constexpr unsigned evaluate_normalization_shift(StorageType m) { unsigned shift = 0; - for (; (ONE & m) == 0 && shift < FPBits::fraction_length; m <<= 1, ++shift) {} + for (; (ONE & m) == 0 && shift < FPBits::fraction_length; m <<= 1, ++shift) + {} return shift; } @@ -104,8 +127,10 @@ namespace ccm::types return; } - if constexpr (support::fp::get_fp_type() == support::fp::FPType::eBinary80) { init_from_bits_float80(bits); } - else + if constexpr (support::fp::get_fp_type() == support::fp::FPType::eBinary80) + { + init_from_bits_float80(bits); + } else { init_from_bits_ieee(bits); } @@ -118,8 +143,7 @@ namespace ccm::types const unsigned shift = evaluate_normalization_shift(bits.get_mantissa()); mantissa = static_cast(bits.get_mantissa() << shift); exponent = 1 - FPBits::exponent_bias - static_cast(shift); - } - else + } else { exponent = bits.get_biased_exponent() - FPBits::exponent_bias; mantissa = ONE | bits.get_mantissa(); @@ -135,21 +159,18 @@ namespace ccm::types const int normalization_shift = static_cast(evaluate_normalization_shift(bits.get_mantissa())); exponent = -16382 - normalization_shift; mantissa = bits.get_mantissa() << static_cast(normalization_shift); - } - else + } else { exponent = -16382; mantissa = ONE | bits.get_mantissa(); } - } - else + } else { if (bits.get_implicit_bit() == 0) { exponent = 0; mantissa = 0; - } - else + } else { exponent = bits.get_biased_exponent() - 16383; mantissa = ONE | bits.get_mantissa(); @@ -161,7 +182,10 @@ namespace ccm::types { int const biased_exponent = exponent + FPBits::exponent_bias; constexpr int max_exponent_value = (1 << FPBits::exponent_length) - 2; - if (biased_exponent > max_exponent_value) { return FPBits::inf(sign).get_val(); } + if (biased_exponent > max_exponent_value) + { + return FPBits::inf(sign).get_val(); + } FPBits result(T(0.0)); result.set_sign(sign); @@ -178,13 +202,21 @@ namespace ccm::types result.set_biased_exponent(0); result.set_mantissa(mantissa >> shift); StorageType new_mantissa = result.get_mantissa(); - if (shift_out_value > halfway_value) { new_mantissa += 1; } - else if (shift_out_value == halfway_value) + if (shift_out_value > halfway_value) + { + new_mantissa += 1; + } else if (shift_out_value == halfway_value) { - if ((result.get_mantissa() & 1) != 0) { new_mantissa += 1; } + if ((result.get_mantissa() & 1) != 0) + { + new_mantissa += 1; + } } result.set_mantissa(new_mantissa); - if (new_mantissa == ONE) { result.set_biased_exponent(1); } + if (new_mantissa == ONE) + { + result.set_biased_exponent(1); + } return result.get_val(); } return result.get_val(); @@ -199,7 +231,10 @@ namespace ccm::types { int biased_exponent = exponent + 16383; constexpr int max_exponent_value = (1 << 15) - 2; - if (biased_exponent > max_exponent_value) { return FPBits::inf(sign).get_val(); } + if (biased_exponent > max_exponent_value) + { + return FPBits::inf(sign).get_val(); + } FPBits result(0.0L); result.set_sign(sign); @@ -216,18 +251,22 @@ namespace ccm::types result.set_biased_exponent(0); result.set_mantissa(mantissa >> shift); StorageType new_mantissa = result.get_mantissa(); - if (shift_out_value > halfway_value) { new_mantissa += 1; } - else if (shift_out_value == halfway_value) + if (shift_out_value > halfway_value) { - if ((result.get_mantissa() & 1) != 0) { new_mantissa += 1; } + new_mantissa += 1; + } else if (shift_out_value == halfway_value) + { + if ((result.get_mantissa() & 1) != 0) + { + new_mantissa += 1; + } } result.set_mantissa(new_mantissa); if (new_mantissa == ONE) { result.set_biased_exponent(1); result.set_implicit_bit(1); - } - else + } else { result.set_implicit_bit(0); } @@ -243,7 +282,6 @@ namespace ccm::types } }; - template - using NormalFloat = NormalizedFloat; + template using NormalFloat = NormalizedFloat; } // namespace ccm::types diff --git a/include/ccmath/internal/types/number_pair.hpp b/include/ccmath/internal/types/number_pair.hpp index 877fbcdc..79f73935 100644 --- a/include/ccmath/internal/types/number_pair.hpp +++ b/include/ccmath/internal/types/number_pair.hpp @@ -14,15 +14,13 @@ namespace ccm::types { - template - struct NumberPair + template struct NumberPair { T hi; T lo; }; - template - std::enable_if_t && std::is_unsigned_v, NumberPair> split(T x) + template std::enable_if_t && std::is_unsigned_v, NumberPair> split(T x) { constexpr std::size_t half_bits_width = sizeof(T) * 4; constexpr T Low_mask = (T{ 1 } << half_bits_width) - T{ 1 }; diff --git a/include/ccmath/internal/types/sign.hpp b/include/ccmath/internal/types/sign.hpp index a3163401..a7a81f71 100644 --- a/include/ccmath/internal/types/sign.hpp +++ b/include/ccmath/internal/types/sign.hpp @@ -15,12 +15,24 @@ namespace ccm::types // A type to interact with signed arithmetic types. struct Sign { - [[nodiscard]] constexpr bool is_pos() const { return !m_is_negative_val; } - [[nodiscard]] constexpr bool is_neg() const { return m_is_negative_val; } - - friend constexpr bool operator==(Sign a, Sign b) { return a.m_is_negative_val == b.m_is_negative_val; } - - friend constexpr bool operator!=(Sign a, Sign b) { return !(a == b); } + [[nodiscard]] constexpr bool is_pos() const + { + return !m_is_negative_val; + } + [[nodiscard]] constexpr bool is_neg() const + { + return m_is_negative_val; + } + + friend constexpr bool operator==(Sign a, Sign b) + { + return a.m_is_negative_val == b.m_is_negative_val; + } + + friend constexpr bool operator!=(Sign a, Sign b) + { + return !(a == b); + } static const Sign POS; static const Sign NEG; diff --git a/include/ccmath/math/basic/fabs.hpp b/include/ccmath/math/basic/fabs.hpp index d5c8e2a4..7f241934 100644 --- a/include/ccmath/math/basic/fabs.hpp +++ b/include/ccmath/math/basic/fabs.hpp @@ -24,10 +24,12 @@ namespace ccm * @param num Floating-point or integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> + template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::fabs_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::fabs_rt(num); + } return func::fabs(num); } @@ -37,8 +39,7 @@ namespace ccm * @param num Floating-point or integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> + template constexpr auto abs(T num) -> std::enable_if_t && std::is_signed_v, T> { // If num is less than zero, return -num, otherwise return num. The explicit cast keeps the // result in T, where -num integer-promotes to int and would otherwise narrow on return for @@ -52,8 +53,7 @@ namespace ccm * @param num Unsigned value. * @return num unchanged. An unsigned value is already non-negative, so its absolute value is itself. */ - template - constexpr auto abs(T num) -> std::enable_if_t, T> + template constexpr auto abs(T num) -> std::enable_if_t, T> { // An unsigned value is already non-negative, so the absolute value is the value itself. // Returning num directly is lossless and type-preserving, where routing through int would @@ -67,9 +67,10 @@ namespace ccm * @param num Floating-point value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fabs(T num) -> std::enable_if_t, T> - { return ccm::abs(num); } + template constexpr auto fabs(T num) -> std::enable_if_t, T> + { + return ccm::abs(num); + } /** * @brief Computes the absolute value of a number. @@ -77,9 +78,10 @@ namespace ccm * @param num Integer value. * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fabs(Integer num) -> std::enable_if_t, double> - { return ccm::abs(static_cast(num)); } + template constexpr auto fabs(Integer num) -> std::enable_if_t, double> + { + return ccm::abs(static_cast(num)); + } /** * @brief Computes the absolute value of a number. @@ -87,7 +89,9 @@ namespace ccm * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ constexpr float fabsf(float num) - { return ccm::abs(num); } + { + return ccm::abs(num); + } /** * @brief Computes the absolute value of a number. @@ -95,7 +99,9 @@ namespace ccm * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ constexpr long double fabsl(long double num) - { return ccm::abs(num); } + { + return ccm::abs(num); + } /** * @brief Computes the absolute value of a number. @@ -103,7 +109,9 @@ namespace ccm * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ constexpr long labs(long num) - { return ccm::abs(num); } + { + return ccm::abs(num); + } /** * @brief Computes the absolute value of a number. @@ -111,7 +119,9 @@ namespace ccm * @return If successful, returns the absolute value of arg (|arg|). The value returned is exact and does not depend on any rounding modes. */ constexpr long long llabs(long long num) - { return ccm::abs(num); } + { + return ccm::abs(num); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/fdim.hpp b/include/ccmath/math/basic/fdim.hpp index cbc6e823..6681db62 100644 --- a/include/ccmath/math/basic/fdim.hpp +++ b/include/ccmath/math/basic/fdim.hpp @@ -24,12 +24,15 @@ namespace ccm * @param y A floating-point or integer values * @return If successful, returns the positive difference between x and y. */ - template , bool> = true> - constexpr T fdim(T x, T y) + template , bool> = true> constexpr T fdim(T x, T y) { - if constexpr (ccm::builtin::has_constexpr_fdim) { return ccm::builtin::fdim_ct(x, y); } - else if (ccm::support::is_constant_evaluated()) { return func::fdim(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_fdim) + { + return ccm::builtin::fdim_ct(x, y); + } else if (ccm::support::is_constant_evaluated()) + { + return func::fdim(x, y); + } else { return ccm::rt::fdim_rt(x, y); } @@ -43,8 +46,7 @@ namespace ccm * @param y A floating-point value. * @return If successful, returns the positive difference between x and y. */ - template && std::is_floating_point_v, bool> = true> - constexpr auto fdim(T x, U y) + template && std::is_floating_point_v, bool> = true> constexpr auto fdim(T x, U y) { using shared_type = std::common_type_t; @@ -58,9 +60,10 @@ namespace ccm * @param y An integral value. * @return If successful, returns the positive difference between x and y. */ - template , bool> = true> - constexpr double fdim(Integer x, Integer y) - { return ccm::fdim(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double fdim(Integer x, Integer y) + { + return ccm::fdim(static_cast(x), static_cast(y)); + } /** * @brief Computes the positive difference of two floating point values (max(0,x−y)) @@ -69,7 +72,9 @@ namespace ccm * @return If successful, returns the positive difference between x and y. */ constexpr float fdimf(float x, float y) - { return ccm::fdim(x, y); } + { + return ccm::fdim(x, y); + } /** * @brief Computes the positive difference of two floating point values (max(0,x−y)) @@ -78,7 +83,9 @@ namespace ccm * @return If successful, returns the positive difference between x and y. */ constexpr long double fdiml(long double x, long double y) - { return ccm::fdim(x, y); } + { + return ccm::fdim(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/fmod.hpp b/include/ccmath/math/basic/fmod.hpp index 3dc9615f..a76422b0 100644 --- a/include/ccmath/math/basic/fmod.hpp +++ b/include/ccmath/math/basic/fmod.hpp @@ -24,12 +24,14 @@ namespace ccm /// @cond CCMATH_INTERNAL namespace internal::impl { - template - constexpr T fmod_impl_check(T x, T y) noexcept + template constexpr T fmod_impl_check(T x, T y) noexcept { if constexpr (std::is_floating_point_v) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::fmod_rt(x, y); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::fmod_rt(x, y); + } } // Special edge cases for floating-point types. @@ -58,7 +60,10 @@ namespace ccm } // If y is ±∞ and x is finite, x is returned. - if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) { return x; } + if (CCM_UNLIKELY(y_bits.is_inf() && x_bits.is_finite())) + { + return x; + } // If either argument is NaN, NaN is returned. if (CCM_UNLIKELY(x_bits.is_nan() || y_bits.is_nan())) @@ -71,16 +76,19 @@ namespace ccm // Exact, magnitude-independent reduction over FPBits that gives the same result in every // rounding mode. long double reduces through the double kernel, matching the fmodl and // remquol convention. - if constexpr (std::is_same_v) { return internal::fmod(x, y); } - else + if constexpr (std::is_same_v) + { + return internal::fmod(x, y); + } else { return static_cast(internal::fmod(static_cast(x), static_cast(y))); } } - template > - constexpr TC fmod_impl_type_check(T x, U y) noexcept - { return fmod_impl_check(static_cast(x), static_cast(y)); } + template > constexpr TC fmod_impl_type_check(T x, U y) noexcept + { + return fmod_impl_check(static_cast(x), static_cast(y)); + } } // namespace internal::impl /// @endcond @@ -92,9 +100,10 @@ namespace ccm * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr Real fmod(Real x, Real y) - { return internal::impl::fmod_impl_check(x, y); } + template , bool> = true> constexpr Real fmod(Real x, Real y) + { + return internal::impl::fmod_impl_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -103,9 +112,10 @@ namespace ccm * @param y An integral value. * @return The floating-point remainder of the division operation x/y. */ - template , bool> = true> - constexpr double fmod(Integer x, Integer y) - { return internal::impl::fmod_impl_type_check(x, y); } + template , bool> = true> constexpr double fmod(Integer x, Integer y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -115,9 +125,10 @@ namespace ccm * @param y A floating-point or integral value. * @return The floating-point remainder of the division operation x/y. */ - template - constexpr auto fmod(T x, T y) - { return internal::impl::fmod_impl_type_check(x, y); } + template constexpr auto fmod(T x, T y) + { + return internal::impl::fmod_impl_type_check(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -126,7 +137,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr float fmodf(float x, float y) - { return fmod(x, y); } + { + return fmod(x, y); + } /** * @brief Returns the floating-point remainder of the division operation x/y. @@ -135,7 +148,9 @@ namespace ccm * @return The floating-point remainder of the division operation x/y. */ constexpr long double fmodl(long double x, long double y) - { return fmod(x, y); } + { + return fmod(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/impl/fmod_impl.hpp b/include/ccmath/math/basic/impl/fmod_impl.hpp index 76424a19..47a4892c 100644 --- a/include/ccmath/math/basic/impl/fmod_impl.hpp +++ b/include/ccmath/math/basic/impl/fmod_impl.hpp @@ -31,8 +31,7 @@ namespace ccm::internal // exponents. The modular product is built a chunk of bits at a time with 64-bit arithmetic, so // the loop runs in gap / chunk steps instead of one step per bit, and the result stays exact for // every magnitude and independent of the active rounding mode. - template - constexpr T fmod_impl(T x, T y) noexcept + template constexpr T fmod_impl(T x, T y) noexcept { static_assert(std::is_floating_point_v, "fmod_impl requires a floating-point type"); // The reduction lifts the significand into a uint64_t and shifts it by chunk = 63 - mant_bits @@ -45,7 +44,10 @@ namespace ccm::internal const FPBits_t y_bits(y); // NaN for an infinite dividend, a zero divisor, or a NaN argument. - if (CCM_UNLIKELY(x_bits.is_inf() || y_bits.is_zero() || x_bits.is_nan() || y_bits.is_nan())) { return FPBits_t::quiet_nan().get_val(); } + if (CCM_UNLIKELY(x_bits.is_inf() || y_bits.is_zero() || x_bits.is_nan() || y_bits.is_nan())) + { + return FPBits_t::quiet_nan().get_val(); + } // Compare magnitudes through the exponent and significand bits. For finite values that integer // order matches the floating-point order, so this also covers fmod(x, +/-inf) = x. @@ -53,9 +55,15 @@ namespace ccm::internal const auto y_mag = y_bits.abs().uintval(); // Any abs(x) < abs(y) returns x unchanged. - if (x_mag < y_mag) { return x; } + if (x_mag < y_mag) + { + return x; + } // abs(x) == abs(y) leaves a zero that keeps the sign of x. - if (x_mag == y_mag) { return ccm::copysign(static_cast(0), x); } + if (x_mag == y_mag) + { + return ccm::copysign(static_cast(0), x); + } constexpr int mant_bits = std::numeric_limits::digits - 1; // Largest shift that keeps a reduced significand (below 2^(mant_bits + 1)) inside 64 bits. @@ -73,7 +81,10 @@ namespace ccm::internal gap -= step; } - if (r == 0) { return ccm::copysign(static_cast(0), x); } + if (r == 0) + { + return ccm::copysign(static_cast(0), x); + } return ccm::copysign(ccm::scalbn(static_cast(r), exp_y - mant_bits), x); } } // namespace impl @@ -81,7 +92,8 @@ namespace ccm::internal // Thin wrapper so the public dispatch reads the same as the other basic functions. float and // double evaluate natively. long double is reduced through the double kernel by the caller, // matching the existing fmodl and remquol convention. - template - constexpr T fmod(T x, T y) noexcept - { return impl::fmod_impl(x, y); } + template constexpr T fmod(T x, T y) noexcept + { + return impl::fmod_impl(x, y); + } } // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/nan_double_impl.hpp b/include/ccmath/math/basic/impl/nan_double_impl.hpp index 1aa69462..39f0e819 100644 --- a/include/ccmath/math/basic/impl/nan_double_impl.hpp +++ b/include/ccmath/math/basic/impl/nan_double_impl.hpp @@ -24,7 +24,10 @@ namespace ccm::internal::impl constexpr double nan_double_impl(const char * arg) noexcept { - if constexpr (!std::numeric_limits::is_iec559) { return 0.0; } + if constexpr (!std::numeric_limits::is_iec559) + { + return 0.0; + } #if (defined(_MSC_VER)) // MSVC and clang-cl both return a quiet NaN without honoring a payload. @@ -71,16 +74,21 @@ namespace ccm::internal::impl { dbl_bits *= 16; dbl_bits += static_cast(ccm::support::helpers::digit_to_int(arg[i])); - if (i >= 15) { dbl_bits %= static_cast(1e18); } + if (i >= 15) + { + dbl_bits %= static_cast(1e18); + } } - } - else + } else { for (std::size_t i = 0; arg[i] != '\0'; ++i) { dbl_bits *= 10; dbl_bits += static_cast(arg[i] - '0'); - if (i >= 15) { dbl_bits %= static_cast(1e18); } + if (i >= 15) + { + dbl_bits %= static_cast(1e18); + } } } // NOLINTEND diff --git a/include/ccmath/math/basic/impl/nan_float_impl.hpp b/include/ccmath/math/basic/impl/nan_float_impl.hpp index 3512376e..d6d0a79e 100644 --- a/include/ccmath/math/basic/impl/nan_float_impl.hpp +++ b/include/ccmath/math/basic/impl/nan_float_impl.hpp @@ -20,7 +20,10 @@ namespace ccm::internal::impl { constexpr float nan_float_impl(const char * arg) noexcept { - if constexpr (!std::numeric_limits::is_iec559) { return 0.0F; } + if constexpr (!std::numeric_limits::is_iec559) + { + return 0.0F; + } #if defined(_MSC_VER) && !defined(__clang__) // Currently, MSVC always returns a Quiet NaN no matter if a payload is @@ -61,16 +64,21 @@ namespace ccm::internal::impl { flt_bits *= 16; flt_bits += static_cast(ccm::support::helpers::digit_to_int(arg[i])); - if (i >= 7) { flt_bits %= static_cast(1e8); } + if (i >= 7) + { + flt_bits %= static_cast(1e8); + } } - } - else + } else { for (std::size_t i = 0; arg[i] != '\0'; ++i) { flt_bits *= 10; flt_bits += static_cast(ccm::support::helpers::digit_to_int(arg[i])); - if (i >= 7) { flt_bits %= static_cast(1e8); } + if (i >= 7) + { + flt_bits %= static_cast(1e8); + } } } diff --git a/include/ccmath/math/basic/impl/nan_ldouble_impl.hpp b/include/ccmath/math/basic/impl/nan_ldouble_impl.hpp index 41238dd0..ab1f3c53 100644 --- a/include/ccmath/math/basic/impl/nan_ldouble_impl.hpp +++ b/include/ccmath/math/basic/impl/nan_ldouble_impl.hpp @@ -25,7 +25,10 @@ namespace ccm::internal::impl constexpr long double nan_ldouble_impl(const char * arg) noexcept { - if constexpr (!std::numeric_limits::is_iec559) { return 0.0; } + if constexpr (!std::numeric_limits::is_iec559) + { + return 0.0; + } #if defined(_MSC_VER) && !defined(__clang__) // Currently, MSVC always returns a Quiet NaN no matter if a payload is @@ -76,16 +79,21 @@ namespace ccm::internal::impl { dbl_bits *= 16; dbl_bits += static_cast(ccm::support::helpers::digit_to_int(arg[i])); - if (i >= 15) { dbl_bits %= static_cast(1e18); } + if (i >= 15) + { + dbl_bits %= static_cast(1e18); + } } - } - else + } else { for (std::size_t i = 0; arg[i] != '\0'; ++i) { dbl_bits *= 10; dbl_bits += static_cast(arg[i] - '0'); - if (i >= 15) { dbl_bits %= static_cast(1e18); } + if (i >= 15) + { + dbl_bits %= static_cast(1e18); + } } } // NOLINTEND diff --git a/include/ccmath/math/basic/impl/remquo_double_impl.hpp b/include/ccmath/math/basic/impl/remquo_double_impl.hpp index 3ec17641..c1144e73 100644 --- a/include/ccmath/math/basic/impl/remquo_double_impl.hpp +++ b/include/ccmath/math/basic/impl/remquo_double_impl.hpp @@ -42,7 +42,10 @@ namespace ccm::internal y_i64 &= 0x7fffffffffffffffULL; // If y is zero. - if (CCM_UNLIKELY(y_i64 == 0)) { return (x * y) / (x * y); } + if (CCM_UNLIKELY(y_i64 == 0)) + { + return (x * y) / (x * y); + } // GCC and Clang do not like comparing signed and unsigned integers. // The outcome of these comparisons is well-defined, so we can safely disable these warnings. @@ -97,8 +100,7 @@ namespace ccm::internal ++computed_quotient; } } - } - else + } else { const double y_half = 0.5 * y; if (x > y_half) @@ -119,13 +121,21 @@ namespace ccm::internal *quo = quotient_sign != 0U ? -computed_quotient : computed_quotient; // Make sure that the correct sign of zero results in round down mode. - if (x == 0.0) { x = 0.0; } - if (x_sign != 0U) { x = -x; } + if (x == 0.0) + { + x = 0.0; + } + if (x_sign != 0U) + { + x = -x; + } return x; } } // namespace impl constexpr double remquo_double(double x, double y, int * quo) noexcept - { return impl::remquo_double_impl(x, y, quo); } + { + return impl::remquo_double_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/remquo_float_impl.hpp b/include/ccmath/math/basic/impl/remquo_float_impl.hpp index 1f2e9090..b101d564 100644 --- a/include/ccmath/math/basic/impl/remquo_float_impl.hpp +++ b/include/ccmath/math/basic/impl/remquo_float_impl.hpp @@ -41,10 +41,16 @@ namespace ccm::internal y_i32 &= 0x7fffffff; // If y is zero. - if (CCM_UNLIKELY(y_i32 == 0)) { return (x * y) / (x * y); } + if (CCM_UNLIKELY(y_i32 == 0)) + { + return (x * y) / (x * y); + } // If x is not finite or y is NaN. - if (CCM_UNLIKELY(x_i32 >= 0x7f800000 || y_i32 > 0x7f800000)) { return (x * y) / (x * y); } // NOLINT(readability-simplify-boolean-expr) + if (CCM_UNLIKELY(x_i32 >= 0x7f800000 || y_i32 > 0x7f800000)) + { + return (x * y) / (x * y); + } // NOLINT(readability-simplify-boolean-expr) if (y_i32 <= 0x7dffffff) { @@ -83,8 +89,7 @@ namespace ccm::internal ++computed_quotient; } } - } - else + } else { const float y_half = 0.5F * y; if (x > y_half) @@ -101,13 +106,21 @@ namespace ccm::internal *quo = quotient_sign != 0 ? -computed_quotient : computed_quotient; // Make sure that the correct sign of zero results in round down mode. - if (x == 0.0F) { x = 0.0F; } - if (x_sign != 0U) { x = -x; } + if (x == 0.0F) + { + x = 0.0F; + } + if (x_sign != 0U) + { + x = -x; + } return x; } } // namespace impl constexpr float remquo_float(float x, float y, int * quo) noexcept - { return impl::remquo_float_impl(x, y, quo); } + { + return impl::remquo_float_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/math/basic/impl/remquo_ldouble_impl.hpp b/include/ccmath/math/basic/impl/remquo_ldouble_impl.hpp index b0627439..7d86260f 100644 --- a/include/ccmath/math/basic/impl/remquo_ldouble_impl.hpp +++ b/include/ccmath/math/basic/impl/remquo_ldouble_impl.hpp @@ -47,5 +47,7 @@ namespace ccm::internal } // namespace impl constexpr long double remquo_ldouble(long double x, long double y, int * quo) noexcept - { return impl::remquo_ldouble_impl(x, y, quo); } + { + return impl::remquo_ldouble_impl(x, y, quo); + } } // namespace ccm::internal diff --git a/include/ccmath/math/basic/max.hpp b/include/ccmath/math/basic/max.hpp index b8ec47c8..65b9e246 100644 --- a/include/ccmath/math/basic/max.hpp +++ b/include/ccmath/math/basic/max.hpp @@ -29,16 +29,19 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T max(T x, T y) noexcept + template constexpr T max(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_fmax) { return ccm::builtin::fmax_ct(x, y); } - else if constexpr (std::is_floating_point_v) + if constexpr (ccm::builtin::has_constexpr_fmax) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::max(x, y); } + return ccm::builtin::fmax_ct(x, y); + } else if constexpr (std::is_floating_point_v) + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::max(x, y); + } return ccm::rt::fmax_rt(x, y); - } - else + } else { return ccm::gen::max(x, y); } @@ -52,8 +55,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return Larger of x and y after converting both values to their common type. */ - template - constexpr auto max(T x, U y) noexcept + template constexpr auto max(T x, U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -69,9 +71,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T fmax(T x, T y) noexcept - { return max(x, y); } + template constexpr T fmax(T x, T y) noexcept + { + return max(x, y); + } /** * @brief Computes the larger of the two values. @@ -81,8 +84,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fmax(T x, U y) noexcept + template constexpr auto fmax(T x, U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -97,9 +99,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr double fmax(Integer x, Integer y) noexcept - { return max(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double fmax(Integer x, Integer y) noexcept + { + return max(static_cast(x), static_cast(y)); + } /** * @brief Computes the larger of the two floating point values. @@ -108,7 +111,9 @@ namespace ccm * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ constexpr float fmaxf(float x, float y) noexcept - { return fmax(x, y); } + { + return fmax(x, y); + } /** * @brief Computes the larger of the two floating point values. @@ -117,7 +122,9 @@ namespace ccm * @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes. */ constexpr long double fmaxl(long double x, long double y) noexcept - { return fmax(x, y); } + { + return fmax(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/min.hpp b/include/ccmath/math/basic/min.hpp index c4623681..920b90f1 100644 --- a/include/ccmath/math/basic/min.hpp +++ b/include/ccmath/math/basic/min.hpp @@ -28,16 +28,19 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T min(const T x, const T y) noexcept + template constexpr T min(const T x, const T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_fmin) { return ccm::builtin::fmin_ct(x, y); } - else if constexpr (std::is_floating_point_v) + if constexpr (ccm::builtin::has_constexpr_fmin) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::min(x, y); } + return ccm::builtin::fmin_ct(x, y); + } else if constexpr (std::is_floating_point_v) + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::min(x, y); + } return ccm::rt::fmin_rt(x, y); - } - else + } else { return ccm::gen::min(x, y); } @@ -51,8 +54,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr T min(const T x, const U y) noexcept + template constexpr T min(const T x, const U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -68,9 +70,10 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr Real fmin(const Real x, const Real y) noexcept - { return min(x, y); } + template , bool> = true> constexpr Real fmin(const Real x, const Real y) noexcept + { + return min(x, y); + } /** * @brief Computes the smaller of the two values. @@ -80,8 +83,7 @@ namespace ccm * @param y Right-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template - constexpr auto fmin(const T x, const U y) noexcept + template constexpr auto fmin(const T x, const U y) noexcept { // Find the common type of the two arguments using shared_type = std::common_type_t; @@ -97,9 +99,10 @@ namespace ccm * @param y Left-hand side of the comparison. * @return If successful, returns the smaller of two floating point values. The value returned is exact and does not depend on any rounding modes. */ - template , bool> = true> - constexpr Integer fmin(const Integer x, const Integer y) noexcept - { return min(x, y); } + template , bool> = true> constexpr Integer fmin(const Integer x, const Integer y) noexcept + { + return min(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/nan.hpp b/include/ccmath/math/basic/nan.hpp index 9dd7ee20..02a2d47c 100644 --- a/include/ccmath/math/basic/nan.hpp +++ b/include/ccmath/math/basic/nan.hpp @@ -22,7 +22,9 @@ namespace ccm * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. */ constexpr double nan(const char * arg) noexcept - { return internal::impl::nan_double_impl(arg); } + { + return internal::impl::nan_double_impl(arg); + } /** * @brief not-a-number (NaN) function @@ -30,7 +32,9 @@ namespace ccm * @return The quiet NaN value that corresponds to the identifying string arg or zero if the implementation does not support quiet NaNs. */ constexpr float nanf(const char * arg) noexcept - { return internal::impl::nan_float_impl(arg); } + { + return internal::impl::nan_float_impl(arg); + } /** * @brief not-a-number (NaN) function diff --git a/include/ccmath/math/basic/remainder.hpp b/include/ccmath/math/basic/remainder.hpp index 8147d740..d28c1481 100644 --- a/include/ccmath/math/basic/remainder.hpp +++ b/include/ccmath/math/basic/remainder.hpp @@ -23,10 +23,12 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y. */ - template , bool> = true> - constexpr T remainder(T x, T y) + template , bool> = true> constexpr T remainder(T x, T y) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::remainder_rt(x, y); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::remainder_rt(x, y); + } // remainder is the remainder component of remquo, so reuse the exact iterative reduction // already implemented there. The quotient rounds to nearest, ties-to-even, which keeps the @@ -45,9 +47,10 @@ namespace ccm * @param y Divisor. * @return The remainder of the division of x by y as a double. */ - template , bool> = true> - constexpr double remainder(Integer x, Integer y) - { return ccm::remainder(static_cast(x), static_cast(y)); } + template , bool> = true> constexpr double remainder(Integer x, Integer y) + { + return ccm::remainder(static_cast(x), static_cast(y)); + } /** * @brief Returns the remainder of the division of x by y. @@ -56,7 +59,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr float remainderf(float x, float y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } /** * @brief Returns the remainder of the division of x by y. @@ -65,7 +70,9 @@ namespace ccm * @return The remainder of the division of x by y. */ constexpr long double remainderl(long double x, long double y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } } // namespace ccm /// @ingroup basic diff --git a/include/ccmath/math/basic/remquo.hpp b/include/ccmath/math/basic/remquo.hpp index e7636696..46c6d32a 100644 --- a/include/ccmath/math/basic/remquo.hpp +++ b/include/ccmath/math/basic/remquo.hpp @@ -43,11 +43,12 @@ namespace ccm * } * @endcode */ - template , bool> = true> - constexpr T remquo(T x, T y, int * quo) + template , bool> = true> constexpr T remquo(T x, T y, int * quo) { - if constexpr (std::is_same_v) { return internal::remquo_float(x, y, quo); } - else + if constexpr (std::is_same_v) + { + return internal::remquo_float(x, y, quo); + } else { // double runs natively. long double delegates to the double kernel, so the narrowing // is made explicit to stay clean under -Wconversion. @@ -119,7 +120,9 @@ namespace ccm * @endcode */ constexpr float remquof(float x, float y, int * quo) - { return ccm::remquo(x, y, quo); } + { + return ccm::remquo(x, y, quo); + } /** * @brief Signed remainder as well as the three last bits of the division operation diff --git a/include/ccmath/math/compare/fpclassify.hpp b/include/ccmath/math/compare/fpclassify.hpp index 977587a1..dc55fd5f 100644 --- a/include/ccmath/math/compare/fpclassify.hpp +++ b/include/ccmath/math/compare/fpclassify.hpp @@ -26,13 +26,24 @@ namespace ccm * @param num The number to classify * @return The classification of the number as an integer */ - template , bool> = true> - constexpr int fpclassify(T num) + template , bool> = true> constexpr int fpclassify(T num) { - if (ccm::isnan(num)) { return ccm::support::helpers::floating_point_defines::eFP_NAN; } - if (ccm::isinf(num)) { return ccm::support::helpers::floating_point_defines::eFP_INFINITE; } - if (num == static_cast(0)) { return ccm::support::helpers::floating_point_defines::eFP_ZERO; } - if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) { return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; } + if (ccm::isnan(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_NAN; + } + if (ccm::isinf(num)) + { + return ccm::support::helpers::floating_point_defines::eFP_INFINITE; + } + if (num == static_cast(0)) + { + return ccm::support::helpers::floating_point_defines::eFP_ZERO; + } + if (ccm::abs(num) < std::numeric_limits::min() && ccm::abs(num) > 0) + { + return ccm::support::helpers::floating_point_defines::eFP_SUBNORMAL; + } return ccm::support::helpers::floating_point_defines::eFP_NORMAL; } } // namespace ccm diff --git a/include/ccmath/math/compare/isfinite.hpp b/include/ccmath/math/compare/isfinite.hpp index 8a63e4bb..723100d1 100644 --- a/include/ccmath/math/compare/isfinite.hpp +++ b/include/ccmath/math/compare/isfinite.hpp @@ -21,11 +21,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(T num) + template , bool> = true> constexpr bool isfinite(T num) { - if constexpr (ccm::builtin::has_constexpr_isfinite) { return ccm::builtin::isfinite_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isfinite) + { + return ccm::builtin::isfinite_ct(num); + } else { using FPBits_t = typename ccm::support::fp::FPBits; const FPBits_t num_bits(num); @@ -38,8 +39,7 @@ namespace ccm * @tparam Integer The type of the integer. * @return true if the number has a finite value, false otherwise. */ - template , bool> = true> - constexpr bool isfinite(Integer /* x */) + template , bool> = true> constexpr bool isfinite(Integer /* x */) { return true; // All integers are finite } diff --git a/include/ccmath/math/compare/isgreater.hpp b/include/ccmath/math/compare/isgreater.hpp index 6daf6328..71eb684d 100644 --- a/include/ccmath/math/compare/isgreater.hpp +++ b/include/ccmath/math/compare/isgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreater(T x, T y) noexcept + template , bool> = true> constexpr bool isgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreater) { return ccm::builtin::isgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreater) + { + return ccm::builtin::isgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x > y); } diff --git a/include/ccmath/math/compare/isgreaterequal.hpp b/include/ccmath/math/compare/isgreaterequal.hpp index c2d8a3c2..4fc0942d 100644 --- a/include/ccmath/math/compare/isgreaterequal.hpp +++ b/include/ccmath/math/compare/isgreaterequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is greater than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool isgreaterequal(T x, T y) noexcept + template , bool> = true> constexpr bool isgreaterequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isgreaterequal) { return ccm::builtin::isgreaterequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isgreaterequal) + { + return ccm::builtin::isgreaterequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x >= y); } diff --git a/include/ccmath/math/compare/isinf.hpp b/include/ccmath/math/compare/isinf.hpp index c66df7d9..7cc1d9c8 100644 --- a/include/ccmath/math/compare/isinf.hpp +++ b/include/ccmath/math/compare/isinf.hpp @@ -24,11 +24,12 @@ namespace ccm * @param num The number to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(T num) noexcept + template , bool> = true> constexpr bool isinf(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isinf) { return ccm::builtin::isinf_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isinf) + { + return ccm::builtin::isinf_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -42,8 +43,7 @@ namespace ccm * @tparam Integer The type of the integer to check. * @return True if the number is infinite, false otherwise. */ - template , bool> = true> - constexpr bool isinf(Integer /* num */) noexcept + template , bool> = true> constexpr bool isinf(Integer /* num */) noexcept { return false; // Integers cannot be infinite } diff --git a/include/ccmath/math/compare/isless.hpp b/include/ccmath/math/compare/isless.hpp index 91972a5d..1478a63c 100644 --- a/include/ccmath/math/compare/isless.hpp +++ b/include/ccmath/math/compare/isless.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second, false otherwise. */ - template , bool> = true> - constexpr bool isless(T x, T y) noexcept + template , bool> = true> constexpr bool isless(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isless) { return ccm::builtin::isless_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isless) + { + return ccm::builtin::isless_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y); } diff --git a/include/ccmath/math/compare/islessequal.hpp b/include/ccmath/math/compare/islessequal.hpp index fe2df043..ab9438bb 100644 --- a/include/ccmath/math/compare/islessequal.hpp +++ b/include/ccmath/math/compare/islessequal.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than or equal to the second, false otherwise. */ - template , bool> = true> - constexpr bool islessequal(T x, T y) noexcept + template , bool> = true> constexpr bool islessequal(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessequal) { return ccm::builtin::islessequal_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessequal) + { + return ccm::builtin::islessequal_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x <= y); } diff --git a/include/ccmath/math/compare/islessgreater.hpp b/include/ccmath/math/compare/islessgreater.hpp index 97b092b3..cf389c6e 100644 --- a/include/ccmath/math/compare/islessgreater.hpp +++ b/include/ccmath/math/compare/islessgreater.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value. * @return true if the first argument is less than the second or greater than the second, false otherwise. */ - template , bool> = true> - constexpr bool islessgreater(T x, T y) noexcept + template , bool> = true> constexpr bool islessgreater(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_islessgreater) { return ccm::builtin::islessgreater_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_islessgreater) + { + return ccm::builtin::islessgreater_ct(x, y); + } else { return !ccm::isunordered(x, y) && (x < y || x > y); } diff --git a/include/ccmath/math/compare/isnan.hpp b/include/ccmath/math/compare/isnan.hpp index 885c86ee..7f99e37c 100644 --- a/include/ccmath/math/compare/isnan.hpp +++ b/include/ccmath/math/compare/isnan.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num The number to check. * @return True if the number is NaN, false otherwise. */ - template , bool> = true> - constexpr bool isnan(T num) noexcept + template , bool> = true> constexpr bool isnan(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnan) { return ccm::builtin::isnan_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnan) + { + return ccm::builtin::isnan_ct(num); + } else { // If we can't use the builtin, fallback to this comparison and hope for the best. using FPBits_t = typename ccm::support::fp::FPBits; @@ -41,8 +42,7 @@ namespace ccm * @tparam Integer The type of the number to check. * @return False, as integers can never be NaN. */ - template , bool> = true> - constexpr bool isnan(Integer /* num */) + template , bool> = true> constexpr bool isnan(Integer /* num */) { return false; // Integers can never be NaN. } diff --git a/include/ccmath/math/compare/isnormal.hpp b/include/ccmath/math/compare/isnormal.hpp index 07df0e64..ded9d3bf 100644 --- a/include/ccmath/math/compare/isnormal.hpp +++ b/include/ccmath/math/compare/isnormal.hpp @@ -23,11 +23,12 @@ namespace ccm * @param num A floating-point or integer value * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(T num) noexcept + template , bool> = true> constexpr bool isnormal(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_isnormal) { return ccm::builtin::isnormal_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_isnormal) + { + return ccm::builtin::isnormal_ct(num); + } else { return num != static_cast(0) && !ccm::isnan(num) && !ccm::isinf(num) && ccm::abs(num) >= std::numeric_limits::min(); } @@ -39,8 +40,9 @@ namespace ccm * @param num A integer value to check. * @return true if the number has a normal value, false otherwise. */ - template , bool> = true> - constexpr bool isnormal(Integer num) noexcept - { return num != static_cast(0); } + template , bool> = true> constexpr bool isnormal(Integer num) noexcept + { + return num != static_cast(0); + } } // namespace ccm diff --git a/include/ccmath/math/compare/isunordered.hpp b/include/ccmath/math/compare/isunordered.hpp index fd5ac05b..463d30b1 100644 --- a/include/ccmath/math/compare/isunordered.hpp +++ b/include/ccmath/math/compare/isunordered.hpp @@ -24,11 +24,12 @@ namespace ccm * @param y A floating-point or integer value * @return true if either x or y is NaN, false otherwise. */ - template , bool> = true> - constexpr bool isunordered(T x, T y) noexcept + template , bool> = true> constexpr bool isunordered(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_isunordered) { return ccm::builtin::isunordered_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_isunordered) + { + return ccm::builtin::isunordered_ct(x, y); + } else { return ccm::isnan(x) || ccm::isnan(y); } @@ -53,8 +54,7 @@ namespace ccm * @param y Value of the right-hand side of the comparison. * @return true if either x or y is NaN, false otherwise. */ - template - constexpr bool isunordered(T x, U y) noexcept + template constexpr bool isunordered(T x, U y) noexcept { using shared_type = std::common_type_t; return static_cast(isunordered(static_cast(x), static_cast(y))); diff --git a/include/ccmath/math/compare/signbit.hpp b/include/ccmath/math/compare/signbit.hpp index db85c2e5..1c1faa5e 100644 --- a/include/ccmath/math/compare/signbit.hpp +++ b/include/ccmath/math/compare/signbit.hpp @@ -30,11 +30,12 @@ namespace ccm * @warning ccm::signbit may fail * to compile if you are not using the cmake build for ccmath or do not have access to __builtin_bit_cast. */ - template , bool> = true> - constexpr bool signbit(T num) noexcept + template , bool> = true> constexpr bool signbit(T num) noexcept { - if constexpr (builtin::has_constexpr_signbit) { return builtin::signbit_ct(num); } - else + if constexpr (builtin::has_constexpr_signbit) + { + return builtin::signbit_ct(num); + } else { // Check for the sign of +0.0 and -0.0 with bit_cast if (num == static_cast(0) || ccm::isnan(num)) diff --git a/include/ccmath/math/expo/exp.hpp b/include/ccmath/math/expo/exp.hpp index 0617fe41..ffd2f702 100644 --- a/include/ccmath/math/expo/exp.hpp +++ b/include/ccmath/math/expo/exp.hpp @@ -28,13 +28,17 @@ namespace ccm * @param num floating-point or integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned. */ - template , bool> = true> - constexpr T exp(T num) + template , bool> = true> constexpr T exp(T num) { - if constexpr (ccm::builtin::has_constexpr_exp) { return ccm::builtin::exp_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_exp) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::exp_rt(num); } + return ccm::builtin::exp_ct(num); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::exp_rt(num); + } return gen::exp_gen(num); } @@ -46,9 +50,10 @@ namespace ccm * @param num integer value * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. */ - template , bool> = true> - constexpr double exp(Integer num) - { return ccm::exp(static_cast(num)); } + template , bool> = true> constexpr double exp(Integer num) + { + return ccm::exp(static_cast(num)); + } /** * @brief Computes e raised to the given power @@ -56,7 +61,9 @@ namespace ccm * @return If no errors occur, the base-e exponential of num (e^num) is returned as float. */ constexpr float expf(float num) - { return ccm::exp(num); } + { + return ccm::exp(num); + } /** * @brief Computes e raised to the given power @@ -64,7 +71,9 @@ namespace ccm * @return If no errors occur, the base-e exponential of num (e^num) is returned as double. */ constexpr long double expl(long double num) - { return ccm::exp(num); } + { + return ccm::exp(num); + } } // namespace ccm diff --git a/include/ccmath/math/expo/exp2.hpp b/include/ccmath/math/expo/exp2.hpp index 306e5d81..395e6d13 100644 --- a/include/ccmath/math/expo/exp2.hpp +++ b/include/ccmath/math/expo/exp2.hpp @@ -31,13 +31,17 @@ namespace ccm * @param num Floating-point or integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned. */ - template , bool> = true> - constexpr T exp2(T num) + template , bool> = true> constexpr T exp2(T num) { - if constexpr (ccm::builtin::has_constexpr_exp2) { return ccm::builtin::exp2_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_exp2) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::exp2_rt(num); } + return ccm::builtin::exp2_ct(num); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::exp2_rt(num); + } return gen::exp2_gen(num); } @@ -49,9 +53,10 @@ namespace ccm * @param num Integer value * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. */ - template , bool> = true> - constexpr double exp2(Integer num) - { return ccm::exp2(static_cast(num)); } + template , bool> = true> constexpr double exp2(Integer num) + { + return ccm::exp2(static_cast(num)); + } /** * @brief Returns 2 raised to the given power (2^x) @@ -59,7 +64,9 @@ namespace ccm * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a float. */ constexpr float exp2f(float num) - { return ccm::exp2(num); } + { + return ccm::exp2(num); + } /** * @brief Returns 2 raised to the given power (2^x) @@ -67,7 +74,9 @@ namespace ccm * @return If no errors occur, the base-2 exponential of num (2^num) is returned as a double. */ constexpr long double exp2l(long double num) - { return ccm::exp2(num); } + { + return ccm::exp2(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/math/expo/expm1.hpp b/include/ccmath/math/expo/expm1.hpp index 5a7a48c4..b0ccaf4a 100644 --- a/include/ccmath/math/expo/expm1.hpp +++ b/include/ccmath/math/expo/expm1.hpp @@ -30,13 +30,17 @@ namespace ccm * @param num Floating-point value. * @return exp(num) - 1. */ - template , bool> = true> - constexpr T expm1(T num) + template , bool> = true> constexpr T expm1(T num) { - if constexpr (ccm::builtin::has_constexpr_expm1) { return ccm::builtin::expm1_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_expm1) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::expm1_rt(num); } + return ccm::builtin::expm1_ct(num); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::expm1_rt(num); + } return gen::expm1_gen(num); } @@ -48,9 +52,10 @@ namespace ccm * @param num Integer value. * @return exp(num) - 1 as double. */ - template , bool> = true> - constexpr double expm1(Integer num) - { return ccm::expm1(static_cast(num)); } + template , bool> = true> constexpr double expm1(Integer num) + { + return ccm::expm1(static_cast(num)); + } /** * @brief Computes exp(num) - 1 for float. @@ -58,7 +63,9 @@ namespace ccm * @return exp(num) - 1 as float. */ constexpr float expm1f(float num) - { return ccm::expm1(num); } + { + return ccm::expm1(num); + } /** * @brief Computes exp(num) - 1 for long double. @@ -66,5 +73,7 @@ namespace ccm * @return exp(num) - 1 as long double. */ constexpr long double expm1l(long double num) - { return ccm::expm1(num); } + { + return ccm::expm1(num); + } } // namespace ccm diff --git a/include/ccmath/math/expo/impl/exp2_data.hpp b/include/ccmath/math/expo/impl/exp2_data.hpp index 20786aef..ebdb42c8 100644 --- a/include/ccmath/math/expo/impl/exp2_data.hpp +++ b/include/ccmath/math/expo/impl/exp2_data.hpp @@ -19,11 +19,9 @@ namespace ccm::internal { - template , bool> = true> - struct exp2_data; + template , bool> = true> struct exp2_data; - template <> - struct exp2_data // NOLINT + template <> struct exp2_data // NOLINT { static constexpr std::size_t table_bits = 5; static constexpr std::size_t poly_order = 3; @@ -45,8 +43,7 @@ namespace ccm::internal }; }; - template <> - struct exp2_data + template <> struct exp2_data { static constexpr std::size_t table_bits = 7; static constexpr std::size_t poly_order = 5; @@ -327,8 +324,6 @@ namespace ccm::internal }; }; - template <> - struct exp2_data : exp2_data - { - }; + template <> struct exp2_data : exp2_data + {}; } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/exp2_double_impl.hpp b/include/ccmath/math/expo/impl/exp2_double_impl.hpp index e9eb5be1..835cafd2 100644 --- a/include/ccmath/math/expo/impl/exp2_double_impl.hpp +++ b/include/ccmath/math/expo/impl/exp2_double_impl.hpp @@ -61,7 +61,10 @@ namespace ccm::internal result = (hi + lo) - 1.0; // Avoid -0.0 with downward rounding. - if (result == 0.0) { result = 0.0; } + if (result == 0.0) + { + result = 0.0; + } } result = 0x1p-1022 * result; return result; @@ -90,19 +93,34 @@ namespace ccm::internal { // Avoid pointless underflow for tiny x. // 0 Is a common input. - if (abs_top - sp::top12_bits_of_double(0x1p-54) >= sp::bitmask_traits::neg_zero) { return x + 1.0; } + if (abs_top - sp::top12_bits_of_double(0x1p-54) >= sp::bitmask_traits::neg_zero) + { + return x + 1.0; + } if (abs_top >= sp::top12_bits_of_double(1024.0)) { - if (sp::double_to_uint64(x) == sp::double_to_uint64(-std::numeric_limits::infinity())) { return 0.0; } + if (sp::double_to_uint64(x) == sp::double_to_uint64(-std::numeric_limits::infinity())) + { + return 0.0; + } - if (abs_top >= sp::top12_bits_of_double(std::numeric_limits::infinity())) { return x + 1.0; } + if (abs_top >= sp::top12_bits_of_double(std::numeric_limits::infinity())) + { + return x + 1.0; + } // Handle overflow - if ((sp::double_to_uint64(x) >> 63) == 0U) { return 0x1p769 * 0x1p769; } + if ((sp::double_to_uint64(x) >> 63) == 0U) + { + return 0x1p769 * 0x1p769; + } // Handle underflow - if (sp::double_to_uint64(x) >= sp::double_to_uint64(-1075.0)) { return 0x1p-767 * 0x1p-767; } + if (sp::double_to_uint64(x) >= sp::double_to_uint64(-1075.0)) + { + return 0x1p-767 * 0x1p-767; + } } if (2 * sp::double_to_uint64(x) > 2 * sp::double_to_uint64(928.0)) @@ -131,7 +149,10 @@ namespace ccm::internal const ccm::double_t tmp = tail + rem * first_polynomial_coefficient + remSqr * (second_polynomial_coefficient + rem * third_polynomial_coefficient) + remSqr * remSqr * (fourth_polynomial_coefficient + rem * fifth_polynomial_coefficient); - if (CCM_UNLIKELY(abs_top == 0.0)) { return handle_special_cases(tmp, sign_bits, expo_int64); } + if (CCM_UNLIKELY(abs_top == 0.0)) + { + return handle_special_cases(tmp, sign_bits, expo_int64); + } const ccm::double_t scale = sp::uint64_to_double(sign_bits); @@ -140,6 +161,8 @@ namespace ccm::internal } // namespace impl constexpr double exp2_double(double num) - { return impl::exp2_double_impl(num); } + { + return impl::exp2_double_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/exp2_float_impl.hpp b/include/ccmath/math/expo/impl/exp2_float_impl.hpp index ed3d2d7a..db87c72a 100644 --- a/include/ccmath/math/expo/impl/exp2_float_impl.hpp +++ b/include/ccmath/math/expo/impl/exp2_float_impl.hpp @@ -44,9 +44,9 @@ namespace ccm::internal { case FE_DOWNWARD: case FE_TOWARDZERO: return FloatBits::max_normal().get_val(); - case FE_UPWARD: - case FE_TONEAREST: [[fallthrough]]; - default: return FloatBits::inf().get_val(); + case FE_UPWARD : + case FE_TONEAREST : [[fallthrough]]; + default : return FloatBits::inf().get_val(); } } @@ -84,20 +84,35 @@ namespace ccm::internal // If |x| >= 128 or x is NaN, then we need to handle the special case. if (const std::uint32_t abs_top = sp::top12_bits_of_float(x) & 0x7ff; CCM_UNLIKELY(abs_top >= sp::top12_bits_of_float(128.0F))) { - if (x_bits.is_inf()) { return x_bits.is_neg() ? 0.0F : x; } + if (x_bits.is_inf()) + { + return x_bits.is_neg() ? 0.0F : x; + } // Use floating-point classification instead of raw unsigned bit comparisons so // large negative finite inputs do not alias the NaN/Inf branch. - if (x_bits.is_nan()) { return x + x; } + if (x_bits.is_nan()) + { + return x + x; + } // Handle overflow - if (x > 0.0F) { return exp2_float_overflow_result(); } + if (x > 0.0F) + { + return exp2_float_overflow_result(); + } // Handle underflow - if (x <= -150.0F) { return exp2_float_underflow_result(); } + if (x <= -150.0F) + { + return exp2_float_underflow_result(); + } // Handle errno underflow. We assume we want errno behavior for underflow. - if (x < -149.0F) { return exp2_float_underflow_result(); } + if (x < -149.0F) + { + return exp2_float_underflow_result(); + } } // x = k/N + r with r in [-1/(2N), 1/(2N)] and int k. @@ -121,6 +136,8 @@ namespace ccm::internal } // namespace impl constexpr float exp2_float(float x) - { return impl::exp2_float_impl(x); } + { + return impl::exp2_float_impl(x); + } } // namespace ccm::internal CCM_RESTORE_MSVC_WARNING() diff --git a/include/ccmath/math/expo/impl/exp_data.hpp b/include/ccmath/math/expo/impl/exp_data.hpp index d10d9e25..5792336c 100644 --- a/include/ccmath/math/expo/impl/exp_data.hpp +++ b/include/ccmath/math/expo/impl/exp_data.hpp @@ -27,11 +27,9 @@ namespace ccm::internal constexpr std::size_t k_exp_table_bits_dbl = 7; constexpr std::size_t k_exp_poly_order_dbl = 5; - template , bool> = true> - struct exp_data; + template , bool> = true> struct exp_data; - template <> - struct exp_data + template <> struct exp_data { double invln2_scaled{ 0x1.71547652b82fep0 * (1 << k_exp_table_bits_flt) }; double shift{ 0x1.8p+52 }; @@ -50,8 +48,7 @@ namespace ccm::internal }; }; - template <> - struct exp_data + template <> struct exp_data { double invln2N{ 0x1.71547652b82fep0 * (1 << k_exp_table_bits_dbl) }; // N/ln2 double shift{ 0x1.8p52 }; @@ -333,8 +330,6 @@ namespace ccm::internal }; }; - template <> - struct exp_data : exp_data - { - }; + template <> struct exp_data : exp_data + {}; } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/exp_double_impl.hpp b/include/ccmath/math/expo/impl/exp_double_impl.hpp index 44fd8695..8d6c2826 100644 --- a/include/ccmath/math/expo/impl/exp_double_impl.hpp +++ b/include/ccmath/math/expo/impl/exp_double_impl.hpp @@ -63,7 +63,10 @@ namespace ccm::internal::impl lo = 1.0 - hi + result + lo; result = support::helpers::narrow_eval(hi + lo) - 1.0; // Prevent -0.0 with downward rounding. - if (result == 0.0) { result = 0.0; } + if (result == 0.0) + { + result = 0.0; + } } result = 0x1p-1022 * result; @@ -92,16 +95,28 @@ namespace ccm::internal::impl ccm::support::top12_bits_of_double(512.0) - ccm::support::top12_bits_of_double(0x1p-54))) { // Avoid raising underflow for tiny x. 0 is a common input. - if (abs_top - support::top12_bits_of_double(0x1p-54) >= 0x80000000) { return 1.0 + x; } + if (abs_top - support::top12_bits_of_double(0x1p-54) >= 0x80000000) + { + return 1.0 + x; + } if (abs_top >= support::top12_bits_of_double(1024.0)) { - if (support::double_to_uint64(x) == support::double_to_uint64(-std::numeric_limits::infinity())) { return 0.0; } + if (support::double_to_uint64(x) == support::double_to_uint64(-std::numeric_limits::infinity())) + { + return 0.0; + } - if (abs_top >= support::top12_bits_of_double(std::numeric_limits::infinity())) { return 1.0 + x; } + if (abs_top >= support::top12_bits_of_double(std::numeric_limits::infinity())) + { + return 1.0 + x; + } // Handle underflow - if ((support::double_to_uint64(x) >> 63) != 0U) { return 0x1p-767 * 0x1p-767; } + if ((support::double_to_uint64(x) >> 63) != 0U) + { + return 0x1p-767 * 0x1p-767; + } // Handle Overflow return 0x1p769 * 0x1p769; @@ -136,7 +151,10 @@ namespace ccm::internal::impl // Worst case error is less than (0.5+1.11/N+(abs poly error * 2^53))+0.25/N ulp. tmp = tail + rem + remSqr * (exp_poly_coeff_one_dbl + rem * exp_poly_coeff_two_dbl) + remSqr * remSqr * (exp_poly_coeff_three_dbl + rem * exp_poly_coeff_four_dbl); - if (CCM_UNLIKELY(abs_top == 0.0)) { return handle_special_case(tmp, sign_bits, expo_int64); } + if (CCM_UNLIKELY(abs_top == 0.0)) + { + return handle_special_case(tmp, sign_bits, expo_int64); + } scale = support::uint64_to_double(sign_bits); @@ -170,15 +188,27 @@ namespace ccm::internal::impl if (CCM_UNLIKELY(abs_top - ccm::support::top12_bits_of_double(0x1p-54) >= ccm::support::top12_bits_of_double(512.0) - ccm::support::top12_bits_of_double(0x1p-54))) { - if (abs_top - support::top12_bits_of_double(0x1p-54) >= 0x80000000) { return x; } + if (abs_top - support::top12_bits_of_double(0x1p-54) >= 0x80000000) + { + return x; + } if (abs_top >= support::top12_bits_of_double(1024.0)) { - if (support::double_to_uint64(x) == support::double_to_uint64(-std::numeric_limits::infinity())) { return -1.0; } + if (support::double_to_uint64(x) == support::double_to_uint64(-std::numeric_limits::infinity())) + { + return -1.0; + } - if (abs_top >= support::top12_bits_of_double(std::numeric_limits::infinity())) { return x + x; } + if (abs_top >= support::top12_bits_of_double(std::numeric_limits::infinity())) + { + return x + x; + } - if ((support::double_to_uint64(x) >> 63) != 0U) { return -1.0; } + if ((support::double_to_uint64(x) >> 63) != 0U) + { + return -1.0; + } return 0x1p769 * 0x1p769; } @@ -203,7 +233,10 @@ namespace ccm::internal::impl tmp = tail + rem + remSqr * (exp_poly_coeff_one_dbl + rem * exp_poly_coeff_two_dbl) + remSqr * remSqr * (exp_poly_coeff_three_dbl + rem * exp_poly_coeff_four_dbl); - if (CCM_UNLIKELY(abs_top == 0.0)) { return handle_special_case(tmp, sign_bits, expo_int64); } + if (CCM_UNLIKELY(abs_top == 0.0)) + { + return handle_special_case(tmp, sign_bits, expo_int64); + } scale = support::uint64_to_double(sign_bits); @@ -212,15 +245,30 @@ namespace ccm::internal::impl constexpr double expm1_double_impl(double x) { - if (x == 0.0) { return x; } + if (x == 0.0) + { + return x; + } - if (CCM_UNLIKELY(ccm::isnan(x))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(x))) + { + return std::numeric_limits::quiet_NaN(); + } - if (x == std::numeric_limits::infinity()) { return std::numeric_limits::infinity(); } + if (x == std::numeric_limits::infinity()) + { + return std::numeric_limits::infinity(); + } - if (x == -std::numeric_limits::infinity()) { return -1.0; } + if (x == -std::numeric_limits::infinity()) + { + return -1.0; + } - if (x >= 0.25 || x <= -0.25) { return expm1_kernel(x); } + if (x >= 0.25 || x <= -0.25) + { + return expm1_kernel(x); + } if (x > 0.0625 || x < -0.0625) { diff --git a/include/ccmath/math/expo/impl/exp_float_impl.hpp b/include/ccmath/math/expo/impl/exp_float_impl.hpp index 96afdbe3..fc49d609 100644 --- a/include/ccmath/math/expo/impl/exp_float_impl.hpp +++ b/include/ccmath/math/expo/impl/exp_float_impl.hpp @@ -51,9 +51,15 @@ namespace ccm::internal::impl if (CCM_UNLIKELY(abs_top >= ccm::support::top12_bits_of_float(88.0F))) { // |x| >= 88 or x is nan. - if (support::float_to_uint32(x) == support::float_to_uint32(-std::numeric_limits::infinity())) { return 0.0F; } + if (support::float_to_uint32(x) == support::float_to_uint32(-std::numeric_limits::infinity())) + { + return 0.0F; + } - if (abs_top >= support::top12_bits_of_float(std::numeric_limits::infinity())) { return x + x; } + if (abs_top >= support::top12_bits_of_float(std::numeric_limits::infinity())) + { + return x + x; + } // Handle overflow if (x > 0x1.62e42ep6F) // x > log(0x1p128) ~= 88.72 @@ -117,13 +123,25 @@ namespace ccm::internal::impl if (CCM_UNLIKELY(abs_top >= ccm::support::top12_bits_of_float(88.0F))) { - if (support::float_to_uint32(x) == support::float_to_uint32(-std::numeric_limits::infinity())) { return -1.0F; } + if (support::float_to_uint32(x) == support::float_to_uint32(-std::numeric_limits::infinity())) + { + return -1.0F; + } - if (abs_top >= support::top12_bits_of_float(std::numeric_limits::infinity())) { return x + x; } + if (abs_top >= support::top12_bits_of_float(std::numeric_limits::infinity())) + { + return x + x; + } - if (x > 0x1.62e42ep6F) { return 0x1p97F * 0x1p97F; } + if (x > 0x1.62e42ep6F) + { + return 0x1p97F * 0x1p97F; + } - if (x < -0x1.9fe368p6F) { return -1.0F; } + if (x < -0x1.9fe368p6F) + { + return -1.0F; + } } scaled_input = exp_inv_ln2_N_flt * x_dbl_t; @@ -145,15 +163,30 @@ namespace ccm::internal::impl constexpr float expm1_float_impl(float x) { - if (x == 0.0F) { return x; } + if (x == 0.0F) + { + return x; + } - if (CCM_UNLIKELY(ccm::isnan(x))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(x))) + { + return std::numeric_limits::quiet_NaN(); + } - if (x == std::numeric_limits::infinity()) { return std::numeric_limits::infinity(); } + if (x == std::numeric_limits::infinity()) + { + return std::numeric_limits::infinity(); + } - if (x == -std::numeric_limits::infinity()) { return -1.0F; } + if (x == -std::numeric_limits::infinity()) + { + return -1.0F; + } - if (x >= 0.25F || x <= -0.25F) { return expm1_kernel(x); } + if (x >= 0.25F || x <= -0.25F) + { + return expm1_kernel(x); + } if (x > 0.0625F || x < -0.0625F) { diff --git a/include/ccmath/math/expo/impl/expm1_impl.hpp b/include/ccmath/math/expo/impl/expm1_impl.hpp index 2ebc761a..0f0a5cdf 100644 --- a/include/ccmath/math/expo/impl/expm1_impl.hpp +++ b/include/ccmath/math/expo/impl/expm1_impl.hpp @@ -16,8 +16,12 @@ namespace ccm::internal { constexpr float expm1_float(float num) noexcept - { return impl::expm1_float_impl(num); } + { + return impl::expm1_float_impl(num); + } constexpr double expm1_double(double num) noexcept - { return impl::expm1_double_impl(num); } + { + return impl::expm1_double_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log10_impl.hpp b/include/ccmath/math/expo/impl/log10_impl.hpp index 627a44a8..42a236da 100644 --- a/include/ccmath/math/expo/impl/log10_impl.hpp +++ b/include/ccmath/math/expo/impl/log10_impl.hpp @@ -17,17 +17,25 @@ namespace ccm::internal::impl { constexpr float log10_float_impl(float x) - { return log_float_impl(x) * static_cast(numbers::log10e_v); } + { + return log_float_impl(x) * static_cast(numbers::log10e_v); + } constexpr double log10_double_impl(double x) - { return log_double_impl(x) * numbers::log10e_v; } + { + return log_double_impl(x) * numbers::log10e_v; + } } // namespace ccm::internal::impl namespace ccm::internal { constexpr float log10_float(float num) noexcept - { return impl::log10_float_impl(num); } + { + return impl::log10_float_impl(num); + } constexpr double log10_double(double num) noexcept - { return impl::log10_double_impl(num); } + { + return impl::log10_double_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log1p_impl.hpp b/include/ccmath/math/expo/impl/log1p_impl.hpp index ad2e99ba..ebb86108 100644 --- a/include/ccmath/math/expo/impl/log1p_impl.hpp +++ b/include/ccmath/math/expo/impl/log1p_impl.hpp @@ -49,7 +49,10 @@ namespace ccm::internal::impl if (x > 0.414284F || x < -0.292893F) { const float u = 1.0F + x; - if (u == 1.0F) { return x; } + if (u == 1.0F) + { + return x; + } return log_float_impl(u); } @@ -71,12 +74,18 @@ namespace ccm::internal::impl { if (ix >= 0xbf800000U) { - if (x == -1.0F) { return -std::numeric_limits::infinity(); } + if (x == -1.0F) + { + return -std::numeric_limits::infinity(); + } return -std::numeric_limits::quiet_NaN(); } - if ((ix << 1U) < (0x33800000U << 1U)) { return x; } + if ((ix << 1U) < (0x33800000U << 1U)) + { + return x; + } if (ix <= 0xbe95f619U) { @@ -84,8 +93,10 @@ namespace ccm::internal::impl c = 0.0; f = x; } + } else if (ix >= 0x7f800000U) + { + return x; } - else if (ix >= 0x7f800000U) { return x; } if (k != 0) { @@ -97,8 +108,7 @@ namespace ccm::internal::impl { c = k >= 2 ? static_cast(1.0F - (u - x)) : static_cast(x - (u - 1.0F)); c /= u; - } - else + } else { c = 0.0; } @@ -124,7 +134,10 @@ namespace ccm::internal::impl if (x > 0.414284 || x < -0.292893) { const double u = 1.0 + x; - if (u == 1.0) { return x; } + if (u == 1.0) + { + return x; + } return log_double_impl(u); } @@ -147,12 +160,18 @@ namespace ccm::internal::impl { if (hx >= 0xbff00000U) { - if (x == -1.0) { return -std::numeric_limits::infinity(); } + if (x == -1.0) + { + return -std::numeric_limits::infinity(); + } return -std::numeric_limits::quiet_NaN(); } - if ((hx << 1U) < (0x3ca00000U << 1U)) { return x; } + if ((hx << 1U) < (0x3ca00000U << 1U)) + { + return x; + } if (hx <= 0xbfd2bec4U) { @@ -160,8 +179,10 @@ namespace ccm::internal::impl c = 0.0; f = x; } + } else if (hx >= 0x7ff00000U) + { + return x; } - else if (hx >= 0x7ff00000U) { return x; } if (k != 0) { @@ -174,8 +195,7 @@ namespace ccm::internal::impl { c = k >= 2 ? 1.0 - (u - x) : x - (u - 1.0); c /= u; - } - else + } else { c = 0.0; } @@ -200,17 +220,27 @@ namespace ccm::internal::impl namespace ccm::internal { - template - constexpr T log1p_zero_result(T num) noexcept - { return num; } + template constexpr T log1p_zero_result(T num) noexcept + { + return num; + } constexpr float log1p_float(float num) noexcept { - if (num == 0.0F) { return log1p_zero_result(num); } + if (num == 0.0F) + { + return log1p_zero_result(num); + } - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } - if (num == std::numeric_limits::infinity()) { return std::numeric_limits::infinity(); } + if (num == std::numeric_limits::infinity()) + { + return std::numeric_limits::infinity(); + } if (num == -1.0F) { ccm::support::fenv::set_errno_if_required(ERANGE); @@ -229,11 +259,20 @@ namespace ccm::internal constexpr double log1p_double(double num) noexcept { - if (num == 0.0) { return log1p_zero_result(num); } + if (num == 0.0) + { + return log1p_zero_result(num); + } - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } - if (num == std::numeric_limits::infinity()) { return std::numeric_limits::infinity(); } + if (num == std::numeric_limits::infinity()) + { + return std::numeric_limits::infinity(); + } if (num == -1.0) { ccm::support::fenv::set_errno_if_required(ERANGE); diff --git a/include/ccmath/math/expo/impl/log2_data.hpp b/include/ccmath/math/expo/impl/log2_data.hpp index 0834636f..10ca2c4c 100644 --- a/include/ccmath/math/expo/impl/log2_data.hpp +++ b/include/ccmath/math/expo/impl/log2_data.hpp @@ -26,11 +26,9 @@ namespace ccm::internal constexpr std::size_t k_log2PolyOrderDbl = 7; constexpr std::size_t k_log2Poly1OrderDbl = 11; - template , bool> = true> - struct log2_data; + template , bool> = true> struct log2_data; - template <> - struct log2_data + template <> struct log2_data { struct TabEntry { @@ -58,8 +56,7 @@ namespace ccm::internal } }; }; - template <> - struct log2_data + template <> struct log2_data { // First coefficient: 0x1.71547652b82fe1777d0ffda0d24p0 double invln2hi{ 0x1.7154765200000p+0 }; @@ -162,8 +159,6 @@ namespace ccm::internal }; }; - template <> - struct log2_data : log2_data - { - }; + template <> struct log2_data : log2_data + {}; } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log2_double_impl.hpp b/include/ccmath/math/expo/impl/log2_double_impl.hpp index beaa79c0..7f437b55 100644 --- a/include/ccmath/math/expo/impl/log2_double_impl.hpp +++ b/include/ccmath/math/expo/impl/log2_double_impl.hpp @@ -50,7 +50,10 @@ namespace ccm::internal if (CCM_UNLIKELY(intX - low < high - low)) { // Handle the case where x is exactly 1.0 - if (CCM_UNLIKELY(intX == ccm::support::double_to_uint64(1.0))) { return 0.0; } + if (CCM_UNLIKELY(intX == ccm::support::double_to_uint64(1.0))) + { + return 0.0; + } const ccm::double_t rem = x - 1.0; const ccm::double_t remHi = support::uint64_to_double(support::double_to_uint64(rem) & -1ULL << 32); @@ -127,7 +130,9 @@ namespace ccm::internal } // namespace impl constexpr double log2_double(double num) noexcept - { return impl::log2_double_impl(num); } + { + return impl::log2_double_impl(num); + } } // namespace ccm::internal CCM_RESTORE_MSVC_WARNING() diff --git a/include/ccmath/math/expo/impl/log2_float_impl.hpp b/include/ccmath/math/expo/impl/log2_float_impl.hpp index 5a21bed1..da487d84 100644 --- a/include/ccmath/math/expo/impl/log2_float_impl.hpp +++ b/include/ccmath/math/expo/impl/log2_float_impl.hpp @@ -34,7 +34,10 @@ namespace ccm::internal std::uint32_t intX = support::float_to_uint32(x); // log2(1) handled in public log2(). Keep impl path consistent for direct calls. - if (CCM_UNLIKELY(intX == 0x3f800000)) { return 0.0F; } + if (CCM_UNLIKELY(intX == 0x3f800000)) + { + return 0.0F; + } if (CCM_UNLIKELY(intX - 0x00800000 >= 0x7f800000 - 0x00800000)) { @@ -81,5 +84,7 @@ namespace ccm::internal } // namespace impl constexpr float log2_float(float num) noexcept - { return impl::log2_float_impl(num); } + { + return impl::log2_float_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log_data.hpp b/include/ccmath/math/expo/impl/log_data.hpp index ed80c2fe..34a5604f 100644 --- a/include/ccmath/math/expo/impl/log_data.hpp +++ b/include/ccmath/math/expo/impl/log_data.hpp @@ -25,11 +25,9 @@ namespace ccm::internal constexpr std::size_t k_logPolyOrderDbl = 6; constexpr std::size_t k_logPoly1OrderDbl = 12; - template , bool> = true> - struct log_data; + template , bool> = true> struct log_data; - template <> - struct log_data + template <> struct log_data { struct TabEntry { @@ -58,8 +56,7 @@ namespace ccm::internal }; }; - template <> - struct log_data + template <> struct log_data { // log(2) generated by Sollya with: // > a = 2^-43 * nearestint(2^43*log(2)); @@ -237,8 +234,6 @@ namespace ccm::internal }; }; - template <> - struct log_data : log_data - { - }; + template <> struct log_data : log_data + {}; } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log_double_impl.hpp b/include/ccmath/math/expo/impl/log_double_impl.hpp index 3d6a58a1..30551ee4 100644 --- a/include/ccmath/math/expo/impl/log_double_impl.hpp +++ b/include/ccmath/math/expo/impl/log_double_impl.hpp @@ -48,7 +48,10 @@ namespace ccm::internal if (CCM_UNLIKELY(intX - low < high - low)) { // Handle the case where x is exactly 1.0 - if (CCM_UNLIKELY(intX == ccm::support::double_to_uint64(1.0))) { return 0; } + if (CCM_UNLIKELY(intX == ccm::support::double_to_uint64(1.0))) + { + return 0; + } const ccm::double_t rem = x - 1.0; const ccm::double_t remSqr = rem * rem; @@ -120,5 +123,7 @@ namespace ccm::internal } // namespace impl constexpr double log_double(double num) noexcept - { return impl::log_double_impl(num); } + { + return impl::log_double_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/impl/log_float_impl.hpp b/include/ccmath/math/expo/impl/log_float_impl.hpp index 7ca84ed5..732b4c1b 100644 --- a/include/ccmath/math/expo/impl/log_float_impl.hpp +++ b/include/ccmath/math/expo/impl/log_float_impl.hpp @@ -52,7 +52,10 @@ namespace ccm::internal intX = ccm::support::float_to_uint32(x); // Correct the sign of zero with downward rounding when x is equal to 1.0 - if (CCM_UNLIKELY(intX == 0x3f800000)) { return 0; } + if (CCM_UNLIKELY(intX == 0x3f800000)) + { + return 0; + } if (CCM_UNLIKELY(intX - 0x00800000 >= 0x7f800000 - 0x00800000)) { @@ -85,5 +88,7 @@ namespace ccm::internal } // namespace impl constexpr float log_float(float num) noexcept - { return impl::log_float_impl(num); } + { + return impl::log_float_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/expo/log.hpp b/include/ccmath/math/expo/log.hpp index 5fe43983..f463c32c 100644 --- a/include/ccmath/math/expo/log.hpp +++ b/include/ccmath/math/expo/log.hpp @@ -31,18 +31,23 @@ namespace ccm * @param num A floating-point or integer value to find the natural logarithm of. * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ - template , bool> = true> - constexpr T log(const T num) noexcept + template , bool> = true> constexpr T log(const T num) noexcept { if constexpr (ccm::builtin::has_constexpr_log) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::log_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::log_ct(num); + } } { // If the argument is 1, exact zero is returned. if (num == static_cast(1)) { - if (ccm::support::is_constant_evaluated()) { return static_cast(0); } + if (ccm::support::is_constant_evaluated()) + { + return static_cast(0); + } #if defined(CCMATH_COMPILER_APPLE_CLANG) || (defined(_MSC_VER) && !defined(__clang__)) return ccm::support::fp::signed_zero_for_current_mode(); #else @@ -67,12 +72,21 @@ namespace ccm } // If the argument is +∞, +∞ is returned. - if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) { return std::numeric_limits::infinity(); } + if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) + { + return std::numeric_limits::infinity(); + } // If the argument is NaN, NaN is returned. - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::log_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::log_rt(num); + } return gen::log_gen(num); } @@ -84,9 +98,10 @@ namespace ccm * @param num An integer value to find the natural logarithm of. * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ - template , bool> = true> - constexpr double log(const Integer num) noexcept - { return ccm::log(static_cast(num)); } + template , bool> = true> constexpr double log(const Integer num) noexcept + { + return ccm::log(static_cast(num)); + } /** * @brief Computes the natural (base e) logarithm (lnx) of a number. @@ -94,7 +109,9 @@ namespace ccm * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ constexpr float logf(const float num) noexcept - { return ccm::log(num); } + { + return ccm::log(num); + } /** * @brief Computes the natural (base e) logarithm (lnx) of a number. @@ -102,7 +119,9 @@ namespace ccm * @return If no errors occur, the natural (base-e) logarithm of num (ln(num) or loge(num)) is returned. */ constexpr long double logl(long double num) noexcept - { return ccm::log(num); } + { + return ccm::log(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/math/expo/log10.hpp b/include/ccmath/math/expo/log10.hpp index d0f4b9f6..25d01958 100644 --- a/include/ccmath/math/expo/log10.hpp +++ b/include/ccmath/math/expo/log10.hpp @@ -36,17 +36,22 @@ namespace ccm * @param num Floating-point value. * @return Base-10 logarithm of num. Returns -inf for zero and NaN for negative inputs. */ - template , bool> = true> - constexpr T log10(T num) + template , bool> = true> constexpr T log10(T num) { if constexpr (ccm::builtin::has_constexpr_log10) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::log10_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::log10_ct(num); + } } { if (num == static_cast(1)) { - if (ccm::support::is_constant_evaluated()) { return static_cast(0); } + if (ccm::support::is_constant_evaluated()) + { + return static_cast(0); + } #if defined(CCMATH_COMPILER_APPLE_CLANG) || (defined(_MSC_VER) && !defined(__clang__)) return ccm::support::fp::signed_zero_for_current_mode(); #else @@ -65,10 +70,19 @@ namespace ccm ccm::support::fenv::raise_except_if_required(FE_INVALID); return -std::numeric_limits::quiet_NaN(); } - if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) { return std::numeric_limits::infinity(); } - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(num == std::numeric_limits::infinity())) + { + return std::numeric_limits::infinity(); + } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::log10_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::log10_rt(num); + } return gen::log10_gen(num); } @@ -80,9 +94,10 @@ namespace ccm * @param num Integer value. * @return Base-10 logarithm of num as double. */ - template , bool> = true> - constexpr double log10(Integer num) - { return ccm::log10(static_cast(num)); } + template , bool> = true> constexpr double log10(Integer num) + { + return ccm::log10(static_cast(num)); + } /** * @brief Computes the base-10 logarithm of a float. @@ -90,7 +105,9 @@ namespace ccm * @return Base-10 logarithm as float. */ constexpr float log10f(float num) - { return ccm::log10(num); } + { + return ccm::log10(num); + } /** * @brief Computes the base-10 logarithm of a long double. @@ -98,5 +115,7 @@ namespace ccm * @return Base-10 logarithm as long double. */ constexpr long double log10l(long double num) - { return ccm::log10(num); } + { + return ccm::log10(num); + } } // namespace ccm diff --git a/include/ccmath/math/expo/log1p.hpp b/include/ccmath/math/expo/log1p.hpp index c80cb49f..9fec1092 100644 --- a/include/ccmath/math/expo/log1p.hpp +++ b/include/ccmath/math/expo/log1p.hpp @@ -35,21 +35,31 @@ namespace ccm * @param num Floating-point value. * @return Natural logarithm of 1 + num. Returns -inf for num == -1 and NaN for num < -1. */ - template , bool> = true> - constexpr T log1p(T num) + template , bool> = true> constexpr T log1p(T num) { - if constexpr (ccm::builtin::has_constexpr_log1p) { return ccm::builtin::log1p_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_log1p) + { + return ccm::builtin::log1p_ct(num); + } else { if (num == static_cast(0)) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::log1p_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::log1p_rt(num); + } return internal::log1p_zero_result(num); } - if (CCM_UNLIKELY(ccm::isnan(num))) { return std::numeric_limits::quiet_NaN(); } + if (CCM_UNLIKELY(ccm::isnan(num))) + { + return std::numeric_limits::quiet_NaN(); + } - if (num == std::numeric_limits::infinity()) { return std::numeric_limits::infinity(); } + if (num == std::numeric_limits::infinity()) + { + return std::numeric_limits::infinity(); + } if (num == static_cast(-1)) { @@ -65,7 +75,10 @@ namespace ccm return -std::numeric_limits::quiet_NaN(); } - if (ccm::support::is_constant_evaluated()) { return gen::log1p_gen(num); } + if (ccm::support::is_constant_evaluated()) + { + return gen::log1p_gen(num); + } return ccm::rt::log1p_rt(num); } @@ -77,9 +90,10 @@ namespace ccm * @param num Integer value. * @return Natural logarithm of 1 + num as double. */ - template , bool> = true> - constexpr double log1p(Integer num) - { return ccm::log1p(static_cast(num)); } + template , bool> = true> constexpr double log1p(Integer num) + { + return ccm::log1p(static_cast(num)); + } /** * @brief Computes log(1 + num) for float. @@ -87,7 +101,9 @@ namespace ccm * @return Natural logarithm of 1 + num as float. */ constexpr float log1pf(float num) - { return ccm::log1p(num); } + { + return ccm::log1p(num); + } /** * @brief Computes log(1 + num) for long double. @@ -95,5 +111,7 @@ namespace ccm * @return Natural logarithm of 1 + num as long double. */ constexpr long double log1pl(long double num) - { return ccm::log1p(num); } + { + return ccm::log1p(num); + } } // namespace ccm diff --git a/include/ccmath/math/expo/log2.hpp b/include/ccmath/math/expo/log2.hpp index 24b0bd59..888986de 100644 --- a/include/ccmath/math/expo/log2.hpp +++ b/include/ccmath/math/expo/log2.hpp @@ -36,12 +36,14 @@ namespace ccm * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number. */ - template , bool> = true> - constexpr T log2(T num) noexcept + template , bool> = true> constexpr T log2(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_log2) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::log2_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::log2_ct(num); + } } { // If the argument is ±0, -∞ is returned @@ -55,7 +57,10 @@ namespace ccm // If the argument is 1, exact zero is returned. if (num == static_cast(1)) { - if (ccm::support::is_constant_evaluated()) { return static_cast(0); } + if (ccm::support::is_constant_evaluated()) + { + return static_cast(0); + } #if defined(CCMATH_COMPILER_APPLE_CLANG) || (defined(_MSC_VER) && !defined(__clang__)) return ccm::support::fp::signed_zero_for_current_mode(); #else @@ -64,7 +69,10 @@ namespace ccm } // If the argument is NaN, NaN is returned. - if (ccm::isnan(num) || num == std::numeric_limits::infinity()) { return num; } + if (ccm::isnan(num) || num == std::numeric_limits::infinity()) + { + return num; + } // If the argument is negative, -NaN is returned #ifdef CCMATH_COMPILER_APPLE_CLANG // Apple clang returns +qNaN @@ -83,7 +91,10 @@ namespace ccm } #endif - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::log2_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::log2_rt(num); + } return gen::log2_gen(num); } @@ -95,9 +106,10 @@ namespace ccm * @param num The number to calculate the base 2 logarithm of. * @return The base 2 logarithm of the number as a double. */ - template , bool> = true> - constexpr double log2(Integer num) noexcept - { return ccm::log2(static_cast(num)); } + template , bool> = true> constexpr double log2(Integer num) noexcept + { + return ccm::log2(static_cast(num)); + } /** * @brief Returns the base 2 logarithm of a number. @@ -105,7 +117,9 @@ namespace ccm * @return The base 2 logarithm of the number as a float. */ constexpr float log2f(float num) - { return ccm::log2(num); } + { + return ccm::log2(num); + } /** * @brief Returns the base 2 logarithm of a number. @@ -113,7 +127,9 @@ namespace ccm * @return The base 2 logarithm of the number as a double. */ constexpr long double log2l(long double num) - { return ccm::log2(num); } + { + return ccm::log2(num); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/math/fmanip/copysign.hpp b/include/ccmath/math/fmanip/copysign.hpp index ef9b0055..d56f1a59 100644 --- a/include/ccmath/math/fmanip/copysign.hpp +++ b/include/ccmath/math/fmanip/copysign.hpp @@ -26,19 +26,26 @@ namespace ccm * @param sgn A floating-point or integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ - template , bool> = true> - constexpr T copysign(T mag, T sgn) + template , bool> = true> constexpr T copysign(T mag, T sgn) { - if constexpr (ccm::builtin::has_constexpr_copysign) { return ccm::builtin::copysign_ct(mag, sgn); } - else + if constexpr (ccm::builtin::has_constexpr_copysign) + { + return ccm::builtin::copysign_ct(mag, sgn); + } else { if (ccm::isnan(mag) || ccm::isnan(sgn)) { - if (ccm::signbit(sgn)) { return -std::numeric_limits::quiet_NaN(); } + if (ccm::signbit(sgn)) + { + return -std::numeric_limits::quiet_NaN(); + } return std::numeric_limits::quiet_NaN(); } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::copysign_rt(mag, sgn); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::copysign_rt(mag, sgn); + } T sign_bit = ccm::signbit(sgn) ? T(-1) : T(1); return ccm::abs(mag) * sign_bit; @@ -52,9 +59,10 @@ namespace ccm * @param sgn A integer value * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ - template , bool> = true> - constexpr double copysign(Integer mag, Integer sgn) - { return ccm::copysign(static_cast(mag), static_cast(sgn)); } + template , bool> = true> constexpr double copysign(Integer mag, Integer sgn) + { + return ccm::copysign(static_cast(mag), static_cast(sgn)); + } /** * @brief Copies the sign of a floating point value. @@ -63,7 +71,9 @@ namespace ccm * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ constexpr float copysignf(float mag, float sgn) - { return ccm::copysign(mag, sgn); } + { + return ccm::copysign(mag, sgn); + } /** * @brief Copies the sign of a floating point value. @@ -72,5 +82,7 @@ namespace ccm * @return If no errors occur, the floating point value with the magnitude of mag and the sign of sgn is returned. */ constexpr long double copysignl(long double mag, long double sgn) - { return ccm::copysign(mag, sgn); } + { + return ccm::copysign(mag, sgn); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/frexp.hpp b/include/ccmath/math/fmanip/frexp.hpp index aa3cd02f..5ec74f64 100644 --- a/include/ccmath/math/fmanip/frexp.hpp +++ b/include/ccmath/math/fmanip/frexp.hpp @@ -26,20 +26,25 @@ namespace ccm * @param exp Pointer that receives the extracted exponent. * @return Normalized fraction in the range [-1, -0.5) or [0.5, 1), or zero when x is zero. */ - template , bool> = true> - constexpr T frexp(T x, int * exp) + template , bool> = true> constexpr T frexp(T x, int * exp) { - if constexpr (ccm::builtin::has_constexpr_frexp) { return ccm::builtin::frexp_ct(x, exp); } - else + if constexpr (ccm::builtin::has_constexpr_frexp) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::frexp_gen(x, *exp); } + return ccm::builtin::frexp_ct(x, exp); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::frexp_gen(x, *exp); + } return ccm::rt::frexp_rt(x, *exp); } } - template , bool> = true> - constexpr T frexp(T x, int & exp) - { return ccm::frexp(x, &exp); } + template , bool> = true> constexpr T frexp(T x, int & exp) + { + return ccm::frexp(x, &exp); + } /** * @brief Decomposes a float into a normalized fraction and a base-2 exponent. @@ -48,10 +53,14 @@ namespace ccm * @return Normalized fraction for x. */ constexpr float frexpf(float x, int * exp) - { return ccm::frexp(x, exp); } + { + return ccm::frexp(x, exp); + } constexpr float frexpf(float x, int & exp) - { return ccm::frexpf(x, &exp); } + { + return ccm::frexpf(x, &exp); + } /** * @brief Decomposes a long double into a normalized fraction and a base-2 exponent. @@ -60,8 +69,12 @@ namespace ccm * @return Normalized fraction for x. */ constexpr long double frexpl(long double x, int * exp) - { return ccm::frexp(x, exp); } + { + return ccm::frexp(x, exp); + } constexpr long double frexpl(long double x, int & exp) - { return ccm::frexpl(x, &exp); } + { + return ccm::frexpl(x, &exp); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/ilogb.hpp b/include/ccmath/math/fmanip/ilogb.hpp index fb2e6ea9..232a1c0d 100644 --- a/include/ccmath/math/fmanip/ilogb.hpp +++ b/include/ccmath/math/fmanip/ilogb.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Floating-point value. * @return Unbiased base-2 exponent as int (or implementation-defined special values for zero/NaN). */ - template , bool> = true> - constexpr int ilogb(T num) noexcept + template , bool> = true> constexpr int ilogb(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_ilogb) { return ccm::builtin::ilogb_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_ilogb) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::ilogb_gen(num); } + return ccm::builtin::ilogb_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::ilogb_gen(num); + } return ccm::rt::ilogb_rt(num); } } @@ -42,7 +46,9 @@ namespace ccm * @return Unbiased base-2 exponent as int. */ constexpr int ilogbf(float num) noexcept - { return ccm::ilogb(num); } + { + return ccm::ilogb(num); + } /** * @brief Extracts the unbiased integer exponent of a long double. @@ -50,5 +56,7 @@ namespace ccm * @return Unbiased base-2 exponent as int. */ constexpr int ilogbl(long double num) noexcept - { return ccm::ilogb(num); } + { + return ccm::ilogb(num); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/impl/modf_impl.hpp b/include/ccmath/math/fmanip/impl/modf_impl.hpp index f8e12154..04d58517 100644 --- a/include/ccmath/math/fmanip/impl/modf_impl.hpp +++ b/include/ccmath/math/fmanip/impl/modf_impl.hpp @@ -18,8 +18,7 @@ namespace ccm::internal::impl { - template - constexpr T modf_impl(T x, T * iptr) noexcept + template constexpr T modf_impl(T x, T * iptr) noexcept { using fp_bits_t = ccm::support::fp::FPBits; @@ -44,7 +43,10 @@ namespace ccm::internal::impl *iptr = ccm::trunc(x); T fractional = x - *iptr; - if (fractional == T{}) { fractional = fp_bits_t::zero(fp_bits_t(x).sign()).get_val(); } + if (fractional == T{}) + { + fractional = fp_bits_t::zero(fp_bits_t(x).sign()).get_val(); + } return fractional; } diff --git a/include/ccmath/math/fmanip/impl/scalbn_double_impl.hpp b/include/ccmath/math/fmanip/impl/scalbn_double_impl.hpp index 12548fc9..25fba469 100644 --- a/include/ccmath/math/fmanip/impl/scalbn_double_impl.hpp +++ b/include/ccmath/math/fmanip/impl/scalbn_double_impl.hpp @@ -35,8 +35,7 @@ namespace ccm::internal exp -= 1023; exp = ccm::min(exp, 1023); } - } - else if (exp < -1022) + } else if (exp < -1022) { tmp *= 0x1p-1022 * 0x1p53; exp += 1022 - 53; @@ -56,8 +55,12 @@ namespace ccm::internal } // namespace impl constexpr double scalbn_double(double arg, int exp) noexcept - { return impl::scalbn_double_impl(arg, exp); } + { + return impl::scalbn_double_impl(arg, exp); + } constexpr double scalbn_double(double arg, long exp) noexcept - { return impl::scalbn_double_impl(arg, static_cast(exp)); } + { + return impl::scalbn_double_impl(arg, static_cast(exp)); + } } // namespace ccm::internal diff --git a/include/ccmath/math/fmanip/impl/scalbn_float_impl.hpp b/include/ccmath/math/fmanip/impl/scalbn_float_impl.hpp index ddaa9e12..d58143ff 100644 --- a/include/ccmath/math/fmanip/impl/scalbn_float_impl.hpp +++ b/include/ccmath/math/fmanip/impl/scalbn_float_impl.hpp @@ -37,8 +37,7 @@ namespace ccm::internal exp -= 127; exp = ccm::min(exp, 127); } - } - else if (exp < -126) + } else if (exp < -126) { tmp *= 0x1p-126F * 0x1p24F; exp += 126 - 24; @@ -58,10 +57,14 @@ namespace ccm::internal } // namespace impl constexpr float scalbn_float(float arg, int exp) noexcept - { return impl::scalbn_float_impl(arg, exp); } + { + return impl::scalbn_float_impl(arg, exp); + } constexpr float scalbn_float(float arg, long exp) noexcept - { return impl::scalbn_float_impl(arg, static_cast(exp)); } + { + return impl::scalbn_float_impl(arg, static_cast(exp)); + } } // namespace ccm::internal CCM_RESTORE_MSVC_WARNING() diff --git a/include/ccmath/math/fmanip/impl/scalbn_ldouble_impl.hpp b/include/ccmath/math/fmanip/impl/scalbn_ldouble_impl.hpp index 2a9105be..603dbad7 100644 --- a/include/ccmath/math/fmanip/impl/scalbn_ldouble_impl.hpp +++ b/include/ccmath/math/fmanip/impl/scalbn_ldouble_impl.hpp @@ -32,21 +32,32 @@ namespace ccm::internal // May be much slower than the double and float version. // Need to benchmark it. // TODO: Possibly implement a bit manipulation version of this function in the future if possible. - if (arg == static_cast(0)) { return arg; } + if (arg == static_cast(0)) + { + return arg; + } - if (ccm::isinf(arg)) { return arg; } + if (ccm::isinf(arg)) + { + return arg; + } - if (exp == static_cast(0)) { return arg; } + if (exp == static_cast(0)) + { + return arg; + } - if (ccm::isnan(arg)) { return std::numeric_limits::quiet_NaN(); } + if (ccm::isnan(arg)) + { + return std::numeric_limits::quiet_NaN(); + } long double mult(1); if (exp > 0) { mult = std::numeric_limits::radix; --exp; - } - else + } else { ++exp; exp = -exp; @@ -59,8 +70,7 @@ namespace ccm::internal { mult *= mult; exp >>= 1; - } - else + } else { arg *= mult; --exp; @@ -75,8 +85,12 @@ namespace ccm::internal } // namespace impl constexpr long double scalbn_ldouble(long double num, int exp) noexcept - { return impl::scalbn_ldouble_impl(num, exp); } + { + return impl::scalbn_ldouble_impl(num, exp); + } constexpr long double scalbn_ldouble(long double num, long exp) noexcept - { return impl::scalbn_ldouble_impl(num, static_cast(exp)); } + { + return impl::scalbn_ldouble_impl(num, static_cast(exp)); + } } // namespace ccm::internal diff --git a/include/ccmath/math/fmanip/ldexp.hpp b/include/ccmath/math/fmanip/ldexp.hpp index 37c6cb7f..e90382db 100644 --- a/include/ccmath/math/fmanip/ldexp.hpp +++ b/include/ccmath/math/fmanip/ldexp.hpp @@ -59,14 +59,19 @@ namespace ccm * @return \f$\text{num} \times 2^\text{exp}\f$. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ - template , bool> = true> - constexpr T ldexp(T num, int exp) + template , bool> = true> constexpr T ldexp(T num, int exp) { if constexpr (ccm::builtin::has_constexpr_ldexp) { - if (ccm::support::is_constant_evaluated()) { return builtin::ldexp_ct(num, exp); } + if (ccm::support::is_constant_evaluated()) + { + return builtin::ldexp_ct(num, exp); + } + } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::ldexp_rt(num, exp); } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::ldexp_rt(num, exp); } return support::helpers::internal_ldexp(num, exp); } @@ -82,9 +87,10 @@ namespace ccm * @return \f$\text{num} \times 2^\text{exp}\f$ as a \c double. * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ - template , bool> = true> - constexpr double ldexp(Integer num, int exp) - { return ccm::ldexp(static_cast(num), exp); } + template , bool> = true> constexpr double ldexp(Integer num, int exp) + { + return ccm::ldexp(static_cast(num), exp); + } /** * @brief Scales a \c float \p num by \f$2^\text{exp}\f$. Equivalent to \c ldexp(num, exp). @@ -97,7 +103,9 @@ namespace ccm * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ constexpr float ldexpf(float num, int exp) noexcept - { return ccm::ldexp(num, exp); } + { + return ccm::ldexp(num, exp); + } /** * @brief Scales a \c long double \p num by \f$2^\text{exp}\f$ using native long double support. @@ -113,6 +121,8 @@ namespace ccm * @attention At compile time errno may or may not be set, depending on the compiler. Assume it is not. */ constexpr long double ldexpl(long double num, int exp) noexcept - { return ccm::ldexp(num, exp); } + { + return ccm::ldexp(num, exp); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/logb.hpp b/include/ccmath/math/fmanip/logb.hpp index 1165c1a5..6b23c1c0 100644 --- a/include/ccmath/math/fmanip/logb.hpp +++ b/include/ccmath/math/fmanip/logb.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Floating-point value. * @return Unbiased exponent of num in the return type T. */ - template , bool> = true> - constexpr T logb(T num) noexcept + template , bool> = true> constexpr T logb(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_logb) { return ccm::builtin::logb_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_logb) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::logb_gen(num); } + return ccm::builtin::logb_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::logb_gen(num); + } return ccm::rt::logb_rt(num); } } @@ -42,7 +46,9 @@ namespace ccm * @return Unbiased exponent as float. */ constexpr float logbf(float num) noexcept - { return ccm::logb(num); } + { + return ccm::logb(num); + } /** * @brief Extracts the unbiased exponent of a long double. @@ -50,5 +56,7 @@ namespace ccm * @return Unbiased exponent as long double. */ constexpr long double logbl(long double num) noexcept - { return ccm::logb(num); } + { + return ccm::logb(num); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/modf.hpp b/include/ccmath/math/fmanip/modf.hpp index 44e08add..c3686cf7 100644 --- a/include/ccmath/math/fmanip/modf.hpp +++ b/include/ccmath/math/fmanip/modf.hpp @@ -26,20 +26,24 @@ namespace ccm * @param iptr Pointer that receives the integral part. * @return Fractional part of x, with the same sign as x. */ - template , bool> = true> - constexpr T modf(T x, T * iptr) noexcept + template , bool> = true> constexpr T modf(T x, T * iptr) noexcept { - if constexpr (ccm::builtin::has_constexpr_modf) { return ccm::builtin::modf_ct(x, iptr); } - else if (!ccm::support::is_constant_evaluated()) + if constexpr (ccm::builtin::has_constexpr_modf) + { + return ccm::builtin::modf_ct(x, iptr); + } else if (!ccm::support::is_constant_evaluated()) { T integer_part{}; const T fractional = ccm::rt::modf_rt(x, integer_part); *iptr = integer_part; return fractional; - } - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } - else if constexpr (std::is_same_v) { return internal::impl::modf_impl(x, iptr); } - else + } else if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } else if constexpr (std::is_same_v) + { + return internal::impl::modf_impl(x, iptr); + } else { double integer_part{}; const double fractional = internal::impl::modf_impl(static_cast(x), &integer_part); @@ -55,7 +59,9 @@ namespace ccm * @return Fractional part of x. */ constexpr float modff(float x, float * iptr) noexcept - { return ccm::modf(x, iptr); } + { + return ccm::modf(x, iptr); + } /** * @brief Decomposes a long double into fractional and integral parts. @@ -64,5 +70,7 @@ namespace ccm * @return Fractional part of x. */ constexpr long double modfl(long double x, long double * iptr) noexcept - { return ccm::modf(x, iptr); } + { + return ccm::modf(x, iptr); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/nextafter.hpp b/include/ccmath/math/fmanip/nextafter.hpp index 7e635755..394ed8fa 100644 --- a/include/ccmath/math/fmanip/nextafter.hpp +++ b/include/ccmath/math/fmanip/nextafter.hpp @@ -26,12 +26,15 @@ namespace ccm * @param to Direction target. * @return Next representable value after from in the direction of to. */ - template , bool> = true> - constexpr T nextafter(T from, T to) noexcept + template , bool> = true> constexpr T nextafter(T from, T to) noexcept { - if constexpr (ccm::builtin::has_constexpr_nextafter) { return ccm::builtin::nextafter_ct(from, to); } - else if (ccm::support::is_constant_evaluated()) { return gen::nextafter_gen(from, to); } - else + if constexpr (ccm::builtin::has_constexpr_nextafter) + { + return ccm::builtin::nextafter_ct(from, to); + } else if (ccm::support::is_constant_evaluated()) + { + return gen::nextafter_gen(from, to); + } else { return ccm::rt::nextafter_rt(from, to); } @@ -47,7 +50,9 @@ namespace ccm */ template && std::is_arithmetic_v, bool> = true> constexpr auto nextafter(Arithmetic1 from, Arithmetic2 to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } /** * @brief Returns the next representable float from from toward to. @@ -56,7 +61,9 @@ namespace ccm * @return Next representable float value. */ constexpr float nextafterf(float from, float to) noexcept - { return ccm::nextafter(from, to); } + { + return ccm::nextafter(from, to); + } /** * @brief Returns the next representable long double from from toward to. @@ -65,5 +72,7 @@ namespace ccm * @return Next representable long double value. */ constexpr long double nextafterl(long double from, long double to) noexcept - { return ccm::nextafter(from, to); } + { + return ccm::nextafter(from, to); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/nextdown.hpp b/include/ccmath/math/fmanip/nextdown.hpp index cabe7732..d9b73546 100644 --- a/include/ccmath/math/fmanip/nextdown.hpp +++ b/include/ccmath/math/fmanip/nextdown.hpp @@ -17,17 +17,23 @@ namespace ccm { - template , bool> = true> - constexpr T nextdown(T x) noexcept - { return ccm::nextafter(x, -std::numeric_limits::infinity()); } + template , bool> = true> constexpr T nextdown(T x) noexcept + { + return ccm::nextafter(x, -std::numeric_limits::infinity()); + } - template , bool> = true> - constexpr double nextdown(Integer x) noexcept - { return ccm::nextdown(static_cast(x)); } + template , bool> = true> constexpr double nextdown(Integer x) noexcept + { + return ccm::nextdown(static_cast(x)); + } constexpr float nextdownf(float x) noexcept - { return ccm::nextdown(x); } + { + return ccm::nextdown(x); + } constexpr long double nextdownl(long double x) noexcept - { return ccm::nextdown(x); } + { + return ccm::nextdown(x); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/nexttoward.hpp b/include/ccmath/math/fmanip/nexttoward.hpp index f5faa001..c4cedc34 100644 --- a/include/ccmath/math/fmanip/nexttoward.hpp +++ b/include/ccmath/math/fmanip/nexttoward.hpp @@ -26,12 +26,15 @@ namespace ccm * @param to Direction target as long double. * @return Next representable value after from toward to. */ - template , bool> = true> - constexpr T nexttoward(T from, long double to) noexcept + template , bool> = true> constexpr T nexttoward(T from, long double to) noexcept { - if constexpr (ccm::builtin::has_constexpr_nexttoward) { return ccm::builtin::nexttoward_ct(from, to); } - else if (ccm::support::is_constant_evaluated()) { return gen::nextafter_gen(from, to); } - else + if constexpr (ccm::builtin::has_constexpr_nexttoward) + { + return ccm::builtin::nexttoward_ct(from, to); + } else if (ccm::support::is_constant_evaluated()) + { + return gen::nextafter_gen(from, to); + } else { return ccm::rt::nexttoward_rt(from, to); } @@ -44,9 +47,10 @@ namespace ccm * @param to Direction target as long double. * @return Next representable double value after converting from to floating-point. */ - template , bool> = true> - constexpr double nexttoward(Integer from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + template , bool> = true> constexpr double nexttoward(Integer from, long double to) noexcept + { + return gen::nextafter_gen(from, to); + } /** * @brief Returns the next representable float from from toward to. @@ -55,7 +59,9 @@ namespace ccm * @return Next representable float value. */ constexpr float nexttowardf(float from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } /** * @brief Returns the next representable long double from from toward to. @@ -64,6 +70,8 @@ namespace ccm * @return Next representable long double value. */ constexpr long double nexttowardl(long double from, long double to) noexcept - { return gen::nextafter_gen(from, to); } + { + return gen::nextafter_gen(from, to); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/nextup.hpp b/include/ccmath/math/fmanip/nextup.hpp index 9f990dd7..64bede4a 100644 --- a/include/ccmath/math/fmanip/nextup.hpp +++ b/include/ccmath/math/fmanip/nextup.hpp @@ -17,17 +17,23 @@ namespace ccm { - template , bool> = true> - constexpr T nextup(T x) noexcept - { return ccm::nextafter(x, std::numeric_limits::infinity()); } + template , bool> = true> constexpr T nextup(T x) noexcept + { + return ccm::nextafter(x, std::numeric_limits::infinity()); + } - template , bool> = true> - constexpr double nextup(Integer x) noexcept - { return ccm::nextup(static_cast(x)); } + template , bool> = true> constexpr double nextup(Integer x) noexcept + { + return ccm::nextup(static_cast(x)); + } constexpr float nextupf(float x) noexcept - { return ccm::nextup(x); } + { + return ccm::nextup(x); + } constexpr long double nextupl(long double x) noexcept - { return ccm::nextup(x); } + { + return ccm::nextup(x); + } } // namespace ccm diff --git a/include/ccmath/math/fmanip/scalbn.hpp b/include/ccmath/math/fmanip/scalbn.hpp index 1fe006ca..b18f93c6 100644 --- a/include/ccmath/math/fmanip/scalbn.hpp +++ b/include/ccmath/math/fmanip/scalbn.hpp @@ -31,17 +31,30 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, int exp) noexcept + template , bool> = true> constexpr T scalbn(T num, int exp) noexcept { - if constexpr (ccm::builtin::has_constexpr_scalbn) { return ccm::builtin::scalbn_ct(num, exp); } - else + if constexpr (ccm::builtin::has_constexpr_scalbn) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::scalbn_rt(num, exp); } - - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + return ccm::builtin::scalbn_ct(num, exp); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::scalbn_rt(num, exp); + } + + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); } } @@ -53,17 +66,30 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned. */ - template , bool> = true> - constexpr T scalbn(T num, long exp) noexcept + template , bool> = true> constexpr T scalbn(T num, long exp) noexcept { - if constexpr (ccm::builtin::has_constexpr_scalbn) { return ccm::builtin::scalbn_ct(num, exp); } - else + if constexpr (ccm::builtin::has_constexpr_scalbn) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::scalbn_rt(num, static_cast(exp)); } - - if constexpr (std::is_same_v) { return internal::scalbn_float(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_double(num, exp); } - if constexpr (std::is_same_v) { return internal::scalbn_ldouble(num, exp); } + return ccm::builtin::scalbn_ct(num, exp); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::scalbn_rt(num, static_cast(exp)); + } + + if constexpr (std::is_same_v) + { + return internal::scalbn_float(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_double(num, exp); + } + if constexpr (std::is_same_v) + { + return internal::scalbn_ldouble(num, exp); + } return static_cast(internal::scalbn_ldouble(num, exp)); } } @@ -75,9 +101,10 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, int exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, int exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -86,17 +113,20 @@ namespace ccm * @param exp Integer value. * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a double. */ - template , bool> = true> - constexpr double scalbn(Integer num, long exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbn(Integer num, long exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } - template , bool> = true> - constexpr T scalbln(T num, long int exp) noexcept - { return ccm::scalbn(num, exp); } + template , bool> = true> constexpr T scalbln(T num, long int exp) noexcept + { + return ccm::scalbn(num, exp); + } - template , bool> = true> - constexpr double scalbln(Integer num, long int exp) noexcept - { return ccm::scalbn(static_cast(num), exp); } + template , bool> = true> constexpr double scalbln(Integer num, long int exp) noexcept + { + return ccm::scalbn(static_cast(num), exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -105,7 +135,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -114,7 +146,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a float. */ constexpr float scalbnf(float num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -123,7 +157,9 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, int exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } /** * @brief Multiplies a number by FLT_RADIX raised to a power @@ -132,13 +168,19 @@ namespace ccm * @return If no errors occur, num multiplied by FLT_RADIX to the power of exp (num×FLT_RADIX^exp) is returned as a long double. */ constexpr long double scalbnl(long double num, long exp) noexcept - { return ccm::scalbn(num, exp); } + { + return ccm::scalbn(num, exp); + } constexpr float scalblnf(float num, long int exp) noexcept - { return ccm::scalbln(num, exp); } + { + return ccm::scalbln(num, exp); + } constexpr long double scalblnl(long double num, long int exp) noexcept - { return ccm::scalbln(num, exp); } + { + return ccm::scalbln(num, exp); + } } // namespace ccm #if defined(_MSC_VER) && !defined(__clang__) diff --git a/include/ccmath/math/hyper/acosh.hpp b/include/ccmath/math/hyper/acosh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/acosh.hpp +++ b/include/ccmath/math/hyper/acosh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/hyper/asinh.hpp b/include/ccmath/math/hyper/asinh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/asinh.hpp +++ b/include/ccmath/math/hyper/asinh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/hyper/atanh.hpp b/include/ccmath/math/hyper/atanh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/atanh.hpp +++ b/include/ccmath/math/hyper/atanh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/hyper/cosh.hpp b/include/ccmath/math/hyper/cosh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/cosh.hpp +++ b/include/ccmath/math/hyper/cosh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/hyper/sinh.hpp b/include/ccmath/math/hyper/sinh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/sinh.hpp +++ b/include/ccmath/math/hyper/sinh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/hyper/tanh.hpp b/include/ccmath/math/hyper/tanh.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/hyper/tanh.hpp +++ b/include/ccmath/math/hyper/tanh.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/misc/gamma.hpp b/include/ccmath/math/misc/gamma.hpp index a84b71d4..d66401eb 100644 --- a/include/ccmath/math/misc/gamma.hpp +++ b/include/ccmath/math/misc/gamma.hpp @@ -25,17 +25,30 @@ namespace ccm * @param num floating-point or integer value * @return If no errors occur, the gamma function value of num is returned. */ - template , bool> = true> - constexpr T tgamma(T num) + template , bool> = true> constexpr T tgamma(T num) { - if constexpr (ccm::builtin::has_constexpr_gamma) { return ccm::builtin::gamma_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_gamma) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::gamma_rt(num); } + return ccm::builtin::gamma_ct(num); + } else + { + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::gamma_rt(num); + } - if constexpr (std::is_same_v) { return internal::gamma_float(num); } - if constexpr (std::is_same_v) { return internal::gamma_double(num); } - if constexpr (std::is_same_v) { return static_cast(internal::gamma_double(static_cast(num))); } + if constexpr (std::is_same_v) + { + return internal::gamma_float(num); + } + if constexpr (std::is_same_v) + { + return internal::gamma_double(num); + } + if constexpr (std::is_same_v) + { + return static_cast(internal::gamma_double(static_cast(num))); + } return static_cast(internal::gamma_double(static_cast(num))); } } @@ -46,17 +59,20 @@ namespace ccm * @param num Integer value. * @return Gamma function value of num as double. */ - template , bool> = true> - constexpr double tgamma(Integer num) - { return ccm::tgamma(static_cast(num)); } + template , bool> = true> constexpr double tgamma(Integer num) + { + return ccm::tgamma(static_cast(num)); + } - template , bool> = true> - constexpr T gamma(T num) - { return ccm::tgamma(num); } + template , bool> = true> constexpr T gamma(T num) + { + return ccm::tgamma(num); + } - template , bool> = true> - constexpr double gamma(Integer num) - { return ccm::tgamma(num); } + template , bool> = true> constexpr double gamma(Integer num) + { + return ccm::tgamma(num); + } /** * @brief Computes the gamma function for float. @@ -64,7 +80,9 @@ namespace ccm * @return Gamma function value of num as float. */ constexpr float tgammaf(float num) - { return ccm::tgamma(num); } + { + return ccm::tgamma(num); + } /** * @brief Computes the gamma function for long double. @@ -72,5 +90,7 @@ namespace ccm * @return Gamma function value of num as long double. */ constexpr long double tgammal(long double num) - { return ccm::tgamma(num); } + { + return ccm::tgamma(num); + } } // namespace ccm diff --git a/include/ccmath/math/misc/impl/gamma_double_impl.hpp b/include/ccmath/math/misc/impl/gamma_double_impl.hpp index 978124e1..75099007 100644 --- a/include/ccmath/math/misc/impl/gamma_double_impl.hpp +++ b/include/ccmath/math/misc/impl/gamma_double_impl.hpp @@ -55,7 +55,7 @@ namespace ccm::internal::impl return ccm::support::multiply_add(d4, p47, p03); } - constexpr double gamma_reduce(double xd, double &w_out) noexcept + constexpr double gamma_reduce(double xd, double & w_out) noexcept { const double m = xd - data::k_gamma_base; const double i = ccm::support::fp::nearest_integer(m); @@ -76,7 +76,10 @@ namespace ccm::internal::impl w *= z; } } - if (i <= -0.5) { w = 1.0 / w; } + if (i <= -0.5) + { + w = 1.0 / w; + } w_out = w; return f; @@ -85,7 +88,10 @@ namespace ccm::internal::impl constexpr double positive_factorial(std::int32_t k) noexcept { double result = 1.0; - for (std::int32_t i = 2; i < k; ++i) { result *= static_cast(i); } + for (std::int32_t i = 2; i < k; ++i) + { + result *= static_cast(i); + } return result; } @@ -127,8 +133,7 @@ namespace ccm::internal::impl { ccm::support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); ccm::support::fenv::set_errno_if_required(ERANGE); - } - else + } else { ccm::support::fenv::raise_except_if_required(FE_INEXACT); } @@ -151,7 +156,10 @@ namespace ccm::internal::impl ccm::support::fenv::set_errno_if_required(EDOM); return fp_bits::quiet_nan().get_val(); } - if (k >= 1 && k <= 170) { return detail::positive_factorial(k); } + if (k >= 1 && k <= 170) + { + return detail::positive_factorial(k); + } } double w{}; @@ -161,8 +169,10 @@ namespace ccm::internal::impl { ccm::support::fenv::raise_except_if_required(FE_OVERFLOW | FE_INEXACT); ccm::support::fenv::set_errno_if_required(ERANGE); + } else if (CCM_UNLIKELY(result == 0.0 && !xbits.is_neg())) + { + ccm::support::fenv::set_errno_if_required(ERANGE); } - else if (CCM_UNLIKELY(result == 0.0 && !xbits.is_neg())) { ccm::support::fenv::set_errno_if_required(ERANGE); } return result; } } // namespace ccm::internal::impl diff --git a/include/ccmath/math/misc/impl/gamma_float_impl.hpp b/include/ccmath/math/misc/impl/gamma_float_impl.hpp index f7800cc2..7a86a4d5 100644 --- a/include/ccmath/math/misc/impl/gamma_float_impl.hpp +++ b/include/ccmath/math/misc/impl/gamma_float_impl.hpp @@ -61,7 +61,10 @@ namespace ccm::internal::impl const double result = gamma_double_impl(static_cast(x)); - if (CCM_UNLIKELY(!xbits.is_neg() && ccm::support::fp::FPBits(result).is_inf())) { return fp_bits::max_normal().get_val(); } + if (CCM_UNLIKELY(!xbits.is_neg() && ccm::support::fp::FPBits(result).is_inf())) + { + return fp_bits::max_normal().get_val(); + } if (CCM_UNLIKELY(xbits.is_neg() && result != 0.0 && fp_bits(static_cast(result)).is_zero())) { diff --git a/include/ccmath/math/misc/impl/gamma_impl.hpp b/include/ccmath/math/misc/impl/gamma_impl.hpp index a990a44c..54244b9d 100644 --- a/include/ccmath/math/misc/impl/gamma_impl.hpp +++ b/include/ccmath/math/misc/impl/gamma_impl.hpp @@ -16,8 +16,12 @@ namespace ccm::internal { constexpr float gamma_float(float x) noexcept - { return impl::gamma_float_impl(x); } + { + return impl::gamma_float_impl(x); + } constexpr double gamma_double(double x) noexcept - { return impl::gamma_double_impl(x); } + { + return impl::gamma_double_impl(x); + } } // namespace ccm::internal diff --git a/include/ccmath/math/misc/impl/lgamma_double_impl.hpp b/include/ccmath/math/misc/impl/lgamma_double_impl.hpp index 237bb9af..e7abbfc8 100644 --- a/include/ccmath/math/misc/impl/lgamma_double_impl.hpp +++ b/include/ccmath/math/misc/impl/lgamma_double_impl.hpp @@ -34,7 +34,9 @@ namespace ccm::internal::impl using fp_bits = ccm::support::fp::FPBits; constexpr double log_abs(double x) noexcept - { return ccm::log(ccm::fabs(x)); } + { + return ccm::log(ccm::fabs(x)); + } constexpr double stirling(double x) noexcept { @@ -53,9 +55,15 @@ namespace ccm::internal::impl constexpr double positive_lfactorial(std::int32_t k) noexcept { - if (k <= 2) { return 0.0; } + if (k <= 2) + { + return 0.0; + } double sum = 0.0; - for (std::int32_t i = 2; i < k; ++i) { sum += ccm::log(static_cast(i)); } + for (std::int32_t i = 2; i < k; ++i) + { + sum += ccm::log(static_cast(i)); + } return sum; } @@ -102,12 +110,21 @@ namespace ccm::internal::impl return fp_bits::inf().get_val(); } const auto k = static_cast(ccm::floor(x)); - if (k <= 170) { return lgamma_detail::positive_lfactorial(k); } + if (k <= 170) + { + return lgamma_detail::positive_lfactorial(k); + } } - if (CCM_UNLIKELY(ccm::fabs(x) <= 0x1p-53)) { return -lgamma_detail::log_abs(x); } + if (CCM_UNLIKELY(ccm::fabs(x) <= 0x1p-53)) + { + return -lgamma_detail::log_abs(x); + } - if (CCM_UNLIKELY(x >= 8.0)) { return lgamma_detail::stirling(x); } + if (CCM_UNLIKELY(x >= 8.0)) + { + return lgamma_detail::stirling(x); + } if (CCM_UNLIKELY(x < 0.0)) { @@ -119,5 +136,7 @@ namespace ccm::internal::impl } constexpr float lgamma_float_impl(float x) noexcept - { return static_cast(lgamma_double_impl(static_cast(x))); } + { + return static_cast(lgamma_double_impl(static_cast(x))); + } } // namespace ccm::internal::impl diff --git a/include/ccmath/math/misc/impl/lgamma_impl.hpp b/include/ccmath/math/misc/impl/lgamma_impl.hpp index 7c70b92f..e5ed053e 100644 --- a/include/ccmath/math/misc/impl/lgamma_impl.hpp +++ b/include/ccmath/math/misc/impl/lgamma_impl.hpp @@ -15,8 +15,12 @@ namespace ccm::internal { constexpr float lgamma_float(float x) noexcept - { return impl::lgamma_float_impl(x); } + { + return impl::lgamma_float_impl(x); + } constexpr double lgamma_double(double x) noexcept - { return impl::lgamma_double_impl(x); } + { + return impl::lgamma_double_impl(x); + } } // namespace ccm::internal diff --git a/include/ccmath/math/misc/lerp.hpp b/include/ccmath/math/misc/lerp.hpp index c0e13459..f888845a 100644 --- a/include/ccmath/math/misc/lerp.hpp +++ b/include/ccmath/math/misc/lerp.hpp @@ -23,17 +23,28 @@ namespace ccm * @param t Interpolation factor. * @return Interpolated value equivalent to a + t * (b - a) with stability handling for edge cases. */ - template - constexpr T lerp(T a, T b, T t) noexcept + template constexpr T lerp(T a, T b, T t) noexcept { - if (ccm::isnan(a) || ccm::isnan(b) || ccm::isnan(t)) { return a + b + t; } + if (ccm::isnan(a) || ccm::isnan(b) || ccm::isnan(t)) + { + return a + b + t; + } - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return t * b + (1 - t) * a; } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) + { + return t * b + (1 - t) * a; + } - if (t == 1) { return b; } + if (t == 1) + { + return b; + } const T x = a + t * (b - a); - if ((t > 1) == (b > a)) { return b < x ? x : b; } + if ((t > 1) == (b > a)) + { + return b < x ? x : b; + } return x < b ? x : b; } @@ -71,12 +82,21 @@ namespace ccm "Do not use ccm::lerp_old it is only being kept as a fallback until ccm::lerp has been validated as conforming to std::lerp")]] constexpr T old_lerp(T a, T b, T t) noexcept { - if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) { return t * b + (1 - t) * a; } + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) + { + return t * b + (1 - t) * a; + } - if (t == 1) { return b; } + if (t == 1) + { + return b; + } const T x = a + t * (b - a); - if ((t > 1) == (b > a)) { return b < x ? x : b; } + if ((t > 1) == (b > a)) + { + return b < x ? x : b; + } return x < b ? x : b; } diff --git a/include/ccmath/math/misc/lgamma.hpp b/include/ccmath/math/misc/lgamma.hpp index bde9d02a..27b509cd 100644 --- a/include/ccmath/math/misc/lgamma.hpp +++ b/include/ccmath/math/misc/lgamma.hpp @@ -25,14 +25,25 @@ namespace ccm * @param num floating-point or integer value * @return If no errors occur, the natural logarithm of the absolute value of the gamma function at num is returned. */ - template , bool> = true> - constexpr T lgamma(T num) + template , bool> = true> constexpr T lgamma(T num) { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::lgamma_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::lgamma_rt(num); + } - if constexpr (std::is_same_v) { return internal::lgamma_float(num); } - if constexpr (std::is_same_v) { return internal::lgamma_double(num); } - if constexpr (std::is_same_v) { return static_cast(internal::lgamma_double(static_cast(num))); } + if constexpr (std::is_same_v) + { + return internal::lgamma_float(num); + } + if constexpr (std::is_same_v) + { + return internal::lgamma_double(num); + } + if constexpr (std::is_same_v) + { + return static_cast(internal::lgamma_double(static_cast(num))); + } return static_cast(internal::lgamma_double(static_cast(num))); } @@ -42,9 +53,10 @@ namespace ccm * @param num Integer value. * @return Natural logarithm of |gamma(num)| as double. */ - template , bool> = true> - constexpr double lgamma(Integer num) - { return ccm::lgamma(static_cast(num)); } + template , bool> = true> constexpr double lgamma(Integer num) + { + return ccm::lgamma(static_cast(num)); + } /** * @brief Computes log-gamma for float. @@ -52,7 +64,9 @@ namespace ccm * @return Natural logarithm of |gamma(num)| as float. */ constexpr float lgammaf(float num) - { return ccm::lgamma(num); } + { + return ccm::lgamma(num); + } /** * @brief Computes log-gamma for long double. @@ -60,5 +74,7 @@ namespace ccm * @return Natural logarithm of |gamma(num)| as long double. */ constexpr long double lgammal(long double num) - { return ccm::lgamma(num); } + { + return ccm::lgamma(num); + } } // namespace ccm diff --git a/include/ccmath/math/nearest/ceil.hpp b/include/ccmath/math/nearest/ceil.hpp index 92fee8b0..d904202b 100644 --- a/include/ccmath/math/nearest/ceil.hpp +++ b/include/ccmath/math/nearest/ceil.hpp @@ -28,22 +28,33 @@ namespace ccm * @param num A floating-point or integer value. * @return The smallest integer value not less than num. */ - template , bool> = true> - constexpr T ceil(T num) noexcept + template , bool> = true> constexpr T ceil(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_ceil) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::ceil_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::ceil_ct(num); + } } { // If num is NaN, NaN is returned. // If num is ±∞ or ±0, num is returned, unmodified. - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::ceil_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::ceil_rt(num); + } const T truncated = ccm::trunc(num); - if (truncated == num || num < static_cast(0)) { return truncated; } + if (truncated == num || num < static_cast(0)) + { + return truncated; + } return truncated + static_cast(1); } } @@ -54,9 +65,10 @@ namespace ccm * @param num Integer value. * @return num converted to double. */ - template , bool> = true> - constexpr double ceil(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double ceil(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Computes the ceiling of a float value. @@ -64,7 +76,9 @@ namespace ccm * @return Smallest integer value not less than num, as float. */ constexpr float ceilf(float num) noexcept - { return ccm::ceil(num); } + { + return ccm::ceil(num); + } /** * @brief Computes the ceiling of a double value. @@ -72,7 +86,9 @@ namespace ccm * @return Smallest integer value not less than num, as double. */ constexpr long double ceill(long double num) noexcept - { return ccm::ceil(num); } + { + return ccm::ceil(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/math/nearest/floor.hpp b/include/ccmath/math/nearest/floor.hpp index 162a363c..5519df2f 100644 --- a/include/ccmath/math/nearest/floor.hpp +++ b/include/ccmath/math/nearest/floor.hpp @@ -27,22 +27,33 @@ namespace ccm * @param num A floating-point or integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr T floor(T num) noexcept + template , bool> = true> constexpr T floor(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_floor) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::floor_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::floor_ct(num); + } } { // If num is NaN, NaN is returned. // If num is ±∞ or ±0, num is returned, unmodified. - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::floor_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::floor_rt(num); + } const T truncated = ccm::trunc(num); - if (truncated == num || num > static_cast(0)) { return truncated; } + if (truncated == num || num > static_cast(0)) + { + return truncated; + } return truncated - static_cast(1); } } @@ -52,8 +63,7 @@ namespace ccm * @param num A integer value. * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ - template , bool> = true> - constexpr double floor(Integer num) noexcept + template , bool> = true> constexpr double floor(Integer num) noexcept { return static_cast(num); // All integers already have a floor value. Just cast to double and return. } @@ -64,7 +74,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr float floorf(float num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } /** * @brief Computes the largest integer value not greater than num. @@ -72,7 +84,9 @@ namespace ccm * @return If no errors occur, the largest integer value not greater than num, that is ⌊num⌋, is returned. */ constexpr long double floorl(long double num) noexcept - { return ccm::floor(num); } + { + return ccm::floor(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/math/nearest/nearbyint.hpp b/include/ccmath/math/nearest/nearbyint.hpp index ff3208d6..f9976b23 100644 --- a/include/ccmath/math/nearest/nearbyint.hpp +++ b/include/ccmath/math/nearest/nearbyint.hpp @@ -30,10 +30,12 @@ namespace ccm * @param num A floating-point value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input floating point value unmodified. */ - template , bool> = true> - constexpr T nearbyint(T num) noexcept + template , bool> = true> constexpr T nearbyint(T num) noexcept { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::nearbyint_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::nearbyint_rt(num); + } constexpr auto rounding_mode{ ccm::support::fenv::get_rounding_mode() }; return ccm::support::fp::directional_round(num, rounding_mode); @@ -45,9 +47,10 @@ namespace ccm * @param num An integral value. * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ - template , bool> = true> - constexpr double nearbyint(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double nearbyint(Integer num) noexcept + { + return static_cast(num); + } /** * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. @@ -55,7 +58,9 @@ namespace ccm * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ constexpr float nearbyintf(float num) noexcept - { return ccm::nearbyint(num); } + { + return ccm::nearbyint(num); + } /** * @brief The nearest integer value to num, according to the rounding mode FE_TONEAREST, is returned. @@ -63,6 +68,8 @@ namespace ccm * @return If no errors occur, the rounded floating point value. Otherwise, returns the input integer value unmodified. */ constexpr long double nearbyintl(long double num) noexcept - { return ccm::nearbyint(num); } + { + return ccm::nearbyint(num); + } } // namespace ccm diff --git a/include/ccmath/math/nearest/rint.hpp b/include/ccmath/math/nearest/rint.hpp index 5f6bec82..2a2d755b 100644 --- a/include/ccmath/math/nearest/rint.hpp +++ b/include/ccmath/math/nearest/rint.hpp @@ -30,10 +30,12 @@ namespace ccm * @param num A floating-point or integer value. * @return The rounded value. */ - template , bool> = true> - constexpr T rint(T num) noexcept + template , bool> = true> constexpr T rint(T num) noexcept { - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::rint_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::rint_rt(num); + } constexpr auto rounding_mode{ ccm::support::fenv::get_rounding_mode() }; return ccm::support::fp::directional_round(num, rounding_mode); @@ -45,9 +47,10 @@ namespace ccm * @param num Integer value. * @return num converted to double. */ - template , bool> = true> - constexpr double rint(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double rint(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Rounds a float to an integer value using the current rounding mode. @@ -55,7 +58,9 @@ namespace ccm * @return Rounded value as float. */ constexpr float rintf(float num) noexcept - { return ccm::rint(num); } + { + return ccm::rint(num); + } /** * @brief Rounds a long double to an integer value using the current rounding mode. @@ -63,7 +68,9 @@ namespace ccm * @return Rounded value as long double. */ constexpr long double rintl(long double num) noexcept - { return ccm::rint(num); } + { + return ccm::rint(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/math/nearest/round.hpp b/include/ccmath/math/nearest/round.hpp index 169a7950..dfc24e8a 100644 --- a/include/ccmath/math/nearest/round.hpp +++ b/include/ccmath/math/nearest/round.hpp @@ -27,19 +27,27 @@ namespace ccm * @param num A floating-point or integer value. * @return The rounded value. */ - template , bool> = true> - constexpr T round(T num) noexcept + template , bool> = true> constexpr T round(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_round) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::round_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::round_ct(num); + } } { // If num is NaN, NaN is returned. // If num is ±∞ or ±0, num is returned, unmodified. - if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) { return num; } + if (ccm::isinf(num) || num == static_cast(0) || ccm::isnan(num)) + { + return num; + } - if (!ccm::support::is_constant_evaluated()) { return ccm::rt::round_rt(num); } + if (!ccm::support::is_constant_evaluated()) + { + return ccm::rt::round_rt(num); + } constexpr int round_mode = static_cast(ccm::support::fp::rounding_mode::eFE_TONEARESTFROMZERO); return ccm::support::fp::directional_round(num, round_mode); @@ -52,9 +60,10 @@ namespace ccm * @param num Integer value. * @return num converted to double. */ - template , bool> = true> - constexpr double round(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double round(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Rounds a float to the nearest integer value, halfway cases away from zero. @@ -62,7 +71,9 @@ namespace ccm * @return Rounded value as float. */ constexpr float roundf(float num) noexcept - { return ccm::round(num); } + { + return ccm::round(num); + } /** * @brief Rounds a double to the nearest integer value, halfway cases away from zero. @@ -70,7 +81,9 @@ namespace ccm * @return Rounded value as double. */ constexpr long double roundl(long double num) noexcept - { return ccm::round(num); } + { + return ccm::round(num); + } } // namespace ccm /// @ingroup nearest diff --git a/include/ccmath/math/nearest/trunc.hpp b/include/ccmath/math/nearest/trunc.hpp index dff21968..a0a4dd4f 100644 --- a/include/ccmath/math/nearest/trunc.hpp +++ b/include/ccmath/math/nearest/trunc.hpp @@ -24,12 +24,14 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr T trunc(T num) noexcept + template , bool> = true> constexpr T trunc(T num) noexcept { if constexpr (ccm::builtin::has_constexpr_trunc) { - if (ccm::support::is_constant_evaluated()) { return ccm::builtin::trunc_ct(num); } + if (ccm::support::is_constant_evaluated()) + { + return ccm::builtin::trunc_ct(num); + } } if (ccm::support::is_constant_evaluated()) { @@ -40,26 +42,37 @@ namespace ccm // If x == ±∞ then return num // If x == ±NaN then return num - if (CCM_UNLIKELY(bits.is_inf_or_nan())) { return num; } + if (CCM_UNLIKELY(bits.is_inf_or_nan())) + { + return num; + } // If x == ±0 then return num - if (CCM_UNLIKELY(num == 0.0)) { return num; } + if (CCM_UNLIKELY(num == 0.0)) + { + return num; + } const int exponent = bits.get_exponent(); // If the exponent is greater than or equal to the fraction length, then we will return the number as is since it is already an integer. - if (exponent >= FPBits_t::fraction_length) { return num; } + if (exponent >= FPBits_t::fraction_length) + { + return num; + } // If our exponent is set up such that the abs(x) is less than 1 we will instead return 0. - if (exponent <= -1) { return FPBits_t::zero(bits.sign()).get_val(); } + if (exponent <= -1) + { + return FPBits_t::zero(bits.sign()).get_val(); + } // Perform the truncation const int trimming_size = FPBits_t::fraction_length - exponent; const auto truncated_mantissa = static_cast((bits.get_mantissa() >> trimming_size) << trimming_size); bits.set_mantissa(truncated_mantissa); return bits.get_val(); - } - else + } else { return ccm::rt::trunc_rt(num); } @@ -71,9 +84,10 @@ namespace ccm * @param num The value to truncate. * @return Returns a truncated value. */ - template , bool> = true> - constexpr double trunc(Integer num) noexcept - { return static_cast(num); } + template , bool> = true> constexpr double trunc(Integer num) noexcept + { + return static_cast(num); + } /** * @brief Specialization for float that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to x. @@ -81,7 +95,9 @@ namespace ccm * @return Returns a truncated float. */ constexpr float truncf(float num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } /** * @brief Specialization for long double that returns the integral value nearest to x with the magnitude of the integral value always less than or equal to @@ -90,5 +106,7 @@ namespace ccm * @return Returns a truncated long double. */ constexpr long double truncl(long double num) noexcept - { return ccm::trunc(num); } + { + return ccm::trunc(num); + } } // namespace ccm diff --git a/include/ccmath/math/power/cbrt.hpp b/include/ccmath/math/power/cbrt.hpp index e6191256..50f68521 100644 --- a/include/ccmath/math/power/cbrt.hpp +++ b/include/ccmath/math/power/cbrt.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Floating-point value. * @return Cube root of num. */ - template , bool> = true> - constexpr T cbrt(T num) noexcept + template , bool> = true> constexpr T cbrt(T num) noexcept { - if constexpr (ccm::builtin::has_constexpr_cbrt) { return ccm::builtin::cbrt_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_cbrt) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::cbrt_gen(num); } + return ccm::builtin::cbrt_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::cbrt_gen(num); + } return ccm::rt::cbrt_rt(num); } } @@ -42,7 +46,9 @@ namespace ccm * @return Cube root of num as float. */ constexpr float cbrtf(float num) noexcept - { return ccm::cbrt(num); } + { + return ccm::cbrt(num); + } /** * @brief Computes the cube root of a long double. @@ -50,7 +56,9 @@ namespace ccm * @return Cube root of num as long double. */ constexpr long double cbrtl(long double num) noexcept - { return ccm::cbrt(num); } + { + return ccm::cbrt(num); + } } // namespace ccm /// @ingroup power diff --git a/include/ccmath/math/power/hypot.hpp b/include/ccmath/math/power/hypot.hpp index ed5e55e5..b539184a 100644 --- a/include/ccmath/math/power/hypot.hpp +++ b/include/ccmath/math/power/hypot.hpp @@ -26,13 +26,17 @@ namespace ccm * @param y Second value. * @return sqrt(x*x + y*y) computed with scaling behavior suitable for floating-point range. */ - template , bool> = true> - constexpr T hypot(T x, T y) noexcept + template , bool> = true> constexpr T hypot(T x, T y) noexcept { - if constexpr (ccm::builtin::has_constexpr_hypot) { return ccm::builtin::hypot_ct(x, y); } - else + if constexpr (ccm::builtin::has_constexpr_hypot) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::hypot_gen(x, y); } + return ccm::builtin::hypot_ct(x, y); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::hypot_gen(x, y); + } return ccm::rt::hypot_rt(x, y); } } @@ -46,13 +50,19 @@ namespace ccm * @return sqrt(x*x + y*y + z*z) computed with the same scaling behavior as the 2-argument overload. */ constexpr float hypot(float x, float y, float z) noexcept - { return ccm::hypot(ccm::hypot(x, y), z); } + { + return ccm::hypot(ccm::hypot(x, y), z); + } constexpr double hypot(double x, double y, double z) noexcept - { return ccm::hypot(ccm::hypot(x, y), z); } + { + return ccm::hypot(ccm::hypot(x, y), z); + } constexpr long double hypot(long double x, long double y, long double z) noexcept - { return ccm::hypot(ccm::hypot(x, y), z); } + { + return ccm::hypot(ccm::hypot(x, y), z); + } template - using cmath_pow_argument_t = std::conditional_t>, double, std::remove_cv_t>; + template using cmath_pow_argument_t = std::conditional_t>, double, std::remove_cv_t>; - template - struct cmath_pow_result + template struct cmath_pow_result { using left_type = cmath_pow_argument_t; using right_type = cmath_pow_argument_t; @@ -33,8 +31,7 @@ namespace ccm::detail std::conditional_t || std::is_same_v, double, float>>; }; - template - using cmath_pow_result_t = typename cmath_pow_result::type; + template using cmath_pow_result_t = typename cmath_pow_result::type; } // namespace ccm::detail namespace ccm @@ -46,8 +43,7 @@ namespace ccm * @param exp Exponent value. * @return base raised to exp. */ - template , bool> = true> - constexpr T pow(T base, T exp) + template , bool> = true> constexpr T pow(T base, T exp) { #ifdef CCM_CONFIG_DETERMINISTIC // long double has no cross-hardware-portable format, so deterministic mode evaluates it in @@ -57,20 +53,24 @@ namespace ccm if constexpr (std::is_same_v) { return static_cast(ccm::pow(static_cast(base), static_cast(exp))); - } - else + } else #endif if constexpr (ccm::builtin::has_constexpr_pow) { // Constant evaluation always rounds to nearest, so the constexpr builtin is correct there. // At runtime the builtin lowers to libm, so defer to the runtime dispatcher which only // trusts the builtin under round to nearest. - if (support::is_constant_evaluated()) { return ccm::builtin::pow_ct(base, exp); } + if (support::is_constant_evaluated()) + { + return ccm::builtin::pow_ct(base, exp); + } return rt::pow_rt(base, exp); - } - else + } else { - if (support::is_constant_evaluated()) { return gen::pow_gen(base, exp); } + if (support::is_constant_evaluated()) + { + return gen::pow_gen(base, exp); + } return rt::pow_rt(base, exp); } } @@ -101,7 +101,9 @@ namespace ccm * @return base raised to exp as float. */ constexpr float powf(float base, float exp) - { return ccm::pow(base, exp); } + { + return ccm::pow(base, exp); + } /** * @brief Raises long double inputs to a power. @@ -113,5 +115,7 @@ namespace ccm * the result to long double. That fallback is not native long-double accuracy yet. */ constexpr long double powl(long double base, long double exp) - { return ccm::pow(base, exp); } + { + return ccm::pow(base, exp); + } } // namespace ccm diff --git a/include/ccmath/math/power/sqrt.hpp b/include/ccmath/math/power/sqrt.hpp index e78b81bd..afb31c20 100644 --- a/include/ccmath/math/power/sqrt.hpp +++ b/include/ccmath/math/power/sqrt.hpp @@ -26,13 +26,17 @@ namespace ccm * @param num Floating-point or integer number. * @return If no errors occur, square root of num (√num), is returned. */ - template , bool> = true> - constexpr T sqrt(T num) + template , bool> = true> constexpr T sqrt(T num) { - if constexpr (ccm::builtin::has_constexpr_sqrt) { return ccm::builtin::sqrt_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_sqrt) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::sqrt_gen(num); } + return ccm::builtin::sqrt_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::sqrt_gen(num); + } return ccm::rt::sqrt_rt(num); } } @@ -43,9 +47,10 @@ namespace ccm * @param num Integer number. * @return If no errors occur, square root of num (√num), is returned. */ - template , bool> = true> - constexpr double sqrt(Integer num) - { return ccm::sqrt(static_cast(num)); } + template , bool> = true> constexpr double sqrt(Integer num) + { + return ccm::sqrt(static_cast(num)); + } /** * @brief Calculates the square root of a number. @@ -53,7 +58,9 @@ namespace ccm * @return If no errors occur, square root of num (√num), is returned. */ constexpr float sqrtf(float num) - { return ccm::sqrt(num); } + { + return ccm::sqrt(num); + } /** * @brief Calculates the square root of a number. @@ -61,7 +68,9 @@ namespace ccm * @return If no errors occur, square root of num (√num), is returned. */ constexpr long double sqrtl(long double num) - { return ccm::sqrt(num); } + { + return ccm::sqrt(num); + } } // namespace ccm /// @ingroup power diff --git a/include/ccmath/math/special/assoc_laguerre.hpp b/include/ccmath/math/special/assoc_laguerre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/assoc_laguerre.hpp +++ b/include/ccmath/math/special/assoc_laguerre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/assoc_legendre.hpp b/include/ccmath/math/special/assoc_legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/assoc_legendre.hpp +++ b/include/ccmath/math/special/assoc_legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/beta.hpp b/include/ccmath/math/special/beta.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/beta.hpp +++ b/include/ccmath/math/special/beta.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/comp_ellint_1.hpp b/include/ccmath/math/special/comp_ellint_1.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/comp_ellint_1.hpp +++ b/include/ccmath/math/special/comp_ellint_1.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/comp_ellint_2.hpp b/include/ccmath/math/special/comp_ellint_2.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/comp_ellint_2.hpp +++ b/include/ccmath/math/special/comp_ellint_2.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/comp_ellint_3.hpp b/include/ccmath/math/special/comp_ellint_3.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/comp_ellint_3.hpp +++ b/include/ccmath/math/special/comp_ellint_3.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/cyl_bessel_i.hpp b/include/ccmath/math/special/cyl_bessel_i.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/cyl_bessel_i.hpp +++ b/include/ccmath/math/special/cyl_bessel_i.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/cyl_bessel_j.hpp b/include/ccmath/math/special/cyl_bessel_j.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/cyl_bessel_j.hpp +++ b/include/ccmath/math/special/cyl_bessel_j.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/cyl_bessel_k.hpp b/include/ccmath/math/special/cyl_bessel_k.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/cyl_bessel_k.hpp +++ b/include/ccmath/math/special/cyl_bessel_k.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/cyl_neumann.hpp b/include/ccmath/math/special/cyl_neumann.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/cyl_neumann.hpp +++ b/include/ccmath/math/special/cyl_neumann.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/ellint_1.hpp b/include/ccmath/math/special/ellint_1.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/ellint_1.hpp +++ b/include/ccmath/math/special/ellint_1.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/ellint_2.hpp b/include/ccmath/math/special/ellint_2.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/ellint_2.hpp +++ b/include/ccmath/math/special/ellint_2.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/ellint_3.hpp b/include/ccmath/math/special/ellint_3.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/ellint_3.hpp +++ b/include/ccmath/math/special/ellint_3.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/expint.hpp b/include/ccmath/math/special/expint.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/expint.hpp +++ b/include/ccmath/math/special/expint.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/hermite.hpp b/include/ccmath/math/special/hermite.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/hermite.hpp +++ b/include/ccmath/math/special/hermite.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/laguerre.hpp b/include/ccmath/math/special/laguerre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/laguerre.hpp +++ b/include/ccmath/math/special/laguerre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/legendre.hpp b/include/ccmath/math/special/legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/legendre.hpp +++ b/include/ccmath/math/special/legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/riemann_zeta.hpp b/include/ccmath/math/special/riemann_zeta.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/riemann_zeta.hpp +++ b/include/ccmath/math/special/riemann_zeta.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/sph_bessel.hpp b/include/ccmath/math/special/sph_bessel.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/sph_bessel.hpp +++ b/include/ccmath/math/special/sph_bessel.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/sph_legendre.hpp b/include/ccmath/math/special/sph_legendre.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/sph_legendre.hpp +++ b/include/ccmath/math/special/sph_legendre.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/special/sph_neumann.hpp b/include/ccmath/math/special/sph_neumann.hpp index 4b94f7db..495db21b 100644 --- a/include/ccmath/math/special/sph_neumann.hpp +++ b/include/ccmath/math/special/sph_neumann.hpp @@ -11,6 +11,4 @@ #pragma once namespace ccm -{ - -} // namespace ccm +{} // namespace ccm diff --git a/include/ccmath/math/trig/acos.hpp b/include/ccmath/math/trig/acos.hpp index 8f59448c..7948119a 100644 --- a/include/ccmath/math/trig/acos.hpp +++ b/include/ccmath/math/trig/acos.hpp @@ -25,20 +25,24 @@ namespace ccm * @param num Floating-point value, typically in [-1, 1]. * @return Inverse cosine of num in radians. */ - template , bool> = true> - constexpr T acos(T num) + template , bool> = true> constexpr T acos(T num) { - if constexpr (ccm::builtin::has_constexpr_acos) { return ccm::builtin::acos_ct(num); } - else if (ccm::support::is_constant_evaluated()) + if constexpr (ccm::builtin::has_constexpr_acos) { - if constexpr (std::is_same_v) { return internal::impl::acos_float(num); } - else if constexpr (std::is_same_v) { return internal::impl::acos_double(num); } - else + return ccm::builtin::acos_ct(num); + } else if (ccm::support::is_constant_evaluated()) + { + if constexpr (std::is_same_v) + { + return internal::impl::acos_float(num); + } else if constexpr (std::is_same_v) + { + return internal::impl::acos_double(num); + } else { return static_cast(internal::impl::acos_double(static_cast(num))); } - } - else + } else { return ccm::rt::acos_rt(num); } @@ -50,9 +54,10 @@ namespace ccm * @param num Input value. * @return Inverse cosine in radians as double. */ - template , bool> = true> - constexpr double acos(Integer num) - { return ccm::acos(static_cast(num)); } + template , bool> = true> constexpr double acos(Integer num) + { + return ccm::acos(static_cast(num)); + } /** * @brief Computes inverse cosine for float. @@ -60,7 +65,9 @@ namespace ccm * @return Inverse cosine in radians as float. */ constexpr float acosf(float num) - { return ccm::acos(num); } + { + return ccm::acos(num); + } /** * @brief Computes inverse cosine for long double. @@ -68,7 +75,9 @@ namespace ccm * @return Inverse cosine in radians as long double. */ constexpr long double acosl(long double num) - { return ccm::acos(num); } + { + return ccm::acos(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/asin.hpp b/include/ccmath/math/trig/asin.hpp index e959c710..91fb36f4 100644 --- a/include/ccmath/math/trig/asin.hpp +++ b/include/ccmath/math/trig/asin.hpp @@ -25,20 +25,24 @@ namespace ccm * @param num Floating-point value, typically in [-1, 1]. * @return Inverse sine of num in radians. */ - template , bool> = true> - constexpr T asin(T num) + template , bool> = true> constexpr T asin(T num) { - if constexpr (ccm::builtin::has_constexpr_asin) { return ccm::builtin::asin_ct(num); } - else if (ccm::support::is_constant_evaluated()) + if constexpr (ccm::builtin::has_constexpr_asin) { - if constexpr (std::is_same_v) { return internal::impl::asin_float(num); } - else if constexpr (std::is_same_v) { return internal::impl::asin_double(num); } - else + return ccm::builtin::asin_ct(num); + } else if (ccm::support::is_constant_evaluated()) + { + if constexpr (std::is_same_v) + { + return internal::impl::asin_float(num); + } else if constexpr (std::is_same_v) + { + return internal::impl::asin_double(num); + } else { return static_cast(internal::impl::asin_double(static_cast(num))); } - } - else + } else { return ccm::rt::asin_rt(num); } @@ -50,9 +54,10 @@ namespace ccm * @param num Input value. * @return Inverse sine in radians as double. */ - template , bool> = true> - constexpr double asin(Integer num) - { return ccm::asin(static_cast(num)); } + template , bool> = true> constexpr double asin(Integer num) + { + return ccm::asin(static_cast(num)); + } /** * @brief Computes inverse sine for float. @@ -60,7 +65,9 @@ namespace ccm * @return Inverse sine in radians as float. */ constexpr float asinf(float num) - { return ccm::asin(num); } + { + return ccm::asin(num); + } /** * @brief Computes inverse sine for long double. @@ -68,7 +75,9 @@ namespace ccm * @return Inverse sine in radians as long double. */ constexpr long double asinl(long double num) - { return ccm::asin(num); } + { + return ccm::asin(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/atan.hpp b/include/ccmath/math/trig/atan.hpp index 906ef43a..a06a56df 100644 --- a/include/ccmath/math/trig/atan.hpp +++ b/include/ccmath/math/trig/atan.hpp @@ -25,20 +25,24 @@ namespace ccm * @param num Floating-point value. * @return Inverse tangent of num in radians. */ - template , bool> = true> - constexpr T atan(T num) + template , bool> = true> constexpr T atan(T num) { - if constexpr (ccm::builtin::has_constexpr_atan) { return ccm::builtin::atan_ct(num); } - else if (ccm::support::is_constant_evaluated()) + if constexpr (ccm::builtin::has_constexpr_atan) { - if constexpr (std::is_same_v) { return internal::impl::atan_float(num); } - else if constexpr (std::is_same_v) { return internal::impl::atan_double(num); } - else + return ccm::builtin::atan_ct(num); + } else if (ccm::support::is_constant_evaluated()) + { + if constexpr (std::is_same_v) + { + return internal::impl::atan_float(num); + } else if constexpr (std::is_same_v) + { + return internal::impl::atan_double(num); + } else { return static_cast(internal::impl::atan_double(static_cast(num))); } - } - else + } else { return ccm::rt::atan_rt(num); } @@ -50,9 +54,10 @@ namespace ccm * @param num Input value. * @return Inverse tangent in radians as double. */ - template , bool> = true> - constexpr double atan(Integer num) - { return ccm::atan(static_cast(num)); } + template , bool> = true> constexpr double atan(Integer num) + { + return ccm::atan(static_cast(num)); + } /** * @brief Computes inverse tangent for float. @@ -60,7 +65,9 @@ namespace ccm * @return Inverse tangent in radians as float. */ constexpr float atanf(float num) - { return ccm::atan(num); } + { + return ccm::atan(num); + } /** * @brief Computes inverse tangent for long double. @@ -68,7 +75,9 @@ namespace ccm * @return Inverse tangent in radians as long double. */ constexpr long double atanl(long double num) - { return ccm::atan(num); } + { + return ccm::atan(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/atan2.hpp b/include/ccmath/math/trig/atan2.hpp index c88dd370..4e5fc3ea 100644 --- a/include/ccmath/math/trig/atan2.hpp +++ b/include/ccmath/math/trig/atan2.hpp @@ -26,20 +26,24 @@ namespace ccm * @param x Denominator component. * @return Angle in radians in the correct quadrant. */ - template , bool> = true> - constexpr T atan2(T y, T x) + template , bool> = true> constexpr T atan2(T y, T x) { - if constexpr (ccm::builtin::has_constexpr_atan2) { return ccm::builtin::atan2_ct(y, x); } - else if (ccm::support::is_constant_evaluated()) + if constexpr (ccm::builtin::has_constexpr_atan2) { - if constexpr (std::is_same_v) { return internal::impl::atan2_float(y, x); } - else if constexpr (std::is_same_v) { return internal::impl::atan2_double(y, x); } - else + return ccm::builtin::atan2_ct(y, x); + } else if (ccm::support::is_constant_evaluated()) + { + if constexpr (std::is_same_v) + { + return internal::impl::atan2_float(y, x); + } else if constexpr (std::is_same_v) + { + return internal::impl::atan2_double(y, x); + } else { return static_cast(internal::impl::atan2_double(static_cast(y), static_cast(x))); } - } - else + } else { return ccm::rt::atan2_rt(y, x); } @@ -52,7 +56,9 @@ namespace ccm * @return Angle in radians as float. */ constexpr float atan2f(float y, float x) - { return ccm::atan2(y, x); } + { + return ccm::atan2(y, x); + } /** * @brief Computes two-argument arc tangent for long double inputs. @@ -61,7 +67,9 @@ namespace ccm * @return Angle in radians as long double. */ constexpr long double atan2l(long double y, long double x) - { return ccm::atan2(y, x); } + { + return ccm::atan2(y, x); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/cos.hpp b/include/ccmath/math/trig/cos.hpp index 37a070f8..a2ece0f1 100644 --- a/include/ccmath/math/trig/cos.hpp +++ b/include/ccmath/math/trig/cos.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Angle in radians. * @return Cosine of num. */ - template , bool> = true> - constexpr T cos(T num) + template , bool> = true> constexpr T cos(T num) { - if constexpr (ccm::builtin::has_constexpr_cos) { return ccm::builtin::cos_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_cos) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::cos_gen(num); } + return ccm::builtin::cos_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::cos_gen(num); + } return ccm::rt::cos_rt(num); } } @@ -42,9 +46,10 @@ namespace ccm * @param num Angle in radians. * @return Cosine of num as double. */ - template , bool> = true> - constexpr double cos(Integer num) - { return ccm::cos(static_cast(num)); } + template , bool> = true> constexpr double cos(Integer num) + { + return ccm::cos(static_cast(num)); + } /** * @brief Computes the cosine of a float angle in radians. @@ -52,7 +57,9 @@ namespace ccm * @return Cosine as float. */ constexpr float cosf(float num) - { return ccm::cos(num); } + { + return ccm::cos(num); + } /** * @brief Computes the cosine of a long double angle in radians. @@ -60,7 +67,9 @@ namespace ccm * @return Cosine as long double. */ constexpr long double cosl(long double num) - { return ccm::cos(num); } + { + return ccm::cos(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/impl/inv_trig_impl.hpp b/include/ccmath/math/trig/impl/inv_trig_impl.hpp index 51f48951..f43ea9dc 100644 --- a/include/ccmath/math/trig/impl/inv_trig_impl.hpp +++ b/include/ccmath/math/trig/impl/inv_trig_impl.hpp @@ -34,8 +34,7 @@ namespace ccm::internal::impl { // Evaluates the asin(x)/x approximation polynomial from inv_trig_data (argument x^2) in // parallel Estrin form, for |x| <= 1/2. - template - constexpr T asin_eval(T xsq) noexcept + template constexpr T asin_eval(T xsq) noexcept { using namespace inv_trig_data; @@ -55,8 +54,7 @@ namespace ccm::internal::impl // Two regions. For |x| <= 1/2, acos(x) = pi/2 - asin(x) via the asin polynomial. For // |x| > 1/2, acos(|x|) = 2 asin(sqrt((1-|x|)/2)), reflected to pi - r when x < 0. - template - constexpr T acos_kernel(T x) noexcept + template constexpr T acos_kernel(T x) noexcept { using namespace inv_trig_data; using fp_bits_t = ccm::support::fp::FPBits; @@ -84,14 +82,16 @@ namespace ccm::internal::impl const T r = ccm::support::multiply_add(cv * u, r3, cv); return x < static_cast(0) ? static_cast(k_pi) - r : r; } - template - constexpr T asin_small_impl(T x) noexcept + template constexpr T asin_small_impl(T x) noexcept { const T ax = ccm::fabs(x); // asin(x) = x + x^3/6 + O(x^5). The cubic coefficient is +1/6 (this fast path previously // used -1/6, which underran by ~x^3/3 over the tiny-|x| band). - if (ax < static_cast(0x1.0p-14)) { return x + x * x * x * static_cast(0x1.5555555555555p-3); } + if (ax < static_cast(0x1.0p-14)) + { + return x + x * x * x * static_cast(0x1.5555555555555p-3); + } // Float evaluates in double then rounds once. The same-type path is in the else so it is // discarded rather than left unreachable for the float instantiation (MSVC C4702). @@ -101,8 +101,7 @@ namespace ccm::internal::impl const double xsq = dx * dx; const double r = asin_eval(xsq); return static_cast(dx + dx * xsq * r); - } - else + } else { const T xsq = x * x; const T r = asin_eval(xsq); @@ -111,14 +110,16 @@ namespace ccm::internal::impl } } // namespace inv_trig_detail - template - constexpr T acos_impl(T x) noexcept + template constexpr T acos_impl(T x) noexcept { using fp_bits_t = ccm::support::fp::FPBits; fp_bits_t bits(x); - if (CCM_UNLIKELY(bits.is_nan())) { return x; } + if (CCM_UNLIKELY(bits.is_nan())) + { + return x; + } if (CCM_UNLIKELY(bits.is_inf())) { @@ -135,35 +136,50 @@ namespace ccm::internal::impl return fp_bits_t::quiet_nan().get_val(); } - if (ax == static_cast(1)) { return x < static_cast(0) ? static_cast(ccm::numbers::pi_v) : static_cast(0); } + if (ax == static_cast(1)) + { + return x < static_cast(0) ? static_cast(ccm::numbers::pi_v) : static_cast(0); + } return inv_trig_detail::acos_kernel(x); } - template - constexpr T asin_impl(T x) noexcept + template constexpr T asin_impl(T x) noexcept { - if (x == static_cast(0)) { return x; } + if (x == static_cast(0)) + { + return x; + } - if (ccm::fabs(x) <= static_cast(0.5)) { return inv_trig_detail::asin_small_impl(x); } + if (ccm::fabs(x) <= static_cast(0.5)) + { + return inv_trig_detail::asin_small_impl(x); + } return static_cast(ccm::numbers::pi_v) / static_cast(2) - acos_impl(x); } - template - constexpr T atan_impl(T x) noexcept + template constexpr T atan_impl(T x) noexcept { // The |x|<=1 path composes sqrt, a division, and asin. In float those roundings compound to // ~5 ULP. Evaluate float atan through the double kernel and round once (double atan is within // a couple of double ULP, so the float result is effectively correctly rounded). The double // path lives in the else so it is discarded for float rather than left as unreachable code, // which MSVC rejects under /W4 (C4702). - if constexpr (std::is_same_v) { return static_cast(atan_impl(static_cast(x))); } - else + if constexpr (std::is_same_v) + { + return static_cast(atan_impl(static_cast(x))); + } else { - if (CCM_UNLIKELY(ccm::isnan(x))) { return x; } + if (CCM_UNLIKELY(ccm::isnan(x))) + { + return x; + } - if (x == static_cast(0)) { return x; } + if (x == static_cast(0)) + { + return x; + } const bool neg = x < static_cast(0); const T ax = ccm::fabs(x); @@ -183,8 +199,7 @@ namespace ccm::internal::impl { const T recip_sq = recip * recip; small = recip + recip_sq * recip * static_cast(-0x1.5555555555555p-2); - } - else + } else { small = atan_impl(recip); } @@ -207,12 +222,14 @@ namespace ccm::internal::impl // Resolves the atan2 special values (NaN, infinite, or zero operands, including signed zero) // first, then reduces the general case to atan(y/x) with a quadrant correction by the sign // of x. - template - constexpr T atan2_impl(T y, T x) noexcept + template constexpr T atan2_impl(T y, T x) noexcept { using fp_bits_t = ccm::support::fp::FPBits; - if (CCM_UNLIKELY(ccm::isnan(y) || ccm::isnan(x))) { return y + x; } + if (CCM_UNLIKELY(ccm::isnan(y) || ccm::isnan(x))) + { + return y + x; + } if (CCM_UNLIKELY(ccm::isinf(x))) { @@ -222,25 +239,49 @@ namespace ccm::internal::impl { if (CCM_UNLIKELY(ccm::isinf(y))) { - if (y > static_cast(0)) { return pi / static_cast(4); } - if (y < static_cast(0)) { return -pi / static_cast(4); } + if (y > static_cast(0)) + { + return pi / static_cast(4); + } + if (y < static_cast(0)) + { + return -pi / static_cast(4); + } return y; } - if (y > static_cast(0)) { return static_cast(0); } - if (y < static_cast(0)) { return static_cast(-0.0); } + if (y > static_cast(0)) + { + return static_cast(0); + } + if (y < static_cast(0)) + { + return static_cast(-0.0); + } return y; } if (CCM_UNLIKELY(ccm::isinf(y))) { - if (y > static_cast(0)) { return static_cast(3) * pi / static_cast(4); } - if (y < static_cast(0)) { return -static_cast(3) * pi / static_cast(4); } + if (y > static_cast(0)) + { + return static_cast(3) * pi / static_cast(4); + } + if (y < static_cast(0)) + { + return -static_cast(3) * pi / static_cast(4); + } return fp_bits_t(y).is_neg() ? -pi : pi; } - if (y > static_cast(0)) { return pi; } - if (y < static_cast(0)) { return -pi; } + if (y > static_cast(0)) + { + return pi; + } + if (y < static_cast(0)) + { + return -pi; + } return fp_bits_t(y).is_neg() ? -pi : pi; } @@ -254,8 +295,14 @@ namespace ccm::internal::impl { const T pi_over_2 = static_cast(ccm::numbers::pi_v) / static_cast(2); - if (y > static_cast(0)) { return pi_over_2; } - if (y < static_cast(0)) { return -pi_over_2; } + if (y > static_cast(0)) + { + return pi_over_2; + } + if (y < static_cast(0)) + { + return -pi_over_2; + } if (fp_bits_t(x).is_neg()) { const T pi = static_cast(ccm::numbers::pi_v); @@ -270,33 +317,52 @@ namespace ccm::internal::impl if (x < static_cast(0)) { - if (y >= static_cast(0)) { return angle + static_cast(ccm::numbers::pi_v); } + if (y >= static_cast(0)) + { + return angle + static_cast(ccm::numbers::pi_v); + } return angle - static_cast(ccm::numbers::pi_v); } return angle; } inline constexpr float acos_float(float x) noexcept - { return acos_impl(x); } + { + return acos_impl(x); + } inline constexpr double acos_double(double x) noexcept - { return acos_impl(x); } + { + return acos_impl(x); + } inline constexpr float asin_float(float x) noexcept - { return asin_impl(x); } + { + return asin_impl(x); + } inline constexpr double asin_double(double x) noexcept - { return asin_impl(x); } + { + return asin_impl(x); + } inline constexpr float atan_float(float x) noexcept - { return atan_impl(x); } + { + return atan_impl(x); + } inline constexpr double atan_double(double x) noexcept - { return atan_impl(x); } + { + return atan_impl(x); + } inline constexpr float atan2_float(float y, float x) noexcept - { return atan2_impl(y, x); } + { + return atan2_impl(y, x); + } inline constexpr double atan2_double(double y, double x) noexcept - { return atan2_impl(y, x); } + { + return atan2_impl(y, x); + } } // namespace ccm::internal::impl diff --git a/include/ccmath/math/trig/impl/sincos_double_impl.hpp b/include/ccmath/math/trig/impl/sincos_double_impl.hpp index 684e4c82..d2c45388 100644 --- a/include/ccmath/math/trig/impl/sincos_double_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_double_impl.hpp @@ -43,21 +43,25 @@ namespace ccm::internal::impl return static_cast(static_cast(k)); } - template - constexpr double sincos_eval(double x) + template constexpr double sincos_eval(double x) { FPBits xbits(x); const std::uint64_t x_abs = ccm::support::bit_cast(x) & 0x7fff'ffff'ffff'ffffULL; // sin(+/-0) = +/-0, cos(+/-0) = 1. Returning x preserves the sign of a signed zero, // which the reconstruction below would otherwise flush to +0. - if (x_abs == 0) { return IsSin ? x : 1.0; } + if (x_abs == 0) + { + return IsSin ? x : 1.0; + } double y{}; unsigned k = 0; - if (x_abs < 0x4130'0000'0000'0000ULL) { k = sincos_range_reduction_small(x, y); } - else + if (x_abs < 0x4130'0000'0000'0000ULL) + { + k = sincos_range_reduction_small(x, y); + } else { if (CCM_UNLIKELY(x_abs >= 0x7ff0'0000'0000'0000ULL)) { @@ -108,8 +112,10 @@ namespace ccm::internal::impl // The cos tail is in the else so it is discarded for the sin instantiation rather than // left as unreachable code, which MSVC rejects under /W4 (C4702). - if constexpr (IsSin) { return support::multiply_add(cos_k, s1, sin_k * c1); } - else + if constexpr (IsSin) + { + return support::multiply_add(cos_k, s1, sin_k * c1); + } else { return support::multiply_add(cos_k, c1, -sin_k * s1); } @@ -118,18 +124,26 @@ namespace ccm::internal::impl } // namespace sincos_double_detail constexpr double sin_double_impl(double x) - { return sincos_double_detail::sincos_eval(x); } + { + return sincos_double_detail::sincos_eval(x); + } constexpr double cos_double_impl(double x) - { return sincos_double_detail::sincos_eval(x); } + { + return sincos_double_detail::sincos_eval(x); + } } // namespace ccm::internal::impl namespace ccm::internal { constexpr double sin_double(double num) noexcept - { return impl::sin_double_impl(num); } + { + return impl::sin_double_impl(num); + } constexpr double cos_double(double num) noexcept - { return impl::cos_double_impl(num); } + { + return impl::cos_double_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/trig/impl/sincos_float_impl.hpp b/include/ccmath/math/trig/impl/sincos_float_impl.hpp index fc1d5b4d..c6574f32 100644 --- a/include/ccmath/math/trig/impl/sincos_float_impl.hpp +++ b/include/ccmath/math/trig/impl/sincos_float_impl.hpp @@ -43,20 +43,24 @@ namespace ccm::internal::impl return static_cast(static_cast(k)); } - template - constexpr float sincosf_eval(float x) + template constexpr float sincosf_eval(float x) { FPBits xbits(x); const std::uint32_t x_abs = ccm::support::bit_cast(x) & 0x7fff'ffffU; // sin(+/-0) = +/-0, cos(+/-0) = 1. Returning x preserves the sign of a signed zero. - if (x_abs == 0) { return IsSin ? x : 1.0f; } + if (x_abs == 0) + { + return IsSin ? x : 1.0f; + } float y{}; unsigned k = 0; - if (x_abs < 0x4880'0000U) { k = sincosf_range_reduction_small(x, y); } - else + if (x_abs < 0x4880'0000U) + { + k = sincosf_range_reduction_small(x, y); + } else { if (CCM_UNLIKELY(x_abs >= 0x7f80'0000U)) { @@ -99,8 +103,10 @@ namespace ccm::internal::impl // The cos tail is in the else so it is discarded for the sin instantiation rather than // left as unreachable code, which MSVC rejects under /W4 (C4702). - if constexpr (IsSin) { return support::multiply_add(cos_k, s1, sin_k * c1); } - else + if constexpr (IsSin) + { + return support::multiply_add(cos_k, s1, sin_k * c1); + } else { return support::multiply_add(cos_k, c1, -sin_k * s1); } @@ -109,18 +115,26 @@ namespace ccm::internal::impl } // namespace sincos_float_detail constexpr float sin_float_impl(float x) - { return sincos_float_detail::sincosf_eval(x); } + { + return sincos_float_detail::sincosf_eval(x); + } constexpr float cos_float_impl(float x) - { return sincos_float_detail::sincosf_eval(x); } + { + return sincos_float_detail::sincosf_eval(x); + } } // namespace ccm::internal::impl namespace ccm::internal { constexpr float sin_float(float num) noexcept - { return impl::sin_float_impl(num); } + { + return impl::sin_float_impl(num); + } constexpr float cos_float(float num) noexcept - { return impl::cos_float_impl(num); } + { + return impl::cos_float_impl(num); + } } // namespace ccm::internal diff --git a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp index 3ec7c50e..b8273c2c 100644 --- a/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp +++ b/include/ccmath/math/trig/impl/sincos_payne_hanek.hpp @@ -65,7 +65,7 @@ namespace ccm::internal::impl::sincos_ph // Add a 64-bit word into the 256-bit accumulator (acc[0] low .. acc[3] high) at limb word, // propagating carries. The accumulator wraps mod 2^256, which is exactly the mod-16 we want. - constexpr void add_at_word(std::array& acc, std::uint64_t val, int word) noexcept + constexpr void add_at_word(std::array & acc, std::uint64_t val, int word) noexcept { while (val != 0 && word < 4) { @@ -78,28 +78,43 @@ namespace ccm::internal::impl::sincos_ph // Add (w << bitpos) into the 256-bit accumulator, bitpos may be negative (low bits are dropped, // they fall below the accumulator's 2^-252 resolution). - constexpr void add_word_at(std::array& acc, std::uint64_t w, long bitpos) noexcept + constexpr void add_word_at(std::array & acc, std::uint64_t w, long bitpos) noexcept { - if (w == 0) { return; } + if (w == 0) + { + return; + } if (bitpos < 0) { const long s = -bitpos; - if (s >= 64) { return; } + if (s >= 64) + { + return; + } w >>= static_cast(s); bitpos = 0; - if (w == 0) { return; } + if (w == 0) + { + return; + } + } + if (bitpos >= 256) + { + return; } - if (bitpos >= 256) { return; } const int word = static_cast(bitpos >> 6); const int off = static_cast(bitpos & 63); const std::uint64_t low = w << off; const std::uint64_t high = (off != 0) ? (w >> (64 - off)) : 0ULL; add_at_word(acc, low, word); - if (high != 0 && word + 1 < 4) { add_at_word(acc, high, word + 1); } + if (high != 0 && word + 1 < 4) + { + add_at_word(acc, high, word + 1); + } } // Returns k (low bits of the pi/8 multiple) and sets y to the reduced argument in [-pi/16, pi/16]. - constexpr unsigned payne_hanek_reduce(double x, double& y) noexcept + constexpr unsigned payne_hanek_reduce(double x, double & y) noexcept { using FPBits = support::fp::FPBits; @@ -117,7 +132,10 @@ namespace ccm::internal::impl::sincos_ph for (int i = 0; i < 32; ++i) { const long shift = binary_exp - 64L * (i + 1) + 252; - if (shift <= -128 || shift >= 256) { continue; } + if (shift <= -128 || shift >= 256) + { + continue; + } const U128 p = mul64(mantissa, FRAC_8_OVER_PI[static_cast(i)]); add_word_at(acc, p.lo, shift); add_word_at(acc, p.hi, shift + 64); diff --git a/include/ccmath/math/trig/sin.hpp b/include/ccmath/math/trig/sin.hpp index ffd50ed2..d6373699 100644 --- a/include/ccmath/math/trig/sin.hpp +++ b/include/ccmath/math/trig/sin.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Angle in radians. * @return Sine of num. */ - template , bool> = true> - constexpr T sin(T num) + template , bool> = true> constexpr T sin(T num) { - if constexpr (ccm::builtin::has_constexpr_sin) { return ccm::builtin::sin_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_sin) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::sin_gen(num); } + return ccm::builtin::sin_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::sin_gen(num); + } return ccm::rt::sin_rt(num); } } @@ -42,9 +46,10 @@ namespace ccm * @param num Angle in radians. * @return Sine of num as double. */ - template , bool> = true> - constexpr double sin(Integer num) - { return ccm::sin(static_cast(num)); } + template , bool> = true> constexpr double sin(Integer num) + { + return ccm::sin(static_cast(num)); + } /** * @brief Computes the sine of a float angle in radians. @@ -52,7 +57,9 @@ namespace ccm * @return Sine as float. */ constexpr float sinf(float num) - { return ccm::sin(num); } + { + return ccm::sin(num); + } /** * @brief Computes the sine of a long double angle in radians. @@ -60,7 +67,9 @@ namespace ccm * @return Sine as long double. */ constexpr long double sinl(long double num) - { return ccm::sin(num); } + { + return ccm::sin(num); + } } // namespace ccm /// @ingroup trig diff --git a/include/ccmath/math/trig/tan.hpp b/include/ccmath/math/trig/tan.hpp index 63231089..e80d5f29 100644 --- a/include/ccmath/math/trig/tan.hpp +++ b/include/ccmath/math/trig/tan.hpp @@ -25,13 +25,17 @@ namespace ccm * @param num Angle in radians. * @return Tangent of num. */ - template , bool> = true> - constexpr T tan(T num) + template , bool> = true> constexpr T tan(T num) { - if constexpr (ccm::builtin::has_constexpr_tan) { return ccm::builtin::tan_ct(num); } - else + if constexpr (ccm::builtin::has_constexpr_tan) { - if (ccm::support::is_constant_evaluated()) { return ccm::gen::tan_gen(num); } + return ccm::builtin::tan_ct(num); + } else + { + if (ccm::support::is_constant_evaluated()) + { + return ccm::gen::tan_gen(num); + } return ccm::rt::tan_rt(num); } } @@ -42,9 +46,10 @@ namespace ccm * @param num Angle in radians. * @return Tangent of num as double. */ - template , bool> = true> - constexpr double tan(Integer num) - { return ccm::tan(static_cast(num)); } + template , bool> = true> constexpr double tan(Integer num) + { + return ccm::tan(static_cast(num)); + } /** * @brief Computes the tangent of a float angle in radians. @@ -52,7 +57,9 @@ namespace ccm * @return Tangent as float. */ constexpr float tanf(float num) - { return ccm::tan(num); } + { + return ccm::tan(num); + } /** * @brief Computes the tangent of a long double angle in radians. @@ -60,7 +67,9 @@ namespace ccm * @return Tangent as long double. */ constexpr long double tanl(long double num) - { return ccm::tan(num); } + { + return ccm::tan(num); + } } // namespace ccm /// @ingroup trig diff --git a/showcase/accuracy/showcase_accuracy.cpp b/showcase/accuracy/showcase_accuracy.cpp index aacac344..a11bee31 100644 --- a/showcase/accuracy/showcase_accuracy.cpp +++ b/showcase/accuracy/showcase_accuracy.cpp @@ -13,15 +13,24 @@ namespace for (int i = 1; i < argc; ++i) { const std::string_view arg = argv[i]; - if (arg.rfind(prefix, 0) == 0) { return std::string(arg.substr(prefix.size())); } + if (arg.rfind(prefix, 0) == 0) + { + return std::string(arg.substr(prefix.size())); + } } return std::nullopt; } std::optional parse_function(std::string_view raw) { - if (raw == "sqrt") { return ccm::showcase::accuracy::unary_function::sqrt_fn; } - if (raw == "sin") { return ccm::showcase::accuracy::unary_function::sin_fn; } + if (raw == "sqrt") + { + return ccm::showcase::accuracy::unary_function::sqrt_fn; + } + if (raw == "sin") + { + return ccm::showcase::accuracy::unary_function::sin_fn; + } return std::nullopt; } } // namespace @@ -52,10 +61,12 @@ int main(int argc, char ** argv) int exit_code = 0; - const auto run_ccmath = [&](ccm::showcase::ccmath_path::path path) - { + const auto run_ccmath = [&](ccm::showcase::ccmath_path::path path) { std::optional path_log; - if (log_output.has_value()) { path_log = *log_output + "." + std::string(ccm::showcase::ccmath_path::name(path)) + ".json"; } + if (log_output.has_value()) + { + path_log = *log_output + "." + std::string(ccm::showcase::ccmath_path::name(path)) + ".json"; + } exit_code |= ccm::showcase::accuracy::run_ccmath_path_campaign(*fn, path, precision, max_ulp, path_log); }; @@ -68,9 +79,10 @@ int main(int argc, char ** argv) run_ccmath(ccm::showcase::ccmath_path::path::runtime_rt); run_ccmath(ccm::showcase::ccmath_path::path::runtime_simd); run_ccmath(ccm::showcase::ccmath_path::path::runtime_builtin); - } - else if (const auto path = ccm::showcase::ccmath_path::parse(path_raw)) { run_ccmath(*path); } - else + } else if (const auto path = ccm::showcase::ccmath_path::parse(path_raw)) + { + run_ccmath(*path); + } else { std::cerr << "unknown path: " << path_raw << '\n'; return 2; @@ -80,7 +92,10 @@ int main(int argc, char ** argv) if (backend_raw == "all" || backend_raw == "gcem") { std::optional gcem_log; - if (log_output.has_value()) { gcem_log = *log_output + ".gcem.json"; } + if (log_output.has_value()) + { + gcem_log = *log_output + ".gcem.json"; + } exit_code |= ccm::showcase::accuracy::run_gcem_campaign(*fn, precision, max_ulp, gcem_log); } diff --git a/showcase/common/accuracy/corpus.hpp b/showcase/common/accuracy/corpus.hpp index 11b124dd..a65b62f8 100644 --- a/showcase/common/accuracy/corpus.hpp +++ b/showcase/common/accuracy/corpus.hpp @@ -10,7 +10,10 @@ namespace ccm::showcase::accuracy { std::vector values; values.reserve(ccm::test::samples::kSqrtDouble.size() + 8); - for (double v : ccm::test::samples::kSqrtDouble) { values.push_back(v); } + for (double v : ccm::test::samples::kSqrtDouble) + { + values.push_back(v); + } values.push_back(1e-200); values.push_back(1e200); values.push_back(0x1.fffffffffffffp-1022); @@ -22,7 +25,10 @@ namespace ccm::showcase::accuracy { std::vector values; values.reserve(ccm::test::samples::kTrigDouble.size() + 4); - for (double v : ccm::test::samples::kTrigDouble) { values.push_back(v); } + for (double v : ccm::test::samples::kTrigDouble) + { + values.push_back(v); + } values.push_back(100.0); values.push_back(-100.0); values.push_back(1e-8); diff --git a/showcase/common/accuracy/unary_mpfr_runner.hpp b/showcase/common/accuracy/unary_mpfr_runner.hpp index 504bbce4..23d32076 100644 --- a/showcase/common/accuracy/unary_mpfr_runner.hpp +++ b/showcase/common/accuracy/unary_mpfr_runner.hpp @@ -29,7 +29,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: return "sqrt"; - case unary_function::sin_fn: return "sin"; + case unary_function::sin_fn : return "sin"; } return "unknown"; } @@ -39,7 +39,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: return sqrt_corpus(); - case unary_function::sin_fn: return sin_corpus(); + case unary_function::sin_fn : return sin_corpus(); } return {}; } @@ -49,7 +49,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: return std::sqrt(x); - case unary_function::sin_fn: return std::sin(x); + case unary_function::sin_fn : return std::sin(x); } return 0.0; } @@ -64,7 +64,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: mpfr_sqrt(result, value, rounding); break; - case unary_function::sin_fn: mpfr_sin(result, value, rounding); break; + case unary_function::sin_fn : mpfr_sin(result, value, rounding); break; } const double out = mpfr_get_d(result, rounding); mpfr_clear(value); @@ -77,7 +77,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: return ccm::showcase::ccmath_path::sqrt_is_supported(path); - case unary_function::sin_fn: return ccm::showcase::ccmath_path::sin_is_supported(path); + case unary_function::sin_fn : return ccm::showcase::ccmath_path::sin_is_supported(path); } return { false, "unknown function" }; } @@ -87,7 +87,7 @@ namespace ccm::showcase::accuracy switch (fn) { case unary_function::sqrt_fn: return ccm::showcase::ccmath_path::invoke_sqrt(path, x); - case unary_function::sin_fn: return ccm::showcase::ccmath_path::invoke_sin(path, x); + case unary_function::sin_fn : return ccm::showcase::ccmath_path::invoke_sin(path, x); } return 0.0; } @@ -101,7 +101,7 @@ namespace ccm::showcase::accuracy }; inline int run_ccmath_path_campaign( - unary_function fn, ccm::showcase::ccmath_path::path path, mpfr_prec_t precision, std::uint64_t max_ulp, const std::optional& output_path) + unary_function fn, ccm::showcase::ccmath_path::path path, mpfr_prec_t precision, std::uint64_t max_ulp, const std::optional & output_path) { const auto support = path_is_supported(fn, path); if (!support.supported) @@ -143,7 +143,7 @@ namespace ccm::showcase::accuracy out << "[\n"; for (std::size_t i = 0; i < events.size(); ++i) { - const auto& event = events[i]; + const auto & event = events[i]; out << " {\"path\":\"" << event.path << "\",\"input_bits\":\"" << event.input_bits << "\",\"actual_bits\":\"" << event.actual_bits << "\",\"expected_bits\":\"" << event.expected_bits << "\",\"ulp_distance\":" << event.ulp_distance << ",\"event_kind\":\"" << event.event_kind << "\"}" << (i + 1 < events.size() ? "," : "") << '\n'; @@ -154,7 +154,7 @@ namespace ccm::showcase::accuracy return summary.failure_count > 0 ? 1 : 0; } - inline int run_gcem_campaign(unary_function fn, mpfr_prec_t precision, std::uint64_t max_ulp, const std::optional& output_path) + inline int run_gcem_campaign(unary_function fn, mpfr_prec_t precision, std::uint64_t max_ulp, const std::optional & output_path) { #if !defined(CCMATH_SHOWCASE_HAS_GCEM) std::cout << "skip gcem accuracy: GCEM backend disabled\n"; @@ -172,13 +172,12 @@ namespace ccm::showcase::accuracy test_case, fn_label, "gcem", - [fn](double value) - { + [fn](double value) { volatile double x = value; switch (fn) { case unary_function::sqrt_fn: return gcem::sqrt(x); - case unary_function::sin_fn: return gcem::sin(x); + case unary_function::sin_fn : return gcem::sin(x); } return 0.0; }, @@ -202,7 +201,7 @@ namespace ccm::showcase::accuracy out << "[\n"; for (std::size_t i = 0; i < events.size(); ++i) { - const auto& event = events[i]; + const auto & event = events[i]; out << " {\"path\":\"gcem\",\"input_bits\":\"" << event.input_bits << "\",\"actual_bits\":\"" << event.actual_bits << "\",\"expected_bits\":\"" << event.expected_bits << "\",\"ulp_distance\":" << event.ulp_distance << ",\"event_kind\":\"" << event.event_kind << "\"}" << (i + 1 < events.size() ? "," : "") << '\n'; diff --git a/showcase/common/backend_traits.hpp b/showcase/common/backend_traits.hpp index fb843a1e..f5845112 100644 --- a/showcase/common/backend_traits.hpp +++ b/showcase/common/backend_traits.hpp @@ -5,15 +5,13 @@ namespace ccm::showcase { - template - concept RuntimeUnaryBackend = requires(double x) + template concept RuntimeUnaryBackend = requires(double x) { { Backend::label() }->std::convertible_to; { Backend::eval(x) }->std::same_as; }; - template - concept ConstexprUnaryBackend = requires(double x) + template concept ConstexprUnaryBackend = requires(double x) { { Backend::label() }->std::convertible_to; { Backend::consteval_probe(x) }->std::same_as; diff --git a/showcase/common/backends/ccmath_backend.hpp b/showcase/common/backends/ccmath_backend.hpp index ae1c6ac0..79938637 100644 --- a/showcase/common/backends/ccmath_backend.hpp +++ b/showcase/common/backends/ccmath_backend.hpp @@ -6,71 +6,131 @@ namespace ccm::showcase::backends { struct ccmath_public { - static constexpr std::string_view label() { return "ccmath/public_default"; } - - static double eval(double x) { return ccmath_path::invoke_sqrt(ccmath_path::path::public_default, x); } + static constexpr std::string_view label() + { + return "ccmath/public_default"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sqrt(ccmath_path::path::public_default, x); + } }; struct ccmath_generic_gen { - static constexpr std::string_view label() { return "ccmath/generic_gen"; } - - static double eval(double x) { return ccmath_path::invoke_sqrt(ccmath_path::path::generic_gen, x); } + static constexpr std::string_view label() + { + return "ccmath/generic_gen"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sqrt(ccmath_path::path::generic_gen, x); + } }; struct ccmath_runtime_rt { - static constexpr std::string_view label() { return "ccmath/runtime_rt"; } - - static double eval(double x) { return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_rt, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_rt"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_rt, x); + } }; struct ccmath_runtime_simd { - static constexpr std::string_view label() { return "ccmath/runtime_simd"; } - - static double eval(double x) { return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_simd, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_simd"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_simd, x); + } }; struct ccmath_runtime_builtin { - static constexpr std::string_view label() { return "ccmath/runtime_builtin"; } - - static double eval(double x) { return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_builtin, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_builtin"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sqrt(ccmath_path::path::runtime_builtin, x); + } }; struct ccmath_sin_public { - static constexpr std::string_view label() { return "ccmath/public_default"; } - - static double eval(double x) { return ccmath_path::invoke_sin(ccmath_path::path::public_default, x); } + static constexpr std::string_view label() + { + return "ccmath/public_default"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sin(ccmath_path::path::public_default, x); + } }; struct ccmath_sin_generic_gen { - static constexpr std::string_view label() { return "ccmath/generic_gen"; } - - static double eval(double x) { return ccmath_path::invoke_sin(ccmath_path::path::generic_gen, x); } + static constexpr std::string_view label() + { + return "ccmath/generic_gen"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sin(ccmath_path::path::generic_gen, x); + } }; struct ccmath_sin_runtime_rt { - static constexpr std::string_view label() { return "ccmath/runtime_rt"; } - - static double eval(double x) { return ccmath_path::invoke_sin(ccmath_path::path::runtime_rt, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_rt"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sin(ccmath_path::path::runtime_rt, x); + } }; struct ccmath_sin_runtime_simd { - static constexpr std::string_view label() { return "ccmath/runtime_simd"; } - - static double eval(double x) { return ccmath_path::invoke_sin(ccmath_path::path::runtime_simd, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_simd"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sin(ccmath_path::path::runtime_simd, x); + } }; struct ccmath_sin_runtime_builtin { - static constexpr std::string_view label() { return "ccmath/runtime_builtin"; } - - static double eval(double x) { return ccmath_path::invoke_sin(ccmath_path::path::runtime_builtin, x); } + static constexpr std::string_view label() + { + return "ccmath/runtime_builtin"; + } + + static double eval(double x) + { + return ccmath_path::invoke_sin(ccmath_path::path::runtime_builtin, x); + } }; } // namespace ccm::showcase::backends diff --git a/showcase/common/backends/gcem_backend.hpp b/showcase/common/backends/gcem_backend.hpp index 5ddd93a7..5daede5c 100644 --- a/showcase/common/backends/gcem_backend.hpp +++ b/showcase/common/backends/gcem_backend.hpp @@ -8,7 +8,10 @@ namespace ccm::showcase::backends { struct gcem_lib { - static constexpr std::string_view label() { return "gcem"; } + static constexpr std::string_view label() + { + return "gcem"; + } static double eval_sqrt(double x) { diff --git a/showcase/common/backends/std_backend.hpp b/showcase/common/backends/std_backend.hpp index ce685b22..14978339 100644 --- a/showcase/common/backends/std_backend.hpp +++ b/showcase/common/backends/std_backend.hpp @@ -8,10 +8,19 @@ namespace ccm::showcase::backends { struct std_lib { - static constexpr std::string_view label() { return "std"; } + static constexpr std::string_view label() + { + return "std"; + } - static double eval_sqrt(double x) { return std::sqrt(ccm::test::runtime_value(x)); } + static double eval_sqrt(double x) + { + return std::sqrt(ccm::test::runtime_value(x)); + } - static double eval_sin(double x) { return std::sin(ccm::test::runtime_value(x)); } + static double eval_sin(double x) + { + return std::sin(ccm::test::runtime_value(x)); + } }; } // namespace ccm::showcase::backends diff --git a/showcase/common/bench/bench_common.hpp b/showcase/common/bench/bench_common.hpp index 065bce8e..84f4bafb 100644 --- a/showcase/common/bench/bench_common.hpp +++ b/showcase/common/bench/bench_common.hpp @@ -8,14 +8,16 @@ namespace ccm::showcase::bench { - template - inline void register_random_doubles(benchmark::State & state, EvalFn eval_fn) + template inline void register_random_doubles(benchmark::State & state, EvalFn eval_fn) { ccm::bench::Randomizer ran; const auto values = ran.generateRandomDoubles(static_cast(state.range(0))); for ([[maybe_unused]] auto _ : state) { - for (double x : values) { benchmark::DoNotOptimize(eval_fn(x)); } + for (double x : values) + { + benchmark::DoNotOptimize(eval_fn(x)); + } } state.SetComplexityN(state.range(0)); } diff --git a/showcase/common/ccmath_paths.hpp b/showcase/common/ccmath_paths.hpp index e0b4a0ad..ac38cc86 100644 --- a/showcase/common/ccmath_paths.hpp +++ b/showcase/common/ccmath_paths.hpp @@ -31,10 +31,10 @@ namespace ccm::showcase::ccmath_path { switch (value) { - case path::public_default: return "public_default"; - case path::generic_gen: return "generic_gen"; - case path::runtime_rt: return "runtime_rt"; - case path::runtime_simd: return "runtime_simd"; + case path::public_default : return "public_default"; + case path::generic_gen : return "generic_gen"; + case path::runtime_rt : return "runtime_rt"; + case path::runtime_simd : return "runtime_simd"; case path::runtime_builtin: return "runtime_builtin"; } return "unknown"; @@ -42,11 +42,26 @@ namespace ccm::showcase::ccmath_path constexpr std::optional parse(std::string_view raw) noexcept { - if (raw == "public_default" || raw == "public") { return path::public_default; } - if (raw == "generic_gen" || raw == "generic_runtime" || raw == "generic") { return path::generic_gen; } - if (raw == "runtime_rt" || raw == "rt") { return path::runtime_rt; } - if (raw == "runtime_simd" || raw == "simd") { return path::runtime_simd; } - if (raw == "runtime_builtin" || raw == "builtin") { return path::runtime_builtin; } + if (raw == "public_default" || raw == "public") + { + return path::public_default; + } + if (raw == "generic_gen" || raw == "generic_runtime" || raw == "generic") + { + return path::generic_gen; + } + if (raw == "runtime_rt" || raw == "rt") + { + return path::runtime_rt; + } + if (raw == "runtime_simd" || raw == "simd") + { + return path::runtime_simd; + } + if (raw == "runtime_builtin" || raw == "builtin") + { + return path::runtime_builtin; + } return std::nullopt; } @@ -62,7 +77,7 @@ namespace ccm::showcase::ccmath_path { case path::public_default: case path::generic_gen: - case path::runtime_rt: return { true, {} }; + case path::runtime_rt : return { true, {} }; case path::runtime_simd: #if defined(CCMATH_HAS_SIMD) return { true, {} }; @@ -85,7 +100,7 @@ namespace ccm::showcase::ccmath_path { case path::public_default: case path::generic_gen: - case path::runtime_rt: return { true, {} }; + case path::runtime_rt : return { true, {} }; case path::runtime_simd: #if defined(CCMATH_HAS_SIMD) && !(CCM_HAS_BUILTIN(__builtin_sin) || defined(__builtin_sin)) return { true, {} }; @@ -108,8 +123,8 @@ namespace ccm::showcase::ccmath_path switch (value) { case path::public_default: return ccm::sqrt(input); - case path::generic_gen: return ccm::gen::sqrt_gen(input); - case path::runtime_rt: return ccm::rt::sqrt_rt(input); + case path::generic_gen : return ccm::gen::sqrt_gen(input); + case path::runtime_rt : return ccm::rt::sqrt_rt(input); case path::runtime_simd: #if defined(CCMATH_HAS_SIMD) return ccm::rt::simd_impl::sqrt_simd_impl(input); @@ -132,8 +147,8 @@ namespace ccm::showcase::ccmath_path switch (value) { case path::public_default: return ccm::sin(input); - case path::generic_gen: return ccm::gen::sin_gen(input); - case path::runtime_rt: return ccm::rt::sin_rt(input); + case path::generic_gen : return ccm::gen::sin_gen(input); + case path::runtime_rt : return ccm::rt::sin_rt(input); case path::runtime_simd: #if defined(CCMATH_HAS_SIMD) && !(CCM_HAS_BUILTIN(__builtin_sin) || defined(__builtin_sin)) return ccm::rt::simd_impl::unary_via_scalar_abi(input, [](double v) { return ccm::gen::sin_gen(v); }); diff --git a/showcase/performance/sin.bench.cpp b/showcase/performance/sin.bench.cpp index 7a727bd8..b1af07e7 100644 --- a/showcase/performance/sin.bench.cpp +++ b/showcase/performance/sin.bench.cpp @@ -5,26 +5,40 @@ namespace { static void BM_sin_std(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::std_lib::eval_sin); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::std_lib::eval_sin); + } static void BM_sin_ccmath_public(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_public::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_public::eval); + } static void BM_sin_ccmath_generic(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_generic_gen::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_generic_gen::eval); + } static void BM_sin_ccmath_rt(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_rt::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_rt::eval); + } static void BM_sin_ccmath_simd(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_simd::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_simd::eval); + } static void BM_sin_ccmath_builtin(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_builtin::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_sin_runtime_builtin::eval); + } #if defined(CCMATH_SHOWCASE_HAS_GCEM) static void BM_sin_gcem(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::gcem_lib::eval_sin); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::gcem_lib::eval_sin); + } #endif } // namespace diff --git a/showcase/performance/sqrt.bench.cpp b/showcase/performance/sqrt.bench.cpp index 03dc3c06..0e6326ea 100644 --- a/showcase/performance/sqrt.bench.cpp +++ b/showcase/performance/sqrt.bench.cpp @@ -5,26 +5,40 @@ namespace { static void BM_sqrt_std(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::std_lib::eval_sqrt); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::std_lib::eval_sqrt); + } static void BM_sqrt_ccmath_public(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_public::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_public::eval); + } static void BM_sqrt_ccmath_generic(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_generic_gen::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_generic_gen::eval); + } static void BM_sqrt_ccmath_rt(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_rt::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_rt::eval); + } static void BM_sqrt_ccmath_simd(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_simd::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_simd::eval); + } static void BM_sqrt_ccmath_builtin(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_builtin::eval); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::ccmath_runtime_builtin::eval); + } #if defined(CCMATH_SHOWCASE_HAS_GCEM) static void BM_sqrt_gcem(benchmark::State & state) - { ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::gcem_lib::eval_sqrt); } + { + ccm::showcase::bench::register_random_doubles(state, ccm::showcase::backends::gcem_lib::eval_sqrt); + } #endif } // namespace diff --git a/tests/conformance/fenv_exception_test.cpp b/tests/conformance/fenv_exception_test.cpp index 9dc3b28a..d3efc2f6 100644 --- a/tests/conformance/fenv_exception_test.cpp +++ b/tests/conformance/fenv_exception_test.cpp @@ -29,9 +29,10 @@ namespace { using ccm::test::runtime_value; - template - void consume(T value) - { [[maybe_unused]] volatile T sink = value; } + template void consume(T value) + { + [[maybe_unused]] volatile T sink = value; + } } // namespace TEST(CcmathFenvExceptionTests, DomainErrorsRaiseInvalidLikeStd) @@ -76,19 +77,16 @@ TEST(CcmathFenvExceptionTests, DomainErrorsRaiseInvalidLikeStd) TEST(CcmathFenvExceptionTests, DomainErrorsIndependentOfRoundingMode) { CCMATH_SKIP_MSVC_FENV_EXCEPTIONS(); - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ForceRoundingMode force(mode); - ASSERT_TRUE(force); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::sqrt(runtime_value(-1.0))); }, [] { consume(std::sqrt(runtime_value(-1.0))); }, FE_INVALID); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::pow(runtime_value(-1.0), runtime_value(0.5))); }, - [] { consume(std::pow(runtime_value(-1.0), runtime_value(0.5))); }, - FE_INVALID); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::gen::pow_gen(runtime_value(-1.0), runtime_value(0.5))); }, - [] { consume(std::pow(runtime_value(-1.0), runtime_value(0.5))); }, - FE_INVALID); - }); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ForceRoundingMode force(mode); + ASSERT_TRUE(force); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::sqrt(runtime_value(-1.0))); }, [] { consume(std::sqrt(runtime_value(-1.0))); }, FE_INVALID); + ccm::test::ExpectFenvFlagsMatchStd( + [] { consume(ccm::pow(runtime_value(-1.0), runtime_value(0.5))); }, [] { consume(std::pow(runtime_value(-1.0), runtime_value(0.5))); }, FE_INVALID); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::gen::pow_gen(runtime_value(-1.0), runtime_value(0.5))); }, + [] { consume(std::pow(runtime_value(-1.0), runtime_value(0.5))); }, + FE_INVALID); + }); } TEST(CcmathFenvExceptionTests, PoleErrorsRaiseDivByZeroLikeStd) @@ -112,18 +110,16 @@ TEST(CcmathFenvExceptionTests, PoleErrorsRaiseDivByZeroLikeStd) TEST(CcmathFenvExceptionTests, PoleErrorsIndependentOfRoundingMode) { CCMATH_SKIP_MSVC_FENV_EXCEPTIONS(); - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ForceRoundingMode force(mode); - ASSERT_TRUE(force); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::pow(runtime_value(0.0), runtime_value(-1.0))); }, - [] { consume(std::pow(runtime_value(0.0), runtime_value(-1.0))); }, - FE_DIVBYZERO); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::gen::pow_gen(runtime_value(0.0), runtime_value(-1.0))); }, - [] { consume(std::pow(runtime_value(0.0), runtime_value(-1.0))); }, - FE_DIVBYZERO); - }); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ForceRoundingMode force(mode); + ASSERT_TRUE(force); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::pow(runtime_value(0.0), runtime_value(-1.0))); }, + [] { consume(std::pow(runtime_value(0.0), runtime_value(-1.0))); }, + FE_DIVBYZERO); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::gen::pow_gen(runtime_value(0.0), runtime_value(-1.0))); }, + [] { consume(std::pow(runtime_value(0.0), runtime_value(-1.0))); }, + FE_DIVBYZERO); + }); } TEST(CcmathFenvExceptionTests, RangeErrorsRaiseOverflowOrUnderflowLikeStd) diff --git a/tests/conformance/fenv_probe_test.cpp b/tests/conformance/fenv_probe_test.cpp index 6b8658ae..6110262d 100644 --- a/tests/conformance/fenv_probe_test.cpp +++ b/tests/conformance/fenv_probe_test.cpp @@ -15,7 +15,10 @@ TEST(CcmathFenvProbeTests, StdFenvSupportsAllRoundingModes) { - if (!ccm::test::FenvIsSupported()) { GTEST_SKIP() << "platform does not support all IEEE rounding modes"; } + if (!ccm::test::FenvIsSupported()) + { + GTEST_SKIP() << "platform does not support all IEEE rounding modes"; + } for (int mode : ccm::test::kStdRoundingModes) { @@ -28,7 +31,10 @@ TEST(CcmathFenvProbeTests, StdFenvSupportsAllRoundingModes) TEST(CcmathFenvProbeTests, CcmGetRoundingModeTracksStdFenv) { - if (!ccm::test::FenvIsSupported()) { GTEST_SKIP() << "platform does not support all IEEE rounding modes"; } + if (!ccm::test::FenvIsSupported()) + { + GTEST_SKIP() << "platform does not support all IEEE rounding modes"; + } for (int mode : ccm::test::kStdRoundingModes) { diff --git a/tests/conformance/fma_rounding_test.cpp b/tests/conformance/fma_rounding_test.cpp index 71279139..75a944b6 100644 --- a/tests/conformance/fma_rounding_test.cpp +++ b/tests/conformance/fma_rounding_test.cpp @@ -25,8 +25,9 @@ namespace TEST(CcmathFmaRoundingConformanceTests, FmafMatchesStdAllModesStructuredCases) { const auto ccm_fmaf = [](float x, float y, float z) { return ccm::fmaf(runtime_value(x), runtime_value(y), runtime_value(z)); }; - const auto std_fmaf = [](float x, float y, float z) - { return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); }; + const auto std_fmaf = [](float x, float y, float z) { + return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); + }; ccm::test::ExpectFpTernaryOverMatchesStdAllModes(ccm::test::samples::kFmaAllModesFloatCases, ccm_fmaf, std_fmaf); } @@ -34,8 +35,9 @@ TEST(CcmathFmaRoundingConformanceTests, FmafMatchesStdAllModesStructuredCases) TEST(CcmathFmaRoundingConformanceTests, FmaMatchesStdAllModesStructuredCases) { const auto ccm_fma = [](double x, double y, double z) { return ccm::fma(runtime_value(x), runtime_value(y), runtime_value(z)); }; - const auto std_fma = [](double x, double y, double z) - { return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); }; + const auto std_fma = [](double x, double y, double z) { + return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); + }; ccm::test::ExpectFpTernaryOverMatchesStdAllModes(ccm::test::samples::kFmaAllModesDoubleCases, ccm_fma, std_fma); } @@ -43,11 +45,13 @@ TEST(CcmathFmaRoundingConformanceTests, FmaMatchesStdAllModesStructuredCases) TEST(CcmathFmaRoundingConformanceTests, ExactCancellationZeroSignMatchesStdAllModes) { const auto ccm_float = [](float x, float y, float z) { return ccm::fmaf(runtime_value(x), runtime_value(y), runtime_value(z)); }; - const auto std_float = [](float x, float y, float z) - { return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); }; + const auto std_float = [](float x, float y, float z) { + return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); + }; const auto ccm_double = [](double x, double y, double z) { return ccm::fma(runtime_value(x), runtime_value(y), runtime_value(z)); }; - const auto std_double = [](double x, double y, double z) - { return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); }; + const auto std_double = [](double x, double y, double z) { + return static_cast(std::fma)(runtime_value(x), runtime_value(y), runtime_value(z)); + }; ccm::test::ExpectFpTernaryMatchesStdAllModes(1.0F, 1.0F, -1.0F, ccm_float, std_float); ccm::test::ExpectFpTernaryMatchesStdAllModes(-1.0F, -1.0F, -1.0F, ccm_float, std_float); diff --git a/tests/conformance/fma_software_fenv_test.cpp b/tests/conformance/fma_software_fenv_test.cpp index 8a555f2a..f7c1aab7 100644 --- a/tests/conformance/fma_software_fenv_test.cpp +++ b/tests/conformance/fma_software_fenv_test.cpp @@ -29,9 +29,10 @@ namespace { using ccm::test::runtime_value; - template - void consume(T value) - { [[maybe_unused]] volatile T sink = value; } + template void consume(T value) + { + [[maybe_unused]] volatile T sink = value; + } } // namespace TEST(CcmathSoftwareFmaFenvTests, InvalidFlagsMatchStd) @@ -73,64 +74,48 @@ TEST(CcmathSoftwareFmaFenvTests, RangeFlagsMatchStdAllModes) { CCMATH_SKIP_MSVC_FENV_EXCEPTIONS(); - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ForceRoundingMode force(mode); - ASSERT_TRUE(force); - - ccm::test::ExpectFenvFlagsMatchStd( - [] - { - consume(ccm::fmaf(runtime_value(std::numeric_limits::max()), runtime_value(2.0F), runtime_value(std::numeric_limits::max()))); - }, - [] - { - consume(std::fmaf(runtime_value(std::numeric_limits::max()), runtime_value(2.0F), runtime_value(std::numeric_limits::max()))); - }, - FE_OVERFLOW | FE_INEXACT); - - ccm::test::ExpectFenvFlagsMatchStd( - [] { consume(ccm::fmaf(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5F), runtime_value(0.0F))); }, - [] { consume(std::fmaf(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5F), runtime_value(0.0F))); }, - FE_UNDERFLOW | FE_INEXACT); - - ccm::test::ExpectFenvFlagsMatchStd( - [] - { - consume(ccm::fma(runtime_value(std::numeric_limits::max()), runtime_value(2.0), runtime_value(std::numeric_limits::max()))); - }, - [] - { - consume(std::fma(runtime_value(std::numeric_limits::max()), runtime_value(2.0), runtime_value(std::numeric_limits::max()))); - }, - FE_OVERFLOW | FE_INEXACT); - - ccm::test::ExpectFenvFlagsMatchStd( - [] { consume(ccm::fma(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5), runtime_value(0.0))); }, - [] { consume(std::fma(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5), runtime_value(0.0))); }, - FE_UNDERFLOW | FE_INEXACT); - }); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ForceRoundingMode force(mode); + ASSERT_TRUE(force); + + ccm::test::ExpectFenvFlagsMatchStd( + [] { consume(ccm::fmaf(runtime_value(std::numeric_limits::max()), runtime_value(2.0F), runtime_value(std::numeric_limits::max()))); }, + [] { consume(std::fmaf(runtime_value(std::numeric_limits::max()), runtime_value(2.0F), runtime_value(std::numeric_limits::max()))); }, + FE_OVERFLOW | FE_INEXACT); + + ccm::test::ExpectFenvFlagsMatchStd( + [] { consume(ccm::fmaf(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5F), runtime_value(0.0F))); }, + [] { consume(std::fmaf(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5F), runtime_value(0.0F))); }, + FE_UNDERFLOW | FE_INEXACT); + + ccm::test::ExpectFenvFlagsMatchStd( + [] { consume(ccm::fma(runtime_value(std::numeric_limits::max()), runtime_value(2.0), runtime_value(std::numeric_limits::max()))); }, + [] { consume(std::fma(runtime_value(std::numeric_limits::max()), runtime_value(2.0), runtime_value(std::numeric_limits::max()))); }, + FE_OVERFLOW | FE_INEXACT); + + ccm::test::ExpectFenvFlagsMatchStd( + [] { consume(ccm::fma(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5), runtime_value(0.0))); }, + [] { consume(std::fma(runtime_value(std::numeric_limits::denorm_min()), runtime_value(0.5), runtime_value(0.0))); }, + FE_UNDERFLOW | FE_INEXACT); + }); } TEST(CcmathSoftwareFmaFenvTests, InexactFlagsMatchStdAllModes) { CCMATH_SKIP_MSVC_FENV_EXCEPTIONS(); - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ForceRoundingMode force(mode); - ASSERT_TRUE(force); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ForceRoundingMode force(mode); + ASSERT_TRUE(force); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::fmaf(runtime_value(0.1F), runtime_value(0.1F), runtime_value(0.0F))); }, - [] { consume(std::fmaf(runtime_value(0.1F), runtime_value(0.1F), runtime_value(0.0F))); }, - FE_INEXACT); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::fmaf(runtime_value(0.1F), runtime_value(0.1F), runtime_value(0.0F))); }, + [] { consume(std::fmaf(runtime_value(0.1F), runtime_value(0.1F), runtime_value(0.0F))); }, + FE_INEXACT); - ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::fma(runtime_value(0.1), runtime_value(0.1), runtime_value(0.0))); }, - [] { consume(std::fma(runtime_value(0.1), runtime_value(0.1), runtime_value(0.0))); }, - FE_INEXACT); - }); + ccm::test::ExpectFenvFlagsMatchStd([] { consume(ccm::fma(runtime_value(0.1), runtime_value(0.1), runtime_value(0.0))); }, + [] { consume(std::fma(runtime_value(0.1), runtime_value(0.1), runtime_value(0.0))); }, + FE_INEXACT); + }); } #undef CCMATH_SKIP_MSVC_FENV_EXCEPTIONS diff --git a/tests/conformance/include_policy_test.cpp b/tests/conformance/include_policy_test.cpp index 2dc96199..4de93129 100644 --- a/tests/conformance/include_policy_test.cpp +++ b/tests/conformance/include_policy_test.cpp @@ -22,7 +22,10 @@ namespace auto current = std::filesystem::path(__FILE__).parent_path(); while (!current.empty()) { - if (std::filesystem::exists(current / "include" / "ccmath")) { return current; } + if (std::filesystem::exists(current / "include" / "ccmath")) + { + return current; + } current = current.parent_path(); } return std::filesystem::path(__FILE__).parent_path(); @@ -44,8 +47,14 @@ TEST(CcmathImplementationPolicyTests, LibraryHeadersDoNotIncludeCmath) for (const auto & entry : std::filesystem::recursive_directory_iterator(include_root)) { - if (!entry.is_regular_file()) { continue; } - if (!is_library_source_file(entry.path())) { continue; } + if (!entry.is_regular_file()) + { + continue; + } + if (!is_library_source_file(entry.path())) + { + continue; + } std::ifstream input(entry.path()); ASSERT_TRUE(input.is_open()) << "failed to open " << entry.path().string(); @@ -65,6 +74,9 @@ TEST(CcmathImplementationPolicyTests, LibraryHeadersDoNotIncludeCmath) EXPECT_TRUE(violations.empty()); if (!violations.empty()) { - for (const std::string & violation : violations) { ADD_FAILURE() << "forbidden include in library header: " << violation; } + for (const std::string & violation : violations) + { + ADD_FAILURE() << "forbidden include in library header: " << violation; + } } } diff --git a/tests/conformance/pow_errno_fenv_test.cpp b/tests/conformance/pow_errno_fenv_test.cpp index 2066b773..ea072710 100644 --- a/tests/conformance/pow_errno_fenv_test.cpp +++ b/tests/conformance/pow_errno_fenv_test.cpp @@ -26,15 +26,20 @@ namespace { using ccm::test::runtime_value; - template - void consume(T value) - { [[maybe_unused]] volatile T sink = value; } + template void consume(T value) + { + [[maybe_unused]] volatile T sink = value; + } CCM_NEVER_INLINE double invoke_pow_gen(double base, double exp) - { return ccm::gen::pow_gen(base, exp); } + { + return ccm::gen::pow_gen(base, exp); + } CCM_NEVER_INLINE float invoke_powf_gen(float base, float exp) - { return ccm::gen::pow_gen(base, exp); } + { + return ccm::gen::pow_gen(base, exp); + } } // namespace TEST(CcmathPowErrnoTests, GenericPowSetsErrnoWhenRuntimeErrnoIsEnabled) @@ -124,29 +129,27 @@ TEST(CcmathPowFenvTests, GenericPowfBaseTwoCorrectlyRoundedAtRangeThresholdsAllM { -152.0F, 0.0F, kMinSub, 0.0F, 0.0F }, } }; - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + for (const ThresholdCase & threshold : kCases) { - for (const ThresholdCase& threshold : kCases) + SCOPED_TRACE(threshold.exponent); + + const float base = runtime_value(2.0F); + const float exponent = runtime_value(threshold.exponent); + const float actual = invoke_powf_gen(base, exponent); + + float expected = threshold.expected_nearest; + switch (ccm::test::ToRoundingModeKind(mode)) { - SCOPED_TRACE(threshold.exponent); - - const float base = runtime_value(2.0F); - const float exponent = runtime_value(threshold.exponent); - const float actual = invoke_powf_gen(base, exponent); - - float expected = threshold.expected_nearest; - switch (ccm::test::ToRoundingModeKind(mode)) - { - case ccm::test::RoundingModeKind::Upward: expected = threshold.expected_upward; break; - case ccm::test::RoundingModeKind::Downward: expected = threshold.expected_downward; break; - case ccm::test::RoundingModeKind::TowardZero: expected = threshold.expected_toward_zero; break; - case ccm::test::RoundingModeKind::Nearest: break; - } - - ccm::test::ExpectFpEq(actual, expected); + case ccm::test::RoundingModeKind::Upward : expected = threshold.expected_upward; break; + case ccm::test::RoundingModeKind::Downward : expected = threshold.expected_downward; break; + case ccm::test::RoundingModeKind::TowardZero: expected = threshold.expected_toward_zero; break; + case ccm::test::RoundingModeKind::Nearest : break; } - }); + + ccm::test::ExpectFpEq(actual, expected); + } + }); } TEST(CcmathPowFenvTests, SignalingNaNsRaiseInvalidLikeStdWhenObservable) diff --git a/tests/conformance/pow_path_config_test.cpp b/tests/conformance/pow_path_config_test.cpp index b442bfbf..a49ff377 100644 --- a/tests/conformance/pow_path_config_test.cpp +++ b/tests/conformance/pow_path_config_test.cpp @@ -6,11 +6,13 @@ namespace { - template - void SmokePath(ccm::test::pow_path::validation_path path) + template void SmokePath(ccm::test::pow_path::validation_path path) { const auto support = ccm::test::pow_path::path_is_supported(path); - if (!support.supported) { GTEST_SKIP() << support.skip_reason; } + if (!support.supported) + { + GTEST_SKIP() << support.skip_reason; + } const auto report = ccm::test::pow_path::make_configuration_report(path); ccm::test::pow_path::print_configuration_banner(report); @@ -22,7 +24,10 @@ namespace { for (T exponent : exponents) { - if (path == ccm::test::pow_path::validation_path::generic_modeled_domain && !(base > T{})) { continue; } + if (path == ccm::test::pow_path::validation_path::generic_modeled_domain && !(base > T{})) + { + continue; + } const T result = ccm::test::pow_path::invoke(path, base, exponent); EXPECT_TRUE(std::isfinite(result) || std::isinf(result) || std::isnan(result)); } @@ -31,34 +36,56 @@ namespace } // namespace TEST(PowPathConfig, PublicDefaultDouble) -{ SmokePath(ccm::test::pow_path::validation_path::public_default); } +{ + SmokePath(ccm::test::pow_path::validation_path::public_default); +} TEST(PowPathConfig, GenericRuntimeDouble) -{ SmokePath(ccm::test::pow_path::validation_path::generic_runtime); } +{ + SmokePath(ccm::test::pow_path::validation_path::generic_runtime); +} TEST(PowPathConfig, GenericModeledDomainDouble) -{ SmokePath(ccm::test::pow_path::validation_path::generic_modeled_domain); } +{ + SmokePath(ccm::test::pow_path::validation_path::generic_modeled_domain); +} TEST(PowPathConfig, RuntimeNoBuiltinDouble) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_no_builtin); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_no_builtin); +} TEST(PowPathConfig, RuntimeSimdDouble) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_simd); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_simd); +} TEST(PowPathConfig, RuntimeBuiltinDouble) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_builtin); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_builtin); +} TEST(PowPathConfig, PublicDefaultFloat) -{ SmokePath(ccm::test::pow_path::validation_path::public_default); } +{ + SmokePath(ccm::test::pow_path::validation_path::public_default); +} TEST(PowPathConfig, GenericRuntimeFloat) -{ SmokePath(ccm::test::pow_path::validation_path::generic_runtime); } +{ + SmokePath(ccm::test::pow_path::validation_path::generic_runtime); +} TEST(PowPathConfig, RuntimeNoBuiltinFloat) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_no_builtin); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_no_builtin); +} TEST(PowPathConfig, RuntimeSimdFloat) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_simd); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_simd); +} TEST(PowPathConfig, RuntimeBuiltinFloat) -{ SmokePath(ccm::test::pow_path::validation_path::runtime_builtin); } +{ + SmokePath(ccm::test::pow_path::validation_path::runtime_builtin); +} diff --git a/tests/conformance/powl_platform_test.cpp b/tests/conformance/powl_platform_test.cpp index 155f8363..916b2b1d 100644 --- a/tests/conformance/powl_platform_test.cpp +++ b/tests/conformance/powl_platform_test.cpp @@ -35,11 +35,17 @@ TEST(PowlPlatform, StdPowRegressionCorpus) if (!ccm::config::reduced_precision_powl_fallback_enabled()) { const auto path = ccm::test::oracle::classify_powl_gen_path(base, exponent); - if (!ccm::test::oracle::powl_path_is_native_phase1(path)) { continue; } + if (!ccm::test::oracle::powl_path_is_native_phase1(path)) + { + continue; + } } const long double expected = std::pow(base, exponent); - if (std::isnan(expected)) { continue; } + if (std::isnan(expected)) + { + continue; + } EXPECT_EQ(ccm::powl(base, exponent), expected); } } diff --git a/tests/conformance/rounding_conformance_test.cpp b/tests/conformance/rounding_conformance_test.cpp index 3721431d..1798ee4b 100644 --- a/tests/conformance/rounding_conformance_test.cpp +++ b/tests/conformance/rounding_conformance_test.cpp @@ -63,19 +63,20 @@ namespace // is not correctly rounded. Both sides run under the forced mode through a runtime value so the // generic kernel reads the dynamic mode instead of folding at compile time. template - void ExpectRuntimeMatchesGenericInDirectedModes(const std::array& inputs, CcmFn ccm_fn, GenFn gen_fn) + void ExpectRuntimeMatchesGenericInDirectedModes(const std::array & inputs, CcmFn ccm_fn, GenFn gen_fn) { - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + if (mode == FE_TONEAREST) { - if (mode == FE_TONEAREST) { return; } // round to nearest keeps the builtin, nothing to pin here - for (double input : inputs) - { - SCOPED_TRACE(input); - const double x = runtime_value(input); - ccm::test::ExpectFpEq(ccm_fn(x), gen_fn(x)); - } - }); + return; + } // round to nearest keeps the builtin, nothing to pin here + for (double input : inputs) + { + SCOPED_TRACE(input); + const double x = runtime_value(input); + ccm::test::ExpectFpEq(ccm_fn(x), gen_fn(x)); + } + }); } } // namespace @@ -93,41 +94,35 @@ TEST(CcmathRoundingConformanceTests, NearbyIntMatchesStdAllModesFloat) TEST(CcmathRoundingConformanceTests, LogOneMatchesStdAllModes) { - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ScopedRoundingMode scope(mode); - ASSERT_TRUE(scope.active()); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ScopedRoundingMode scope(mode); + ASSERT_TRUE(scope.active()); - const double one = runtime_value(1.0); - ccm::test::ExpectFpEq(ccm::log(one), std::log(one)); - }); + const double one = runtime_value(1.0); + ccm::test::ExpectFpEq(ccm::log(one), std::log(one)); + }); } TEST(CcmathRoundingConformanceTests, Log10OneMatchesStdAllModes) { - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ScopedRoundingMode scope(mode); - ASSERT_TRUE(scope.active()); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ScopedRoundingMode scope(mode); + ASSERT_TRUE(scope.active()); - const double one = runtime_value(1.0); - ccm::test::ExpectFpEq(ccm::log10(one), std::log10(one)); - }); + const double one = runtime_value(1.0); + ccm::test::ExpectFpEq(ccm::log10(one), std::log10(one)); + }); } TEST(CcmathRoundingConformanceTests, Log2OneMatchesStdAllModes) { - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ScopedRoundingMode scope(mode); - ASSERT_TRUE(scope.active()); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ScopedRoundingMode scope(mode); + ASSERT_TRUE(scope.active()); - const double one = runtime_value(1.0); - ccm::test::ExpectFpEq(ccm::log2(one), std::log2(one)); - }); + const double one = runtime_value(1.0); + ccm::test::ExpectFpEq(ccm::log2(one), std::log2(one)); + }); } TEST(CcmathRoundingConformanceTests, Log1pZeroMatchesStdAllModes) @@ -155,10 +150,14 @@ TEST(CcmathRoundingConformanceTests, SqrtMatchesStdAllModes) } TEST(CcmathRoundingConformanceTests, RintMatchesStdAllModesDouble) -{ ccm::test::ExpectFpUnaryOverMatchesStdAllModes(ccm::test::samples::kNearbyIntProbeDouble, ccm::rint, static_cast(std::rint)); } +{ + ccm::test::ExpectFpUnaryOverMatchesStdAllModes(ccm::test::samples::kNearbyIntProbeDouble, ccm::rint, static_cast(std::rint)); +} TEST(CcmathRoundingConformanceTests, RintMatchesStdAllModesFloat) -{ ccm::test::ExpectFpUnaryOverMatchesStdAllModes(ccm::test::samples::kNearbyIntProbeFloat, ccm::rint, static_cast(std::rint)); } +{ + ccm::test::ExpectFpUnaryOverMatchesStdAllModes(ccm::test::samples::kNearbyIntProbeFloat, ccm::rint, static_cast(std::rint)); +} TEST(CcmathRoundingConformanceTests, CeilTruncRoundFloorIndependentOfMode) { @@ -172,21 +171,19 @@ TEST(CcmathRoundingConformanceTests, FmodRemainderIndependentOfRoundingMode) { // fmod and remainder are exact operations, so the result must not vary with the active rounding // mode. Both operands go through runtime_value so the kernels read the dynamic mode at run time. - ccm::test::ForEachRoundingModeOrSkip( - [&](int mode) - { - ccm::test::ScopedRoundingMode scope(mode); - ASSERT_TRUE(scope.active()); - - ccm::test::ExpectFpEq(ccm::fmod(runtime_value(10.0), runtime_value(3.0)), 1.0); - ccm::test::ExpectFpEq(ccm::fmod(runtime_value(7.5), runtime_value(2.0)), 1.5); - ccm::test::ExpectFpEq(ccm::fmod(runtime_value(-10.0), runtime_value(3.0)), -1.0); - ccm::test::ExpectFpEq(ccm::fmod(runtime_value(1e30), runtime_value(3.0)), 1.0); - ccm::test::ExpectFpEq(ccm::internal::fmod(runtime_value(1e300), runtime_value(7.0)), 1.0); - - ccm::test::ExpectFpEq(ccm::remainder(runtime_value(5.0), runtime_value(3.0)), -1.0); - ccm::test::ExpectFpEq(ccm::remainder(runtime_value(7.5), runtime_value(2.0)), -0.5); - }); + ccm::test::ForEachRoundingModeOrSkip([&](int mode) { + ccm::test::ScopedRoundingMode scope(mode); + ASSERT_TRUE(scope.active()); + + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(10.0), runtime_value(3.0)), 1.0); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(7.5), runtime_value(2.0)), 1.5); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(-10.0), runtime_value(3.0)), -1.0); + ccm::test::ExpectFpEq(ccm::fmod(runtime_value(1e30), runtime_value(3.0)), 1.0); + ccm::test::ExpectFpEq(ccm::internal::fmod(runtime_value(1e300), runtime_value(7.0)), 1.0); + + ccm::test::ExpectFpEq(ccm::remainder(runtime_value(5.0), runtime_value(3.0)), -1.0); + ccm::test::ExpectFpEq(ccm::remainder(runtime_value(7.5), runtime_value(2.0)), -0.5); + }); } // The following pin the FE_TONEAREST guard in the expo runtime headers: outside round to nearest diff --git a/tests/conformance/ulp_all_modes_test.cpp b/tests/conformance/ulp_all_modes_test.cpp index 8f4b259f..11a370cf 100644 --- a/tests/conformance/ulp_all_modes_test.cpp +++ b/tests/conformance/ulp_all_modes_test.cpp @@ -17,22 +17,34 @@ #include TEST(CcmathAllModesUlpTests, ExpDouble) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::exp, static_cast(std::exp)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::exp, static_cast(std::exp)); +} TEST(CcmathAllModesUlpTests, LogDouble) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log, static_cast(std::log)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log, static_cast(std::log)); +} TEST(CcmathAllModesUlpTests, Log2Double) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log2, static_cast(std::log2)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log2, static_cast(std::log2)); +} TEST(CcmathAllModesUlpTests, Log10Double) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log10, static_cast(std::log10)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log10, static_cast(std::log10)); +} TEST(CcmathAllModesUlpTests, SinDouble) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::sin, static_cast(std::sin)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::sin, static_cast(std::sin)); +} TEST(CcmathAllModesUlpTests, CosDouble) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::cos, static_cast(std::cos)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::cos, static_cast(std::cos)); +} // [c.math]/1: validates that the double overload preserves the standard C library behavior across active rounding modes. TEST(CcmathAllModesUlpTests, PowDouble) @@ -42,10 +54,16 @@ TEST(CcmathAllModesUlpTests, PowDouble) } TEST(CcmathAllModesUlpTests, Expm1Double) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::expm1, static_cast(std::expm1)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::expm1, static_cast(std::expm1)); +} TEST(CcmathAllModesUlpTests, Log1pDouble) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log1p, static_cast(std::log1p)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kAllModesProbeDouble, ccm::log1p, static_cast(std::log1p)); +} TEST(CcmathAllModesUlpTests, Log2Float) -{ ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kExpoFloat, ccm::log2, static_cast(std::log2)); } +{ + ccm::test::ExpectUlpUnaryOverAllRoundingModes(ccm::test::samples::kExpoFloat, ccm::log2, static_cast(std::log2)); +} diff --git a/tests/conformance/worst_case_ulp_test.cpp b/tests/conformance/worst_case_ulp_test.cpp index 92ffd00e..a4d9e3bc 100644 --- a/tests/conformance/worst_case_ulp_test.cpp +++ b/tests/conformance/worst_case_ulp_test.cpp @@ -19,7 +19,7 @@ namespace { template - void ExpectWorstCaseUnaryOver(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn) + void ExpectWorstCaseUnaryOver(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn) { for (T input : inputs) { @@ -29,7 +29,7 @@ namespace } template - void ExpectWorstCaseBinaryOver(const std::array& bases, const std::array& exponents, CcmFn ccm_fn, StdFn std_fn) + void ExpectWorstCaseBinaryOver(const std::array & bases, const std::array & exponents, CcmFn ccm_fn, StdFn std_fn) { for (std::size_t i = 0; i < N; ++i) { @@ -40,9 +40,9 @@ namespace } template - void ExpectWorstCaseBinaryPairsOver(const std::array, N>& cases, CcmFn ccm_fn, StdFn std_fn) + void ExpectWorstCaseBinaryPairsOver(const std::array, N> & cases, CcmFn ccm_fn, StdFn std_fn) { - for (const auto& test_case : cases) + for (const auto & test_case : cases) { SCOPED_TRACE(test_case.base); SCOPED_TRACE(test_case.exponent); @@ -53,68 +53,110 @@ namespace } // namespace TEST(CcmathWorstCaseUlpTests, SinFloatExceptional) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinFloatExceptional, ccm::sin, static_cast(std::sin)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinFloatExceptional, ccm::sin, static_cast(std::sin)); +} TEST(CcmathWorstCaseUlpTests, SinFloatLargeReduction) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinFloatLargeReduction, ccm::sin, static_cast(std::sin)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinFloatLargeReduction, ccm::sin, static_cast(std::sin)); +} TEST(CcmathWorstCaseUlpTests, CosFloatExceptional) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCosFloatExceptional, ccm::cos, static_cast(std::cos)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCosFloatExceptional, ccm::cos, static_cast(std::cos)); +} TEST(CcmathWorstCaseUlpTests, SinDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinDoubleHard, ccm::sin, static_cast(std::sin)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinDoubleHard, ccm::sin, static_cast(std::sin)); +} TEST(CcmathWorstCaseUlpTests, CosDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinDoubleHard, ccm::cos, static_cast(std::cos)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSinDoubleHard, ccm::cos, static_cast(std::cos)); +} TEST(CcmathWorstCaseUlpTests, Expm1FloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpm1FloatHard, ccm::expm1, static_cast(std::expm1)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpm1FloatHard, ccm::expm1, static_cast(std::expm1)); +} TEST(CcmathWorstCaseUlpTests, Expm1DoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpm1DoubleHard, ccm::expm1, static_cast(std::expm1)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpm1DoubleHard, ccm::expm1, static_cast(std::expm1)); +} TEST(CcmathWorstCaseUlpTests, ExpFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpFloatHard, ccm::exp, static_cast(std::exp)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpFloatHard, ccm::exp, static_cast(std::exp)); +} TEST(CcmathWorstCaseUlpTests, ExpDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpDoubleHard, ccm::exp, static_cast(std::exp)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExpDoubleHard, ccm::exp, static_cast(std::exp)); +} TEST(CcmathWorstCaseUlpTests, Log1pFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLog1pFloatHard, ccm::log1p, static_cast(std::log1p)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLog1pFloatHard, ccm::log1p, static_cast(std::log1p)); +} TEST(CcmathWorstCaseUlpTests, Log1pDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLog1pDoubleHard, ccm::log1p, static_cast(std::log1p)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLog1pDoubleHard, ccm::log1p, static_cast(std::log1p)); +} TEST(CcmathWorstCaseUlpTests, LogFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogFloatHard, ccm::log, static_cast(std::log)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogFloatHard, ccm::log, static_cast(std::log)); +} TEST(CcmathWorstCaseUlpTests, LogDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log, static_cast(std::log)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log, static_cast(std::log)); +} TEST(CcmathWorstCaseUlpTests, Log2DoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log2, static_cast(std::log2)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log2, static_cast(std::log2)); +} TEST(CcmathWorstCaseUlpTests, Log10DoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log10, static_cast(std::log10)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kLogDoubleHard, ccm::log10, static_cast(std::log10)); +} TEST(CcmathWorstCaseUlpTests, Exp2FloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExp2FloatHard, ccm::exp2, static_cast(std::exp2)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExp2FloatHard, ccm::exp2, static_cast(std::exp2)); +} TEST(CcmathWorstCaseUlpTests, Exp2DoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExp2DoubleHard, ccm::exp2, static_cast(std::exp2)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kExp2DoubleHard, ccm::exp2, static_cast(std::exp2)); +} TEST(CcmathWorstCaseUlpTests, SqrtFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSqrtFloatHard, ccm::sqrt, static_cast(std::sqrt)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSqrtFloatHard, ccm::sqrt, static_cast(std::sqrt)); +} TEST(CcmathWorstCaseUlpTests, SqrtDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSqrtDoubleHard, ccm::sqrt, static_cast(std::sqrt)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kSqrtDoubleHard, ccm::sqrt, static_cast(std::sqrt)); +} // [c.math]/1: validates that the double overload tracks the C library semantics even on the known hardest pow inputs in this suite. TEST(CcmathWorstCaseUlpTests, PowDoubleHard) -{ ExpectWorstCaseBinaryPairsOver(ccm::test::worst_case::kPowDoubleHard, ccm::pow, static_cast(std::pow)); } +{ + ExpectWorstCaseBinaryPairsOver(ccm::test::worst_case::kPowDoubleHard, ccm::pow, static_cast(std::pow)); +} TEST(CcmathWorstCaseUlpTests, PowFloatHard) -{ ExpectWorstCaseBinaryPairsOver(ccm::test::worst_case::kPowFloatHard, ccm::powf, static_cast(std::pow)); } +{ + ExpectWorstCaseBinaryPairsOver(ccm::test::worst_case::kPowFloatHard, ccm::powf, static_cast(std::pow)); +} TEST(CcmathWorstCaseUlpTests, PowLongDoubleHard) { @@ -128,26 +170,39 @@ TEST(CcmathWorstCaseUlpTests, PowLongDoubleHard) } TEST(CcmathWorstCaseUlpTests, AsinDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAsinDoubleHard, ccm::asin, static_cast(std::asin)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAsinDoubleHard, ccm::asin, static_cast(std::asin)); +} TEST(CcmathWorstCaseUlpTests, AsinFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAsinFloatHard, ccm::asin, static_cast(std::asin)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAsinFloatHard, ccm::asin, static_cast(std::asin)); +} TEST(CcmathWorstCaseUlpTests, AtanDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAtanDoubleHard, ccm::atan, static_cast(std::atan)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kAtanDoubleHard, ccm::atan, static_cast(std::atan)); +} TEST(CcmathWorstCaseUlpTests, CbrtDoubleHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCbrtDoubleHard, ccm::cbrt, static_cast(std::cbrt)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCbrtDoubleHard, ccm::cbrt, static_cast(std::cbrt)); +} TEST(CcmathWorstCaseUlpTests, CbrtFloatHard) -{ ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCbrtFloatHard, ccm::cbrt, static_cast(std::cbrt)); } +{ + ExpectWorstCaseUnaryOver(ccm::test::worst_case::kCbrtFloatHard, ccm::cbrt, static_cast(std::cbrt)); +} TEST(CcmathWorstCaseUlpTests, GammaDoubleHard) { for (double x : ccm::test::worst_case::kGammaDoubleHard) { SCOPED_TRACE(x); - if (x < 0.0 && x == std::trunc(x)) { continue; } + if (x < 0.0 && x == std::trunc(x)) + { + continue; + } ccm::test::ExpectUlpUnaryVsStd(x, ccm::gamma, static_cast(std::tgamma)); } } diff --git a/tests/proofs/math/power/pow_accuracy_probe.cpp b/tests/proofs/math/power/pow_accuracy_probe.cpp index cf596149..64dc00e3 100644 --- a/tests/proofs/math/power/pow_accuracy_probe.cpp +++ b/tests/proofs/math/power/pow_accuracy_probe.cpp @@ -44,7 +44,7 @@ namespace #endif } - const char* target_arch() + const char * target_arch() { #if defined(__aarch64__) || defined(_M_ARM64) return "aarch64"; @@ -59,19 +59,19 @@ namespace #endif } - const char* rounding_mode_name(int mode) + const char * rounding_mode_name(int mode) { switch (mode) { - case FE_TONEAREST: return "FE_TONEAREST"; - case FE_DOWNWARD: return "FE_DOWNWARD"; - case FE_UPWARD: return "FE_UPWARD"; + case FE_TONEAREST : return "FE_TONEAREST"; + case FE_DOWNWARD : return "FE_DOWNWARD"; + case FE_UPWARD : return "FE_UPWARD"; case FE_TOWARDZERO: return "FE_TOWARDZERO"; - default: return "unknown"; + default : return "unknown"; } } - const char* fast_math_status() + const char * fast_math_status() { #if defined(__FAST_MATH__) return "on"; @@ -81,8 +81,7 @@ namespace } } // namespace -template -int64_t ulp_difference_local(T a, T b) +template int64_t ulp_difference_local(T a, T b) { const std::uint64_t distance = ccm::test::ulp::distance_or_max(a, b); return (distance > static_cast(std::numeric_limits::max())) ? std::numeric_limits::max() : static_cast(distance); @@ -97,30 +96,41 @@ struct Stats void add(int64_t ulp) { - if (ulp < min) { min = ulp; } - if (ulp > max) { max = ulp; } + if (ulp < min) + { + min = ulp; + } + if (ulp > max) + { + max = ulp; + } sum += static_cast(ulp); ++count; } - long double avg() const { return count == 0 ? 0.0L : (sum / static_cast(count)); } + long double avg() const + { + return count == 0 ? 0.0L : (sum / static_cast(count)); + } }; -template -Stats measure_suite(const std::array& bases, const std::array& exponents, Fn fn, RefFn ref) +template Stats measure_suite(const std::array & bases, const std::array & exponents, Fn fn, RefFn ref) { Stats stats; for (std::size_t i = 0; i < bases.size(); ++i) { const T expected = ref(bases[i], exponents[i]); - if (std::isnan(expected)) { continue; } + if (std::isnan(expected)) + { + continue; + } stats.add(ulp_difference_local(fn(bases[i], exponents[i]), expected)); } return stats; } template -Stats measure_grid(const std::array& bases, const std::array& exponents, Fn fn, RefFn ref) +Stats measure_grid(const std::array & bases, const std::array & exponents, Fn fn, RefFn ref) { Stats stats; for (T base : bases) @@ -128,15 +138,17 @@ Stats measure_grid(const std::array& bases, const std::array& expo for (T exponent : exponents) { const T expected = ref(base, exponent); - if (std::isnan(expected)) { continue; } + if (std::isnan(expected)) + { + continue; + } stats.add(ulp_difference_local(fn(base, exponent), expected)); } } return stats; } -template -Stats measure_random(Fn fn, RefFn ref) +template Stats measure_random(Fn fn, RefFn ref) { Stats stats; std::mt19937_64 rng(0xCCBEEF1234ULL); @@ -148,15 +160,17 @@ Stats measure_random(Fn fn, RefFn ref) const T base = static_cast(base_dist(rng)); const T exponent = static_cast(exponent_dist(rng)); const T expected = ref(base, exponent); - if (std::isnan(expected)) { continue; } + if (std::isnan(expected)) + { + continue; + } stats.add(ulp_difference_local(fn(base, exponent), expected)); } return stats; } -template -void print_stats(const char* label, const Stats& stats) +template void print_stats(const char * label, const Stats & stats) { std::cout << label << ": count=" << stats.count << " min=" << (stats.count == 0 ? 0 : stats.min) << " max=" << stats.max << " avg=" << std::fixed << std::setprecision(9) << static_cast(stats.avg()) << '\n'; diff --git a/tests/shared/oracle/big_int_gmp.cpp b/tests/shared/oracle/big_int_gmp.cpp index 8e464a24..48a821fd 100644 --- a/tests/shared/oracle/big_int_gmp.cpp +++ b/tests/shared/oracle/big_int_gmp.cpp @@ -14,8 +14,7 @@ namespace { - template - using BigInt = ccm::types::BigInt; + template using BigInt = ccm::types::BigInt; using U256 = BigInt<256, false>; using S256 = BigInt<256, true>; @@ -24,34 +23,46 @@ namespace { mpz_t value; - mpz_holder() { mpz_init(value); } + mpz_holder() + { + mpz_init(value); + } - mpz_holder(mpz_holder&& other) noexcept + mpz_holder(mpz_holder && other) noexcept { mpz_init(value); mpz_swap(value, other.value); } - mpz_holder& operator=(mpz_holder&& other) noexcept + mpz_holder & operator=(mpz_holder && other) noexcept { - if (this != &other) { mpz_swap(value, other.value); } + if (this != &other) + { + mpz_swap(value, other.value); + } return *this; } - mpz_holder(const mpz_holder&) = delete; - mpz_holder& operator=(const mpz_holder&) = delete; + mpz_holder(const mpz_holder &) = delete; + mpz_holder & operator=(const mpz_holder &) = delete; - ~mpz_holder() { mpz_clear(value); } + ~mpz_holder() + { + mpz_clear(value); + } }; - template - [[nodiscard]] Big make_big(const std::array& words) - { return Big(words); } + template [[nodiscard]] Big make_big(const std::array & words) + { + return Big(words); + } // GMP bit counts are mp_bitcnt_t (32-bit unsigned long on Windows). Every count we pass is // bounded well below that, so the narrowing cast is safe. [[nodiscard]] constexpr mp_bitcnt_t to_bitcnt(std::size_t bits) - { return static_cast(bits); } + { + return static_cast(bits); + } [[nodiscard]] std::array generated_words(std::uint64_t seed) { @@ -87,7 +98,10 @@ namespace { 0xFFFFFFFF00000000ULL, 0x00000000FFFFFFFFULL, 0x7FFFFFFF80000000ULL, 0x800000007FFFFFFFULL }, } }; - for (const auto& pattern : patterns) { out.push_back(make_big(pattern)); } + for (const auto & pattern : patterns) + { + out.push_back(make_big(pattern)); + } for (std::uint64_t seed : { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL, 0x9E3779B97F4A7C15ULL, 0xD1B54A32D192ED03ULL, 0x94D049BB133111EBULL }) { out.push_back(make_big(generated_words(seed))); @@ -116,7 +130,10 @@ namespace { 0x001FFFFFFFFFFFFEULL, 0x001FFFFFFFFFFFFEULL, 0ULL, 0x8000000000000000ULL }, } }; - for (const auto& pattern : patterns) { out.push_back(make_big(pattern)); } + for (const auto & pattern : patterns) + { + out.push_back(make_big(pattern)); + } for (std::uint64_t seed : { 0x2545F4914F6CDD1DULL, 0x94D049BB133111EBULL, 0xD1B54A32D192ED03ULL }) { auto words = generated_words(seed); @@ -126,18 +143,19 @@ namespace return out; } - template - [[nodiscard]] mpz_holder mpz_from_unsigned_big(const Big& value) + template [[nodiscard]] mpz_holder mpz_from_unsigned_big(const Big & value) { mpz_holder out; std::array words{}; - for (std::size_t i = 0; i < Big::WORD_COUNT; ++i) { words[i] = value[i]; } + for (std::size_t i = 0; i < Big::WORD_COUNT; ++i) + { + words[i] = value[i]; + } mpz_import(out.value, words.size(), -1, sizeof(std::uint64_t), 0, 0, words.data()); return out; } - template - [[nodiscard]] mpz_holder mpz_from_signed_big(const BigInt& value) + template [[nodiscard]] mpz_holder mpz_from_signed_big(const BigInt & value) { mpz_holder out = mpz_from_unsigned_big(value); if (value.is_neg()) @@ -150,29 +168,32 @@ namespace return out; } - template - [[nodiscard]] bool matches_unsigned_bits(const Big& actual, const mpz_t expected) + template [[nodiscard]] bool matches_unsigned_bits(const Big & actual, const mpz_t expected) { mpz_holder reduced; mpz_fdiv_r_2exp(reduced.value, expected, to_bitcnt(Big::BITS)); for (std::size_t bit = 0; bit < Big::BITS; ++bit) { - if (actual.get_bit(bit) != (mpz_tstbit(reduced.value, to_bitcnt(bit)) != 0)) { return false; } + if (actual.get_bit(bit) != (mpz_tstbit(reduced.value, to_bitcnt(bit)) != 0)) + { + return false; + } } return true; } - template - [[nodiscard]] std::string bits_hex(const Big& value) + template [[nodiscard]] std::string bits_hex(const Big & value) { std::ostringstream out; out << "0x"; - for (std::size_t i = Big::WORD_COUNT; i-- > 0;) { out << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << value[i]; } + for (std::size_t i = Big::WORD_COUNT; i-- > 0;) + { + out << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << value[i]; + } return out.str(); } - template - int fail(const char* label, const Big& lhs, const Big& rhs) + template int fail(const char * label, const Big & lhs, const Big & rhs) { std::cerr << label << '\n' << "lhs=" << bits_hex(lhs) << '\n' << "rhs=" << bits_hex(rhs) << '\n'; return 1; @@ -182,9 +203,9 @@ namespace int main() { const auto unsigned_cases = build_unsigned_cases(); - for (const auto& lhs : unsigned_cases) + for (const auto & lhs : unsigned_cases) { - for (const auto& rhs : unsigned_cases) + for (const auto & rhs : unsigned_cases) { const mpz_holder lhs_mpz = mpz_from_unsigned_big(lhs); const mpz_holder rhs_mpz = mpz_from_unsigned_big(rhs); @@ -192,27 +213,45 @@ int main() mpz_holder expected; mpz_add(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs + rhs, expected.value)) { return fail("unsigned add mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs + rhs, expected.value)) + { + return fail("unsigned add mismatch", lhs, rhs); + } mpz_sub(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs - rhs, expected.value)) { return fail("unsigned sub mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs - rhs, expected.value)) + { + return fail("unsigned sub mismatch", lhs, rhs); + } mpz_mul(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs * rhs, expected.value)) { return fail("unsigned mul mismatch", lhs, rhs); } - if (!matches_unsigned_bits(lhs.ful_mul(rhs), expected.value)) { return fail("unsigned full mul mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs * rhs, expected.value)) + { + return fail("unsigned mul mismatch", lhs, rhs); + } + if (!matches_unsigned_bits(lhs.ful_mul(rhs), expected.value)) + { + return fail("unsigned full mul mismatch", lhs, rhs); + } if (!rhs.is_zero()) { mpz_holder q; mpz_holder r; mpz_tdiv_qr(q.value, r.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs / rhs, q.value)) { return fail("unsigned div mismatch", lhs, rhs); } - if (!matches_unsigned_bits(lhs % rhs, r.value)) { return fail("unsigned mod mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs / rhs, q.value)) + { + return fail("unsigned div mismatch", lhs, rhs); + } + if (!matches_unsigned_bits(lhs % rhs, r.value)) + { + return fail("unsigned mod mismatch", lhs, rhs); + } } } } - for (const auto& numerator : unsigned_cases) + for (const auto & numerator : unsigned_cases) { const mpz_holder numerator_mpz = mpz_from_unsigned_big(numerator); for (std::uint32_t divisor : { 1U, 3U, 5U, 0x7FFFFFFFU, 0x80000001U, 0xFFFFFFFFU }) @@ -256,31 +295,49 @@ int main() } const auto signed_cases = build_signed_cases(); - for (const auto& lhs : signed_cases) + for (const auto & lhs : signed_cases) { - for (const auto& rhs : signed_cases) + for (const auto & rhs : signed_cases) { const mpz_holder lhs_mpz = mpz_from_signed_big(lhs); const mpz_holder rhs_mpz = mpz_from_signed_big(rhs); mpz_holder expected; mpz_add(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs + rhs, expected.value)) { return fail("signed add raw mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs + rhs, expected.value)) + { + return fail("signed add raw mismatch", lhs, rhs); + } mpz_sub(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs - rhs, expected.value)) { return fail("signed sub raw mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs - rhs, expected.value)) + { + return fail("signed sub raw mismatch", lhs, rhs); + } mpz_mul(expected.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs * rhs, expected.value)) { return fail("signed mul raw mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs * rhs, expected.value)) + { + return fail("signed mul raw mismatch", lhs, rhs); + } - if (rhs.is_zero()) { continue; } + if (rhs.is_zero()) + { + continue; + } mpz_holder q; mpz_holder r; mpz_tdiv_qr(q.value, r.value, lhs_mpz.value, rhs_mpz.value); - if (!matches_unsigned_bits(lhs / rhs, q.value)) { return fail("signed div mismatch", lhs, rhs); } - if (!matches_unsigned_bits(lhs % rhs, r.value)) { return fail("signed mod mismatch", lhs, rhs); } + if (!matches_unsigned_bits(lhs / rhs, q.value)) + { + return fail("signed div mismatch", lhs, rhs); + } + if (!matches_unsigned_bits(lhs % rhs, r.value)) + { + return fail("signed mod mismatch", lhs, rhs); + } } } diff --git a/tests/shared/oracle/coremath_oracle_harness.hpp b/tests/shared/oracle/coremath_oracle_harness.hpp index f63dcf0d..2d81e224 100644 --- a/tests/shared/oracle/coremath_oracle_harness.hpp +++ b/tests/shared/oracle/coremath_oracle_harness.hpp @@ -10,7 +10,7 @@ namespace ccm::test::oracle { template - inline std::optional> evaluate_binary_coremath_case_in_mode(const pow_case& test_case, + inline std::optional> evaluate_binary_coremath_case_in_mode(const pow_case & test_case, std::string_view function_name, std::string_view path_name, int rounding_mode, @@ -18,11 +18,11 @@ namespace ccm::test::oracle ReferenceFn reference_fn, EligibilityFn is_reference_case_fn, TruthFn matches_truth_fn, - run_summary& summary, + run_summary & summary, std::uint64_t seed = 0, std::string_view search_mode = {}, std::string_view mismatch_note = "bit-exact mismatch vs CORE-MATH reference", - std::vector>* event_log = nullptr) + std::vector> * event_log = nullptr) { if (!is_reference_case_fn(test_case.base, test_case.exponent)) { @@ -43,7 +43,10 @@ namespace ccm::test::oracle const T expected = reference_fn(test_case.base, test_case.exponent); const T actual = actual_fn(test_case.base, test_case.exponent); - if (oracle_fp_bits_match(actual, expected)) { return std::nullopt; } + if (oracle_fp_bits_match(actual, expected)) + { + return std::nullopt; + } if (matches_truth_fn(test_case.base, test_case.exponent, actual)) { @@ -69,7 +72,10 @@ namespace ccm::test::oracle } ++summary.failure_count; - if (summary.max_observed_ulp == 0) { record_worst_case(summary, 1, test_case.base, test_case.exponent, actual, expected); } + if (summary.max_observed_ulp == 0) + { + record_worst_case(summary, 1, test_case.base, test_case.exponent, actual, expected); + } auto record = make_failure_record(function_name, path_name, @@ -85,25 +91,28 @@ namespace ccm::test::oracle search_mode, mismatch_note, "coremath_bit_mismatch"); - if (event_log != nullptr) { event_log->push_back(record); } + if (event_log != nullptr) + { + event_log->push_back(record); + } return record; } template - inline void evaluate_binary_coremath_case_all_modes(const pow_case& test_case, + inline void evaluate_binary_coremath_case_all_modes(const pow_case & test_case, std::string_view function_name, std::string_view path_name, - const std::vector& rounding_modes, + const std::vector & rounding_modes, ActualFn actual_fn, ReferenceFn reference_fn, EligibilityFn is_reference_case_fn, TruthFn matches_truth_fn, - run_summary& summary, - std::vector>& failures, - std::uint64_t seed = 0, - std::string_view search_mode = {}, - std::string_view mismatch_note = "bit-exact mismatch vs CORE-MATH reference", - std::vector>* event_log = nullptr) + run_summary & summary, + std::vector> & failures, + std::uint64_t seed = 0, + std::string_view search_mode = {}, + std::string_view mismatch_note = "bit-exact mismatch vs CORE-MATH reference", + std::vector> * event_log = nullptr) { for (const int rounding_mode : rounding_modes) { @@ -121,7 +130,10 @@ namespace ccm::test::oracle mismatch_note, event_log)) { - if (event_log == nullptr) { failures.push_back(*failure); } + if (event_log == nullptr) + { + failures.push_back(*failure); + } } } } diff --git a/tests/shared/oracle/coremath_pow_cases.hpp b/tests/shared/oracle/coremath_pow_cases.hpp index 894de7ba..d3466d93 100644 --- a/tests/shared/oracle/coremath_pow_cases.hpp +++ b/tests/shared/oracle/coremath_pow_cases.hpp @@ -6,15 +6,21 @@ namespace ccm::test::oracle::coremath_cases { inline std::vector all_powf_domains() - { return pow_cases::all_powf_domains(); } + { + return pow_cases::all_powf_domains(); + } inline std::vector> build_double_cases(campaign_mode mode, std::uint64_t seed) - { return pow_cases::build_double_cases(mode, seed, is_coremath_oracle_case); } + { + return pow_cases::build_double_cases(mode, seed, is_coremath_oracle_case); + } inline std::vector> build_float_cases(campaign_mode mode, - const std::set& domain_filter, + const std::set & domain_filter, std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) - { return pow_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped, is_coremath_oracle_case); } + std::vector & domains_covered, + std::vector & domains_skipped) + { + return pow_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped, is_coremath_oracle_case); + } } // namespace ccm::test::oracle::coremath_cases diff --git a/tests/shared/oracle/coremath_pow_common.hpp b/tests/shared/oracle/coremath_pow_common.hpp index 70b5a523..3636085d 100644 --- a/tests/shared/oracle/coremath_pow_common.hpp +++ b/tests/shared/oracle/coremath_pow_common.hpp @@ -12,12 +12,15 @@ namespace ccm::test::oracle { - template - inline T coremath_pow_reference(T base, T exponent) + template inline T coremath_pow_reference(T base, T exponent) { - if constexpr (std::is_same_v) { return cr_powf(base, exponent); } - else if constexpr (std::is_same_v) { return cr_pow(base, exponent); } - else + if constexpr (std::is_same_v) + { + return cr_powf(base, exponent); + } else if constexpr (std::is_same_v) + { + return cr_pow(base, exponent); + } else { return static_cast(cr_pow(static_cast(base), static_cast(exponent))); } @@ -61,20 +64,32 @@ namespace ccm::test::oracle // sign of x^y - 1 = sign(y) * sign(|x| - 1). Outside this region cr_pow is reliable. inline std::optional coremath_double_near_one_truth(double base, double exponent) { - if (!(base > 0.0) || !std::isfinite(base) || !std::isfinite(exponent) || base == 1.0) { return std::nullopt; } + if (!(base > 0.0) || !std::isfinite(base) || !std::isfinite(exponent) || base == 1.0) + { + return std::nullopt; + } int e_x = 0; std::frexp(base, &e_x); // base in [0.5, 1) * 2^e_x, so |log2(base)| <= |e_x| + 1 const double log2_bound = std::fabs(static_cast(e_x)) + 1.0; - if (!(std::fabs(exponent) * log2_bound < 0x1.0p-54)) { return std::nullopt; } + if (!(std::fabs(exponent) * log2_bound < 0x1.0p-54)) + { + return std::nullopt; + } const int sign_y = (exponent > 0.0) ? 1 : (exponent < 0.0) ? -1 : 0; const int sign_log = (base > 1.0) ? 1 : (base < 1.0) ? -1 : 0; const int sign_delta = sign_y * sign_log; // sign of x^y - 1 const int mode = std::fegetround(); - if (sign_delta > 0) { return mode == FE_UPWARD ? std::nextafter(1.0, 2.0) : 1.0; } - if (sign_delta < 0) { return (mode == FE_DOWNWARD || mode == FE_TOWARDZERO) ? std::nextafter(1.0, 0.0) : 1.0; } + if (sign_delta > 0) + { + return mode == FE_UPWARD ? std::nextafter(1.0, 2.0) : 1.0; + } + if (sign_delta < 0) + { + return (mode == FE_DOWNWARD || mode == FE_TOWARDZERO) ? std::nextafter(1.0, 0.0) : 1.0; + } return 1.0; } @@ -82,35 +97,32 @@ namespace ccm::test::oracle // correctly rounded (confirmed by a higher-precision cross-check), i.e. the oracle // is wrong for this case. float cross-checks against the round-to-odd double oracle, // double resolves the near-1 region where cr_pow ignores directed rounding. - template - inline bool function_matches_high_precision_truth([[maybe_unused]] T base, [[maybe_unused]] T exponent, [[maybe_unused]] T actual) + template inline bool function_matches_high_precision_truth([[maybe_unused]] T base, [[maybe_unused]] T exponent, [[maybe_unused]] T actual) { if constexpr (std::is_same_v) { const float truth = coremath_float_truth(base, exponent); return oracle_fp_bits_match(actual, truth); - } - else if constexpr (std::is_same_v) + } else if constexpr (std::is_same_v) { const std::optional truth = coremath_double_near_one_truth(base, exponent); return truth.has_value() && oracle_fp_bits_match(actual, *truth); - } - else + } else { return false; } } template - inline std::optional> evaluate_case_in_mode(const pow_case& test_case, + inline std::optional> evaluate_case_in_mode(const pow_case & test_case, std::string_view function_name, std::string_view path_name, int rounding_mode, Fn fn, - run_summary& summary, - std::uint64_t seed = 0, - std::string_view search_mode = {}, - std::vector>* event_log = nullptr) + run_summary & summary, + std::uint64_t seed = 0, + std::string_view search_mode = {}, + std::vector> * event_log = nullptr) { return evaluate_binary_coremath_case_in_mode( test_case, @@ -129,16 +141,16 @@ namespace ccm::test::oracle } template - inline void evaluate_case_all_modes(const pow_case& test_case, + inline void evaluate_case_all_modes(const pow_case & test_case, std::string_view function_name, std::string_view path_name, - const std::vector& rounding_modes, + const std::vector & rounding_modes, Fn fn, - run_summary& summary, - std::vector>& failures, - std::uint64_t seed = 0, - std::string_view search_mode = {}, - std::vector>* event_log = nullptr) + run_summary & summary, + std::vector> & failures, + std::uint64_t seed = 0, + std::string_view search_mode = {}, + std::vector> * event_log = nullptr) { evaluate_binary_coremath_case_all_modes( test_case, diff --git a/tests/shared/oracle/coremath_pow_double.cpp b/tests/shared/oracle/coremath_pow_double.cpp index ec22271b..06b4ef41 100644 --- a/tests/shared/oracle/coremath_pow_double.cpp +++ b/tests/shared/oracle/coremath_pow_double.cpp @@ -1,4 +1,6 @@ #include "oracle/runners/coremath_pow_runner.hpp" -int main(int argc, char** argv) -{ return ccm::test::oracle::pow_coremath::run_campaign(argc, argv); } +int main(int argc, char ** argv) +{ + return ccm::test::oracle::pow_coremath::run_campaign(argc, argv); +} diff --git a/tests/shared/oracle/coremath_pow_float.cpp b/tests/shared/oracle/coremath_pow_float.cpp index 6f9d80f8..152f8e37 100644 --- a/tests/shared/oracle/coremath_pow_float.cpp +++ b/tests/shared/oracle/coremath_pow_float.cpp @@ -1,4 +1,6 @@ #include "oracle/runners/coremath_pow_runner.hpp" -int main(int argc, char** argv) -{ return ccm::test::oracle::pow_coremath::run_campaign(argc, argv); } +int main(int argc, char ** argv) +{ + return ccm::test::oracle::pow_coremath::run_campaign(argc, argv); +} diff --git a/tests/shared/oracle/cross_libm_common.hpp b/tests/shared/oracle/cross_libm_common.hpp index 0eb713b2..319a6d55 100644 --- a/tests/shared/oracle/cross_libm_common.hpp +++ b/tests/shared/oracle/cross_libm_common.hpp @@ -61,13 +61,24 @@ namespace ccm::test::oracle::cross_libm return backends; } - template - inline disagreement_kind classify(T lhs, T rhs) + template inline disagreement_kind classify(T lhs, T rhs) { - if (lhs == rhs) { return disagreement_kind::exact_match; } - if (std::isnan(lhs) && std::isnan(rhs)) { return disagreement_kind::exceptional_policy; } - if (std::isinf(lhs) && std::isinf(rhs) && std::signbit(lhs) == std::signbit(rhs)) { return disagreement_kind::exact_match; } - if (lhs == T{} && rhs == T{}) { return disagreement_kind::signed_zero; } + if (lhs == rhs) + { + return disagreement_kind::exact_match; + } + if (std::isnan(lhs) && std::isnan(rhs)) + { + return disagreement_kind::exceptional_policy; + } + if (std::isinf(lhs) && std::isinf(rhs) && std::signbit(lhs) == std::signbit(rhs)) + { + return disagreement_kind::exact_match; + } + if (lhs == T{} && rhs == T{}) + { + return disagreement_kind::signed_zero; + } return disagreement_kind::ulp_ordinary; } @@ -75,11 +86,11 @@ namespace ccm::test::oracle::cross_libm { switch (kind) { - case disagreement_kind::ulp_ordinary: return "ulp_ordinary"; + case disagreement_kind::ulp_ordinary : return "ulp_ordinary"; case disagreement_kind::exceptional_policy: return "exceptional_policy"; - case disagreement_kind::nan_sign: return "nan_sign"; - case disagreement_kind::signed_zero: return "signed_zero"; - case disagreement_kind::exact_match: return "exact_match"; + case disagreement_kind::nan_sign : return "nan_sign"; + case disagreement_kind::signed_zero : return "signed_zero"; + case disagreement_kind::exact_match : return "exact_match"; } return "unknown"; } diff --git a/tests/shared/oracle/cross_libm_pow.cpp b/tests/shared/oracle/cross_libm_pow.cpp index 77f54552..2ca58aad 100644 --- a/tests/shared/oracle/cross_libm_pow.cpp +++ b/tests/shared/oracle/cross_libm_pow.cpp @@ -24,12 +24,18 @@ namespace std::vector build_corpus(const std::string & corpus) { std::vector cases; - for (const auto & hard : ccm::test::worst_case::kPowDoubleHard) { cases.push_back({ hard.base, hard.exponent, {} }); } + for (const auto & hard : ccm::test::worst_case::kPowDoubleHard) + { + cases.push_back({ hard.base, hard.exponent, {} }); + } if (corpus == "extended") { for (double base : { 0.5, 1.0, 2.0, -2.0, 1e-300, 1e300 }) { - for (double exponent : { -3.0, -0.5, 0.0, 0.5, 2.0, 10.0 }) { cases.push_back({ base, exponent, {} }); } + for (double exponent : { -3.0, -0.5, 0.0, 0.5, 2.0, 10.0 }) + { + cases.push_back({ base, exponent, {} }); + } } } return cases; @@ -37,7 +43,10 @@ namespace double eval_backend(const backend_info & backend, double base, double exponent) { - if (backend.id == "std_pow" || backend.id == "platform_libm") { return std::pow(base, exponent); } + if (backend.id == "std_pow" || backend.id == "platform_libm") + { + return std::pow(base, exponent); + } return std::pow(base, exponent); } } // namespace @@ -53,15 +62,20 @@ int main(int argc, char ** argv) std::vector active; for (const auto & backend : backends) { - if (backend.available) { active.push_back(backend); } + if (backend.available) + { + active.push_back(backend); + } } auto cases = build_corpus(corpus); std::size_t disagreement_count = 0; std::ofstream out(output, std::ios::trunc); - if (format == "csv") { out << "base,exponent,backend_a,backend_b,kind,result_a,result_b\n"; } - else + if (format == "csv") + { + out << "base,exponent,backend_a,backend_b,kind,result_a,result_b\n"; + } else { out << "[\n"; } @@ -69,7 +83,10 @@ int main(int argc, char ** argv) bool first_json = true; for (auto & test_case : cases) { - for (const auto & backend : active) { test_case.results[backend.id] = eval_backend(backend, test_case.base, test_case.exponent); } + for (const auto & backend : active) + { + test_case.results[backend.id] = eval_backend(backend, test_case.base, test_case.exponent); + } const double mpfr_ref = ccm::test::oracle::mpfr_pow_reference(test_case.base, test_case.exponent, 256, ccm::test::oracle::current_mpfr_rounding_mode()); @@ -78,17 +95,22 @@ int main(int argc, char ** argv) for (std::size_t j = i + 1; j < active.size(); ++j) { const auto kind = ccm::test::oracle::cross_libm::classify(test_case.results[active[i].id], test_case.results[active[j].id]); - if (kind == disagreement_kind::exact_match) { continue; } + if (kind == disagreement_kind::exact_match) + { + continue; + } ++disagreement_count; if (format == "csv") { out << test_case.base << ',' << test_case.exponent << ',' << active[i].id << ',' << active[j].id << ',' << ccm::test::oracle::cross_libm::disagreement_name(kind) << ',' << test_case.results[active[i].id] << ',' << test_case.results[active[j].id] << '\n'; - } - else + } else { - if (!first_json) { out << ",\n"; } + if (!first_json) + { + out << ",\n"; + } first_json = false; out << " {\n"; out << " \"base_bits\": \"" << ccm::test::oracle::bits_hex(test_case.base) << "\",\n"; @@ -105,7 +127,10 @@ int main(int argc, char ** argv) } } - if (format != "csv") { out << "\n]\n"; } + if (format != "csv") + { + out << "\n]\n"; + } std::cout << "cross-libm pow corpus=" << corpus << " active_backends=" << active.size() << " disagreements=" << disagreement_count << " output=" << output << '\n'; @@ -114,6 +139,9 @@ int main(int argc, char ** argv) std::cout << " backend " << backend.id << " available=" << (backend.available ? "yes" : "no") << " label=" << backend.label << '\n'; } - if (fail_on_disagreement && disagreement_count > 0) { return 1; } + if (fail_on_disagreement && disagreement_count > 0) + { + return 1; + } return 0; } diff --git a/tests/shared/oracle/mpfr_fma_common.hpp b/tests/shared/oracle/mpfr_fma_common.hpp index 2daca605..62c08d50 100644 --- a/tests/shared/oracle/mpfr_fma_common.hpp +++ b/tests/shared/oracle/mpfr_fma_common.hpp @@ -30,22 +30,31 @@ namespace ccm::test::oracle::fma_oracle { switch (path) { - case validation_path::public_default: return "public_default"; + case validation_path::public_default : return "public_default"; case validation_path::software_fallback: return "software_fallback"; - case validation_path::quiet_generic: return "quiet_generic"; + case validation_path::quiet_generic : return "quiet_generic"; } return "unknown"; } inline std::optional parse_path(std::string_view raw) { - if (raw == "public" || raw == "public_default") { return validation_path::public_default; } - if (raw == "software" || raw == "software_fallback") { return validation_path::software_fallback; } - if (raw == "quiet" || raw == "quiet_generic") { return validation_path::quiet_generic; } + if (raw == "public" || raw == "public_default") + { + return validation_path::public_default; + } + if (raw == "software" || raw == "software_fallback") + { + return validation_path::software_fallback; + } + if (raw == "quiet" || raw == "quiet_generic") + { + return validation_path::quiet_generic; + } return std::nullopt; } - inline std::vector parse_paths(int argc, char** argv) + inline std::vector parse_paths(int argc, char ** argv) { std::vector paths; const auto raw = option_value(argc, argv, "--path="); @@ -56,23 +65,33 @@ namespace ccm::test::oracle::fma_oracle { const std::size_t comma = view.find(','); const std::string_view token = view.substr(0, comma); - if (auto parsed = parse_path(token)) { paths.push_back(*parsed); } - if (comma == std::string_view::npos) { break; } + if (auto parsed = parse_path(token)) + { + paths.push_back(*parsed); + } + if (comma == std::string_view::npos) + { + break; + } view.remove_prefix(comma + 1); } } - if (paths.empty()) { paths.push_back(validation_path::public_default); } + if (paths.empty()) + { + paths.push_back(validation_path::public_default); + } return paths; } - template - inline T invoke(validation_path path, T x, T y, T z) + template inline T invoke(validation_path path, T x, T y, T z) { switch (path) { case validation_path::public_default: - if constexpr (std::is_same_v) { return ccm::fmaf(ccm::test::runtime_value(x), ccm::test::runtime_value(y), ccm::test::runtime_value(z)); } - else + if constexpr (std::is_same_v) + { + return ccm::fmaf(ccm::test::runtime_value(x), ccm::test::runtime_value(y), ccm::test::runtime_value(z)); + } else { return ccm::fma(ccm::test::runtime_value(x), ccm::test::runtime_value(y), ccm::test::runtime_value(z)); } @@ -84,18 +103,18 @@ namespace ccm::test::oracle::fma_oracle return ccm::support::fp::public_fma(ccm::test::runtime_value(x), ccm::test::runtime_value(y), ccm::test::runtime_value(z)); } - template - inline T std_fma_reference(T x, T y, T z) + template inline T std_fma_reference(T x, T y, T z) { - if constexpr (std::is_same_v) { return std::fmaf(x, y, z); } - else + if constexpr (std::is_same_v) + { + return std::fmaf(x, y, z); + } else { return std::fma(x, y, z); } } - template - inline T mpfr_fma_reference(T x, T y, T z, mpfr_prec_t precision, mpfr_rnd_t rounding) + template inline T mpfr_fma_reference(T x, T y, T z, mpfr_prec_t precision, mpfr_rnd_t rounding) { mpfr_t x_mpfr; mpfr_t y_mpfr; @@ -119,16 +138,20 @@ namespace ccm::test::oracle::fma_oracle return result; } - template - inline void append_structured_cases(std::vector>& cases) + template inline void append_structured_cases(std::vector> & cases) { if constexpr (std::is_same_v) { - for (const auto& input : ccm::test::samples::kFmaFloatCases) { cases.push_back({ input.x, input.y, input.z, "structured regression corpus" }); } - } - else + for (const auto & input : ccm::test::samples::kFmaFloatCases) + { + cases.push_back({ input.x, input.y, input.z, "structured regression corpus" }); + } + } else { - for (const auto& input : ccm::test::samples::kFmaDoubleCases) { cases.push_back({ input.x, input.y, input.z, "structured regression corpus" }); } + for (const auto & input : ccm::test::samples::kFmaDoubleCases) + { + cases.push_back({ input.x, input.y, input.z, "structured regression corpus" }); + } } const T one = static_cast(1); @@ -144,16 +167,14 @@ namespace ccm::test::oracle::fma_oracle { cases.push_back({ min_normal, 0x1.0p-23F, -min_subnormal, "float exact subnormal cancellation" }); cases.push_back({ min_normal, 0x1.0p-24F, -min_subnormal, "float sticky-bit underflow boundary" }); - } - else + } else { cases.push_back({ min_normal, 0x1.0p-52, -min_subnormal, "double exact subnormal cancellation" }); cases.push_back({ min_normal, 0x1.0p-53, -min_subnormal, "double sticky-bit underflow boundary" }); } } - template - inline void append_special_matrix(std::vector>& cases) + template inline void append_special_matrix(std::vector> & cases) { const std::array values = { -std::numeric_limits::infinity(), @@ -172,7 +193,10 @@ namespace ccm::test::oracle::fma_oracle { for (T y : values) { - for (T z : values) { cases.push_back({ x, y, z, "special-value matrix" }); } + for (T z : values) + { + cases.push_back({ x, y, z, "special-value matrix" }); + } } } @@ -181,8 +205,7 @@ namespace ccm::test::oracle::fma_oracle cases.push_back({ static_cast(1), static_cast(1), std::numeric_limits::quiet_NaN(), "quiet NaN addend" }); } - template - inline void append_random_cases(std::vector>& cases, campaign_mode mode, std::uint64_t seed) + template inline void append_random_cases(std::vector> & cases, campaign_mode mode, std::uint64_t seed) { const std::size_t count = mode == campaign_mode::quick ? (std::is_same_v ? 1024U : 512U) : (mode == campaign_mode::extended ? (std::is_same_v ? 8192U : 4096U) @@ -198,8 +221,7 @@ namespace ccm::test::oracle::fma_oracle ccm::support::bit_cast(static_cast(rng())), "deterministic random bit-pattern campaign", }); - } - else + } else { cases.push_back({ ccm::support::bit_cast(rng()), @@ -211,8 +233,7 @@ namespace ccm::test::oracle::fma_oracle } } - template - inline std::vector> build_cases(campaign_mode mode, std::uint64_t seed) + template inline std::vector> build_cases(campaign_mode mode, std::uint64_t seed) { std::vector> cases; append_structured_cases(cases); @@ -221,18 +242,17 @@ namespace ccm::test::oracle::fma_oracle return cases; } - template - inline int run_campaign(int argc, char** argv, std::string_view function_name, mpfr_prec_t default_precision) + template inline int run_campaign(int argc, char ** argv, std::string_view function_name, mpfr_prec_t default_precision) { const auto mode = parse_mode(option_value(argc, argv, "--mode=")); const auto output_path = resolve_event_log_path(argc, argv); const std::uint64_t seed = - parse_option_or(option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xF6A600D5EEDULL); + parse_option_or(option_value(argc, argv, "--seed="), [](const std::string & value) { return std::stoull(value); }, 0xF6A600D5EEDULL); const std::uint64_t max_ulp = parse_option_or( - option_value(argc, argv, "--max-ulp="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 0); + option_value(argc, argv, "--max-ulp="), [](const std::string & value) { return static_cast(std::stoull(value)); }, 0); const mpfr_prec_t precision = parse_option_or( option_value(argc, argv, "--oracle-precision="), - [](const std::string& value) { return static_cast(std::stoul(value)); }, + [](const std::string & value) { return static_cast(std::stoul(value)); }, default_precision); const auto paths = parse_paths(argc, argv); @@ -251,16 +271,20 @@ namespace ccm::test::oracle::fma_oracle for (const int rounding_mode : rounding_modes) { ScopedMpfrRoundingMode scope(rounding_mode); - if (!scope) { continue; } - for (const auto& test_case : cases) + if (!scope) + { + continue; + } + for (const auto & test_case : cases) { if (auto failure = evaluate_ternary_mpfr_case( test_case, function_name, path_name(path), [path](T x, T y, T z) { return invoke(path, x, y, z); }, - [](T x, T y, T z, mpfr_prec_t oracle_precision, mpfr_rnd_t rounding) - { return mpfr_fma_reference(x, y, z, oracle_precision, rounding); }, + [](T x, T y, T z, mpfr_prec_t oracle_precision, mpfr_rnd_t rounding) { + return mpfr_fma_reference(x, y, z, oracle_precision, rounding); + }, [](T x, T y, T z) { return std_fma_reference(x, y, z); }, precision, max_ulp, diff --git a/tests/shared/oracle/mpfr_fma_double.cpp b/tests/shared/oracle/mpfr_fma_double.cpp index 1742a57f..1393b341 100644 --- a/tests/shared/oracle/mpfr_fma_double.cpp +++ b/tests/shared/oracle/mpfr_fma_double.cpp @@ -1,4 +1,6 @@ #include "mpfr_fma_common.hpp" int main(int argc, char ** argv) -{ return ccm::test::oracle::fma_oracle::run_campaign(argc, argv, "ccm::fma", 256); } +{ + return ccm::test::oracle::fma_oracle::run_campaign(argc, argv, "ccm::fma", 256); +} diff --git a/tests/shared/oracle/mpfr_fma_float.cpp b/tests/shared/oracle/mpfr_fma_float.cpp index 5264631d..6c15dbfe 100644 --- a/tests/shared/oracle/mpfr_fma_float.cpp +++ b/tests/shared/oracle/mpfr_fma_float.cpp @@ -1,4 +1,6 @@ #include "mpfr_fma_common.hpp" int main(int argc, char ** argv) -{ return ccm::test::oracle::fma_oracle::run_campaign(argc, argv, "ccm::fmaf", 192); } +{ + return ccm::test::oracle::fma_oracle::run_campaign(argc, argv, "ccm::fmaf", 192); +} diff --git a/tests/shared/oracle/mpfr_oracle_harness.hpp b/tests/shared/oracle/mpfr_oracle_harness.hpp index 15ed1367..94120a9b 100644 --- a/tests/shared/oracle/mpfr_oracle_harness.hpp +++ b/tests/shared/oracle/mpfr_oracle_harness.hpp @@ -19,11 +19,11 @@ namespace ccm::test::oracle { switch (fe_mode) { - case FE_UPWARD: return MPFR_RNDU; - case FE_DOWNWARD: return MPFR_RNDD; + case FE_UPWARD : return MPFR_RNDU; + case FE_DOWNWARD : return MPFR_RNDD; case FE_TOWARDZERO: return MPFR_RNDZ; - case FE_TONEAREST: - default: return MPFR_RNDN; + case FE_TONEAREST : + default : return MPFR_RNDN; } } @@ -31,7 +31,9 @@ namespace ccm::test::oracle // lockstep with the active hardware mode. Reading the default here means the oracle and // the function under test always agree on the mode being measured. inline mpfr_rnd_t current_mpfr_rounding_mode() - { return mpfr_get_default_rounding_mode(); } + { + return mpfr_get_default_rounding_mode(); + } // Set the active hardware rounding mode and the MPFR default rounding mode together so a // directed-rounding campaign measures the function under test and the oracle in the same @@ -41,39 +43,59 @@ namespace ccm::test::oracle public: explicit ScopedMpfrRoundingMode(int fe_mode) : guard_(fe_mode), previous_(mpfr_get_default_rounding_mode()) { - if (guard_) { mpfr_set_default_rounding_mode(fe_to_mpfr_rounding(fe_mode)); } + if (guard_) + { + mpfr_set_default_rounding_mode(fe_to_mpfr_rounding(fe_mode)); + } } - ~ScopedMpfrRoundingMode() { mpfr_set_default_rounding_mode(previous_); } + ~ScopedMpfrRoundingMode() + { + mpfr_set_default_rounding_mode(previous_); + } - ScopedMpfrRoundingMode(const ScopedMpfrRoundingMode&) = delete; - ScopedMpfrRoundingMode& operator=(const ScopedMpfrRoundingMode&) = delete; + ScopedMpfrRoundingMode(const ScopedMpfrRoundingMode &) = delete; + ScopedMpfrRoundingMode & operator=(const ScopedMpfrRoundingMode &) = delete; - explicit operator bool() const { return static_cast(guard_); } + explicit operator bool() const + { + return static_cast(guard_); + } private: ccm::test::ForceRoundingMode guard_; mpfr_rnd_t previous_; }; - template - inline void mpfr_set_scalar(mpfr_t value, T input) + template inline void mpfr_set_scalar(mpfr_t value, T input) { - if constexpr (std::is_same_v) { mpfr_set_flt(value, input, MPFR_RNDN); } - else if constexpr (std::is_same_v) { mpfr_set_d(value, input, MPFR_RNDN); } - else if constexpr (std::is_same_v) { mpfr_set_ld(value, input, MPFR_RNDN); } + if constexpr (std::is_same_v) + { + mpfr_set_flt(value, input, MPFR_RNDN); + } else if constexpr (std::is_same_v) + { + mpfr_set_d(value, input, MPFR_RNDN); + } else if constexpr (std::is_same_v) + { + mpfr_set_ld(value, input, MPFR_RNDN); + } } - template - inline T mpfr_get_scalar(const mpfr_t value, mpfr_rnd_t rounding) + template inline T mpfr_get_scalar(const mpfr_t value, mpfr_rnd_t rounding) { - if constexpr (std::is_same_v) { return mpfr_get_flt(value, rounding); } - else if constexpr (std::is_same_v) { return mpfr_get_d(value, rounding); } - else if constexpr (std::is_same_v) { return mpfr_get_ld(value, rounding); } + if constexpr (std::is_same_v) + { + return mpfr_get_flt(value, rounding); + } else if constexpr (std::is_same_v) + { + return mpfr_get_d(value, rounding); + } else if constexpr (std::is_same_v) + { + return mpfr_get_ld(value, rounding); + } } - template - inline bool oracle_match(T actual, T expected, std::uint64_t max_ulp, std::uint64_t& ulp_distance, std::string& notes) + template inline bool oracle_match(T actual, T expected, std::uint64_t max_ulp, std::uint64_t & ulp_distance, std::string & notes) { const auto distance = ccm::test::ulp::classify_distance(actual, expected); ulp_distance = distance.distance; @@ -118,21 +140,30 @@ namespace ccm::test::oracle return true; } - template - inline bool exceptional_or_zero_match(T actual, T reference) + template inline bool exceptional_or_zero_match(T actual, T reference) { - if (std::isnan(actual)) { return std::isnan(reference); } - if (std::isinf(actual)) { return actual == reference; } - if (actual == T{} && reference == T{}) { return std::signbit(actual) == std::signbit(reference); } + if (std::isnan(actual)) + { + return std::isnan(reference); + } + if (std::isinf(actual)) + { + return actual == reference; + } + if (actual == T{} && reference == T{}) + { + return std::signbit(actual) == std::signbit(reference); + } return actual == reference; } - template - inline bool is_exceptional_or_zero_result(T value) - { return std::isnan(value) || std::isinf(value) || value == T{}; } + template inline bool is_exceptional_or_zero_result(T value) + { + return std::isnan(value) || std::isinf(value) || value == T{}; + } template - inline std::optional> evaluate_binary_mpfr_case(const pow_case& test_case, + inline std::optional> evaluate_binary_mpfr_case(const pow_case & test_case, std::string_view function_name, std::string_view path_name, ccm::test::pow_path::validation_path path, @@ -143,12 +174,12 @@ namespace ccm::test::oracle SkipReasonFn skip_reason_fn, mpfr_prec_t oracle_precision, std::uint64_t max_ulp, - run_summary& summary, - std::uint64_t target_ulp = 0, - std::uint64_t seed = 0, - std::string_view search_mode = {}, - std::string_view mismatch_note = "exceptional mismatch vs std reference", - std::vector>* event_log = nullptr) + run_summary & summary, + std::uint64_t target_ulp = 0, + std::uint64_t seed = 0, + std::string_view search_mode = {}, + std::string_view mismatch_note = "exceptional mismatch vs std reference", + std::vector> * event_log = nullptr) { if (const auto skip = skip_reason_fn(path, test_case.base, test_case.exponent)) { @@ -161,8 +192,7 @@ namespace ccm::test::oracle const mpfr_rnd_t rounding = current_mpfr_rounding_mode(); const T actual = actual_fn(test_case.base, test_case.exponent); - const auto finish_case = [&](T expected) -> std::optional> - { + const auto finish_case = [&](T expected) -> std::optional> { std::uint64_t distance = 0; std::string notes; const bool pass = oracle_match(actual, expected, max_ulp, distance, notes); @@ -208,7 +238,10 @@ namespace ccm::test::oracle search_mode, notes, "mpfr_hard_failure"); - if (event_log != nullptr) { event_log->push_back(record); } + if (event_log != nullptr) + { + event_log->push_back(record); + } return record; }; @@ -228,7 +261,10 @@ namespace ccm::test::oracle const bool matches_mpfr = exceptional_or_zero_match(actual, mpfr_expected); if (matches_std || matches_mpfr) { - if (matches_std && !matches_mpfr) { ++summary.mpfr_policy_mismatch_count; } + if (matches_std && !matches_mpfr) + { + ++summary.mpfr_policy_mismatch_count; + } return std::nullopt; } @@ -247,7 +283,10 @@ namespace ccm::test::oracle search_mode, mismatch_note, "mpfr_hard_failure"); - if (event_log != nullptr) { event_log->push_back(record); } + if (event_log != nullptr) + { + event_log->push_back(record); + } return record; } @@ -257,8 +296,7 @@ namespace ccm::test::oracle return finish_case(reference_fn(test_case.base, test_case.exponent, oracle_precision, rounding)); } - template - struct ternary_case + template struct ternary_case { T x; T y; @@ -266,8 +304,7 @@ namespace ccm::test::oracle std::string provenance; }; - template - struct ternary_failure_record + template struct ternary_failure_record { std::string function_name; std::string input_type; @@ -298,8 +335,7 @@ namespace ccm::test::oracle std::string event_kind; }; - template - struct ternary_run_summary + template struct ternary_run_summary { std::size_t case_count = 0; std::size_t failure_count = 0; @@ -313,7 +349,7 @@ namespace ccm::test::oracle }; template - inline void record_worst_ternary_case(ternary_run_summary& summary, std::uint64_t ulp_distance, const ternary_case& test_case, T actual, T expected) + inline void record_worst_ternary_case(ternary_run_summary & summary, std::uint64_t ulp_distance, const ternary_case & test_case, T actual, T expected) { if (ulp_distance >= summary.max_observed_ulp) { @@ -373,7 +409,7 @@ namespace ccm::test::oracle } template - inline std::optional> evaluate_ternary_mpfr_case(const ternary_case& test_case, + inline std::optional> evaluate_ternary_mpfr_case(const ternary_case & test_case, std::string_view function_name, std::string_view path_name, ActualFn actual_fn, @@ -382,7 +418,7 @@ namespace ccm::test::oracle mpfr_prec_t oracle_precision, std::uint64_t max_ulp, std::uint64_t seed, - ternary_run_summary& summary, + ternary_run_summary & summary, std::string_view mismatch_note = "exceptional mismatch vs std reference") { ++summary.case_count; @@ -398,7 +434,10 @@ namespace ccm::test::oracle const bool matches_mpfr = exceptional_or_zero_match(actual, mpfr_expected); if (matches_std || matches_mpfr) { - if (matches_std && !matches_mpfr) { ++summary.mpfr_policy_mismatch_count; } + if (matches_std && !matches_mpfr) + { + ++summary.mpfr_policy_mismatch_count; + } return std::nullopt; } @@ -421,7 +460,10 @@ namespace ccm::test::oracle std::string notes; const bool pass = oracle_match(actual, mpfr_expected, max_ulp, distance, notes); record_worst_ternary_case(summary, distance, test_case, actual, mpfr_expected); - if (pass) { return std::nullopt; } + if (pass) + { + return std::nullopt; + } ++summary.failure_count; return make_ternary_failure_record(function_name, @@ -438,15 +480,13 @@ namespace ccm::test::oracle notes.empty() ? "ULP threshold exceeded" : notes); } - template - struct unary_case + template struct unary_case { T input; std::string provenance; }; - template - struct unary_failure_record + template struct unary_failure_record { std::string function_name; std::string input_type; @@ -472,8 +512,7 @@ namespace ccm::test::oracle std::string event_kind; }; - template - struct unary_run_summary + template struct unary_run_summary { std::size_t case_count = 0; std::size_t failure_count = 0; @@ -486,7 +525,7 @@ namespace ccm::test::oracle }; template - inline void record_worst_unary_case(unary_run_summary& summary, std::uint64_t ulp_distance, const unary_case& test_case, T actual, T expected) + inline void record_worst_unary_case(unary_run_summary & summary, std::uint64_t ulp_distance, const unary_case & test_case, T actual, T expected) { if (ulp_distance >= summary.max_observed_ulp) { @@ -537,7 +576,7 @@ namespace ccm::test::oracle } template - inline std::optional> evaluate_unary_mpfr_case(const unary_case& test_case, + inline std::optional> evaluate_unary_mpfr_case(const unary_case & test_case, std::string_view function_name, std::string_view path_name, ActualFn actual_fn, @@ -546,10 +585,10 @@ namespace ccm::test::oracle mpfr_prec_t oracle_precision, std::uint64_t max_ulp, std::uint64_t seed, - unary_run_summary& summary, + unary_run_summary & summary, std::uint64_t target_ulp = 0, std::string_view mismatch_note = "exceptional mismatch vs std reference", - std::vector>* event_log = nullptr) + std::vector> * event_log = nullptr) { ++summary.case_count; @@ -558,8 +597,7 @@ namespace ccm::test::oracle const T std_expected = exceptional_reference_fn(test_case.input); const T mpfr_expected = reference_fn(test_case.input, oracle_precision, rounding); - const auto finish_case = [&](T expected) -> std::optional> - { + const auto finish_case = [&](T expected) -> std::optional> { std::uint64_t distance = 0; std::string notes; const bool pass = oracle_match(actual, expected, max_ulp, distance, notes); @@ -598,7 +636,10 @@ namespace ccm::test::oracle static_cast(oracle_precision), seed, notes.empty() ? "ULP threshold exceeded" : notes); - if (event_log != nullptr) { event_log->push_back(record); } + if (event_log != nullptr) + { + event_log->push_back(record); + } return record; }; @@ -608,7 +649,10 @@ namespace ccm::test::oracle const bool matches_mpfr = exceptional_or_zero_match(actual, mpfr_expected); if (matches_std || matches_mpfr) { - if (matches_std && !matches_mpfr) { ++summary.mpfr_policy_mismatch_count; } + if (matches_std && !matches_mpfr) + { + ++summary.mpfr_policy_mismatch_count; + } return std::nullopt; } @@ -623,7 +667,10 @@ namespace ccm::test::oracle static_cast(oracle_precision), seed, mismatch_note); - if (event_log != nullptr) { event_log->push_back(record); } + if (event_log != nullptr) + { + event_log->push_back(record); + } return record; } diff --git a/tests/shared/oracle/mpfr_pow_common.hpp b/tests/shared/oracle/mpfr_pow_common.hpp index be0cdd1d..d289a0e7 100644 --- a/tests/shared/oracle/mpfr_pow_common.hpp +++ b/tests/shared/oracle/mpfr_pow_common.hpp @@ -20,27 +20,44 @@ namespace ccm::test::oracle { switch (format) { - case ccm::config::LongDoubleFormat::Double: return 256; - case ccm::config::LongDoubleFormat::X87Extended: return 256; + case ccm::config::LongDoubleFormat::Double : return 256; + case ccm::config::LongDoubleFormat::X87Extended : return 256; case ccm::config::LongDoubleFormat::IEEEBinary128: return 256; - case ccm::config::LongDoubleFormat::Unknown: return 256; + case ccm::config::LongDoubleFormat::Unknown : return 256; } return 256; } inline bool powl_exceptional_case(long double base, long double exponent) { - if (std::isnan(base) || std::isnan(exponent)) { return true; } - if (!std::isfinite(base) || !std::isfinite(exponent)) { return true; } - if (base == 0.0L) { return true; } - if (base < 0.0L) { return true; } - if (exponent == 0.0L) { return true; } - if (base == 1.0L) { return true; } + if (std::isnan(base) || std::isnan(exponent)) + { + return true; + } + if (!std::isfinite(base) || !std::isfinite(exponent)) + { + return true; + } + if (base == 0.0L) + { + return true; + } + if (base < 0.0L) + { + return true; + } + if (exponent == 0.0L) + { + return true; + } + if (base == 1.0L) + { + return true; + } return false; } - template - inline T mpfr_pow_reference(T base, T exponent, mpfr_prec_t precision, mpfr_rnd_t rounding) + template inline T mpfr_pow_reference(T base, T exponent, mpfr_prec_t precision, mpfr_rnd_t rounding) { mpfr_t base_mpfr; mpfr_t exp_mpfr; @@ -61,18 +78,18 @@ namespace ccm::test::oracle } template - inline std::optional> evaluate_case(const pow_case& test_case, + inline std::optional> evaluate_case(const pow_case & test_case, std::string_view function_name, std::string_view path_name, ccm::test::pow_path::validation_path path, Fn fn, mpfr_prec_t oracle_precision, std::uint64_t max_ulp, - run_summary& summary, - std::uint64_t target_ulp = 0, - std::uint64_t seed = 0, - std::string_view search_mode = {}, - std::vector>* event_log = nullptr) + run_summary & summary, + std::uint64_t target_ulp = 0, + std::uint64_t seed = 0, + std::string_view search_mode = {}, + std::vector> * event_log = nullptr) { return evaluate_binary_mpfr_case( test_case, diff --git a/tests/shared/oracle/mpfr_pow_double.cpp b/tests/shared/oracle/mpfr_pow_double.cpp index 0cacb850..45e8456a 100644 --- a/tests/shared/oracle/mpfr_pow_double.cpp +++ b/tests/shared/oracle/mpfr_pow_double.cpp @@ -1,4 +1,6 @@ #include "oracle/runners/mpfr_pow_runner.hpp" -int main(int argc, char** argv) -{ return ccm::test::oracle::pow_mpfr::run_campaign(argc, argv); } +int main(int argc, char ** argv) +{ + return ccm::test::oracle::pow_mpfr::run_campaign(argc, argv); +} diff --git a/tests/shared/oracle/mpfr_pow_float.cpp b/tests/shared/oracle/mpfr_pow_float.cpp index ebdf6b3c..0ed26b08 100644 --- a/tests/shared/oracle/mpfr_pow_float.cpp +++ b/tests/shared/oracle/mpfr_pow_float.cpp @@ -1,4 +1,6 @@ #include "oracle/runners/mpfr_pow_runner.hpp" -int main(int argc, char** argv) -{ return ccm::test::oracle::pow_mpfr::run_campaign(argc, argv); } +int main(int argc, char ** argv) +{ + return ccm::test::oracle::pow_mpfr::run_campaign(argc, argv); +} diff --git a/tests/shared/oracle/mpfr_powl.cpp b/tests/shared/oracle/mpfr_powl.cpp index 7fe7b31d..671037da 100644 --- a/tests/shared/oracle/mpfr_powl.cpp +++ b/tests/shared/oracle/mpfr_powl.cpp @@ -21,7 +21,10 @@ namespace std::vector> cases; for (long double base : bases) { - for (long double exponent : exponents) { cases.push_back({ base, exponent, "ld64 conservative oracle grid" }); } + for (long double exponent : exponents) + { + cases.push_back({ base, exponent, "ld64 conservative oracle grid" }); + } } return cases; } @@ -39,7 +42,10 @@ namespace }; for (long double base : special_bases) { - for (long double exponent : special_exponents) { cases.push_back({ base, exponent, "ld80 special-value matrix" }); } + for (long double exponent : special_exponents) + { + cases.push_back({ base, exponent, "ld80 special-value matrix" }); + } } return cases; } @@ -54,7 +60,10 @@ namespace cases.push_back({ base, exponent, "ld80 bounded integer exponent corpus" }); } } - for (long double exponent : { 0x1.0p62L, 0x1.0000000000001p62L, 0x1.0p63L }) { cases.push_back({ -1.0L, exponent, "ld80 parity threshold corpus" }); } + for (long double exponent : { 0x1.0p62L, 0x1.0000000000001p62L, 0x1.0p63L }) + { + cases.push_back({ -1.0L, exponent, "ld80 parity threshold corpus" }); + } return cases; } @@ -65,7 +74,10 @@ namespace { for (long double exponent : { -1000.0L, -0.5L, 0.25L, 2.0L, 1000.0L }) { - if (base == 1.0L) { continue; } + if (base == 1.0L) + { + continue; + } cases.push_back({ base, exponent, "ld80 general finite structured grid" }); } } @@ -112,7 +124,7 @@ namespace std::vector case_results; }; - powl_case_result evaluate_powl_case(const pow_case& test_case, mpfr_prec_t precision, std::uint64_t max_ulp, std::uint64_t target_ulp) + powl_case_result evaluate_powl_case(const pow_case & test_case, mpfr_prec_t precision, std::uint64_t max_ulp, std::uint64_t target_ulp) { powl_case_result result; result.test_case = test_case; @@ -165,7 +177,7 @@ namespace return result; } - void accumulate_report(powl_campaign_report& report, const powl_case_result& result) + void accumulate_report(powl_campaign_report & report, const powl_case_result & result) { report.case_results.push_back(result); switch (result.disposition) @@ -173,14 +185,20 @@ namespace case powl_case_disposition::validated_native: ++report.validated_native_count; ++report.case_count; - if (result.ulp_distance >= report.max_observed_ulp) { report.max_observed_ulp = result.ulp_distance; } - if (result.above_target) { ++report.above_target_count; } + if (result.ulp_distance >= report.max_observed_ulp) + { + report.max_observed_ulp = result.ulp_distance; + } + if (result.above_target) + { + ++report.above_target_count; + } break; case powl_case_disposition::validated_exceptional: ++report.validated_exceptional_count; ++report.case_count; break; - case powl_case_disposition::skipped_unsupported: ++report.skipped_unsupported_count; break; + case powl_case_disposition::skipped_unsupported : ++report.skipped_unsupported_count; break; case powl_case_disposition::skipped_reduced_precision: ++report.skipped_reduced_precision_count; break; case powl_case_disposition::failed: ++report.failure_count; @@ -189,20 +207,20 @@ namespace } } - const char* disposition_name(powl_case_disposition value) + const char * disposition_name(powl_case_disposition value) { switch (value) { - case powl_case_disposition::validated_native: return "validated_native"; - case powl_case_disposition::validated_exceptional: return "validated_exceptional"; - case powl_case_disposition::skipped_unsupported: return "skipped_unsupported"; + case powl_case_disposition::validated_native : return "validated_native"; + case powl_case_disposition::validated_exceptional : return "validated_exceptional"; + case powl_case_disposition::skipped_unsupported : return "skipped_unsupported"; case powl_case_disposition::skipped_reduced_precision: return "skipped_reduced_precision"; - case powl_case_disposition::failed: return "failed"; + case powl_case_disposition::failed : return "failed"; } return "failed"; } - void write_json(const std::string& path, const std::vector& reports, ccm::config::LongDoubleFormat format) + void write_json(const std::string & path, const std::vector & reports, ccm::config::LongDoubleFormat format) { std::ofstream out(path, std::ios::trunc); out << "{\n"; @@ -217,7 +235,7 @@ namespace out << " \"corpora\": [\n"; for (std::size_t i = 0; i < reports.size(); ++i) { - const auto& report = reports[i]; + const auto & report = reports[i]; out << " {\n"; out << " \"corpus\": \"" << ccm::test::oracle::json_escape(report.corpus) << "\",\n"; out << " \"validated_native_count\": " << report.validated_native_count << ",\n"; @@ -230,7 +248,7 @@ namespace out << " \"cases\": [\n"; for (std::size_t j = 0; j < report.case_results.size(); ++j) { - const auto& item = report.case_results[j]; + const auto & item = report.case_results[j]; out << " {\n"; out << " \"base_bits\": \"" << ccm::test::oracle::bits_hex(item.test_case.base) << "\",\n"; out << " \"exponent_bits\": \"" << ccm::test::oracle::bits_hex(item.test_case.exponent) << "\",\n"; @@ -249,9 +267,9 @@ namespace out << "}\n"; } - powl_campaign_report run_corpus(const std::string& corpus_name, - const std::vector>& cases, - const std::vector& rounding_modes, + powl_campaign_report run_corpus(const std::string & corpus_name, + const std::vector> & cases, + const std::vector & rounding_modes, ccm::config::LongDoubleFormat format, mpfr_prec_t precision, std::uint64_t max_ulp, @@ -265,8 +283,14 @@ namespace for (const int rounding_mode : rounding_modes) { ccm::test::oracle::ScopedMpfrRoundingMode scope(rounding_mode); - if (!scope) { continue; } - for (const auto& test_case : cases) { accumulate_report(report, evaluate_powl_case(test_case, precision, max_ulp, target_ulp)); } + if (!scope) + { + continue; + } + for (const auto & test_case : cases) + { + accumulate_report(report, evaluate_powl_case(test_case, precision, max_ulp, target_ulp)); + } } std::cout << "powl corpus=" << corpus_name << " format=" << ccm::config::long_double_format_name(format) << " rounding_modes=" << rounding_modes.size() @@ -277,9 +301,12 @@ namespace << " oracle_precision=" << precision << " configuration=" << ccm::test::oracle::configuration_name() << " compiler=" << ccm::test::oracle::compiler_id() << " platform=" << ccm::test::oracle::platform_id() << '\n'; - for (const auto& item : report.case_results) + for (const auto & item : report.case_results) { - if (item.disposition != powl_case_disposition::failed) { continue; } + if (item.disposition != powl_case_disposition::failed) + { + continue; + } std::cout << " FAIL base_bits=" << ccm::test::oracle::bits_hex(item.test_case.base) << " exp_bits=" << ccm::test::oracle::bits_hex(item.test_case.exponent) << " path=" << powl_path_name(item.path) << " disposition=" << disposition_name(item.disposition) << " notes=" << item.notes << '\n'; @@ -289,7 +316,7 @@ namespace } } // namespace -int main(int argc, char** argv) +int main(int argc, char ** argv) { const auto json_output = ccm::test::oracle::option_value(argc, argv, "--json-output=").value_or("mpfr-powl-summary.json"); @@ -304,12 +331,12 @@ int main(int argc, char** argv) constexpr auto format = ccm::config::detect_long_double_format(); const mpfr_prec_t precision = ccm::test::oracle::powl_oracle_precision(format); const std::uint64_t max_ulp = ccm::test::oracle::parse_option_or( - ccm::test::oracle::option_value(argc, argv, "--max-ulp="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 4); + ccm::test::oracle::option_value(argc, argv, "--max-ulp="), [](const std::string & value) { return static_cast(std::stoull(value)); }, 4); // Hard ceiling stays at 4 ULP. The correctly-rounded target is 0.5 ULP, which in the // integer ULP-distance metric versus the rounded MPFR reference is distance 0. const std::uint64_t target_ulp = ccm::test::oracle::parse_option_or( ccm::test::oracle::option_value(argc, argv, "--target-ulp="), - [](const std::string& value) { return static_cast(std::stoull(value)); }, + [](const std::string & value) { return static_cast(std::stoull(value)); }, 0); const auto rounding_modes = ccm::test::oracle::parse_rounding_modes(argc, argv); mpfr_set_default_rounding_mode(MPFR_RNDN); @@ -343,9 +370,12 @@ int main(int argc, char** argv) std::cout << " json=" << json_output << '\n'; } - for (const auto& report : reports) + for (const auto & report : reports) { - if (report.failure_count > 0) { return 1; } + if (report.failure_count > 0) + { + return 1; + } } return 0; diff --git a/tests/shared/oracle/mpfr_unary.cpp b/tests/shared/oracle/mpfr_unary.cpp index f9e85afb..b888214e 100644 --- a/tests/shared/oracle/mpfr_unary.cpp +++ b/tests/shared/oracle/mpfr_unary.cpp @@ -73,7 +73,7 @@ namespace // reference and the exceptional libm cross-check are unaffected by the choice. template int run_standard(int argc, - char** argv, + char ** argv, std::string_view name, GenFn gen_scalar, PublicFn public_scalar, @@ -87,26 +87,24 @@ namespace argc, argv, name, - [gen_scalar, public_scalar, std_scalar, impl](const std::array& inputs) -> T - { + [gen_scalar, public_scalar, std_scalar, impl](const std::array & inputs) -> T { switch (impl) { - case oracle::impl_kind::libm: return std_scalar(inputs[0]); + case oracle::impl_kind::libm : return std_scalar(inputs[0]); case oracle::impl_kind::public_api: return public_scalar(inputs[0]); - case oracle::impl_kind::generic: break; + case oracle::impl_kind::generic : break; } return gen_scalar(inputs[0]); }, oracle::make_unary_ref(op), - [std_scalar](const std::array& inputs) { return std_scalar(inputs[0]); }, + [std_scalar](const std::array & inputs) { return std_scalar(inputs[0]); }, [dom](std::uint64_t seed, std::size_t count) { return oracle::build_unary_corpus(dom, seed, count); }, std::array{ dom }, oracle::unary_structured_seed(std_scalar, family), oracle::impl_token(impl)); } - template - int dispatch(std::string_view function, int argc, char** argv) + template int dispatch(std::string_view function, int argc, char ** argv) { if (function == "exp") { @@ -244,10 +242,11 @@ namespace argc, argv, "ccm::asin", - [](T x) -> T - { - if constexpr (std::is_same_v) { return ccm::internal::impl::asin_float(ccm::test::runtime_value(x)); } - else + [](T x) -> T { + if constexpr (std::is_same_v) + { + return ccm::internal::impl::asin_float(ccm::test::runtime_value(x)); + } else { return ccm::internal::impl::asin_double(ccm::test::runtime_value(x)); } @@ -264,10 +263,11 @@ namespace argc, argv, "ccm::acos", - [](T x) -> T - { - if constexpr (std::is_same_v) { return ccm::internal::impl::acos_float(ccm::test::runtime_value(x)); } - else + [](T x) -> T { + if constexpr (std::is_same_v) + { + return ccm::internal::impl::acos_float(ccm::test::runtime_value(x)); + } else { return ccm::internal::impl::acos_double(ccm::test::runtime_value(x)); } @@ -284,10 +284,11 @@ namespace argc, argv, "ccm::atan", - [](T x) -> T - { - if constexpr (std::is_same_v) { return ccm::internal::impl::atan_float(ccm::test::runtime_value(x)); } - else + [](T x) -> T { + if constexpr (std::is_same_v) + { + return ccm::internal::impl::atan_float(ccm::test::runtime_value(x)); + } else { return ccm::internal::impl::atan_double(ccm::test::runtime_value(x)); } @@ -330,10 +331,11 @@ namespace argc, argv, "ccm::tgamma", - [](T x) -> T - { - if constexpr (std::is_same_v) { return ccm::internal::gamma_float(ccm::test::runtime_value(x)); } - else + [](T x) -> T { + if constexpr (std::is_same_v) + { + return ccm::internal::gamma_float(ccm::test::runtime_value(x)); + } else { return ccm::internal::gamma_double(ccm::test::runtime_value(x)); } @@ -351,22 +353,23 @@ namespace argc, argv, "ccm::lgamma", - [impl](const std::array& inputs) -> T - { + [impl](const std::array & inputs) -> T { switch (impl) { - case oracle::impl_kind::libm: return std::lgamma(inputs[0]); + case oracle::impl_kind::libm : return std::lgamma(inputs[0]); case oracle::impl_kind::public_api: return ccm::lgamma(ccm::test::runtime_value(inputs[0])); - case oracle::impl_kind::generic: break; + case oracle::impl_kind::generic : break; } - if constexpr (std::is_same_v) { return ccm::internal::lgamma_float(ccm::test::runtime_value(inputs[0])); } - else + if constexpr (std::is_same_v) + { + return ccm::internal::lgamma_float(ccm::test::runtime_value(inputs[0])); + } else { return ccm::internal::lgamma_double(ccm::test::runtime_value(inputs[0])); } }, oracle::make_lgamma_ref(), - [](const std::array& inputs) { return std::lgamma(inputs[0]); }, + [](const std::array & inputs) { return std::lgamma(inputs[0]); }, [](std::uint64_t seed, std::size_t count) { return oracle::build_unary_corpus(domain{ -50.0, kInf, true }, seed, count); }, std::array{ domain{ -50.0, kInf, true } }, oracle::unary_structured_seed([](T x) { return std::lgamma(x); }, oracle::function_family::gamma), @@ -379,18 +382,17 @@ namespace argc, argv, "ccm::pow", - [impl](const std::array& inputs) -> T - { + [impl](const std::array & inputs) -> T { switch (impl) { - case oracle::impl_kind::libm: return std::pow(inputs[0], inputs[1]); + case oracle::impl_kind::libm : return std::pow(inputs[0], inputs[1]); case oracle::impl_kind::public_api: return ccm::pow(ccm::test::runtime_value(inputs[0]), ccm::test::runtime_value(inputs[1])); - case oracle::impl_kind::generic: break; + case oracle::impl_kind::generic : break; } return ccm::gen::pow_gen(ccm::test::runtime_value(inputs[0]), ccm::test::runtime_value(inputs[1])); }, oracle::make_pow_ref(), - [](const std::array& inputs) { return std::pow(inputs[0], inputs[1]); }, + [](const std::array & inputs) { return std::pow(inputs[0], inputs[1]); }, [](std::uint64_t seed, std::size_t count) { return oracle::build_pow_corpus(seed, count); }, std::array{ domain{ -kInf, kInf, true }, domain{ -kInf, kInf, true } }, oracle::pow_special_matrix(), @@ -402,19 +404,30 @@ namespace } } // namespace -int main(int argc, char** argv) +int main(int argc, char ** argv) { std::string function = "exp"; std::string type = "double"; for (int i = 1; i < argc; ++i) { const std::string_view arg(argv[i]); - if (arg.rfind("--function=", 0) == 0) { function = std::string(arg.substr(std::string_view("--function=").size())); } - else if (arg.rfind("--type=", 0) == 0) { type = std::string(arg.substr(std::string_view("--type=").size())); } + if (arg.rfind("--function=", 0) == 0) + { + function = std::string(arg.substr(std::string_view("--function=").size())); + } else if (arg.rfind("--type=", 0) == 0) + { + type = std::string(arg.substr(std::string_view("--type=").size())); + } } - if (type == "float") { return dispatch(function, argc, argv); } - if (type == "double") { return dispatch(function, argc, argv); } + if (type == "float") + { + return dispatch(function, argc, argv); + } + if (type == "double") + { + return dispatch(function, argc, argv); + } std::cerr << "unknown --type=" << type << " (expected float or double)\n"; return 2; diff --git a/tests/shared/oracle/mpfr_unary_common.hpp b/tests/shared/oracle/mpfr_unary_common.hpp index 492ba8e2..2fd6b3ab 100644 --- a/tests/shared/oracle/mpfr_unary_common.hpp +++ b/tests/shared/oracle/mpfr_unary_common.hpp @@ -50,41 +50,51 @@ namespace ccm::test::oracle::unary_oracle // Per-thread MPFR scratch with N operands plus an output, reused across evaluations so the // exhaustive sweep does not allocate on every one of billions of references. Precision is fixed // for a campaign, so the grow case only runs on the first call. - template - struct mpfr_scratch + template struct mpfr_scratch { mpfr_t in[N]; mpfr_t out; mpfr_prec_t precision = 0; - mpfr_scratch() = default; - mpfr_scratch(const mpfr_scratch&) = delete; - mpfr_scratch& operator=(const mpfr_scratch&) = delete; + mpfr_scratch() = default; + mpfr_scratch(const mpfr_scratch &) = delete; + mpfr_scratch & operator=(const mpfr_scratch &) = delete; ~mpfr_scratch() { if (precision != 0) { - for (std::size_t i = 0; i < N; ++i) { mpfr_clear(in[i]); } + for (std::size_t i = 0; i < N; ++i) + { + mpfr_clear(in[i]); + } mpfr_clear(out); } } void ensure(mpfr_prec_t wanted) { - if (precision >= wanted) { return; } + if (precision >= wanted) + { + return; + } if (precision != 0) { - for (std::size_t i = 0; i < N; ++i) { mpfr_clear(in[i]); } + for (std::size_t i = 0; i < N; ++i) + { + mpfr_clear(in[i]); + } mpfr_clear(out); } - for (std::size_t i = 0; i < N; ++i) { mpfr_init2(in[i], wanted); } + for (std::size_t i = 0; i < N; ++i) + { + mpfr_init2(in[i], wanted); + } mpfr_init2(out, wanted); precision = wanted; } }; - template - inline mpfr_scratch& thread_scratch() + template inline mpfr_scratch & thread_scratch() { thread_local mpfr_scratch scratch; return scratch; @@ -95,12 +105,10 @@ namespace ccm::test::oracle::unary_oracle // make_*_ref return a reference callable T(const std::array&, precision, rounding). They // are the one place the differing MPFR signatures are handled. - template - inline auto make_unary_ref(mpfr_unary_op op) + template inline auto make_unary_ref(mpfr_unary_op op) { - return [op](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T - { - mpfr_scratch<1>& scratch = thread_scratch<1>(); + return [op](const std::array & inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T { + mpfr_scratch<1> & scratch = thread_scratch<1>(); scratch.ensure(precision); mpfr_set_scalar(scratch.in[0], inputs[0]); op(scratch.out, scratch.in[0], rounding); @@ -108,12 +116,10 @@ namespace ccm::test::oracle::unary_oracle }; } - template - inline auto make_lgamma_ref() + template inline auto make_lgamma_ref() { - return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T - { - mpfr_scratch<1>& scratch = thread_scratch<1>(); + return [](const std::array & inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T { + mpfr_scratch<1> & scratch = thread_scratch<1>(); scratch.ensure(precision); mpfr_set_scalar(scratch.in[0], inputs[0]); int sign = 0; @@ -122,12 +128,10 @@ namespace ccm::test::oracle::unary_oracle }; } - template - inline auto make_pow_ref() + template inline auto make_pow_ref() { - return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T - { - mpfr_scratch<2>& scratch = thread_scratch<2>(); + return [](const std::array & inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T { + mpfr_scratch<2> & scratch = thread_scratch<2>(); scratch.ensure(precision); mpfr_set_scalar(scratch.in[0], inputs[0]); mpfr_set_scalar(scratch.in[1], inputs[1]); @@ -136,12 +140,10 @@ namespace ccm::test::oracle::unary_oracle }; } - template - inline auto make_fma_ref() + template inline auto make_fma_ref() { - return [](const std::array& inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T - { - mpfr_scratch<3>& scratch = thread_scratch<3>(); + return [](const std::array & inputs, mpfr_prec_t precision, mpfr_rnd_t rounding) -> T { + mpfr_scratch<3> & scratch = thread_scratch<3>(); scratch.ensure(precision); mpfr_set_scalar(scratch.in[0], inputs[0]); mpfr_set_scalar(scratch.in[1], inputs[1]); @@ -151,12 +153,14 @@ namespace ccm::test::oracle::unary_oracle }; } - template - inline mpfr_prec_t default_precision() + template inline mpfr_prec_t default_precision() { // Well above the table-maker's-dilemma hardness for these functions at each width, so the // MPFR value rounds to the correctly-rounded result of the target type. - if constexpr (std::is_same_v) { return 96; } + if constexpr (std::is_same_v) + { + return 96; + } return 256; } @@ -165,10 +169,11 @@ namespace ccm::test::oracle::unary_oracle inline constexpr std::size_t ulp_bucket_count = 6; inline std::size_t ulp_bucket(std::uint64_t distance) - { return distance < ulp_bucket_count ? static_cast(distance) : ulp_bucket_count - 1; } + { + return distance < ulp_bucket_count ? static_cast(distance) : ulp_bucket_count - 1; + } - template - struct campaign_summary + template struct campaign_summary { std::size_t case_count = 0; std::size_t failure_count = 0; @@ -182,15 +187,17 @@ namespace ccm::test::oracle::unary_oracle T worst_expected{}; }; - template - inline void merge_summary(campaign_summary& into, const campaign_summary& from) + template inline void merge_summary(campaign_summary & into, const campaign_summary & from) { into.case_count += from.case_count; into.failure_count += from.failure_count; into.above_target_count += from.above_target_count; into.finite_count += from.finite_count; into.ulp_sum += from.ulp_sum; - for (std::size_t i = 0; i < ulp_bucket_count; ++i) { into.ulp_histogram[i] += from.ulp_histogram[i]; } + for (std::size_t i = 0; i < ulp_bucket_count; ++i) + { + into.ulp_histogram[i] += from.ulp_histogram[i]; + } if (from.max_observed_ulp >= into.max_observed_ulp) { into.max_observed_ulp = from.max_observed_ulp; @@ -208,19 +215,27 @@ namespace ccm::test::oracle::unary_oracle class threshold_recorder { public: - threshold_recorder(const std::string& output_path, std::uint64_t threshold) : out_(output_path, std::ios::trunc), threshold_(threshold) {} + threshold_recorder(const std::string & output_path, std::uint64_t threshold) : out_(output_path, std::ios::trunc), threshold_(threshold) {} - bool active() const { return out_.is_open(); } + bool active() const + { + return out_.is_open(); + } - template - void maybe_record(std::uint64_t distance, const std::array& inputs) + template void maybe_record(std::uint64_t distance, const std::array & inputs) { - if (distance < threshold_) { return; } + if (distance < threshold_) + { + return; + } std::ostringstream line; line << std::setprecision(std::numeric_limits::max_digits10); for (std::size_t i = 0; i < N; ++i) { - if (i != 0) { line << ' '; } + if (i != 0) + { + line << ' '; + } line << inputs[i]; } const std::string text = line.str(); @@ -238,15 +253,15 @@ namespace ccm::test::oracle::unary_oracle // Exceptional and zero results are matched against libm or MPFR rather than scored as a ULP // distance, exactly as the per-arity evaluators in the shared harness do. template - inline void evaluate_case(const std::array& inputs, + inline void evaluate_case(const std::array & inputs, KernelFn kernel, RefFn reference, StdFn std_reference, mpfr_prec_t precision, mpfr_rnd_t rounding, std::uint64_t max_ulp, - campaign_summary& summary, - threshold_recorder* recorder) + campaign_summary & summary, + threshold_recorder * recorder) { ++summary.case_count; const T actual = kernel(inputs); @@ -258,9 +273,15 @@ namespace ccm::test::oracle::unary_oracle if (is_exceptional_or_zero_result(actual)) { const T std_expected = std_reference(inputs); - if (exceptional_or_zero_match(actual, std_expected)) { return; } + if (exceptional_or_zero_match(actual, std_expected)) + { + return; + } const T mpfr_expected = reference(inputs, precision, rounding); - if (!exceptional_or_zero_match(actual, mpfr_expected)) { ++summary.failure_count; } + if (!exceptional_or_zero_match(actual, mpfr_expected)) + { + ++summary.failure_count; + } return; } @@ -269,14 +290,20 @@ namespace ccm::test::oracle::unary_oracle if (is_exceptional_or_zero_result(mpfr_expected)) { const T std_expected = std_reference(inputs); - if (!exceptional_or_zero_match(actual, std_expected) && !exceptional_or_zero_match(actual, mpfr_expected)) { ++summary.failure_count; } + if (!exceptional_or_zero_match(actual, std_expected) && !exceptional_or_zero_match(actual, mpfr_expected)) + { + ++summary.failure_count; + } return; } std::uint64_t distance = 0; std::string notes; const bool pass = oracle_match(actual, mpfr_expected, max_ulp, distance, notes); - if (recorder != nullptr) { recorder->maybe_record(distance, inputs); } + if (recorder != nullptr) + { + recorder->maybe_record(distance, inputs); + } summary.ulp_sum += static_cast(distance); ++summary.ulp_histogram[ulp_bucket(distance)]; ++summary.finite_count; @@ -287,12 +314,16 @@ namespace ccm::test::oracle::unary_oracle summary.worst_actual = actual; summary.worst_expected = mpfr_expected; } - if (!pass) { ++summary.failure_count; } - else if (distance > 0) { ++summary.above_target_count; } + if (!pass) + { + ++summary.failure_count; + } else if (distance > 0) + { + ++summary.above_target_count; + } } - template - struct campaign_report + template struct campaign_report { std::string function_name; std::string input_type; @@ -319,20 +350,21 @@ namespace ccm::test::oracle::unary_oracle std::string timestamp; }; - template - inline std::string join_input_bits(const std::array& inputs) + template inline std::string join_input_bits(const std::array & inputs) { std::string joined; for (std::size_t i = 0; i < N; ++i) { - if (i != 0) { joined += ","; } + if (i != 0) + { + joined += ","; + } joined += bits_hex(inputs[i]); } return joined; } - template - inline void write_campaign_summary_json(const std::string& output_path, const campaign_report& report) + template inline void write_campaign_summary_json(const std::string & output_path, const campaign_report & report) { std::ofstream out(output_path, std::ios::trunc); out << "{\n"; @@ -352,7 +384,10 @@ namespace ccm::test::oracle::unary_oracle out << " \"ulp_histogram\": ["; for (std::size_t i = 0; i < ulp_bucket_count; ++i) { - if (i != 0) { out << ", "; } + if (i != 0) + { + out << ", "; + } out << report.ulp_histogram[i]; } out << "],\n"; @@ -369,24 +404,27 @@ namespace ccm::test::oracle::unary_oracle out << "}\n"; } - inline const char* current_mode_token(int mode) + inline const char * current_mode_token(int mode) { switch (mode) { - case FE_TONEAREST: return "nearest"; - case FE_UPWARD: return "upward"; - case FE_DOWNWARD: return "downward"; + case FE_TONEAREST : return "nearest"; + case FE_UPWARD : return "upward"; + case FE_DOWNWARD : return "downward"; case FE_TOWARDZERO: return "towardzero"; - default: return "unknown"; + default : return "unknown"; } } - inline std::string rounding_modes_label(const std::vector& modes) + inline std::string rounding_modes_label(const std::vector & modes) { std::string label; for (std::size_t i = 0; i < modes.size(); ++i) { - if (i != 0) { label += ","; } + if (i != 0) + { + label += ","; + } label += current_mode_token(modes[i]); } return label; @@ -403,8 +441,7 @@ namespace ccm::test::oracle::unary_oracle int max_exp = 1023; }; - template - inline std::vector structured_inputs() + template inline std::vector structured_inputs() { const T inf = std::numeric_limits::infinity(); const T nan = std::numeric_limits::quiet_NaN(); @@ -468,12 +505,21 @@ namespace ccm::test::oracle::unary_oracle libm, }; - inline impl_kind parse_impl(int argc, char** argv) + inline impl_kind parse_impl(int argc, char ** argv) { const auto raw = option_value(argc, argv, "--impl="); - if (!raw.has_value() || *raw == "gen" || *raw == "generic") { return impl_kind::generic; } - if (*raw == "public") { return impl_kind::public_api; } - if (*raw == "libm" || *raw == "std") { return impl_kind::libm; } + if (!raw.has_value() || *raw == "gen" || *raw == "generic") + { + return impl_kind::generic; + } + if (*raw == "public") + { + return impl_kind::public_api; + } + if (*raw == "libm" || *raw == "std") + { + return impl_kind::libm; + } std::cerr << "unknown --impl=" << *raw << " (expected gen, public, or libm), using gen\n"; return impl_kind::generic; } @@ -482,9 +528,9 @@ namespace ccm::test::oracle::unary_oracle { switch (impl) { - case impl_kind::generic: return "gen"; + case impl_kind::generic : return "gen"; case impl_kind::public_api: return "public"; - case impl_kind::libm: return "libm"; + case impl_kind::libm : return "libm"; } return "gen"; } @@ -493,13 +539,11 @@ namespace ccm::test::oracle::unary_oracle // points and cancellation regions where faithful-but-not-correctly-rounded results tend to hide. // These are seeded into the default corpus so every sweep measures them regardless of the random // draw. The boundary inputs (overflow and underflow) are found separately by unary_boundary_cases. - template - inline std::vector unary_extra_inputs(function_family family) + template inline std::vector unary_extra_inputs(function_family family) { std::vector values; const auto add = [&](double value) { values.push_back(static_cast(value)); }; - const auto add_pow2 = [&](int lo, int hi) - { + const auto add_pow2 = [&](int lo, int hi) { for (int k = lo; k <= hi; ++k) { add(std::ldexp(1.0, k)); @@ -511,7 +555,10 @@ namespace ccm::test::oracle::unary_oracle switch (family) { case function_family::exp: - for (int n = -16; n <= 16; ++n) { add(static_cast(n)); } // integers, exp2 lands on a power of two + for (int n = -16; n <= 16; ++n) + { + add(static_cast(n)); + } // integers, exp2 lands on a power of two add_pow2(-28, 28); add(std::log(2.0)); add(std::log(10.0)); @@ -562,12 +609,21 @@ namespace ccm::test::oracle::unary_oracle break; } case function_family::power: - for (int n = 1; n <= 16; ++n) { add(static_cast(n) * static_cast(n)); } // perfect squares - for (int n = -5; n <= 5; ++n) { add(static_cast(n) * static_cast(n) * static_cast(n)); } // perfect cubes, cbrt of negatives + for (int n = 1; n <= 16; ++n) + { + add(static_cast(n) * static_cast(n)); + } // perfect squares + for (int n = -5; n <= 5; ++n) + { + add(static_cast(n) * static_cast(n) * static_cast(n)); + } // perfect cubes, cbrt of negatives add_pow2(-28, 28); break; case function_family::gamma: - for (int n = 0; n <= 20; ++n) { add(-static_cast(n)); } // poles at zero and the negative integers + for (int n = 0; n <= 20; ++n) + { + add(-static_cast(n)); + } // poles at zero and the negative integers for (int n = 0; n <= 10; ++n) { const T pole = static_cast(-n); // step a few ULP off each pole, where the dynamic range is largest @@ -576,8 +632,14 @@ namespace ccm::test::oracle::unary_oracle add(-static_cast(n) + 1.0e-3); add(-static_cast(n) - 1.0e-3); } - for (int n = 1; n <= 15; ++n) { add(static_cast(n)); } // tgamma(n) is (n-1)! - for (int n = 0; n <= 10; ++n) { add(static_cast(n) + 0.5); } // half-integers + for (int n = 1; n <= 15; ++n) + { + add(static_cast(n)); + } // tgamma(n) is (n-1)! + for (int n = 0; n <= 10; ++n) + { + add(static_cast(n) + 0.5); + } // half-integers break; case function_family::generic: add_pow2(-16, 16); break; } @@ -589,8 +651,7 @@ namespace ccm::test::oracle::unary_oracle // arguments special at once, so these enumerated corners (pow(x, +/-0) is 1, pow(+/-1, inf) is 1, // pow(negative, non-integer) is NaN, pow(+/-0, negative odd) is +/-inf, and the rest) are measured // only because they are seeded here. - template - inline std::vector> pow_special_matrix() + template inline std::vector> pow_special_matrix() { const T inf = std::numeric_limits::infinity(); const T nan = std::numeric_limits::quiet_NaN(); @@ -638,7 +699,10 @@ namespace ccm::test::oracle::unary_oracle cases.reserve(bases.size() * exponents.size()); for (const T base : bases) { - for (const T exponent : exponents) { cases.push_back({ base, exponent }); } + for (const T exponent : exponents) + { + cases.push_back({ base, exponent }); + } } return cases; } @@ -648,21 +712,24 @@ namespace ccm::test::oracle::unary_oracle // infinity or underflows to zero. These adjacent floats are where directed-rounding overflow handling // is decided, so they belong in every corpus. Only the families with a clean monotone boundary run a // search; the gate on the endpoint result keeps it from firing where there is no transition. - template - inline std::vector> unary_boundary_cases(StdScalar std_fn, function_family family) + template inline std::vector> unary_boundary_cases(StdScalar std_fn, function_family family) { std::vector> out; const T anchor = static_cast(1); // finite for every function here - const auto bisect = [&](T special_pt, auto is_past) - { + const auto bisect = [&](T special_pt, auto is_past) { T finite_pt = anchor; for (int i = 0; i < 5000; ++i) { const T mid = finite_pt + (special_pt - finite_pt) / static_cast(2); - if (mid == finite_pt || mid == special_pt) { break; } - if (is_past(std_fn(mid))) { special_pt = mid; } - else + if (mid == finite_pt || mid == special_pt) + { + break; + } + if (is_past(std_fn(mid))) + { + special_pt = mid; + } else { finite_pt = mid; } @@ -676,18 +743,26 @@ namespace ccm::test::oracle::unary_oracle const auto is_inf = [](T value) { return std::isinf(value); }; const auto is_zero = [](T value) { return value == static_cast(0); }; - if (has_overflow && is_inf(std_fn(std::numeric_limits::max()))) { bisect(std::numeric_limits::max(), is_inf); } - if (has_underflow && is_zero(std_fn(std::numeric_limits::lowest()))) { bisect(std::numeric_limits::lowest(), is_zero); } + if (has_overflow && is_inf(std_fn(std::numeric_limits::max()))) + { + bisect(std::numeric_limits::max(), is_inf); + } + if (has_underflow && is_zero(std_fn(std::numeric_limits::lowest()))) + { + bisect(std::numeric_limits::lowest(), is_zero); + } return out; } // The full per-function deterministic seed for the unary case: family hard cases plus the boundary // inputs, returned as arity-1 cases for the corpus. - template - inline std::vector> unary_structured_seed(StdScalar std_fn, function_family family) + template inline std::vector> unary_structured_seed(StdScalar std_fn, function_family family) { std::vector> out; - for (const T value : unary_extra_inputs(family)) { out.push_back({ value }); } + for (const T value : unary_extra_inputs(family)) + { + out.push_back({ value }); + } const auto boundary = unary_boundary_cases(std_fn, family); out.insert(out.end(), boundary.begin(), boundary.end()); return out; @@ -697,20 +772,22 @@ namespace ccm::test::oracle::unary_oracle { switch (mode) { - case campaign_mode::quick: return 500000; + case campaign_mode::quick : return 500000; case campaign_mode::extended: return 5000000; - case campaign_mode::full: - case campaign_mode::release: return 50000000; + case campaign_mode::full : + case campaign_mode::release : return 50000000; } return 500000; } // Unary corpus: structured edges, a per-function domain draw, and broad bit patterns. - template - inline std::vector> build_unary_corpus(const domain& dom, std::uint64_t seed, std::size_t count) + template inline std::vector> build_unary_corpus(const domain & dom, std::uint64_t seed, std::size_t count) { std::vector> cases; - for (const T value : structured_inputs()) { cases.push_back({ value }); } + for (const T value : structured_inputs()) + { + cases.push_back({ value }); + } std::mt19937_64 rng(seed); if (dom.log_magnitude) @@ -721,24 +798,34 @@ namespace ccm::test::oracle::unary_oracle for (std::size_t i = 0; i < count; ++i) { double value = std::ldexp(mant(rng), exp_dist(rng)); - if (both_signs && (rng() & 1U) != 0U) { value = -value; } - if (value >= dom.lo && value <= dom.hi) { cases.push_back({ static_cast(value) }); } + if (both_signs && (rng() & 1U) != 0U) + { + value = -value; + } + if (value >= dom.lo && value <= dom.hi) + { + cases.push_back({ static_cast(value) }); + } } - } - else + } else { const double lo = std::isfinite(dom.lo) ? dom.lo : -1.0e6; const double hi = std::isfinite(dom.hi) ? dom.hi : 1.0e6; std::uniform_real_distribution uniform(lo, hi); - for (std::size_t i = 0; i < count; ++i) { cases.push_back({ static_cast(uniform(rng)) }); } + for (std::size_t i = 0; i < count; ++i) + { + cases.push_back({ static_cast(uniform(rng)) }); + } } std::mt19937_64 bits(seed ^ 0x9E3779B97F4A7C15ULL); const std::size_t bit_pattern_count = count / 4 + 1; for (std::size_t i = 0; i < bit_pattern_count; ++i) { - if constexpr (std::is_same_v) { cases.push_back({ ccm::support::bit_cast(static_cast(bits())) }); } - else + if constexpr (std::is_same_v) + { + cases.push_back({ ccm::support::bit_cast(static_cast(bits())) }); + } else { cases.push_back({ ccm::support::bit_cast(bits()) }); } @@ -748,8 +835,7 @@ namespace ccm::test::oracle::unary_oracle // pow corpus: the targeted finite-result band and hard-case families plus broad bit patterns, // reusing the pow case generators from oracle_campaign_common.hpp. - template - inline std::vector> build_pow_corpus(std::uint64_t seed, std::size_t count) + template inline std::vector> build_pow_corpus(std::uint64_t seed, std::size_t count) { // Generate in chunks so the heavier pow_case intermediate (it carries a provenance string) is // never fully materialized at once. Only the compact array corpus is kept, which keeps @@ -766,24 +852,32 @@ namespace ccm::test::oracle::unary_oracle const std::size_t n = std::min(chunk_size, count - done); chunk.clear(); add_targeted_random_cases(chunk, seed + done, n, "pow targeted finite band"); - for (const auto& test_case : chunk) { cases.push_back({ test_case.base, test_case.exponent }); } + for (const auto & test_case : chunk) + { + cases.push_back({ test_case.base, test_case.exponent }); + } } for (std::size_t done = 0; done < random_count; done += chunk_size) { const std::size_t n = std::min(chunk_size, random_count - done); chunk.clear(); add_random_cases(chunk, (seed ^ 0x9E3779B97F4A7C15ULL) + done, n, "pow whole-range bit pattern"); - for (const auto& test_case : chunk) { cases.push_back({ test_case.base, test_case.exponent }); } + for (const auto & test_case : chunk) + { + cases.push_back({ test_case.base, test_case.exponent }); + } } return cases; } // True when value sits inside the closed domain [lo, hi]. NaN is never in a numeric region, and an // infinity counts only when the matching bound is infinite, which keeps structured edges sensible. - template - inline bool in_domain(const domain& dom, T value) + template inline bool in_domain(const domain & dom, T value) { - if (std::isnan(value)) { return false; } + if (std::isnan(value)) + { + return false; + } const double as_double = static_cast(value); return as_double >= dom.lo && as_double <= dom.hi; } @@ -792,8 +886,7 @@ namespace ccm::test::oracle::unary_oracle // shape for functions whose error is per-binade), otherwise the draw is uniform over [lo, hi]. The // log draw retries a few times to land inside [lo, hi] before clamping, so a tight clip stays honest // without risking an unbounded loop. - template - inline T draw_in_domain(const domain& dom, std::mt19937_64& rng) + template inline T draw_in_domain(const domain & dom, std::mt19937_64 & rng) { if (dom.log_magnitude) { @@ -803,8 +896,14 @@ namespace ccm::test::oracle::unary_oracle for (int attempt = 0; attempt < 8; ++attempt) { double value = std::ldexp(mant(rng), exp_dist(rng)); - if (both_signs && (rng() & 1U) != 0U) { value = -value; } - if (value >= dom.lo && value <= dom.hi) { return static_cast(value); } + if (both_signs && (rng() & 1U) != 0U) + { + value = -value; + } + if (value >= dom.lo && value <= dom.hi) + { + return static_cast(value); + } } return static_cast(std::min(std::max(0.0, dom.lo), dom.hi)); } @@ -819,7 +918,7 @@ namespace ccm::test::oracle::unary_oracle // confined and N == 1 the in-region structured edges are prepended; a confined sweep is region draws // only, which is what you want when aiming a deep sample at one band. template - inline std::vector> build_region_corpus(const std::array& domains, std::uint64_t seed, std::size_t count, bool confine) + inline std::vector> build_region_corpus(const std::array & domains, std::uint64_t seed, std::size_t count, bool confine) { std::vector> cases; cases.reserve(count + 32); @@ -829,17 +928,26 @@ namespace ccm::test::oracle::unary_oracle { for (const T value : structured_inputs()) { - if (in_domain(domains[0], value)) { cases.push_back({ value }); } + if (in_domain(domains[0], value)) + { + cases.push_back({ value }); + } } } } std::array rng; - for (std::size_t axis = 0; axis < N; ++axis) { rng[axis] = std::mt19937_64(seed + 0x9E3779B97F4A7C15ULL * (axis + 1)); } + for (std::size_t axis = 0; axis < N; ++axis) + { + rng[axis] = std::mt19937_64(seed + 0x9E3779B97F4A7C15ULL * (axis + 1)); + } for (std::size_t i = 0; i < count; ++i) { std::array input{}; - for (std::size_t axis = 0; axis < N; ++axis) { input[axis] = draw_in_domain(domains[axis], rng[axis]); } + for (std::size_t axis = 0; axis < N; ++axis) + { + input[axis] = draw_in_domain(domains[axis], rng[axis]); + } cases.push_back(input); } return cases; @@ -847,11 +955,12 @@ namespace ccm::test::oracle::unary_oracle // Bit pattern of a scalar as an unsigned integer, used as a total-order key for deduplication. // Keying on bits keeps -0.0 distinct from +0.0 and keeps distinct NaN payloads distinct. - template - inline auto canonical_bits(T value) + template inline auto canonical_bits(T value) { - if constexpr (sizeof(T) == 4) { return ccm::support::bit_cast(value); } - else + if constexpr (sizeof(T) == 4) + { + return ccm::support::bit_cast(value); + } else { return ccm::support::bit_cast(value); } @@ -861,24 +970,27 @@ namespace ccm::test::oracle::unary_oracle // input twice. Sorting by bit pattern and removing adjacent equals happens in place, so it adds no // memory beyond the corpus itself and scales to the largest samples. Corpus order does not affect // the measurement, so the reordering is harmless. Returns how many repeats were removed. - template - inline std::size_t deduplicate_cases(std::vector>& cases) + template inline std::size_t deduplicate_cases(std::vector> & cases) { - const auto less = [](const std::array& a, const std::array& b) - { + const auto less = [](const std::array & a, const std::array & b) { for (std::size_t i = 0; i < N; ++i) { const auto ka = canonical_bits(a[i]); const auto kb = canonical_bits(b[i]); - if (ka != kb) { return ka < kb; } + if (ka != kb) + { + return ka < kb; + } } return false; }; - const auto equal = [](const std::array& a, const std::array& b) - { + const auto equal = [](const std::array & a, const std::array & b) { for (std::size_t i = 0; i < N; ++i) { - if (canonical_bits(a[i]) != canonical_bits(b[i])) { return false; } + if (canonical_bits(a[i]) != canonical_bits(b[i])) + { + return false; + } } return true; }; @@ -892,8 +1004,7 @@ namespace ccm::test::oracle::unary_oracle // in decimal or 0x hex. This is the same shape --record-output writes, so a dump of hard cases from // one run replays directly, and external worst-case tables (Lefevre, CORE-MATH, RLIBM) drop straight // in. Malformed lines are skipped with a count on stderr. - template - inline std::vector> read_input_file(const std::string& path) + template inline std::vector> read_input_file(const std::string & path) { std::vector> cases; std::ifstream in(path); @@ -910,11 +1021,22 @@ namespace ccm::test::oracle::unary_oracle std::array values{}; std::size_t parsed = 0; double token = 0.0; - while (parsed < N && (stream >> token)) { values[parsed++] = static_cast(token); } - if (parsed == N) { cases.push_back(values); } - else if (!line.empty()) { ++skipped; } + while (parsed < N && (stream >> token)) + { + values[parsed++] = static_cast(token); + } + if (parsed == N) + { + cases.push_back(values); + } else if (!line.empty()) + { + ++skipped; + } + } + if (skipped != 0) + { + std::cerr << "skipped " << skipped << " malformed line(s) in " << path << '\n'; } - if (skipped != 0) { std::cerr << "skipped " << skipped << " malformed line(s) in " << path << '\n'; } return cases; } @@ -922,8 +1044,7 @@ namespace ccm::test::oracle::unary_oracle // The correctly-rounded result of a function near a seed is most likely to sit just over a rounding // boundary at an adjacent float, so probing neighbours of the known-interesting inputs is a cheap way // to catch hard-to-round cases the random draw misses. - template - inline void add_neighbor_probes(std::vector>& cases) + template inline void add_neighbor_probes(std::vector> & cases) { const std::size_t original = cases.size(); cases.reserve(original * (2 * N + 1)); @@ -947,34 +1068,57 @@ namespace ccm::test::oracle::unary_oracle // Read one axis domain from the command line, falling back to the per-function default. Each field // has an unindexed flag that applies to every axis and an indexed flag (--lo0, --hi1, ...) that wins // for that axis, so a unary sweep stays simple while pow can clip each argument independently. - inline domain parse_axis_domain(int argc, char** argv, std::size_t index, const domain& fallback) + inline domain parse_axis_domain(int argc, char ** argv, std::size_t index, const domain & fallback) { domain dom = fallback; const std::string idx = std::to_string(index); - const auto field = [&](const std::string& base) -> std::optional - { - if (auto indexed = option_value(argc, argv, base + idx + "=")) { return indexed; } + const auto field = [&](const std::string & base) -> std::optional { + if (auto indexed = option_value(argc, argv, base + idx + "=")) + { + return indexed; + } return option_value(argc, argv, base + "="); }; - if (const auto value = field("--lo")) { dom.lo = std::stod(*value); } - if (const auto value = field("--hi")) { dom.hi = std::stod(*value); } - if (const auto value = field("--min-exp")) { dom.min_exp = std::stoi(*value); } - if (const auto value = field("--max-exp")) { dom.max_exp = std::stoi(*value); } - if (const auto value = field("--scale")) { dom.log_magnitude = (*value == "log"); } + if (const auto value = field("--lo")) + { + dom.lo = std::stod(*value); + } + if (const auto value = field("--hi")) + { + dom.hi = std::stod(*value); + } + if (const auto value = field("--min-exp")) + { + dom.min_exp = std::stoi(*value); + } + if (const auto value = field("--max-exp")) + { + dom.max_exp = std::stoi(*value); + } + if (const auto value = field("--scale")) + { + dom.log_magnitude = (*value == "log"); + } return dom; } // True when any axis-domain flag is present, which switches the corpus from the per-function default // builder over to the confined region builder. - inline bool domain_flags_present(int argc, char** argv, std::size_t arity) + inline bool domain_flags_present(int argc, char ** argv, std::size_t arity) { - static const char* const bases[] = { "--lo", "--hi", "--min-exp", "--max-exp", "--scale" }; - for (const char* base : bases) + static const char * const bases[] = { "--lo", "--hi", "--min-exp", "--max-exp", "--scale" }; + for (const char * base : bases) { - if (option_value(argc, argv, std::string(base) + "=")) { return true; } + if (option_value(argc, argv, std::string(base) + "=")) + { + return true; + } for (std::size_t index = 0; index < arity; ++index) { - if (option_value(argc, argv, std::string(base) + std::to_string(index) + "=")) { return true; } + if (option_value(argc, argv, std::string(base) + std::to_string(index) + "=")) + { + return true; + } } } return false; @@ -993,7 +1137,7 @@ namespace ccm::test::oracle::unary_oracle // covering interval(s): non-negative floats are monotone in their bit pattern, negatives are monotone // in reverse, and a range straddling zero becomes one interval per side. With no clip it is the whole // space. - inline std::vector float_exhaustive_intervals(int argc, char** argv, const domain& axis0) + inline std::vector float_exhaustive_intervals(int argc, char ** argv, const domain & axis0) { if (const auto raw = option_value(argc, argv, "--exhaustive-range=")) { @@ -1008,8 +1152,14 @@ namespace ccm::test::oracle::unary_oracle { const float lo = static_cast(axis0.lo); const float hi = static_cast(axis0.hi); - if (lo >= 0.0F) { return { bit_interval{ to_bits(lo), to_bits(hi) + 1 } }; } - if (hi <= 0.0F) { return { bit_interval{ to_bits(hi), to_bits(lo) + 1 } }; } + if (lo >= 0.0F) + { + return { bit_interval{ to_bits(lo), to_bits(hi) + 1 } }; + } + if (hi <= 0.0F) + { + return { bit_interval{ to_bits(hi), to_bits(lo) + 1 } }; + } return { bit_interval{ to_bits(-0.0F), to_bits(lo) + 1 }, bit_interval{ to_bits(0.0F), to_bits(hi) + 1 } }; } @@ -1018,10 +1168,13 @@ namespace ccm::test::oracle::unary_oracle inline std::string format_magnitude(std::uint64_t value) { - if (value < 1000) { return std::to_string(value); } - static const char* const suffix[] = { "", "K", "M", "B", "T" }; - double scaled = static_cast(value); - std::size_t step = 0; + if (value < 1000) + { + return std::to_string(value); + } + static const char * const suffix[] = { "", "K", "M", "B", "T" }; + double scaled = static_cast(value); + std::size_t step = 0; while (scaled >= 1000.0 && step + 1 < std::size(suffix)) { scaled /= 1000.0; @@ -1035,10 +1188,16 @@ namespace ccm::test::oracle::unary_oracle inline std::string format_duration(double seconds) { std::ostringstream out; - if (seconds < 90.0) { out << static_cast(seconds) << 's'; } - else if (seconds < 5400.0) { out << std::fixed << std::setprecision(1) << seconds / 60.0 << 'm'; } - else if (seconds < 172800.0) { out << std::fixed << std::setprecision(1) << seconds / 3600.0 << 'h'; } - else + if (seconds < 90.0) + { + out << static_cast(seconds) << 's'; + } else if (seconds < 5400.0) + { + out << std::fixed << std::setprecision(1) << seconds / 60.0 << 'm'; + } else if (seconds < 172800.0) + { + out << std::fixed << std::setprecision(1) << seconds / 3600.0 << 'h'; + } else { out << std::fixed << std::setprecision(1) << seconds / 86400.0 << 'd'; } @@ -1049,21 +1208,26 @@ namespace ccm::test::oracle::unary_oracle // dispatched, so it runs a touch ahead of cases finished) and reports percent, throughput, and an // ETA every couple of seconds. Construction starts the thread, destruction stops and joins it, so a // pass scopes the ticker around its worker block. - template - class progress_ticker + template class progress_ticker { public: - progress_ticker(const std::atomic& counter, std::uint64_t chunk, std::uint64_t total, std::string label) - : counter_(counter), chunk_(chunk), total_(total), label_(std::move(label)) + progress_ticker(const std::atomic & counter, std::uint64_t chunk, std::uint64_t total, std::string label) + : counter_(counter), + chunk_(chunk), + total_(total), + label_(std::move(label)) { worker_ = std::thread([this]() { run(); }); } - progress_ticker(const progress_ticker&) = delete; - progress_ticker& operator=(const progress_ticker&) = delete; + progress_ticker(const progress_ticker &) = delete; + progress_ticker & operator=(const progress_ticker &) = delete; ~progress_ticker() { stop_.store(true, std::memory_order_relaxed); - if (worker_.joinable()) { worker_.join(); } + if (worker_.joinable()) + { + worker_.join(); + } } private: @@ -1073,8 +1237,14 @@ namespace ccm::test::oracle::unary_oracle bool printed = false; while (!stop_.load(std::memory_order_relaxed)) { - for (int i = 0; i < 20 && !stop_.load(std::memory_order_relaxed); ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - if (stop_.load(std::memory_order_relaxed)) { break; } + for (int i = 0; i < 20 && !stop_.load(std::memory_order_relaxed); ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (stop_.load(std::memory_order_relaxed)) + { + break; + } const std::uint64_t done = std::min(static_cast(counter_.load(std::memory_order_relaxed)) * chunk_, total_); const double seconds = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); const double rate = seconds > 0.0 ? static_cast(done) / seconds : 0.0; @@ -1085,10 +1255,13 @@ namespace ccm::test::oracle::unary_oracle << " " << std::flush; printed = true; } - if (printed) { std::cerr << '\n'; } + if (printed) + { + std::cerr << '\n'; + } } - const std::atomic& counter_; + const std::atomic & counter_; std::uint64_t chunk_; std::uint64_t total_; std::string label_; @@ -1101,18 +1274,21 @@ namespace ccm::test::oracle::unary_oracle // the exhaustive sweep uses. The MPFR default rounding mode is set once per pass and only read by // the workers, and each worker sets its own hardware rounding mode. template - inline campaign_summary run_sampled(const std::vector>& cases, - const std::vector& rounding_modes, + inline campaign_summary run_sampled(const std::vector> & cases, + const std::vector & rounding_modes, mpfr_prec_t precision, std::uint64_t max_ulp, unsigned int thread_count, KernelFn kernel, RefFn reference, StdFn std_reference, - threshold_recorder* recorder, + threshold_recorder * recorder, bool show_progress) { - if (thread_count == 0U) { thread_count = 1U; } + if (thread_count == 0U) + { + thread_count = 1U; + } constexpr std::size_t chunk_size = std::size_t{ 1 } << 14; const std::size_t total_cases = cases.size(); @@ -1137,28 +1313,38 @@ namespace ccm::test::oracle::unary_oracle for (unsigned int t = 0; t < thread_count; ++t) { - workers.emplace_back( - [&, t]() + workers.emplace_back([&, t]() { + ccm::test::ScopedRoundingMode hardware(mode); + if (!hardware.active()) + { + return; + } + const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); + campaign_summary & summary = partials[t]; + for (;;) { - ccm::test::ScopedRoundingMode hardware(mode); - if (!hardware.active()) { return; } - const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); - campaign_summary& summary = partials[t]; - for (;;) + const std::size_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; + if (begin >= total_cases) { - const std::size_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; - if (begin >= total_cases) { break; } - const std::size_t end = std::min(begin + chunk_size, total_cases); - for (std::size_t i = begin; i < end; ++i) - { - evaluate_case(cases[i], kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); - } + break; + } + const std::size_t end = std::min(begin + chunk_size, total_cases); + for (std::size_t i = begin; i < end; ++i) + { + evaluate_case(cases[i], kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); } - }); + } + }); + } + for (auto & worker : workers) + { + worker.join(); } - for (auto& worker : workers) { worker.join(); } ticker.reset(); - for (const auto& partial : partials) { merge_summary(total, partial); } + for (const auto & partial : partials) + { + merge_summary(total, partial); + } } mpfr_set_default_rounding_mode(saved_default); @@ -1170,31 +1356,39 @@ namespace ccm::test::oracle::unary_oracle // shared counter, so every core stays busy on the expensive band whether the sweep is the whole // 2^32 or a single targeted sub-range. template - inline campaign_summary run_exhaustive_float(const std::vector& intervals, - const std::vector& rounding_modes, + inline campaign_summary run_exhaustive_float(const std::vector & intervals, + const std::vector & rounding_modes, mpfr_prec_t precision, std::uint64_t max_ulp, unsigned int thread_count, KernelFn kernel, RefFn reference, StdFn std_reference, - threshold_recorder* recorder, + threshold_recorder * recorder, bool show_progress) { - if (thread_count == 0U) { thread_count = 1U; } + if (thread_count == 0U) + { + thread_count = 1U; + } constexpr std::uint64_t chunk_size = std::uint64_t{ 1 } << 20; std::uint64_t total_patterns = 0; - for (const bit_interval& interval : intervals) { total_patterns += interval.end - interval.begin; } + for (const bit_interval & interval : intervals) + { + total_patterns += interval.end - interval.begin; + } // Map a flat index across all intervals back to its bit pattern. The interval count is one or two // in practice, so the linear walk is negligible next to an MPFR reference call. - const auto pattern_at = [&intervals](std::uint64_t index) -> std::uint32_t - { - for (const bit_interval& interval : intervals) + const auto pattern_at = [&intervals](std::uint64_t index) -> std::uint32_t { + for (const bit_interval & interval : intervals) { const std::uint64_t span = interval.end - interval.begin; - if (index < span) { return static_cast(interval.begin + index); } + if (index < span) + { + return static_cast(interval.begin + index); + } index -= span; } return 0; @@ -1221,31 +1415,44 @@ namespace ccm::test::oracle::unary_oracle for (unsigned int t = 0; t < thread_count; ++t) { - workers.emplace_back( - [&, t]() + workers.emplace_back([&, t]() { + ccm::test::ScopedRoundingMode hardware(mode); + if (!hardware.active()) + { + return; + } + const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); + campaign_summary & summary = partials[t]; + for (;;) { - ccm::test::ScopedRoundingMode hardware(mode); - if (!hardware.active()) { return; } - const mpfr_rnd_t rounding = mpfr_get_default_rounding_mode(); - campaign_summary& summary = partials[t]; - for (;;) + const std::uint64_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; + if (begin >= total_patterns) { - const std::uint64_t begin = next_chunk.fetch_add(1, std::memory_order_relaxed) * chunk_size; - if (begin >= total_patterns) { break; } - const std::uint64_t end = std::min(begin + chunk_size, total_patterns); - for (std::uint64_t index = begin; index < end; ++index) + break; + } + const std::uint64_t end = std::min(begin + chunk_size, total_patterns); + for (std::uint64_t index = begin; index < end; ++index) + { + const float input = ccm::support::bit_cast(pattern_at(index)); + if (std::isnan(input)) { - const float input = ccm::support::bit_cast(pattern_at(index)); - if (std::isnan(input)) { continue; } - evaluate_case( - std::array{ input }, kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); + continue; } + evaluate_case( + std::array{ input }, kernel, reference, std_reference, precision, rounding, max_ulp, summary, recorder); } - }); + } + }); + } + for (auto & worker : workers) + { + worker.join(); } - for (auto& worker : workers) { worker.join(); } ticker.reset(); - for (const auto& partial : partials) { merge_summary(total, partial); } + for (const auto & partial : partials) + { + merge_summary(total, partial); + } } mpfr_set_default_rounding_mode(saved_default); @@ -1254,13 +1461,13 @@ namespace ccm::test::oracle::unary_oracle template inline int run_campaign(int argc, - char** argv, + char ** argv, std::string_view function_name, KernelFn kernel, RefFn reference, StdFn std_reference, CorpusFn build_corpus, - const std::array& default_domains, + const std::array & default_domains, std::vector> structured_seed, std::string_view implementation = "gen") { @@ -1274,36 +1481,42 @@ namespace ccm::test::oracle::unary_oracle const bool probe_neighbors = has_flag(argc, argv, "--probe-neighbors"); const auto input_file = option_value(argc, argv, "--input-file="); const std::uint64_t seed = - parse_option_or(option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xCCA77811ULL); + parse_option_or(option_value(argc, argv, "--seed="), [](const std::string & value) { return std::stoull(value); }, 0xCCA77811ULL); const std::uint64_t max_ulp = parse_option_or( - option_value(argc, argv, "--max-ulp="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 4); + option_value(argc, argv, "--max-ulp="), [](const std::string & value) { return static_cast(std::stoull(value)); }, 4); const mpfr_prec_t precision = parse_option_or( option_value(argc, argv, "--oracle-precision="), - [](const std::string& value) { return static_cast(std::stoul(value)); }, + [](const std::string & value) { return static_cast(std::stoul(value)); }, default_precision()); const std::size_t count = parse_option_or( - option_value(argc, argv, "--count="), [](const std::string& value) { return static_cast(std::stoull(value)); }, sampled_count(mode)); + option_value(argc, argv, "--count="), [](const std::string & value) { return static_cast(std::stoull(value)); }, sampled_count(mode)); const unsigned int thread_count = parse_option_or( option_value(argc, argv, "--threads="), - [](const std::string& value) { return static_cast(std::stoul(value)); }, + [](const std::string & value) { return static_cast(std::stoul(value)); }, std::max(1U, std::thread::hardware_concurrency())); const std::uint64_t record_threshold = parse_option_or( - option_value(argc, argv, "--threshold="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 1); + option_value(argc, argv, "--threshold="), [](const std::string & value) { return static_cast(std::stoull(value)); }, 1); const auto record_output = option_value(argc, argv, "--record-output="); // Per-argument domains: start from the function defaults, then let the command line clip any axis // (--lo/--hi/--scale/--min-exp/--max-exp, optionally suffixed with the argument index). Any such // flag, or --confine, switches the corpus to the confined region builder for every arity. std::array domains; - for (std::size_t axis = 0; axis < N; ++axis) { domains[axis] = parse_axis_domain(argc, argv, axis, default_domains[axis]); } + for (std::size_t axis = 0; axis < N; ++axis) + { + domains[axis] = parse_axis_domain(argc, argv, axis, default_domains[axis]); + } const bool region_mode = confine || domain_flags_present(argc, argv, N); // Optional sink for the inputs whose ULP distance reaches --threshold (default 1), written to // the --record-output text file one case per line as they are found. Omit --record-output and // no record file is written, the same way omitting --log-output writes no summary JSON. std::unique_ptr recorder; - if (record_output.has_value()) { recorder = std::make_unique(*record_output, record_threshold); } - threshold_recorder* recorder_ptr = recorder.get(); + if (record_output.has_value()) + { + recorder = std::make_unique(*record_output, record_threshold); + } + threshold_recorder * recorder_ptr = recorder.get(); const bool run_exhaustive = exhaustive_flag && N == 1 && std::is_same_v; @@ -1312,13 +1525,19 @@ namespace ccm::test::oracle::unary_oracle // when --probe-neighbors is set. A targeted region sweep skips the global seed but still honours // an explicit --input-file. The exhaustive sweep already covers these inputs and uses none. std::vector> seeded; - if (!region_mode) { seeded = std::move(structured_seed); } + if (!region_mode) + { + seeded = std::move(structured_seed); + } if (input_file.has_value()) { auto file_cases = read_input_file(*input_file); seeded.insert(seeded.end(), file_cases.begin(), file_cases.end()); } - if (probe_neighbors && !seeded.empty()) { add_neighbor_probes(seeded); } + if (probe_neighbors && !seeded.empty()) + { + add_neighbor_probes(seeded); + } std::vector intervals; std::string corpus = region_mode ? (confine ? "confined region" : "region") : "sampled"; @@ -1333,23 +1552,28 @@ namespace ccm::test::oracle::unary_oracle << " modes=" << rounding_modes.size() << " precision=" << precision << " threads=" << thread_count << '\n'; if (run_exhaustive) { - for (const bit_interval& interval : intervals) + for (const bit_interval & interval : intervals) { std::cout << " sweep [0x" << std::hex << interval.begin << ", 0x" << (interval.end - 1) << std::dec << "] " << (interval.end - interval.begin) << " patterns\n"; } - } - else if (region_mode) + } else if (region_mode) { for (std::size_t axis = 0; axis < N; ++axis) { std::cout << " arg" << axis << " lo=" << domains[axis].lo << " hi=" << domains[axis].hi << " scale=" << (domains[axis].log_magnitude ? "log" : "linear"); - if (domains[axis].log_magnitude) { std::cout << " exp=[" << domains[axis].min_exp << ", " << domains[axis].max_exp << "]"; } + if (domains[axis].log_magnitude) + { + std::cout << " exp=[" << domains[axis].min_exp << ", " << domains[axis].max_exp << "]"; + } std::cout << '\n'; } } - if (recorder_ptr != nullptr) { std::cout << "recording inputs with ulp >= " << record_threshold << " to " << *record_output << '\n'; } + if (recorder_ptr != nullptr) + { + std::cout << "recording inputs with ulp >= " << record_threshold << " to " << *record_output << '\n'; + } if (!seeded.empty() && !run_exhaustive) { std::cout << "seeded cases: " << seeded.size() << (probe_neighbors ? " (with neighbour probes)" : "") << '\n'; @@ -1358,17 +1582,22 @@ namespace ccm::test::oracle::unary_oracle const auto started = std::chrono::steady_clock::now(); campaign_summary summary; - const auto sample = [&]() - { + const auto sample = [&]() { auto cases = region_mode ? build_region_corpus(domains, seed, count, confine) : build_corpus(seed, count); - if (!seeded.empty()) { cases.insert(cases.end(), seeded.begin(), seeded.end()); } + if (!seeded.empty()) + { + cases.insert(cases.end(), seeded.begin(), seeded.end()); + } // A random sweep can draw the same input more than once and across the structured edges, so // drop exact-bit repeats unless --allow-duplicates is given. The exhaustive sweep is unique // already and never reaches here. if (!allow_duplicates) { const std::size_t removed = deduplicate_cases(cases); - if (removed != 0) { std::cout << "deduplicated: removed " << removed << " repeat input" << (removed == 1 ? "" : "s") << '\n'; } + if (removed != 0) + { + std::cout << "deduplicated: removed " << removed << " repeat input" << (removed == 1 ? "" : "s") << '\n'; + } } return run_sampled(cases, rounding_modes, precision, max_ulp, thread_count, kernel, reference, std_reference, recorder_ptr, show_progress); }; @@ -1379,13 +1608,11 @@ namespace ccm::test::oracle::unary_oracle { summary = run_exhaustive_float( intervals, rounding_modes, precision, max_ulp, thread_count, kernel, reference, std_reference, recorder_ptr, show_progress); - } - else + } else { summary = sample(); } - } - else + } else { summary = sample(); } @@ -1436,7 +1663,10 @@ namespace ccm::test::oracle::unary_oracle std::cout << "summary: " << *output_path << '\n'; } - if (summary.failure_count != 0) { std::cout << "reference disagreements (inf/zero/overflow/ulp ceiling): " << summary.failure_count << '\n'; } + if (summary.failure_count != 0) + { + std::cout << "reference disagreements (inf/zero/overflow/ulp ceiling): " << summary.failure_count << '\n'; + } return summary.failure_count == 0 ? 0 : 1; } } // namespace ccm::test::oracle::unary_oracle diff --git a/tests/shared/oracle/oracle_campaign_common.hpp b/tests/shared/oracle/oracle_campaign_common.hpp index de5cefb1..8dc51aa5 100644 --- a/tests/shared/oracle/oracle_campaign_common.hpp +++ b/tests/shared/oracle/oracle_campaign_common.hpp @@ -28,19 +28,16 @@ namespace ccm::test::oracle release, }; - template - struct pow_case + template struct pow_case { T base; T exponent; std::string provenance; }; - template - using binary_case = pow_case; + template using binary_case = pow_case; - template - struct failure_record + template struct failure_record { std::string function_name; std::string input_type; @@ -72,8 +69,7 @@ namespace ccm::test::oracle std::string event_kind; }; - template - struct run_summary + template struct run_summary { std::size_t case_count = 0; std::size_t skipped_count = 0; @@ -93,8 +89,7 @@ namespace ccm::test::oracle T worst_expected{}; }; - template - struct campaign_report + template struct campaign_report { std::string configuration_name; std::string path; @@ -128,24 +123,28 @@ namespace ccm::test::oracle { switch (std::fegetround()) { - case FE_TONEAREST: return "FE_TONEAREST"; - case FE_UPWARD: return "FE_UPWARD"; - case FE_DOWNWARD: return "FE_DOWNWARD"; + case FE_TONEAREST : return "FE_TONEAREST"; + case FE_UPWARD : return "FE_UPWARD"; + case FE_DOWNWARD : return "FE_DOWNWARD"; case FE_TOWARDZERO: return "FE_TOWARDZERO"; - default: return "unknown"; + default : return "unknown"; } } inline std::string configuration_name() - { return pow_configuration_name(); } + { + return pow_configuration_name(); + } - template - inline constexpr const char* builtin_status() - { return pow_builtin_status(); } + template inline constexpr const char * builtin_status() + { + return pow_builtin_status(); + } - template - inline bool is_modeled_generic_pow_case(T base, T exponent) - { return std::isfinite(base) && std::isfinite(exponent) && base > T{}; } + template inline bool is_modeled_generic_pow_case(T base, T exponent) + { + return std::isfinite(base) && std::isfinite(exponent) && base > T{}; + } inline bool uses_public_mpfr_oracle([[maybe_unused]] ccm::test::pow_path::validation_path path) { @@ -158,44 +157,63 @@ namespace ccm::test::oracle inline std::string oracle_policy_name(ccm::test::pow_path::validation_path path) { - if (uses_public_mpfr_oracle(path)) { return "mpfr_full"; } + if (uses_public_mpfr_oracle(path)) + { + return "mpfr_full"; + } return "kernel_finite_mpfr_with_std_exceptional"; } inline std::string coremath_oracle_policy_name() - { return "coremath_cr_finite_all_modes"; } + { + return "coremath_cr_finite_all_modes"; + } - template - inline std::optional kernel_path_skip_reason(ccm::test::pow_path::validation_path path, T base, T exponent) + template inline std::optional kernel_path_skip_reason(ccm::test::pow_path::validation_path path, T base, T exponent) { - if (uses_public_mpfr_oracle(path)) { return std::nullopt; } + if (uses_public_mpfr_oracle(path)) + { + return std::nullopt; + } if (path == ccm::test::pow_path::validation_path::generic_modeled_domain) { - if (!is_modeled_generic_pow_case(base, exponent)) { return "outside modeled generic positive finite domain"; } + if (!is_modeled_generic_pow_case(base, exponent)) + { + return "outside modeled generic positive finite domain"; + } return std::nullopt; } - if (!is_modeled_generic_pow_case(base, exponent)) { return "exceptional input outside generic kernel finite domain"; } + if (!is_modeled_generic_pow_case(base, exponent)) + { + return "exceptional input outside generic kernel finite domain"; + } return std::nullopt; } - template - inline bool is_coremath_oracle_case(T base, T exponent) + template inline bool is_coremath_oracle_case(T base, T exponent) { - if (!std::isfinite(base) || !std::isfinite(exponent)) { return false; } - if (base == T{} || exponent == T{}) { return false; } - if (base < T{} && static_cast(std::trunc(static_cast(exponent))) != exponent) { return false; } + if (!std::isfinite(base) || !std::isfinite(exponent)) + { + return false; + } + if (base == T{} || exponent == T{}) + { + return false; + } + if (base < T{} && static_cast(std::trunc(static_cast(exponent))) != exponent) + { + return false; + } return true; } - template - inline bool oracle_fp_bits_match(T actual, T expected) + template inline bool oracle_fp_bits_match(T actual, T expected) { using fp_bits = ccm::support::fp::FPBits; return fp_bits(actual).uintval() == fp_bits(expected).uintval(); } - template - inline void record_worst_case(run_summary& summary, std::uint64_t ulp_distance, T base, T exponent, T actual, T expected) + template inline void record_worst_case(run_summary & summary, std::uint64_t ulp_distance, T base, T exponent, T actual, T expected) { if (ulp_distance >= summary.max_observed_ulp) { @@ -254,28 +272,30 @@ namespace ccm::test::oracle }; } - template - inline bool is_hard_oracle_failure(const failure_record& event) - { return event.event_kind == "mpfr_hard_failure" || event.event_kind == "coremath_bit_mismatch"; } + template inline bool is_hard_oracle_failure(const failure_record & event) + { + return event.event_kind == "mpfr_hard_failure" || event.event_kind == "coremath_bit_mismatch"; + } - template - inline bool has_hard_oracle_failure(const std::vector>& events) + template inline bool has_hard_oracle_failure(const std::vector> & events) { - for (const auto& event : events) + for (const auto & event : events) { - if (is_hard_oracle_failure(event)) { return true; } + if (is_hard_oracle_failure(event)) + { + return true; + } } return false; } - template - inline void write_failure_json(const std::string& output_path, const std::vector>& failures) + template inline void write_failure_json(const std::string & output_path, const std::vector> & failures) { std::ofstream out(output_path, std::ios::trunc); out << "[\n"; for (std::size_t i = 0; i < failures.size(); ++i) { - const auto& failure = failures[i]; + const auto & failure = failures[i]; out << " {\n"; out << " \"function_name\": \"" << json_escape(failure.function_name) << "\",\n"; out << " \"input_type\": \"" << json_escape(failure.input_type) << "\",\n"; @@ -307,12 +327,14 @@ namespace ccm::test::oracle // Echo hard-failure records to stdout so CI logs surface the offending input without an // uploaded artifact. The JSON event log keeps the full record. This is the at-a-glance view. - template - inline void print_hard_failures(const std::vector>& events) + template inline void print_hard_failures(const std::vector> & events) { - for (const auto& event : events) + for (const auto & event : events) { - if (!is_hard_oracle_failure(event)) { continue; } + if (!is_hard_oracle_failure(event)) + { + continue; + } std::cout << "HARD FAILURE " << event.event_kind << " fn=" << event.function_name << " path=" << event.path << " rounding=" << event.rounding_mode << " fma=" << event.fma_enabled << " base=" << event.base_bits << " exponent=" << event.exponent_bits << " actual=" << event.actual_bits << " expected=" << event.expected_bits << " ulp=" << event.ulp_distance << " provenance=\"" << event.provenance << "\" notes=\"" @@ -320,41 +342,63 @@ namespace ccm::test::oracle } } - inline std::optional option_value(int argc, char** argv, std::string_view prefix) + inline std::optional option_value(int argc, char ** argv, std::string_view prefix) { for (int i = 1; i < argc; ++i) { const std::string_view arg(argv[i]); - if (arg.rfind(prefix, 0) == 0) { return std::string(arg.substr(prefix.size())); } + if (arg.rfind(prefix, 0) == 0) + { + return std::string(arg.substr(prefix.size())); + } } return std::nullopt; } - inline std::optional resolve_event_log_path(int argc, char** argv) + inline std::optional resolve_event_log_path(int argc, char ** argv) { - if (const auto log_output = option_value(argc, argv, "--log-output=")) { return *log_output; } - if (const auto json_output = option_value(argc, argv, "--json-output=")) { return *json_output; } + if (const auto log_output = option_value(argc, argv, "--log-output=")) + { + return *log_output; + } + if (const auto json_output = option_value(argc, argv, "--json-output=")) + { + return *json_output; + } return std::nullopt; } - inline bool has_flag(int argc, char** argv, std::string_view flag) + inline bool has_flag(int argc, char ** argv, std::string_view flag) { for (int i = 1; i < argc; ++i) { - if (std::string_view(argv[i]) == flag) { return true; } + if (std::string_view(argv[i]) == flag) + { + return true; + } } return false; } - template - inline T parse_option_or(const std::optional& raw_value, Parser parser, T fallback) - { return raw_value.has_value() ? parser(*raw_value) : fallback; } + template inline T parse_option_or(const std::optional & raw_value, Parser parser, T fallback) + { + return raw_value.has_value() ? parser(*raw_value) : fallback; + } - inline campaign_mode parse_mode(const std::optional& raw_mode) + inline campaign_mode parse_mode(const std::optional & raw_mode) { - if (!raw_mode.has_value() || *raw_mode == "quick") { return campaign_mode::quick; } - if (*raw_mode == "extended") { return campaign_mode::extended; } - if (*raw_mode == "release" || *raw_mode == "full") { return campaign_mode::release; } + if (!raw_mode.has_value() || *raw_mode == "quick") + { + return campaign_mode::quick; + } + if (*raw_mode == "extended") + { + return campaign_mode::extended; + } + if (*raw_mode == "release" || *raw_mode == "full") + { + return campaign_mode::release; + } return campaign_mode::quick; } @@ -362,15 +406,15 @@ namespace ccm::test::oracle { switch (mode) { - case campaign_mode::quick: return "quick"; + case campaign_mode::quick : return "quick"; case campaign_mode::extended: return "extended"; - case campaign_mode::full: - case campaign_mode::release: return "release"; + case campaign_mode::full : + case campaign_mode::release : return "release"; } return "quick"; } - inline std::vector parse_paths(int argc, char** argv) + inline std::vector parse_paths(int argc, char ** argv) { std::vector paths; const auto raw = option_value(argc, argv, "--path="); @@ -381,19 +425,31 @@ namespace ccm::test::oracle { const std::size_t comma = view.find(','); const std::string_view token = view.substr(0, comma); - if (auto parsed = ccm::test::pow_path::parse_path(token)) { paths.push_back(*parsed); } - if (comma == std::string_view::npos) { break; } + if (auto parsed = ccm::test::pow_path::parse_path(token)) + { + paths.push_back(*parsed); + } + if (comma == std::string_view::npos) + { + break; + } view.remove_prefix(comma + 1); } } - if (paths.empty()) { paths.push_back(ccm::test::pow_path::validation_path::public_default); } + if (paths.empty()) + { + paths.push_back(ccm::test::pow_path::validation_path::public_default); + } return paths; } - inline std::vector parse_rounding_modes(int argc, char** argv) + inline std::vector parse_rounding_modes(int argc, char ** argv) { const auto raw = option_value(argc, argv, "--rounding-modes="); - if (!raw.has_value() || *raw == "all") { return { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; } + if (!raw.has_value() || *raw == "all") + { + return { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; + } std::vector modes; std::string_view view = *raw; @@ -401,19 +457,33 @@ namespace ccm::test::oracle { const std::size_t comma = view.find(','); const std::string_view token = view.substr(0, comma); - if (token == "nearest" || token == "FE_TONEAREST") { modes.push_back(FE_TONEAREST); } - else if (token == "upward" || token == "FE_UPWARD") { modes.push_back(FE_UPWARD); } - else if (token == "downward" || token == "FE_DOWNWARD") { modes.push_back(FE_DOWNWARD); } - else if (token == "towardzero" || token == "FE_TOWARDZERO") { modes.push_back(FE_TOWARDZERO); } - if (comma == std::string_view::npos) { break; } + if (token == "nearest" || token == "FE_TONEAREST") + { + modes.push_back(FE_TONEAREST); + } else if (token == "upward" || token == "FE_UPWARD") + { + modes.push_back(FE_UPWARD); + } else if (token == "downward" || token == "FE_DOWNWARD") + { + modes.push_back(FE_DOWNWARD); + } else if (token == "towardzero" || token == "FE_TOWARDZERO") + { + modes.push_back(FE_TOWARDZERO); + } + if (comma == std::string_view::npos) + { + break; + } view.remove_prefix(comma + 1); } - if (modes.empty()) { modes = { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; } + if (modes.empty()) + { + modes = { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; + } return modes; } - template - inline void write_campaign_summary_json(const std::string& output_path, const campaign_report& report) + template inline void write_campaign_summary_json(const std::string & output_path, const campaign_report & report) { std::ofstream out(output_path, std::ios::trunc); out << "{\n"; @@ -430,14 +500,20 @@ namespace ccm::test::oracle for (std::size_t i = 0; i < report.domains_covered.size(); ++i) { out << "\"" << json_escape(report.domains_covered[i]) << "\""; - if (i + 1 < report.domains_covered.size()) { out << ", "; } + if (i + 1 < report.domains_covered.size()) + { + out << ", "; + } } out << "],\n"; out << " \"domains_skipped\": ["; for (std::size_t i = 0; i < report.domains_skipped.size(); ++i) { out << "\"" << json_escape(report.domains_skipped[i]) << "\""; - if (i + 1 < report.domains_skipped.size()) { out << ", "; } + if (i + 1 < report.domains_skipped.size()) + { + out << ", "; + } } out << "],\n"; out << " \"case_count\": " << report.case_count << ",\n"; @@ -462,11 +538,11 @@ namespace ccm::test::oracle inline campaign_report make_campaign_report(std::string_view path_name, ccm::test::pow_path::validation_path path, campaign_mode mode, - const run_summary& summary, + const run_summary & summary, std::uint64_t seed, std::uint64_t elapsed_ms, - const std::vector& domains_covered, - const std::vector& domains_skipped) + const std::vector & domains_covered, + const std::vector & domains_skipped) { return campaign_report{ configuration_name(), @@ -501,11 +577,11 @@ namespace ccm::test::oracle template inline campaign_report make_coremath_campaign_report(std::string_view path_name, campaign_mode mode, - const run_summary& summary, + const run_summary & summary, std::uint64_t seed, std::uint64_t elapsed_ms, - const std::vector& domains_covered, - const std::vector& domains_skipped) + const std::vector & domains_covered, + const std::vector & domains_skipped) { return campaign_report{ configuration_name(), @@ -539,7 +615,7 @@ namespace ccm::test::oracle template inline void run_path_campaign(ccm::test::pow_path::validation_path path, - run_summary& summary, + run_summary & summary, std::string_view summary_prefix, ExecuteFn execute_cases, ReportFn build_report, @@ -561,8 +637,7 @@ namespace ccm::test::oracle print_report(report, summary_path); } - template - inline void add_random_cases(std::vector>& cases, std::uint64_t seed, std::size_t count, const char* provenance) + template inline void add_random_cases(std::vector> & cases, std::uint64_t seed, std::size_t count, const char * provenance) { std::mt19937_64 rng(seed); for (std::size_t i = 0; i < count; ++i) @@ -576,8 +651,7 @@ namespace ccm::test::oracle ccm::support::bit_cast(exp_bits), provenance, }); - } - else + } else { cases.push_back(pow_case{ ccm::support::bit_cast(rng()), @@ -595,7 +669,7 @@ namespace ccm::test::oracle // near-one, far-from-one-with-integer-exponent, and negative-base families that previously // hid faithful-but-not-correctly-rounded results. template - inline void add_targeted_random_cases(std::vector>& cases, std::uint64_t seed, std::size_t count, const char* provenance) + inline void add_targeted_random_cases(std::vector> & cases, std::uint64_t seed, std::size_t count, const char * provenance) { using uint_t = std::conditional_t, std::uint32_t, std::uint64_t>; @@ -616,11 +690,17 @@ namespace ccm::test::oracle for (std::size_t i = 0; i < per; ++i) { T base = static_cast(std::ldexp(unit(rng), base_exp(rng))); - if (rng() & 1U) { base = -base; } + if (rng() & 1U) + { + base = -base; + } const double l2 = std::log2(std::abs(static_cast(base))) + 1e-300; std::uniform_real_distribution yd(-static_cast(max_exp) / std::abs(l2), static_cast(max_exp) / std::abs(l2)); T exponent = static_cast(yd(rng)); - if (base < static_cast(0)) { exponent = std::trunc(exponent); } // negative base needs integer exponent + if (base < static_cast(0)) + { + exponent = std::trunc(exponent); + } // negative base needs integer exponent add(base, exponent); } @@ -639,10 +719,16 @@ namespace ccm::test::oracle for (std::size_t i = 0; i < per; ++i) { T base = static_cast(std::ldexp(unit(rng), base_exp(rng) / 2)); - if (rng() & 1U) { base = -base; } + if (rng() & 1U) + { + base = -base; + } std::uniform_int_distribution nd(-700, 700); T exponent = static_cast(nd(rng)); - if ((rng() & 1U) && base > static_cast(0)) { exponent += static_cast(0.5); } // half-integer only for base > 0 + if ((rng() & 1U) && base > static_cast(0)) + { + exponent += static_cast(0.5); + } // half-integer only for base > 0 add(base, exponent); } diff --git a/tests/shared/oracle/oracle_campaign_ternary.hpp b/tests/shared/oracle/oracle_campaign_ternary.hpp index bb042add..f1667d5b 100644 --- a/tests/shared/oracle/oracle_campaign_ternary.hpp +++ b/tests/shared/oracle/oracle_campaign_ternary.hpp @@ -9,18 +9,18 @@ namespace ccm::test::oracle { - template - inline bool has_ternary_hard_failure(const std::vector>& events) - { return !events.empty(); } + template inline bool has_ternary_hard_failure(const std::vector> & events) + { + return !events.empty(); + } - template - inline void write_ternary_failure_json(const std::string& output_path, const std::vector>& events) + template inline void write_ternary_failure_json(const std::string & output_path, const std::vector> & events) { std::ofstream out(output_path, std::ios::trunc); out << "[\n"; for (std::size_t i = 0; i < events.size(); ++i) { - const auto& event = events[i]; + const auto & event = events[i]; out << " {\n"; out << " \"function_name\": \"" << json_escape(event.function_name) << "\",\n"; out << " \"input_type\": \"" << json_escape(event.input_type) << "\",\n"; @@ -49,10 +49,9 @@ namespace ccm::test::oracle out << "]\n"; } - template - inline void print_ternary_hard_failures(const std::vector>& events) + template inline void print_ternary_hard_failures(const std::vector> & events) { - for (const auto& event : events) + for (const auto & event : events) { std::cout << "HARD FAILURE " << event.event_kind << " fn=" << event.function_name << " path=" << event.path << " rounding=" << event.rounding_mode << " x=" << event.x_bits << " y=" << event.y_bits << " z=" << event.z_bits << " actual=" << event.actual_bits diff --git a/tests/shared/oracle/oracle_campaign_unary.hpp b/tests/shared/oracle/oracle_campaign_unary.hpp index 566d5cd4..77a090ef 100644 --- a/tests/shared/oracle/oracle_campaign_unary.hpp +++ b/tests/shared/oracle/oracle_campaign_unary.hpp @@ -9,24 +9,25 @@ namespace ccm::test::oracle { - template - inline bool has_unary_hard_failure(const std::vector>& events) + template inline bool has_unary_hard_failure(const std::vector> & events) { - for (const auto& event : events) + for (const auto & event : events) { - if (event.event_kind == "mpfr_hard_failure") { return true; } + if (event.event_kind == "mpfr_hard_failure") + { + return true; + } } return false; } - template - inline void write_unary_failure_json(const std::string& output_path, const std::vector>& events) + template inline void write_unary_failure_json(const std::string & output_path, const std::vector> & events) { std::ofstream out(output_path, std::ios::trunc); out << "[\n"; for (std::size_t i = 0; i < events.size(); ++i) { - const auto& event = events[i]; + const auto & event = events[i]; out << " {\n"; out << " \"function_name\": \"" << json_escape(event.function_name) << "\",\n"; out << " \"input_type\": \"" << json_escape(event.input_type) << "\",\n"; @@ -52,12 +53,14 @@ namespace ccm::test::oracle out << "]\n"; } - template - inline void print_unary_hard_failures(const std::vector>& events) + template inline void print_unary_hard_failures(const std::vector> & events) { - for (const auto& event : events) + for (const auto & event : events) { - if (event.event_kind != "mpfr_hard_failure") { continue; } + if (event.event_kind != "mpfr_hard_failure") + { + continue; + } std::cout << "HARD FAILURE " << event.event_kind << " fn=" << event.function_name << " path=" << event.path << " rounding=" << event.rounding_mode << " input=" << event.input_bits << " actual=" << event.actual_bits << " expected=" << event.expected_bits << " ulp=" << event.ulp_distance << " provenance=\"" << event.provenance << "\" notes=\"" << event.notes << "\"\n"; diff --git a/tests/shared/oracle/pow_adversarial_modes.hpp b/tests/shared/oracle/pow_adversarial_modes.hpp index f26dd2cb..a37c8330 100644 --- a/tests/shared/oracle/pow_adversarial_modes.hpp +++ b/tests/shared/oracle/pow_adversarial_modes.hpp @@ -13,11 +13,9 @@ namespace ccm::test::oracle::adversarial { - template - using pow_case = ccm::test::oracle::pow_case; + template using pow_case = ccm::test::oracle::pow_case; - template - inline void add_near_one(std::vector>& cases) + template inline void add_near_one(std::vector> & cases) { for (T exponent : { static_cast(-3.5), static_cast(-0.5), static_cast(0.5), static_cast(3.5), static_cast(10) }) { @@ -27,8 +25,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_near_negative_one(std::vector>& cases) + template inline void add_near_negative_one(std::vector> & cases) { for (T exponent : { static_cast(2), static_cast(3), static_cast(4), static_cast(3.5), static_cast(0.5) }) { @@ -38,8 +35,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_near_zero(std::vector>& cases) + template inline void add_near_zero(std::vector> & cases) { for (T exponent : { static_cast(-1), static_cast(0.5), static_cast(2), static_cast(3) }) { @@ -49,8 +45,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_near_power_of_two(std::vector>& cases) + template inline void add_near_power_of_two(std::vector> & cases) { for (int exp2 : { -10, -1, 0, 1, 10, 20 }) { @@ -64,20 +59,21 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_near_integer_exponent(std::vector>& cases) + template inline void add_near_integer_exponent(std::vector> & cases) { for (T base : { static_cast(1.25), static_cast(2), static_cast(-2), static_cast(0.5) }) { for (T delta : { static_cast(-0.5), static_cast(-1e-6), static_cast(1e-6), static_cast(0.5) }) { - for (int integer : { -3, -1, 0, 1, 3, 7 }) { cases.push_back({ base, static_cast(integer) + delta, "near-integer-exponent" }); } + for (int integer : { -3, -1, 0, 1, 3, 7 }) + { + cases.push_back({ base, static_cast(integer) + delta, "near-integer-exponent" }); + } } } } - template - inline void add_negative_base_parity(std::vector>& cases) + template inline void add_negative_base_parity(std::vector> & cases) { for (T exponent : { static_cast(2), static_cast(3), static_cast(4), static_cast(3.5), static_cast(0.5) }) { @@ -85,8 +81,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_overflow_boundary(std::vector>& cases) + template inline void add_overflow_boundary(std::vector> & cases) { if constexpr (std::is_same_v) { @@ -94,8 +89,7 @@ namespace ccm::test::oracle::adversarial { cases.push_back({ static_cast(2), exponent, "overflow-boundary" }); } - } - else + } else { for (T exponent : { static_cast(1023), static_cast(1024), static_cast(1025) }) { @@ -104,8 +98,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_underflow_boundary(std::vector>& cases) + template inline void add_underflow_boundary(std::vector> & cases) { if constexpr (std::is_same_v) { @@ -113,8 +106,7 @@ namespace ccm::test::oracle::adversarial { cases.push_back({ static_cast(2), exponent, "underflow-boundary" }); } - } - else + } else { for (T exponent : { static_cast(-1074), static_cast(-1075), static_cast(-1076) }) { @@ -123,8 +115,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_subnormal_output(std::vector>& cases, std::uint64_t seed, std::size_t count) + template inline void add_subnormal_output(std::vector> & cases, std::uint64_t seed, std::size_t count) { std::mt19937_64 rng(seed); for (std::size_t i = 0; i < count; ++i) @@ -135,8 +126,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_table_boundary(std::vector>& cases) + template inline void add_table_boundary(std::vector> & cases) { for (int bucket = 0; bucket < 128; ++bucket) { @@ -147,8 +137,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline void add_field_mutation(std::string_view mode, std::uint64_t seed, std::size_t count, std::vector>& cases) + template inline void add_field_mutation(std::string_view mode, std::uint64_t seed, std::size_t count, std::vector> & cases) { std::mt19937_64 rng(seed); for (std::size_t i = 0; i < count; ++i) @@ -160,22 +149,19 @@ namespace ccm::test::oracle::adversarial if (mode == "exponent-field-mutation" || mode == "exponent-mutation") { base_bits = (base_bits & 0x807fffffu) | ((static_cast(rng()) & 0xffu) << 23); - } - else + } else { base_bits ^= static_cast(1u << (rng() % 23)); } cases.push_back({ ccm::support::bit_cast(base_bits), ccm::support::bit_cast(exp_bits), std::string(mode) }); - } - else + } else { std::uint64_t base_bits = rng(); std::uint64_t exp_bits = rng(); if (mode == "exponent-field-mutation" || mode == "exponent-mutation") { base_bits = (base_bits & 0x800fffffffffffffull) | ((rng() & 0x7ffull) << 52); - } - else + } else { base_bits ^= (1ull << (rng() % 52)); } @@ -184,8 +170,7 @@ namespace ccm::test::oracle::adversarial } } - template - inline std::vector> build_search_cases(std::string_view mode, std::uint64_t seed, std::size_t count) + template inline std::vector> build_search_cases(std::string_view mode, std::uint64_t seed, std::size_t count) { std::vector> cases; if (mode == "random-bit-pattern") @@ -215,7 +200,7 @@ namespace ccm::test::oracle::adversarial } if (mode == "nextafter-neighborhood") { - for (const auto& hard_case : ccm::test::worst_case::kPowDoubleHard) + for (const auto & hard_case : ccm::test::worst_case::kPowDoubleHard) { const T base = static_cast(hard_case.base); const T exponent = static_cast(hard_case.exponent); diff --git a/tests/shared/oracle/pow_adversarial_search.cpp b/tests/shared/oracle/pow_adversarial_search.cpp index 4c375525..aa81639b 100644 --- a/tests/shared/oracle/pow_adversarial_search.cpp +++ b/tests/shared/oracle/pow_adversarial_search.cpp @@ -15,14 +15,14 @@ namespace ccm::test::pow_path::validation_path path, std::string_view search_mode, Fn fn, - const std::vector>& cases, + const std::vector> & cases, mpfr_prec_t precision, std::uint64_t max_ulp, std::uint64_t seed) { std::vector> failures; ccm::test::oracle::run_summary summary; - for (const auto& test_case : cases) + for (const auto & test_case : cases) { if (auto failure = ccm::test::oracle::evaluate_case( test_case, function_name, path_name, path, fn, precision, max_ulp, summary, /*target_ulp=*/0, seed, search_mode)) @@ -30,32 +30,35 @@ namespace failures.push_back(*failure); } } - std::sort(failures.begin(), failures.end(), [](const auto& lhs, const auto& rhs) { return lhs.ulp_distance > rhs.ulp_distance; }); + std::sort(failures.begin(), failures.end(), [](const auto & lhs, const auto & rhs) { return lhs.ulp_distance > rhs.ulp_distance; }); return failures; } - ccm::test::pow_path::validation_path parse_search_path(int argc, char** argv) + ccm::test::pow_path::validation_path parse_search_path(int argc, char ** argv) { const std::string path = ccm::test::oracle::option_value(argc, argv, "--path=").value_or("public_default"); - if (auto parsed = ccm::test::pow_path::parse_path(path)) { return *parsed; } + if (auto parsed = ccm::test::pow_path::parse_path(path)) + { + return *parsed; + } return ccm::test::pow_path::validation_path::public_default; } } // namespace -int main(int argc, char** argv) +int main(int argc, char ** argv) { const std::string type = ccm::test::oracle::option_value(argc, argv, "--type=").value_or("double"); const std::string mode = ccm::test::oracle::option_value(argc, argv, "--mode=").value_or("random-bit-pattern"); const std::string output_path = ccm::test::oracle::option_value(argc, argv, "--json-output=").value_or("pow-search-results.json"); const std::uint64_t seed = ccm::test::oracle::parse_option_or( - ccm::test::oracle::option_value(argc, argv, "--seed="), [](const std::string& value) { return std::stoull(value); }, 0xBAD5EEDULL); + ccm::test::oracle::option_value(argc, argv, "--seed="), [](const std::string & value) { return std::stoull(value); }, 0xBAD5EEDULL); const std::size_t count = ccm::test::oracle::parse_option_or( - ccm::test::oracle::option_value(argc, argv, "--count="), [](const std::string& value) { return static_cast(std::stoull(value)); }, 4096); + ccm::test::oracle::option_value(argc, argv, "--count="), [](const std::string & value) { return static_cast(std::stoull(value)); }, 4096); const std::uint64_t max_ulp = ccm::test::oracle::parse_option_or( - ccm::test::oracle::option_value(argc, argv, "--max-ulp="), [](const std::string& value) { return std::stoull(value); }, 4); + ccm::test::oracle::option_value(argc, argv, "--max-ulp="), [](const std::string & value) { return std::stoull(value); }, 4); const mpfr_prec_t precision = ccm::test::oracle::parse_option_or( ccm::test::oracle::option_value(argc, argv, "--oracle-precision="), - [](const std::string& value) { return static_cast(std::stoul(value)); }, + [](const std::string & value) { return static_cast(std::stoul(value)); }, 256); const auto path = parse_search_path(argc, argv); @@ -67,8 +70,7 @@ int main(int argc, char** argv) std::cout << "SKIP path=" << ccm::test::pow_path::path_name(path) << " reason=" << support.skip_reason << '\n'; return 0; } - } - else + } else { const auto support = ccm::test::pow_path::path_is_supported(path); if (!support.supported) diff --git a/tests/shared/oracle/pow_cases.hpp b/tests/shared/oracle/pow_cases.hpp index 69c5592c..c5c807b4 100644 --- a/tests/shared/oracle/pow_cases.hpp +++ b/tests/shared/oracle/pow_cases.hpp @@ -24,14 +24,19 @@ namespace ccm::test::oracle::pow_cases } template - void append_double_cases(std::vector>& cases, campaign_mode mode, std::uint64_t seed, EligibilityFn eligible) + void append_double_cases(std::vector> & cases, campaign_mode mode, std::uint64_t seed, EligibilityFn eligible) { - auto maybe_add = [&](double base, double exponent, const char* provenance) - { - if (eligible(base, exponent)) { cases.push_back({ base, exponent, provenance }); } + auto maybe_add = [&](double base, double exponent, const char * provenance) { + if (eligible(base, exponent)) + { + cases.push_back({ base, exponent, provenance }); + } }; - for (const auto& legacy_case : ccm::test::worst_case::kPowDoubleHard) { maybe_add(legacy_case.base, legacy_case.exponent, legacy_case.provenance); } + for (const auto & legacy_case : ccm::test::worst_case::kPowDoubleHard) + { + maybe_add(legacy_case.base, legacy_case.exponent, legacy_case.provenance); + } const std::array special_bases = { -std::numeric_limits::infinity(), @@ -50,7 +55,10 @@ namespace ccm::test::oracle::pow_cases }; for (double base : special_bases) { - for (double exponent : special_exponents) { maybe_add(base, exponent, "special-value matrix"); } + for (double exponent : special_exponents) + { + maybe_add(base, exponent, "special-value matrix"); + } } for (const double base : { @@ -97,8 +105,14 @@ namespace ccm::test::oracle::pow_cases maybe_add(-2.0, exponent, "negative base near non-integer exponent boundary"); } - for (const auto exponent : { 1023.0, std::nextafter(1024.0, 0.0), 1024.0, 1025.0 }) { maybe_add(2.0, exponent, "overflow-threshold campaign"); } - for (const auto exponent : { -1074.0, -1075.0, -1076.0 }) { maybe_add(2.0, exponent, "underflow-threshold campaign"); } + for (const auto exponent : { 1023.0, std::nextafter(1024.0, 0.0), 1024.0, 1025.0 }) + { + maybe_add(2.0, exponent, "overflow-threshold campaign"); + } + for (const auto exponent : { -1074.0, -1075.0, -1076.0 }) + { + maybe_add(2.0, exponent, "underflow-threshold campaign"); + } const std::size_t random_count = mode == campaign_mode::quick ? 512 : (mode == campaign_mode::extended ? 4096 : 16384); std::vector> random_cases; @@ -108,14 +122,16 @@ namespace ccm::test::oracle::pow_cases // over/underflow-adjacent scaled results, and integer/half-integer and negative-base // exponents. The seed is offset so the two streams do not correlate. add_targeted_random_cases(random_cases, seed ^ 0x9E3779B97F4A7C15ULL, random_count * 2, "targeted finite-band random campaign"); - for (auto& test_case : random_cases) + for (auto & test_case : random_cases) { - if (eligible(test_case.base, test_case.exponent)) { cases.push_back(std::move(test_case)); } + if (eligible(test_case.base, test_case.exponent)) + { + cases.push_back(std::move(test_case)); + } } } - template - std::vector> build_double_cases(campaign_mode mode, std::uint64_t seed, EligibilityFn eligible) + template std::vector> build_double_cases(campaign_mode mode, std::uint64_t seed, EligibilityFn eligible) { std::vector> cases; append_double_cases(cases, mode, seed, eligible); @@ -129,10 +145,10 @@ namespace ccm::test::oracle::pow_cases template std::vector> build_float_cases(campaign_mode mode, - const std::set& domain_filter, + const std::set & domain_filter, std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped, + std::vector & domains_covered, + std::vector & domains_skipped, EligibilityFn eligible) { namespace powf_domains = ccm::test::oracle::powf_domains; @@ -141,24 +157,30 @@ namespace ccm::test::oracle::pow_cases auto domain_enabled = [&](std::string_view domain) { return filter_all || domain_filter.find(domain) != domain_filter.end(); }; - auto mark_domain = [&](std::string_view domain, bool enabled) - { - if (enabled) { domains_covered.emplace_back(domain); } - else + auto mark_domain = [&](std::string_view domain, bool enabled) { + if (enabled) + { + domains_covered.emplace_back(domain); + } else { domains_skipped.emplace_back(domain); } }; - auto maybe_add = [&](float base, float exponent, const char* provenance) - { - if (eligible(base, exponent)) { cases.push_back({ base, exponent, provenance }); } + auto maybe_add = [&](float base, float exponent, const char * provenance) { + if (eligible(base, exponent)) + { + cases.push_back({ base, exponent, provenance }); + } }; if (domain_enabled("structured-corpus")) { mark_domain("structured-corpus", true); - for (const auto& hard_case : ccm::test::worst_case::kPowFloatHard) { maybe_add(hard_case.base, hard_case.exponent, hard_case.provenance); } + for (const auto & hard_case : ccm::test::worst_case::kPowFloatHard) + { + maybe_add(hard_case.base, hard_case.exponent, hard_case.provenance); + } const std::array special_bases = { -std::numeric_limits::infinity(), @@ -177,7 +199,10 @@ namespace ccm::test::oracle::pow_cases }; for (float base : special_bases) { - for (float exponent : special_exponents) { maybe_add(base, exponent, "special-value matrix"); } + for (float exponent : special_exponents) + { + maybe_add(base, exponent, "special-value matrix"); + } } const int bucket_step = (mode == campaign_mode::quick) ? 16 : 1; @@ -192,14 +217,16 @@ namespace ccm::test::oracle::pow_cases const std::size_t random_count = mode == campaign_mode::quick ? 1024 : (mode == campaign_mode::extended ? 8192 : 32768); std::vector> random_cases; add_random_cases(random_cases, seed, random_count, "deterministic random bit-pattern campaign"); - for (auto& test_case : random_cases) { maybe_add(test_case.base, test_case.exponent, test_case.provenance.c_str()); } - } - else + for (auto & test_case : random_cases) + { + maybe_add(test_case.base, test_case.exponent, test_case.provenance.c_str()); + } + } else { mark_domain("structured-corpus", false); } - const auto add_case = [&](float base, float exponent, const char* provenance) { maybe_add(base, exponent, provenance); }; + const auto add_case = [&](float base, float exponent, const char * provenance) { maybe_add(base, exponent, provenance); }; const auto filter_copy = domain_filter; const std::size_t before = cases.size(); @@ -226,8 +253,7 @@ namespace ccm::test::oracle::pow_cases { mark_domain("unit-interval", true); powf_domains::add_unit_interval(mode, add_case, { "mantissa-sweep" }); - } - else + } else { mark_domain("unit-interval", false); } @@ -246,7 +272,7 @@ namespace ccm::test::oracle::pow_cases if (!filter_all) { - for (const auto& domain : all_powf_domains()) + for (const auto & domain : all_powf_domains()) { if (domain_filter.find(domain) == domain_filter.end() && std::find(domains_covered.begin(), domains_covered.end(), domain) == domains_covered.end() && @@ -261,10 +287,10 @@ namespace ccm::test::oracle::pow_cases } inline std::vector> build_float_cases(campaign_mode mode, - const std::set& domain_filter, + const std::set & domain_filter, std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) + std::vector & domains_covered, + std::vector & domains_skipped) { return build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped, [](float, float) { return true; }); } diff --git a/tests/shared/oracle/powf_exhaustive_domains.hpp b/tests/shared/oracle/powf_exhaustive_domains.hpp index 8c8abf97..2c33ff61 100644 --- a/tests/shared/oracle/powf_exhaustive_domains.hpp +++ b/tests/shared/oracle/powf_exhaustive_domains.hpp @@ -9,30 +9,43 @@ namespace ccm::test::oracle::powf_domains { - inline std::set parse_domain_filter(const std::optional& raw_domains) + inline std::set parse_domain_filter(const std::optional & raw_domains) { std::set domains; - if (!raw_domains.has_value() || raw_domains->empty()) { return domains; } + if (!raw_domains.has_value() || raw_domains->empty()) + { + return domains; + } std::string_view view = *raw_domains; while (!view.empty()) { const std::size_t comma = view.find(','); const std::string_view token = view.substr(0, comma); - if (!token.empty()) { domains.insert(token); } - if (comma == std::string_view::npos) { break; } + if (!token.empty()) + { + domains.insert(token); + } + if (comma == std::string_view::npos) + { + break; + } view.remove_prefix(comma + 1); } return domains; } - inline bool domain_enabled(const std::set& filter, std::string_view domain) - { return filter.empty() || filter.find(domain) != filter.end(); } + inline bool domain_enabled(const std::set & filter, std::string_view domain) + { + return filter.empty() || filter.find(domain) != filter.end(); + } - template - void add_mantissa_sweep(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_mantissa_sweep(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "mantissa-sweep")) { return; } + if (!domain_enabled(filter, "mantissa-sweep")) + { + return; + } for (float exponent : { 2.0F, 0.5F, -1.0F, 3.14159265F }) { @@ -44,34 +57,46 @@ namespace ccm::test::oracle::powf_domains } } - template - void add_all_x_for_y(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_all_x_for_y(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "all-x-for-y")) { return; } + if (!domain_enabled(filter, "all-x-for-y")) + { + return; + } const std::uint32_t step = (mode == campaign_mode::quick) ? (1u << 20) : (mode == campaign_mode::extended ? (1u << 12) : (1u << 8)); for (float exponent : { 2.0F, -1.0F, 0.5F }) { - for (std::uint32_t bits = 0; bits < 0x7f800000u; bits += step) { add_case(ccm::support::bit_cast(bits), exponent, "all-x-for-selected-y"); } + for (std::uint32_t bits = 0; bits < 0x7f800000u; bits += step) + { + add_case(ccm::support::bit_cast(bits), exponent, "all-x-for-selected-y"); + } } } - template - void add_all_y_for_x(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_all_y_for_x(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "all-y-for-x")) { return; } + if (!domain_enabled(filter, "all-y-for-x")) + { + return; + } const std::uint32_t step = (mode == campaign_mode::quick) ? (1u << 20) : (mode == campaign_mode::extended ? (1u << 12) : (1u << 8)); for (float base : { 1.25F, 2.0F, 0.5F, -2.0F }) { - for (std::uint32_t bits = 0; bits < 0x7f800000u; bits += step) { add_case(base, ccm::support::bit_cast(bits), "all-y-for-selected-x"); } + for (std::uint32_t bits = 0; bits < 0x7f800000u; bits += step) + { + add_case(base, ccm::support::bit_cast(bits), "all-y-for-selected-x"); + } } } - template - void add_exponent_field_sweep(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_exponent_field_sweep(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "exponent-field-sweep")) { return; } + if (!domain_enabled(filter, "exponent-field-sweep")) + { + return; + } for (const float base : { 1.0F, -1.0F, 2.0F, -2.0F, 0x1.0p-126F, 0x1.0p-149F }) { @@ -91,10 +116,12 @@ namespace ccm::test::oracle::powf_domains } } - template - void add_subnormal_x(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_subnormal_x(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "subnormal-x")) { return; } + if (!domain_enabled(filter, "subnormal-x")) + { + return; + } const std::uint32_t step = (mode == campaign_mode::quick) ? (1u << 15) : (mode == campaign_mode::extended ? (1u << 8) : 1u); for (std::uint32_t bits = 1; bits < 0x00800000u; bits += step) @@ -104,38 +131,55 @@ namespace ccm::test::oracle::powf_domains } } - template - void add_unit_interval(campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_unit_interval(campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "unit-interval")) { return; } + if (!domain_enabled(filter, "unit-interval")) + { + return; + } add_mantissa_sweep(mode, add_case, { "mantissa-sweep" }); } - template - void add_negative_base([[maybe_unused]] campaign_mode mode, AddCaseFn add_case, const std::set& filter) + template void add_negative_base([[maybe_unused]] campaign_mode mode, AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "negative-base")) { return; } + if (!domain_enabled(filter, "negative-base")) + { + return; + } for (float exponent : { -3.0F, -2.0F, -1.0F, 1.0F, 2.0F, 3.0F, 8388607.0F, 8388608.0F, 8388609.0F, 16777216.0F }) { add_case(-1.0F, exponent, "integer-parity threshold campaign"); } - for (float exponent : { 0.5F, 1.5F, 3.5F, std::nextafter(1.0F, 2.0F) }) { add_case(-2.0F, exponent, "negative-base non-integer boundary campaign"); } + for (float exponent : { 0.5F, 1.5F, 3.5F, std::nextafter(1.0F, 2.0F) }) + { + add_case(-2.0F, exponent, "negative-base non-integer boundary campaign"); + } } - template - void add_overflow_threshold(AddCaseFn add_case, const std::set& filter) + template void add_overflow_threshold(AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "overflow-threshold")) { return; } + if (!domain_enabled(filter, "overflow-threshold")) + { + return; + } - for (float exponent : { 126.0F, 127.0F, 128.0F, 129.0F }) { add_case(2.0F, exponent, "overflow-threshold campaign"); } + for (float exponent : { 126.0F, 127.0F, 128.0F, 129.0F }) + { + add_case(2.0F, exponent, "overflow-threshold campaign"); + } } - template - void add_underflow_threshold(AddCaseFn add_case, const std::set& filter) + template void add_underflow_threshold(AddCaseFn add_case, const std::set & filter) { - if (!domain_enabled(filter, "underflow-threshold")) { return; } + if (!domain_enabled(filter, "underflow-threshold")) + { + return; + } - for (float exponent : { -150.0F, -151.0F, -152.0F }) { add_case(2.0F, exponent, "underflow-threshold campaign"); } + for (float exponent : { -150.0F, -151.0F, -152.0F }) + { + add_case(2.0F, exponent, "underflow-threshold campaign"); + } } } // namespace ccm::test::oracle::powf_domains diff --git a/tests/shared/oracle/powl_path_reporting.hpp b/tests/shared/oracle/powl_path_reporting.hpp index 53228077..fda391c5 100644 --- a/tests/shared/oracle/powl_path_reporting.hpp +++ b/tests/shared/oracle/powl_path_reporting.hpp @@ -42,13 +42,13 @@ namespace ccm::test::oracle case PowlImplementationPath::Ld64DoubleAlias: case PowlImplementationPath::Ld80SpecialCase: case PowlImplementationPath::Ld80BoundedInteger: - case PowlImplementationPath::Ld80GeneralFinite: return false; - case PowlImplementationPath::Ld80ReducedPrecisionFallback: - case PowlImplementationPath::Ld128ReducedPrecisionFallback: + case PowlImplementationPath::Ld80GeneralFinite : return false; + case PowlImplementationPath::Ld80ReducedPrecisionFallback : + case PowlImplementationPath::Ld128ReducedPrecisionFallback : case PowlImplementationPath::UnknownReducedPrecisionFallback: return true; - case PowlImplementationPath::Ld80Unsupported: - case PowlImplementationPath::Ld128Unsupported: - case PowlImplementationPath::UnknownUnsupported: return false; + case PowlImplementationPath::Ld80Unsupported : + case PowlImplementationPath::Ld128Unsupported : + case PowlImplementationPath::UnknownUnsupported : return false; } return false; } @@ -60,8 +60,8 @@ namespace ccm::test::oracle case PowlImplementationPath::Ld64DoubleAlias: case PowlImplementationPath::Ld80SpecialCase: case PowlImplementationPath::Ld80BoundedInteger: - case PowlImplementationPath::Ld80GeneralFinite: return true; - default: return false; + case PowlImplementationPath::Ld80GeneralFinite : return true; + default : return false; } } @@ -69,16 +69,16 @@ namespace ccm::test::oracle { switch (path) { - case PowlImplementationPath::Ld64DoubleAlias: return "ld64_double_alias"; - case PowlImplementationPath::Ld80SpecialCase: return "ld80_special_case"; - case PowlImplementationPath::Ld80BoundedInteger: return "ld80_bounded_integer"; - case PowlImplementationPath::Ld80GeneralFinite: return "ld80_general_finite"; - case PowlImplementationPath::Ld80ReducedPrecisionFallback: return "ld80_reduced_precision_fallback"; - case PowlImplementationPath::Ld80Unsupported: return "ld80_unsupported"; - case PowlImplementationPath::Ld128ReducedPrecisionFallback: return "ld128_reduced_precision_fallback"; - case PowlImplementationPath::Ld128Unsupported: return "ld128_unsupported"; + case PowlImplementationPath::Ld64DoubleAlias : return "ld64_double_alias"; + case PowlImplementationPath::Ld80SpecialCase : return "ld80_special_case"; + case PowlImplementationPath::Ld80BoundedInteger : return "ld80_bounded_integer"; + case PowlImplementationPath::Ld80GeneralFinite : return "ld80_general_finite"; + case PowlImplementationPath::Ld80ReducedPrecisionFallback : return "ld80_reduced_precision_fallback"; + case PowlImplementationPath::Ld80Unsupported : return "ld80_unsupported"; + case PowlImplementationPath::Ld128ReducedPrecisionFallback : return "ld128_reduced_precision_fallback"; + case PowlImplementationPath::Ld128Unsupported : return "ld128_unsupported"; case PowlImplementationPath::UnknownReducedPrecisionFallback: return "unknown_reduced_precision_fallback"; - case PowlImplementationPath::UnknownUnsupported: return "unknown_unsupported"; + case PowlImplementationPath::UnknownUnsupported : return "unknown_unsupported"; } return "unknown_unsupported"; } @@ -87,24 +87,48 @@ namespace ccm::test::oracle { constexpr bool is_ld80_special_case_route(long double base, long double exp) noexcept { - if (exp == 0.0L) { return true; } - if (base == 1.0L) { return true; } - if (ccm::isnan(base) || ccm::isnan(exp)) { return true; } + if (exp == 0.0L) + { + return true; + } + if (base == 1.0L) + { + return true; + } + if (ccm::isnan(base) || ccm::isnan(exp)) + { + return true; + } using FPBits_t = support::fp::FPBits; const FPBits_t base_bits(base); const FPBits_t exp_bits(exp); - if (base_bits.is_zero()) { return true; } - if (base_bits.is_inf()) { return true; } - if (exp_bits.is_inf()) { return true; } - if (base_bits.is_neg() && !gen::internal::powl_bits::is_integer(exp)) { return true; } + if (base_bits.is_zero()) + { + return true; + } + if (base_bits.is_inf()) + { + return true; + } + if (exp_bits.is_inf()) + { + return true; + } + if (base_bits.is_neg() && !gen::internal::powl_bits::is_integer(exp)) + { + return true; + } return false; } constexpr bool is_ld80_bounded_integer_route([[maybe_unused]] long double base, long double exp) noexcept { - if (is_ld80_special_case_route(base, exp)) { return false; } + if (is_ld80_special_case_route(base, exp)) + { + return false; + } std::int64_t ignored = 0; return gen::internal::powl_bits::try_extract_int64(exp, ignored); @@ -112,8 +136,14 @@ namespace ccm::test::oracle constexpr PowlImplementationPath classify_ld80_path(long double base, long double exp) noexcept { - if (is_ld80_special_case_route(base, exp)) { return PowlImplementationPath::Ld80SpecialCase; } - if (is_ld80_bounded_integer_route(base, exp)) { return PowlImplementationPath::Ld80BoundedInteger; } + if (is_ld80_special_case_route(base, exp)) + { + return PowlImplementationPath::Ld80SpecialCase; + } + if (is_ld80_bounded_integer_route(base, exp)) + { + return PowlImplementationPath::Ld80BoundedInteger; + } return PowlImplementationPath::Ld80GeneralFinite; } @@ -123,7 +153,7 @@ namespace ccm::test::oracle { switch (config::detect_long_double_format()) { - case config::LongDoubleFormat::Double: return PowlImplementationPath::Ld64DoubleAlias; + case config::LongDoubleFormat::Double : return PowlImplementationPath::Ld64DoubleAlias; case config::LongDoubleFormat::X87Extended: return powl_path_detail::classify_ld80_path(base, exp); case config::LongDoubleFormat::IEEEBinary128: return config::reduced_precision_powl_fallback_enabled() ? PowlImplementationPath::Ld128ReducedPrecisionFallback diff --git a/tests/shared/oracle/powl_platform_characterization.cpp b/tests/shared/oracle/powl_platform_characterization.cpp index 7208f4d0..5577a14b 100644 --- a/tests/shared/oracle/powl_platform_characterization.cpp +++ b/tests/shared/oracle/powl_platform_characterization.cpp @@ -30,9 +30,15 @@ namespace std::string detect_powl_path() { #if defined(CCMATH_HAS_CONSTEXPR_BUILTIN_POW) - if (ccm::builtin::has_constexpr_pow) { return "constexpr_builtin"; } + if (ccm::builtin::has_constexpr_pow) + { + return "constexpr_builtin"; + } #endif - if (ccm::builtin::has_runtime_pow) { return "runtime_builtin"; } + if (ccm::builtin::has_runtime_pow) + { + return "runtime_builtin"; + } #if defined(CCMATH_HAS_SIMD) return "runtime_simd_or_generic"; #else @@ -78,7 +84,10 @@ namespace summary.worst_actual = actual; summary.worst_expected = expected_ld; } - if (!pass) { ++summary.failure_count; } + if (!pass) + { + ++summary.failure_count; + } } } @@ -132,7 +141,10 @@ int main(int argc, char ** argv) << " powl_path=" << report.powl_path << '\n'; // ReSharper disable once CppDFAConstantConditions std::cout << " mpfr_oracle_supported=" << (report.mpfr_oracle_supported ? "yes" : "no"); - if (!report.mpfr_skip_reason.empty()) { std::cout << " reason=" << report.mpfr_skip_reason; } + if (!report.mpfr_skip_reason.empty()) + { + std::cout << " reason=" << report.mpfr_skip_reason; + } std::cout << '\n'; // ReSharper disable once CppDFAConstantConditions if (report.mpfr_oracle_supported) diff --git a/tests/shared/oracle/runners/coremath_pow_runner.hpp b/tests/shared/oracle/runners/coremath_pow_runner.hpp index 537a1c60..df32ccb1 100644 --- a/tests/shared/oracle/runners/coremath_pow_runner.hpp +++ b/tests/shared/oracle/runners/coremath_pow_runner.hpp @@ -15,11 +15,9 @@ namespace ccm::test::oracle::pow_coremath namespace powf_domains = ccm::test::oracle::powf_domains; using ccm::test::pow_path::validation_path; - template - struct traits; + template struct traits; - template <> - struct traits + template <> struct traits { static constexpr std::string_view function_name = "ccm::pow"; static constexpr std::string_view summary_prefix = "coremath-pow-double-"; @@ -27,12 +25,13 @@ namespace ccm::test::oracle::pow_coremath static constexpr std::string_view corpus_label = "finite-binary64-corpus"; static std::vector> - build_cases(campaign_mode mode, std::set const&, std::uint64_t seed, std::vector&, std::vector&) - { return coremath_cases::build_double_cases(mode, seed); } + build_cases(campaign_mode mode, std::set const &, std::uint64_t seed, std::vector &, std::vector &) + { + return coremath_cases::build_double_cases(mode, seed); + } }; - template <> - struct traits + template <> struct traits { static constexpr std::string_view function_name = "ccm::powf"; static constexpr std::string_view summary_prefix = "coremath-powf-"; @@ -40,31 +39,32 @@ namespace ccm::test::oracle::pow_coremath static constexpr std::string_view corpus_label = "finite-binary32-corpus"; static std::vector> build_cases(campaign_mode mode, - std::set const& domain_filter, + std::set const & domain_filter, std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) - { return coremath_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); } + std::vector & domains_covered, + std::vector & domains_skipped) + { + return coremath_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); + } }; template inline void run_path(validation_path path, - std::vector> const& cases, - std::vector const& rounding_modes, + std::vector> const & cases, + std::vector const & rounding_modes, std::uint64_t seed, campaign_mode mode, - std::vector const& domains_covered, - std::vector const& domains_skipped, - std::vector>& events, - run_summary& summary) + std::vector const & domains_covered, + std::vector const & domains_skipped, + std::vector> & events, + run_summary & summary) { run_path_campaign( path, summary, traits::summary_prefix, - [&](run_summary& path_summary) - { - for (auto const& test_case : cases) + [&](run_summary & path_summary) { + for (auto const & test_case : cases) { evaluate_case_all_modes( test_case, @@ -79,32 +79,34 @@ namespace ccm::test::oracle::pow_coremath &events); } }, - [&](run_summary const& path_summary, std::uint64_t elapsed_ms) - { return make_coremath_campaign_report(pow_path::path_name(path), mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); }, - [&](auto const& report, std::string const& summary_path) - { + [&](run_summary const & path_summary, std::uint64_t elapsed_ms) { + return make_coremath_campaign_report(pow_path::path_name(path), mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); + }, + [&](auto const & report, std::string const & summary_path) { std::cout << "path=" << report.path << " configuration=" << report.configuration_name << " cases=" << report.case_count << " skipped=" << report.skipped_count << " max_ulp=" << report.max_observed_ulp << " failures=" << report.failure_count << " elapsed_ms=" << report.elapsed_ms << " summary=" << summary_path << '\n'; }); } - template - inline int run_campaign(int argc, char** argv) + template inline int run_campaign(int argc, char ** argv) { auto const mode = parse_mode(option_value(argc, argv, "--mode=")); auto const output_path = resolve_event_log_path(argc, argv); auto const domain_raw = option_value(argc, argv, "--domain="); auto const domain_filter = powf_domains::parse_domain_filter(domain_raw); std::uint64_t const seed = parse_option_or( - option_value(argc, argv, "--seed="), [](std::string const& value) { return std::stoull(value); }, traits::default_seed); + option_value(argc, argv, "--seed="), [](std::string const & value) { return std::stoull(value); }, traits::default_seed); auto paths = parse_paths(argc, argv); auto const rounding_modes = parse_rounding_modes(argc, argv); std::vector domains_covered; std::vector domains_skipped; - if constexpr (std::is_same_v) { domains_covered = { std::string(traits::corpus_label) }; } + if constexpr (std::is_same_v) + { + domains_covered = { std::string(traits::corpus_label) }; + } auto const cases = traits::build_cases(mode, domain_filter, seed, domains_covered, domains_skipped); std::vector> events; diff --git a/tests/shared/oracle/runners/mpfr_pow_runner.hpp b/tests/shared/oracle/runners/mpfr_pow_runner.hpp index ef711033..a672f400 100644 --- a/tests/shared/oracle/runners/mpfr_pow_runner.hpp +++ b/tests/shared/oracle/runners/mpfr_pow_runner.hpp @@ -15,11 +15,9 @@ namespace ccm::test::oracle::pow_mpfr namespace powf_domains = ccm::test::oracle::powf_domains; using ccm::test::pow_path::validation_path; - template - struct traits; + template struct traits; - template <> - struct traits + template <> struct traits { static constexpr std::string_view function_name = "ccm::pow"; static constexpr std::string_view summary_prefix = "mpfr-pow-double-"; @@ -28,12 +26,13 @@ namespace ccm::test::oracle::pow_mpfr static constexpr std::string_view corpus_label = "structured-binary64-corpus"; static std::vector> - build_cases(campaign_mode mode, std::set const&, std::uint64_t seed, std::vector&, std::vector&) - { return pow_cases::build_double_cases(mode, seed); } + build_cases(campaign_mode mode, std::set const &, std::uint64_t seed, std::vector &, std::vector &) + { + return pow_cases::build_double_cases(mode, seed); + } }; - template <> - struct traits + template <> struct traits { static constexpr std::string_view function_name = "ccm::powf"; static constexpr std::string_view summary_prefix = "mpfr-powf-"; @@ -42,40 +41,47 @@ namespace ccm::test::oracle::pow_mpfr static constexpr std::string_view corpus_label = "structured-binary32-corpus"; static std::vector> build_cases(campaign_mode mode, - std::set const& domain_filter, + std::set const & domain_filter, std::uint64_t seed, - std::vector& domains_covered, - std::vector& domains_skipped) - { return pow_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); } + std::vector & domains_covered, + std::vector & domains_skipped) + { + return pow_cases::build_float_cases(mode, domain_filter, seed, domains_covered, domains_skipped); + } }; template inline void run_path(validation_path path, - std::vector> const& cases, - std::vector const& rounding_modes, + std::vector> const & cases, + std::vector const & rounding_modes, mpfr_prec_t precision, std::uint64_t max_ulp, std::uint64_t target_ulp, std::uint64_t seed, campaign_mode mode, - std::vector const& domains_covered, - std::vector const& domains_skipped, - std::vector>& events, - run_summary& summary) + std::vector const & domains_covered, + std::vector const & domains_skipped, + std::vector> & events, + run_summary & summary) { run_path_campaign( path, summary, traits::summary_prefix, - [&](run_summary& path_summary) - { + [&](run_summary & path_summary) { for (int rounding_mode : rounding_modes) { ScopedMpfrRoundingMode scope(rounding_mode); - if (!scope) { continue; } - for (auto const& test_case : cases) + if (!scope) { - if (path == validation_path::generic_modeled_domain && !is_modeled_generic_pow_case(test_case.base, test_case.exponent)) { continue; } + continue; + } + for (auto const & test_case : cases) + { + if (path == validation_path::generic_modeled_domain && !is_modeled_generic_pow_case(test_case.base, test_case.exponent)) + { + continue; + } (void)evaluate_case( test_case, traits::function_name, @@ -92,10 +98,10 @@ namespace ccm::test::oracle::pow_mpfr } } }, - [&](run_summary const& path_summary, std::uint64_t elapsed_ms) - { return make_campaign_report(pow_path::path_name(path), path, mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); }, - [&](auto const& report, std::string const& summary_path) - { + [&](run_summary const & path_summary, std::uint64_t elapsed_ms) { + return make_campaign_report(pow_path::path_name(path), path, mode, path_summary, seed, elapsed_ms, domains_covered, domains_skipped); + }, + [&](auto const & report, std::string const & summary_path) { std::cout << "path=" << report.path << " configuration=" << report.configuration_name << " rounding_modes=" << rounding_modes.size() << " cases=" << report.case_count << " skipped=" << report.skipped_count << " mpfr_policy_mismatches=" << report.mpfr_policy_mismatch_count << " max_ulp=" << report.max_observed_ulp @@ -104,22 +110,21 @@ namespace ccm::test::oracle::pow_mpfr }); } - template - inline int run_campaign(int argc, char** argv) + template inline int run_campaign(int argc, char ** argv) { auto const mode = parse_mode(option_value(argc, argv, "--mode=")); auto const output_path = resolve_event_log_path(argc, argv); auto const domain_raw = option_value(argc, argv, "--domain="); auto const domain_filter = powf_domains::parse_domain_filter(domain_raw); std::uint64_t const seed = parse_option_or( - option_value(argc, argv, "--seed="), [](std::string const& value) { return std::stoull(value); }, traits::default_seed); + option_value(argc, argv, "--seed="), [](std::string const & value) { return std::stoull(value); }, traits::default_seed); std::uint64_t const max_ulp = parse_option_or( - option_value(argc, argv, "--max-ulp="), [](std::string const& value) { return static_cast(std::stoull(value)); }, 4); + option_value(argc, argv, "--max-ulp="), [](std::string const & value) { return static_cast(std::stoull(value)); }, 4); std::uint64_t const target_ulp = parse_option_or( - option_value(argc, argv, "--target-ulp="), [](std::string const& value) { return static_cast(std::stoull(value)); }, 0); + option_value(argc, argv, "--target-ulp="), [](std::string const & value) { return static_cast(std::stoull(value)); }, 0); mpfr_prec_t const precision = parse_option_or( option_value(argc, argv, "--oracle-precision="), - [](std::string const& value) { return static_cast(std::stoul(value)); }, + [](std::string const & value) { return static_cast(std::stoul(value)); }, traits::default_precision); auto paths = parse_paths(argc, argv); @@ -134,7 +139,10 @@ namespace ccm::test::oracle::pow_mpfr std::vector domains_covered; std::vector domains_skipped; - if constexpr (std::is_same_v) { domains_covered = { std::string(traits::corpus_label) }; } + if constexpr (std::is_same_v) + { + domains_covered = { std::string(traits::corpus_label) }; + } auto const cases = traits::build_cases(mode, domain_filter, seed, domains_covered, domains_skipped); std::vector> events; diff --git a/tests/shared/utils/campaign_metadata.hpp b/tests/shared/utils/campaign_metadata.hpp index e42ec363..0ebedf36 100644 --- a/tests/shared/utils/campaign_metadata.hpp +++ b/tests/shared/utils/campaign_metadata.hpp @@ -92,27 +92,38 @@ namespace ccm::test return oss.str(); } - template - inline constexpr const char * scalar_type_name() + template inline constexpr const char * scalar_type_name() { - if constexpr (std::is_same_v) { return "float"; } - else if constexpr (std::is_same_v) { return "double"; } - else if constexpr (std::is_same_v) { return "long double"; } - else + if constexpr (std::is_same_v) + { + return "float"; + } else if constexpr (std::is_same_v) + { + return "double"; + } else if constexpr (std::is_same_v) + { + return "long double"; + } else { return "unknown"; } } - template - inline constexpr const char * pow_builtin_status() - { return ccm::builtin::has_runtime_pow ? "available" : "unavailable"; } + template inline constexpr const char * pow_builtin_status() + { + return ccm::builtin::has_runtime_pow ? "available" : "unavailable"; + } - template - inline constexpr const char * fma_builtin_status() + template inline constexpr const char * fma_builtin_status() { - if constexpr (ccm::builtin::runtime_builtin_fma_trusted) { return "trusted"; } - if constexpr (ccm::builtin::has_fma) { return "available"; } + if constexpr (ccm::builtin::runtime_builtin_fma_trusted) + { + return "trusted"; + } + if constexpr (ccm::builtin::has_fma) + { + return "available"; + } return "unavailable"; } @@ -125,8 +136,7 @@ namespace ccm::test #endif } - template - inline std::string bits_hex(T value) + template inline std::string bits_hex(T value) { using fp_bits = ccm::support::fp::FPBits; using storage_type = typename fp_bits::storage_type; @@ -137,8 +147,7 @@ namespace ccm::test if constexpr (sizeof(storage_type) <= sizeof(std::uint64_t)) { oss << std::setw(static_cast(sizeof(storage_type) * 2)) << static_cast(bits); - } - else + } else { const auto hi = static_cast(bits >> 64); const auto lo = static_cast(bits); @@ -155,9 +164,9 @@ namespace ccm::test switch (ch) { case '\\': oss << "\\\\"; break; - case '"': oss << "\\\""; break; + case '"' : oss << "\\\""; break; case '\n': oss << "\\n"; break; - default: oss << ch; break; + default : oss << ch; break; } } return oss.str(); diff --git a/tests/shared/utils/conformance_suite.hpp b/tests/shared/utils/conformance_suite.hpp index e34057e8..5bee25a1 100644 --- a/tests/shared/utils/conformance_suite.hpp +++ b/tests/shared/utils/conformance_suite.hpp @@ -24,12 +24,14 @@ namespace ccm::test void ExpectUlpUnaryVsStdInMode(int rounding_mode, T input, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { ForceRoundingMode force(rounding_mode); - if (!force) { ADD_FAILURE() << "could not set " << RoundingModeName(rounding_mode); } + if (!force) + { + ADD_FAILURE() << "could not set " << RoundingModeName(rounding_mode); + } ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), max_ulp); } - template - void ExpectFenvFlagsMatchStd(CcmFn ccm_fn, StdFn std_fn, int expected_flags_mask = FE_ALL_EXCEPT) + template void ExpectFenvFlagsMatchStd(CcmFn ccm_fn, StdFn std_fn, int expected_flags_mask = FE_ALL_EXCEPT) { ScopedFenvEnvironment env; ASSERT_TRUE(env.active()); @@ -45,8 +47,7 @@ namespace ccm::test EXPECT_EQ(ccm_flags & expected_flags_mask, std_flags & expected_flags_mask); } - template - void ExpectFenvFlags(Fn fn, int expected_flags, int flags_mask = FE_ALL_EXCEPT) + template void ExpectFenvFlags(Fn fn, int expected_flags, int flags_mask = FE_ALL_EXCEPT) { ScopedFenvEnvironment env; ASSERT_TRUE(env.active()); @@ -60,12 +61,15 @@ namespace ccm::test void ExpectUlpBinaryVsStdInMode(int rounding_mode, T x, T y, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { ForceRoundingMode force(rounding_mode); - if (!force) { ADD_FAILURE() << "could not set " << RoundingModeName(rounding_mode); } + if (!force) + { + ADD_FAILURE() << "could not set " << RoundingModeName(rounding_mode); + } ExpectSameFloatingAsStd(ccm_fn(x, y), std_fn(x, y), max_ulp); } template - void ExpectUlpUnaryOverInMode(int rounding_mode, const std::array& inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + void ExpectUlpUnaryOverInMode(int rounding_mode, const std::array & inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { for (T input : inputs) { @@ -75,45 +79,46 @@ namespace ccm::test } template - void ExpectUlpUnaryOverAllRoundingModes(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + void ExpectUlpUnaryOverAllRoundingModes(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { ForEachRoundingModeOrSkip([&](int mode) { ExpectUlpUnaryOverInMode(mode, inputs, ccm_fn, std_fn, max_ulp); }); } template void ExpectUlpBinaryOverAllRoundingModes( - const std::array& bases, const std::array& exponents, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + const std::array & bases, const std::array & exponents, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { static_assert(N > 0, "need at least one (base, exponent) pair"); - ForEachRoundingModeOrSkip( - [&](int mode) + ForEachRoundingModeOrSkip([&](int mode) { + for (std::size_t i = 0; i < N; ++i) { - for (std::size_t i = 0; i < N; ++i) + SCOPED_TRACE(bases[i]); + SCOPED_TRACE(exponents[i]); + if (std::isnan(std_fn(bases[i], exponents[i]))) { - SCOPED_TRACE(bases[i]); - SCOPED_TRACE(exponents[i]); - if (std::isnan(std_fn(bases[i], exponents[i]))) { continue; } - ExpectUlpBinaryVsStdInMode(mode, bases[i], exponents[i], ccm_fn, std_fn, max_ulp); + continue; } - }); + ExpectUlpBinaryVsStdInMode(mode, bases[i], exponents[i], ccm_fn, std_fn, max_ulp); + } + }); } - template - void ExpectFpTernaryMatchesStdAllModes(T x, T y, T z, CcmFn ccm_fn, StdFn std_fn) + template void ExpectFpTernaryMatchesStdAllModes(T x, T y, T z, CcmFn ccm_fn, StdFn std_fn) { - ForEachRoundingModeOrSkip( - [&](int mode) + ForEachRoundingModeOrSkip([&](int mode) { + ForceRoundingMode force(mode); + if (!force) { - ForceRoundingMode force(mode); - if (!force) { ADD_FAILURE() << "could not set " << RoundingModeName(mode); } - ExpectFpEq(ccm_fn(x, y, z), std_fn(x, y, z)); - }); + ADD_FAILURE() << "could not set " << RoundingModeName(mode); + } + ExpectFpEq(ccm_fn(x, y, z), std_fn(x, y, z)); + }); } template - void ExpectFpTernaryOverMatchesStdAllModes(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn) + void ExpectFpTernaryOverMatchesStdAllModes(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn) { - for (const auto& input : inputs) + for (const auto & input : inputs) { SCOPED_TRACE(input.x); SCOPED_TRACE(input.y); @@ -123,20 +128,20 @@ namespace ccm::test } /// Bit-exact check under each rounding mode. Both sides evaluated inside the forced mode. - template - void ExpectFpUnaryMatchesStdAllModes(T input, CcmFn ccm_fn, StdFn std_fn) + template void ExpectFpUnaryMatchesStdAllModes(T input, CcmFn ccm_fn, StdFn std_fn) { - ForEachRoundingModeOrSkip( - [&](int mode) + ForEachRoundingModeOrSkip([&](int mode) { + ForceRoundingMode force(mode); + if (!force) { - ForceRoundingMode force(mode); - if (!force) { ADD_FAILURE() << "could not set " << RoundingModeName(mode); } - ExpectFpEq(ccm_fn(input), std_fn(input)); - }); + ADD_FAILURE() << "could not set " << RoundingModeName(mode); + } + ExpectFpEq(ccm_fn(input), std_fn(input)); + }); } template - void ExpectFpUnaryOverMatchesStdAllModes(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn) + void ExpectFpUnaryOverMatchesStdAllModes(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn) { for (T input : inputs) { @@ -145,17 +150,17 @@ namespace ccm::test } } - template - void ExpectDomainEdgeMatchesStdAllModes(T input, CcmFn ccm_fn, StdFn std_fn) + template void ExpectDomainEdgeMatchesStdAllModes(T input, CcmFn ccm_fn, StdFn std_fn) { - ForEachRoundingModeOrSkip( - [&](int mode) + ForEachRoundingModeOrSkip([&](int mode) { + SCOPED_TRACE(input); + ForceRoundingMode force(mode); + if (!force) { - SCOPED_TRACE(input); - ForceRoundingMode force(mode); - if (!force) { ADD_FAILURE() << "could not set " << RoundingModeName(mode); } - ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), kMaxAllowedUlp); - }); + ADD_FAILURE() << "could not set " << RoundingModeName(mode); + } + ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), kMaxAllowedUlp); + }); } } // namespace ccm::test @@ -164,7 +169,10 @@ namespace ccm::test do \ { \ ::ccm::test::ForceRoundingMode __ccm_force_round((rounding_mode)); \ - if (__ccm_force_round) { CCM_EXPECT_FP_EQ((actual), (expected)); } \ + if (__ccm_force_round) \ + { \ + CCM_EXPECT_FP_EQ((actual), (expected)); \ + } \ } while (0) #define CCM_EXPECT_FP_EQ_ALL_ROUNDING(expected, actual) \ diff --git a/tests/shared/utils/fenv_fixture.hpp b/tests/shared/utils/fenv_fixture.hpp index e2932d04..9dae87a4 100644 --- a/tests/shared/utils/fenv_fixture.hpp +++ b/tests/shared/utils/fenv_fixture.hpp @@ -28,15 +28,15 @@ namespace ccm::test inline constexpr std::array kStdRoundingModes = { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; - inline constexpr const char *RoundingModeName(int rounding_mode) + inline constexpr const char * RoundingModeName(int rounding_mode) { switch (rounding_mode) { - case FE_TONEAREST: return "FE_TONEAREST"; - case FE_UPWARD: return "FE_UPWARD"; - case FE_DOWNWARD: return "FE_DOWNWARD"; + case FE_TONEAREST : return "FE_TONEAREST"; + case FE_UPWARD : return "FE_UPWARD"; + case FE_DOWNWARD : return "FE_DOWNWARD"; case FE_TOWARDZERO: return "FE_TOWARDZERO"; - default: return "unknown"; + default : return "unknown"; } } @@ -44,16 +44,19 @@ namespace ccm::test { switch (rounding_mode) { - case FE_UPWARD: return RoundingModeKind::Upward; - case FE_DOWNWARD: return RoundingModeKind::Downward; + case FE_UPWARD : return RoundingModeKind::Upward; + case FE_DOWNWARD : return RoundingModeKind::Downward; case FE_TOWARDZERO: return RoundingModeKind::TowardZero; - default: return RoundingModeKind::Nearest; + default : return RoundingModeKind::Nearest; } } inline bool TrySetRoundingMode(int rounding_mode) { - if (std::fesetround(rounding_mode) != 0) { return false; } + if (std::fesetround(rounding_mode) != 0) + { + return false; + } return std::fegetround() == rounding_mode; } @@ -77,15 +80,21 @@ namespace ccm::test public: explicit ScopedRoundingMode(int rounding_mode) : saved_(std::fegetround()), active_(TrySetRoundingMode(rounding_mode)) {} - ScopedRoundingMode(const ScopedRoundingMode &) = delete; - ScopedRoundingMode &operator=(const ScopedRoundingMode &) = delete; + ScopedRoundingMode(const ScopedRoundingMode &) = delete; + ScopedRoundingMode & operator=(const ScopedRoundingMode &) = delete; ~ScopedRoundingMode() { - if (active_) { std::fesetround(saved_); } + if (active_) + { + std::fesetround(saved_); + } } - [[nodiscard]] bool active() const { return active_; } + [[nodiscard]] bool active() const + { + return active_; + } private: int saved_; @@ -97,15 +106,21 @@ namespace ccm::test public: ScopedFenvEnvironment() : active_(std::fegetenv(&saved_) == 0) {} - ScopedFenvEnvironment(const ScopedFenvEnvironment &) = delete; - ScopedFenvEnvironment &operator=(const ScopedFenvEnvironment &) = delete; + ScopedFenvEnvironment(const ScopedFenvEnvironment &) = delete; + ScopedFenvEnvironment & operator=(const ScopedFenvEnvironment &) = delete; ~ScopedFenvEnvironment() { - if (active_) { std::fesetenv(&saved_); } + if (active_) + { + std::fesetenv(&saved_); + } } - [[nodiscard]] bool active() const { return active_; } + [[nodiscard]] bool active() const + { + return active_; + } private: std::fenv_t saved_{}; @@ -113,10 +128,14 @@ namespace ccm::test }; inline bool ClearFenvExceptions() - { return std::feclearexcept(FE_ALL_EXCEPT) == 0; } + { + return std::feclearexcept(FE_ALL_EXCEPT) == 0; + } inline int CurrentFenvExceptions() - { return std::fetestexcept(FE_ALL_EXCEPT); } + { + return std::fetestexcept(FE_ALL_EXCEPT); + } /// LLVM-libc-style RAII wrapper. Restores the previous mode on destruction. class ForceRoundingMode @@ -124,16 +143,21 @@ namespace ccm::test public: explicit ForceRoundingMode(int rounding_mode) : scope_(rounding_mode) {} - [[nodiscard]] bool succeeded() const { return scope_.active(); } + [[nodiscard]] bool succeeded() const + { + return scope_.active(); + } - explicit operator bool() const { return scope_.active(); } + explicit operator bool() const + { + return scope_.active(); + } private: ScopedRoundingMode scope_; }; - template - void ForEachRoundingMode(Fn &&fn) + template void ForEachRoundingMode(Fn && fn) { for (int mode : kStdRoundingModes) { @@ -148,10 +172,12 @@ namespace ccm::test } } - template - void ForEachRoundingModeOrSkip(Fn &&fn) + template void ForEachRoundingModeOrSkip(Fn && fn) { - if (!FenvIsSupported()) { GTEST_SKIP() << "floating-point rounding modes are not supported on this platform"; } + if (!FenvIsSupported()) + { + GTEST_SKIP() << "floating-point rounding modes are not supported on this platform"; + } ForEachRoundingMode(std::forward(fn)); } diff --git a/tests/shared/utils/fp_matcher.hpp b/tests/shared/utils/fp_matcher.hpp index 8dd33c74..d5d41443 100644 --- a/tests/shared/utils/fp_matcher.hpp +++ b/tests/shared/utils/fp_matcher.hpp @@ -20,8 +20,7 @@ namespace ccm::test { /// Bit-exact floating-point equality (LLVM libc EXPECT_FP_EQ semantics). - template - void ExpectFpEq(T actual, T expected) + template void ExpectFpEq(T actual, T expected) { static_assert(std::is_floating_point_v, "T must be floating-point"); @@ -38,23 +37,25 @@ namespace ccm::test EXPECT_EQ(actual_bits.uintval(), expected_bits.uintval()) << "actual=" << actual << " expected=" << expected; } - template - void ExpectFpNe(T actual, T expected) + template void ExpectFpNe(T actual, T expected) { using FPBits = ccm::support::fp::FPBits; FPBits actual_bits(actual); FPBits expected_bits(expected); - if (actual_bits.is_nan()) { EXPECT_FALSE(expected_bits.is_nan()); } - else if (expected_bits.is_nan()) { EXPECT_TRUE(actual_bits.is_nan()); } - else + if (actual_bits.is_nan()) + { + EXPECT_FALSE(expected_bits.is_nan()); + } else if (expected_bits.is_nan()) + { + EXPECT_TRUE(actual_bits.is_nan()); + } else { EXPECT_NE(actual_bits.uintval(), expected_bits.uintval()); } } - template - void ExpectSignedZero(T value, bool negative) + template void ExpectSignedZero(T value, bool negative) { EXPECT_EQ(value, T{}); EXPECT_EQ(std::signbit(value), negative); diff --git a/tests/shared/utils/helpers.hpp b/tests/shared/utils/helpers.hpp index c4cac47d..93d9e756 100644 --- a/tests/shared/utils/helpers.hpp +++ b/tests/shared/utils/helpers.hpp @@ -7,8 +7,7 @@ #include #include -template -int64_t ulp_difference(T a, T b) +template int64_t ulp_difference(T a, T b) { static_assert(std::is_floating_point_v, "T must be a floating-point type."); @@ -16,20 +15,25 @@ int64_t ulp_difference(T a, T b) return (distance > static_cast(std::numeric_limits::max())) ? std::numeric_limits::max() : static_cast(distance); } -template -class ULPEqualsMatcher : public ::testing::MatcherInterface +template class ULPEqualsMatcher : public ::testing::MatcherInterface { public: ULPEqualsMatcher(T expected, int64_t max_ulp) : expected_(expected), max_ulp_(max_ulp) { static_assert(std::is_floating_point_v, "Expected must be a floating-point type."); - if (max_ulp_ < 0) { throw std::invalid_argument("Maximum allowed ULPs must be non-negative."); } + if (max_ulp_ < 0) + { + throw std::invalid_argument("Maximum allowed ULPs must be non-negative."); + } } bool MatchAndExplain(T actual, ::testing::MatchResultListener * listener) const override { const int64_t ulp_diff = ulp_difference(actual, expected_); - if (ulp_diff <= max_ulp_) { return true; } + if (ulp_diff <= max_ulp_) + { + return true; + } if (listener) { *listener << "ULP Difference Exceeded:\n" @@ -41,23 +45,31 @@ class ULPEqualsMatcher : public ::testing::MatcherInterface return false; } - void DescribeTo(std::ostream * os) const override { *os << "is within " << max_ulp_ << " ULPs of " << expected_; } + void DescribeTo(std::ostream * os) const override + { + *os << "is within " << max_ulp_ << " ULPs of " << expected_; + } - void DescribeNegationTo(std::ostream * os) const override { *os << "is not within " << max_ulp_ << " ULPs of " << expected_; } + void DescribeNegationTo(std::ostream * os) const override + { + *os << "is not within " << max_ulp_ << " ULPs of " << expected_; + } private: T expected_; int64_t max_ulp_; }; -template -::testing::Matcher IsWithinULP(T expected, int64_t max_ulp) -{ return ::testing::MakeMatcher(new ULPEqualsMatcher(expected, max_ulp)); } +template ::testing::Matcher IsWithinULP(T expected, int64_t max_ulp) +{ + return ::testing::MakeMatcher(new ULPEqualsMatcher(expected, max_ulp)); +} // Convenience Matcher for IsWithinULP with a maximum ULP difference of 1. -template -::testing::Matcher IsWithinOneULP(T expected) -{ return IsWithinULP(expected, 1); } +template ::testing::Matcher IsWithinOneULP(T expected) +{ + return IsWithinULP(expected, 1); +} // EXPECT_ULP_NEAR checks if the ACTUAL value is within a specified maximum ULP difference from the EXPECTED value. #define EXPECT_ULP_NEAR(ACTUAL, EXPECTED, MAX_ULP) EXPECT_THAT(ACTUAL, IsWithinULP(EXPECTED, MAX_ULP)) diff --git a/tests/shared/utils/math_samples.hpp b/tests/shared/utils/math_samples.hpp index 6a6c6aba..139d6982 100644 --- a/tests/shared/utils/math_samples.hpp +++ b/tests/shared/utils/math_samples.hpp @@ -16,8 +16,7 @@ namespace ccm::test::samples { - template - struct ternary_input + template struct ternary_input { T x; T y; diff --git a/tests/shared/utils/pow_path_dispatch.hpp b/tests/shared/utils/pow_path_dispatch.hpp index 086431aa..1c3c8081 100644 --- a/tests/shared/utils/pow_path_dispatch.hpp +++ b/tests/shared/utils/pow_path_dispatch.hpp @@ -31,24 +31,42 @@ namespace ccm::test::pow_path { switch (path) { - case validation_path::public_default: return "public_default"; - case validation_path::generic_runtime: return "generic_runtime"; + case validation_path::public_default : return "public_default"; + case validation_path::generic_runtime : return "generic_runtime"; case validation_path::generic_modeled_domain: return "generic_modeled_domain"; - case validation_path::runtime_no_builtin: return "runtime_no_builtin"; - case validation_path::runtime_simd: return "runtime_simd"; - case validation_path::runtime_builtin: return "runtime_builtin"; + case validation_path::runtime_no_builtin : return "runtime_no_builtin"; + case validation_path::runtime_simd : return "runtime_simd"; + case validation_path::runtime_builtin : return "runtime_builtin"; } return "unknown"; } inline std::optional parse_path(std::string_view raw) { - if (raw == "public" || raw == "public_default" || raw == "public_api_runtime") { return validation_path::public_default; } - if (raw == "generic" || raw == "generic_runtime" || raw == "generic_pow_gen") { return validation_path::generic_runtime; } - if (raw == "generic_modeled_domain" || raw == "generic_pow_gen_modeled_domain") { return validation_path::generic_modeled_domain; } - if (raw == "runtime_no_builtin") { return validation_path::runtime_no_builtin; } - if (raw == "runtime_simd") { return validation_path::runtime_simd; } - if (raw == "runtime_builtin") { return validation_path::runtime_builtin; } + if (raw == "public" || raw == "public_default" || raw == "public_api_runtime") + { + return validation_path::public_default; + } + if (raw == "generic" || raw == "generic_runtime" || raw == "generic_pow_gen") + { + return validation_path::generic_runtime; + } + if (raw == "generic_modeled_domain" || raw == "generic_pow_gen_modeled_domain") + { + return validation_path::generic_modeled_domain; + } + if (raw == "runtime_no_builtin") + { + return validation_path::runtime_no_builtin; + } + if (raw == "runtime_simd") + { + return validation_path::runtime_simd; + } + if (raw == "runtime_builtin") + { + return validation_path::runtime_builtin; + } return std::nullopt; } @@ -58,8 +76,7 @@ namespace ccm::test::pow_path std::string skip_reason; }; - template - inline path_support path_is_supported(validation_path path) + template inline path_support path_is_supported(validation_path path) { switch (path) { @@ -74,33 +91,42 @@ namespace ccm::test::pow_path #endif case validation_path::runtime_simd: #if defined(CCMATH_HAS_SIMD) - if constexpr (std::is_same_v || std::is_same_v) { return { true, {} }; } + if constexpr (std::is_same_v || std::is_same_v) + { + return { true, {} }; + } return { false, "runtime_simd is only supported for float and double" }; #else return { false, "runtime_simd requires runtime SIMD to be compiled in" }; #endif case validation_path::runtime_builtin: - if constexpr (ccm::builtin::has_runtime_pow) { return { true, {} }; } + if constexpr (ccm::builtin::has_runtime_pow) + { + return { true, {} }; + } return { false, "runtime_builtin is unavailable on this toolchain" }; } return { false, "unknown path" }; } - template - inline T invoke(validation_path path, T base, T exponent) + template inline T invoke(validation_path path, T base, T exponent) { switch (path) { case validation_path::public_default: - if constexpr (std::is_same_v) { return ccm::powf(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); } - else if constexpr (std::is_same_v) { return ccm::pow(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); } - else + if constexpr (std::is_same_v) + { + return ccm::powf(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); + } else if constexpr (std::is_same_v) + { + return ccm::pow(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); + } else { return ccm::powl(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); } case validation_path::generic_runtime: case validation_path::generic_modeled_domain: return ccm::gen::pow_gen(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); - case validation_path::runtime_no_builtin: return ccm::rt::pow_rt(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); + case validation_path::runtime_no_builtin : return ccm::rt::pow_rt(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); case validation_path::runtime_simd: #if defined(CCMATH_HAS_SIMD) return ccm::rt::simd_impl::pow_simd_impl(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); @@ -111,8 +137,10 @@ namespace ccm::test::pow_path // pow_rt is only defined where has_runtime_pow holds (GCC/Clang). On other // toolchains this path reports unsupported via path_is_supported, but the switch must // still compile, so fall back to the generic kernel in the discarded branch. - if constexpr (ccm::builtin::has_runtime_pow) { return ccm::builtin::pow_rt(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); } - else + if constexpr (ccm::builtin::has_runtime_pow) + { + return ccm::builtin::pow_rt(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); + } else { return ccm::gen::pow_gen(ccm::test::runtime_value(base), ccm::test::runtime_value(exponent)); } @@ -132,8 +160,7 @@ namespace ccm::test::pow_path std::string simd_available; }; - template - inline configuration_report make_configuration_report(validation_path path) + template inline configuration_report make_configuration_report(validation_path path) { return configuration_report{ ccm::test::pow_configuration_name(), path_name(path), ccm::test::compiler_id(), ccm::test::platform_id(), @@ -158,6 +185,8 @@ namespace ccm::test::pow_path } inline void print_configuration_banner(const configuration_report & report) - { std::cout << "pow validation configuration: " << configuration_report_json(report) << '\n'; } + { + std::cout << "pow validation configuration: " << configuration_report_json(report) << '\n'; + } } // namespace ccm::test::pow_path diff --git a/tests/shared/utils/std_compare.hpp b/tests/shared/utils/std_compare.hpp index 80b6d1fd..9aebeb99 100644 --- a/tests/shared/utils/std_compare.hpp +++ b/tests/shared/utils/std_compare.hpp @@ -22,29 +22,25 @@ namespace ccm::test { - template - void ExpectSameAsStd(Actual actual, Expected expected, int64_t max_ulp = kMaxAllowedUlp) + template void ExpectSameAsStd(Actual actual, Expected expected, int64_t max_ulp = kMaxAllowedUlp) { using common_type = std::common_type_t; if constexpr (std::is_floating_point_v) { ExpectSameFloatingAsStd(static_cast(actual), static_cast(expected), max_ulp); - } - else + } else { EXPECT_EQ(actual, expected); } } - template - void ExpectUnaryMatchesStd(T input, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + template void ExpectUnaryMatchesStd(T input, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { SCOPED_TRACE(input); ExpectSameAsStd(ccm_fn(input), std_fn(input), max_ulp); } - template - void ExpectBinaryMatchesStd(T x, T y, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + template void ExpectBinaryMatchesStd(T x, T y, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { SCOPED_TRACE(x); SCOPED_TRACE(y); @@ -60,19 +56,20 @@ namespace ccm::test ExpectSameAsStd(ccm_fn(x, y, z), std_fn(x, y, z), max_ulp); } - template - void ExpectFrexpMatchesStd(T input) + template void ExpectFrexpMatchesStd(T input) { int ccm_exp{}; int std_exp{}; T const actual = ccm::frexp(input, ccm_exp); T const expected = std::frexp(input, &std_exp); ExpectSameFloatingAsStd(actual, expected); - if (std::isfinite(input) || input == T(0)) { EXPECT_EQ(ccm_exp, std_exp); } + if (std::isfinite(input) || input == T(0)) + { + EXPECT_EQ(ccm_exp, std_exp); + } } - template - void ExpectModfMatchesStd(T input) + template void ExpectModfMatchesStd(T input) { T ccm_int{}; T std_int{}; @@ -82,15 +79,17 @@ namespace ccm::test ExpectFpEq(ccm_int, std_int); } - template - void ExpectRemquoMatchesStd(T x, T y) + template void ExpectRemquoMatchesStd(T x, T y) { int ccm_quo{}; int std_quo{}; T const actual = ccm::remquo(x, y, &ccm_quo); T const expected = std::remquo(x, y, &std_quo); ExpectSameFloatingAsStd(actual, expected); - if (!std::isnan(expected)) { EXPECT_EQ(ccm_quo & 7, std_quo & 7); } + if (!std::isnan(expected)) + { + EXPECT_EQ(ccm_quo & 7, std_quo & 7); + } } } // namespace ccm::test diff --git a/tests/shared/utils/test_runtime.hpp b/tests/shared/utils/test_runtime.hpp index b50d8398..cc5cf081 100644 --- a/tests/shared/utils/test_runtime.hpp +++ b/tests/shared/utils/test_runtime.hpp @@ -3,8 +3,7 @@ namespace ccm::test { // Prevent constexpr evaluation of inputs when exercising runtime dispatch paths. - template - inline T runtime_value(T value) + template inline T runtime_value(T value) { volatile T sink = value; return sink; diff --git a/tests/shared/utils/ulp_distance.hpp b/tests/shared/utils/ulp_distance.hpp index 3e99aad6..8d9a45d4 100644 --- a/tests/shared/utils/ulp_distance.hpp +++ b/tests/shared/utils/ulp_distance.hpp @@ -17,8 +17,7 @@ namespace ccm::test::ulp infinity_mismatch, }; - template - struct distance_result + template struct distance_result { static_assert(std::is_floating_point_v, "T must be floating-point"); @@ -28,20 +27,20 @@ namespace ccm::test::ulp std::uint64_t distance = 0; }; - template - constexpr std::uint64_t saturating_distance(Storage lhs, Storage rhs) + template constexpr std::uint64_t saturating_distance(Storage lhs, Storage rhs) { const Storage diff = (lhs > rhs) ? (lhs - rhs) : (rhs - lhs); - if constexpr (sizeof(Storage) <= sizeof(std::uint64_t)) { return static_cast(diff); } - else + if constexpr (sizeof(Storage) <= sizeof(std::uint64_t)) + { + return static_cast(diff); + } else { return (diff > static_cast(std::numeric_limits::max())) ? std::numeric_limits::max() : static_cast(diff); } } - template - constexpr auto ordered_bits(ccm::support::fp::FPBits bits) + template constexpr auto ordered_bits(ccm::support::fp::FPBits bits) { using fp_bits = ccm::support::fp::FPBits; using storage_type = typename fp_bits::storage_type; @@ -55,8 +54,7 @@ namespace ccm::test::ulp return fp_bits::sign_mask + raw; } - template - constexpr distance_result classify_distance(T lhs, T rhs) + template constexpr distance_result classify_distance(T lhs, T rhs) { static_assert(std::is_floating_point_v, "T must be floating-point"); @@ -67,8 +65,14 @@ namespace ccm::test::ulp const bool exact_bitwise_equal = lhs_bits.uintval() == rhs_bits.uintval(); const bool numerically_equal = (lhs == rhs) || (lhs_bits.is_zero() && rhs_bits.is_zero()); - if (lhs_bits.is_nan() && rhs_bits.is_nan()) { return distance_result{ relation::both_nan, exact_bitwise_equal, false, 0 }; } - if (lhs_bits.is_nan() || rhs_bits.is_nan()) { return distance_result{ relation::nan_mismatch, exact_bitwise_equal, false, 0 }; } + if (lhs_bits.is_nan() && rhs_bits.is_nan()) + { + return distance_result{ relation::both_nan, exact_bitwise_equal, false, 0 }; + } + if (lhs_bits.is_nan() || rhs_bits.is_nan()) + { + return distance_result{ relation::nan_mismatch, exact_bitwise_equal, false, 0 }; + } if (lhs_bits.is_inf() && rhs_bits.is_inf()) { if (lhs_bits.sign() == rhs_bits.sign()) @@ -77,7 +81,10 @@ namespace ccm::test::ulp } return distance_result{ relation::infinity_mismatch, exact_bitwise_equal, false, 0 }; } - if (lhs_bits.is_inf() || rhs_bits.is_inf()) { return distance_result{ relation::infinity_mismatch, exact_bitwise_equal, false, 0 }; } + if (lhs_bits.is_inf() || rhs_bits.is_inf()) + { + return distance_result{ relation::infinity_mismatch, exact_bitwise_equal, false, 0 }; + } if (lhs_bits.is_zero() && rhs_bits.is_zero()) { // Signed zero is numerically equal but still exposed through exact_bitwise_equal. @@ -92,12 +99,12 @@ namespace ccm::test::ulp }; } - template - constexpr bool has_finite_distance(const distance_result& result) - { return result.kind == relation::finite || result.kind == relation::both_infinity_same_sign; } + template constexpr bool has_finite_distance(const distance_result & result) + { + return result.kind == relation::finite || result.kind == relation::both_infinity_same_sign; + } - template - constexpr std::uint64_t distance_or_max(T lhs, T rhs) + template constexpr std::uint64_t distance_or_max(T lhs, T rhs) { const auto result = classify_distance(lhs, rhs); return has_finite_distance(result) ? result.distance : std::numeric_limits::max(); diff --git a/tests/shared/utils/ulp_suite.hpp b/tests/shared/utils/ulp_suite.hpp index d4b0b64e..5f8817b0 100644 --- a/tests/shared/utils/ulp_suite.hpp +++ b/tests/shared/utils/ulp_suite.hpp @@ -26,8 +26,7 @@ namespace ccm::test // with a TODO in the implementation plus a regression test. inline constexpr int64_t kMaxAllowedUlp = 4; - template - void ExpectSameFloatingAsStd(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) + template void ExpectSameFloatingAsStd(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) { static_assert(std::is_floating_point_v, "T must be floating-point"); @@ -52,17 +51,20 @@ namespace ccm::test EXPECT_LE(ulp_difference(actual, expected), max_ulp) << "actual=" << actual << " expected=" << expected; } - template - void ExpectUlpNearVsStd(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) - { ExpectSameFloatingAsStd(actual, expected, max_ulp); } + template void ExpectUlpNearVsStd(T actual, T expected, int64_t max_ulp = kMaxAllowedUlp) + { + ExpectSameFloatingAsStd(actual, expected, max_ulp); + } - template - void ExpectUlpUnaryVsStd(T input, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) - { ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), max_ulp); } + template void ExpectUlpUnaryVsStd(T input, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + { + ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), max_ulp); + } - template - void ExpectUlpBinaryVsStd(T x, T y, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) - { ExpectSameFloatingAsStd(ccm_fn(x, y), std_fn(x, y), max_ulp); } + template void ExpectUlpBinaryVsStd(T x, T y, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + { + ExpectSameFloatingAsStd(ccm_fn(x, y), std_fn(x, y), max_ulp); + } template void ExpectUlpUnaryOver(const T (&inputs)[N], CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) @@ -75,7 +77,7 @@ namespace ccm::test } template - void ExpectUlpUnaryOver(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) + void ExpectUlpUnaryOver(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn, int64_t max_ulp = kMaxAllowedUlp) { for (T input : inputs) { @@ -84,15 +86,13 @@ namespace ccm::test } } - template - void ExpectDomainEdgeMatchesStd(T input, CcmFn ccm_fn, StdFn std_fn) + template void ExpectDomainEdgeMatchesStd(T input, CcmFn ccm_fn, StdFn std_fn) { SCOPED_TRACE(input); ExpectSameFloatingAsStd(ccm_fn(input), std_fn(input), kMaxAllowedUlp); } - template - void ExpectCcmNegativeDomainNaN(T input, CcmFn ccm_fn) + template void ExpectCcmNegativeDomainNaN(T input, CcmFn ccm_fn) { SCOPED_TRACE(input); const T result = ccm_fn(input); @@ -100,8 +100,9 @@ namespace ccm::test EXPECT_TRUE(std::signbit(result)); } - template - bool IsLikelyUnimplementedStub(T probe, CcmFn ccm_fn, StdFn std_fn) - { return ccm_fn(probe) == T{} && std_fn(probe) != T{}; } + template bool IsLikelyUnimplementedStub(T probe, CcmFn ccm_fn, StdFn std_fn) + { + return ccm_fn(probe) == T{} && std_fn(probe) != T{}; + } } // namespace ccm::test diff --git a/tests/shared/utils/worst_case_samples.hpp b/tests/shared/utils/worst_case_samples.hpp index d5c0563d..ba5bfb06 100644 --- a/tests/shared/utils/worst_case_samples.hpp +++ b/tests/shared/utils/worst_case_samples.hpp @@ -16,8 +16,7 @@ namespace ccm::test::worst_case { - template - struct PowCase + template struct PowCase { T base; T exponent; diff --git a/tests/src/math/basic/abs_test.cpp b/tests/src/math/basic/abs_test.cpp index 3f4e0024..a8212231 100644 --- a/tests/src/math/basic/abs_test.cpp +++ b/tests/src/math/basic/abs_test.cpp @@ -31,8 +31,7 @@ namespace using testing::TestWithParam; using testing::ValuesIn; - template - struct AbsTestParams + template struct AbsTestParams { T input{}; T expected{}; @@ -184,17 +183,13 @@ namespace } // namespace class CcmathAbsDoubleTests : public TestWithParam> -{ -}; +{}; class CcmathAbsFloatTests : public TestWithParam> -{ -}; +{}; class CcmathAbsLongDoubleTests : public TestWithParam> -{ -}; +{}; class CcmathAbsIntTests : public TestWithParam> -{ -}; +{}; INSTANTIATE_TEST_SUITE_P(AbsDoubleTests, CcmathAbsDoubleTests, ValuesIn(kAbsDoubleTestParams)); INSTANTIATE_TEST_SUITE_P(AbsFloatTests, CcmathAbsFloatTests, ValuesIn(kAbsFloatTestParams)); diff --git a/tests/src/math/basic/fdim_test.cpp b/tests/src/math/basic/fdim_test.cpp index 2efbcecb..78ccff50 100644 --- a/tests/src/math/basic/fdim_test.cpp +++ b/tests/src/math/basic/fdim_test.cpp @@ -23,8 +23,7 @@ namespace using testing::TestWithParam; using testing::ValuesIn; - template - struct FDimTestParams + template struct FDimTestParams { T x{}; T y{}; @@ -127,14 +126,11 @@ namespace } // namespace class CcmathFDimDoubleTests : public TestWithParam> -{ -}; +{}; class CcmathFDimFloatTests : public TestWithParam> -{ -}; +{}; class CcmathFDimLongDoubleTests : public TestWithParam> -{ -}; +{}; INSTANTIATE_TEST_SUITE_P(FDimDoubleTests, CcmathFDimDoubleTests, ValuesIn(kFDimDoubleTestParams)); INSTANTIATE_TEST_SUITE_P(FDimFloatTests, CcmathFDimFloatTests, ValuesIn(kFDimFloatTestParams)); diff --git a/tests/src/math/basic/fma_constexpr_upward_config_test.cpp b/tests/src/math/basic/fma_constexpr_upward_config_test.cpp index 8978c128..a617c9a2 100644 --- a/tests/src/math/basic/fma_constexpr_upward_config_test.cpp +++ b/tests/src/math/basic/fma_constexpr_upward_config_test.cpp @@ -22,4 +22,6 @@ namespace } // namespace int main() -{ return 0; } +{ + return 0; +} diff --git a/tests/src/math/basic/fma_correct_rounding_test.cpp b/tests/src/math/basic/fma_correct_rounding_test.cpp index 563b527b..59c943d5 100644 --- a/tests/src/math/basic/fma_correct_rounding_test.cpp +++ b/tests/src/math/basic/fma_correct_rounding_test.cpp @@ -112,14 +112,14 @@ namespace { 0x4000000000000001ULL, 0x4000000000000000ULL, 0x4000000000000001ULL, 0x4000000000000000ULL } }, }; - constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; - constexpr std::array kName = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; + constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; + constexpr std::array kName = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; } // namespace TEST(CcmathFmaCorrectRounding, FloatAllModes) { const int saved = std::fegetround(); - for (const auto& c : kFloatCases) + for (const auto & c : kFloatCases) { const float x = f_from_bits(c.x); const float y = f_from_bits(c.y); @@ -139,7 +139,7 @@ TEST(CcmathFmaCorrectRounding, FloatAllModes) TEST(CcmathFmaCorrectRounding, DoubleAllModes) { const int saved = std::fegetround(); - for (const auto& c : kDoubleCases) + for (const auto & c : kDoubleCases) { const double x = d_from_bits(c.x); const double y = d_from_bits(c.y); diff --git a/tests/src/math/basic/fma_test.cpp b/tests/src/math/basic/fma_test.cpp index b5ca2a97..ec8e1366 100644 --- a/tests/src/math/basic/fma_test.cpp +++ b/tests/src/math/basic/fma_test.cpp @@ -26,12 +26,15 @@ namespace { - template - constexpr bool same_bits(T lhs, T rhs) + template constexpr bool same_bits(T lhs, T rhs) { - if constexpr (std::is_same_v) { return ccm::support::bit_cast(lhs) == ccm::support::bit_cast(rhs); } - else if constexpr (std::is_same_v) { return ccm::support::bit_cast(lhs) == ccm::support::bit_cast(rhs); } - else + if constexpr (std::is_same_v) + { + return ccm::support::bit_cast(lhs) == ccm::support::bit_cast(rhs); + } else if constexpr (std::is_same_v) + { + return ccm::support::bit_cast(lhs) == ccm::support::bit_cast(rhs); + } else { return lhs == rhs; } @@ -72,7 +75,9 @@ namespace #if !CCMATH_SUPPORTS_DEEP_CONSTEXPR TEST(CcmathBasicTests, FmaDoubleFusedResidualRuntime) -{ EXPECT_TRUE(same_bits(ccm::fma(0x1.ffffffffffep-1, 0x1.ffffffffffep-1, -(0x1.ffffffffffep-1 * 0x1.ffffffffffep-1)), 0x1p-88)); } +{ + EXPECT_TRUE(same_bits(ccm::fma(0x1.ffffffffffep-1, 0x1.ffffffffffep-1, -(0x1.ffffffffffep-1 * 0x1.ffffffffffep-1)), 0x1p-88)); +} #endif TEST(CcmathBasicTests, FmaMatchesStdStructuredFloatCases) diff --git a/tests/src/math/basic/fmod_test.cpp b/tests/src/math/basic/fmod_test.cpp index fe79df19..8a366f60 100644 --- a/tests/src/math/basic/fmod_test.cpp +++ b/tests/src/math/basic/fmod_test.cpp @@ -102,7 +102,10 @@ TEST(CcmathBasicTests, FmodKernelRandomBitExactVsStd) const std::uint64_t by = bd(rng); std::memcpy(&x, &bx, sizeof x); std::memcpy(&y, &by, sizeof y); - if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0) { continue; } + if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0) + { + continue; + } ccm::test::ExpectFpEq(ccm::internal::fmod(x, y), std::fmod(x, y)); ++checked; } @@ -118,7 +121,10 @@ TEST(CcmathBasicTests, FmodKernelRandomBitExactVsStd) const std::uint32_t by = bf(rngf); std::memcpy(&x, &bx, sizeof x); std::memcpy(&y, &by, sizeof y); - if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0F) { continue; } + if (!std::isfinite(x) || !std::isfinite(y) || y == 0.0F) + { + continue; + } ccm::test::ExpectFpEq(ccm::internal::fmod(x, y), std::fmod(x, y)); ++checked; } diff --git a/tests/src/math/basic/nan_test.cpp b/tests/src/math/basic/nan_test.cpp index 8b0a70b5..e2a4e53f 100644 --- a/tests/src/math/basic/nan_test.cpp +++ b/tests/src/math/basic/nan_test.cpp @@ -171,18 +171,22 @@ TEST(CcmathBasicTests, Nan_LDouble64bit) * Because this is where the information to determine NaN stops and * the remainder afterward is padding that can't be determined or tested. */ -template -void removePaddingBits(std::array& byteArray) +template void removePaddingBits(std::array & byteArray) { // If the array has 10 or fewer elements, there's nothing to zero out - if (N <= 10) { return; } + if (N <= 10) + { + return; + } // Zero out everything after the 10th element - for (std::size_t i = 10; i < N; ++i) { byteArray.at(i) = std::byte{ 0 }; } + for (std::size_t i = 10; i < N; ++i) + { + byteArray.at(i) = std::byte{ 0 }; + } } -template -std::array ldoubleToByteArray(long double value) +template std::array ldoubleToByteArray(long double value) { std::array byteArray{}; std::memcpy(byteArray.data(), &value, sizeof(long double)); @@ -246,8 +250,7 @@ TEST(CcmathBasicTests, Nan_LDouble80bit) #elif LDBL_MANT_DIG == 113 // Does not strip padding as there shouldn't be any if ldouble is 128 bits -template -std::array ldoubleToByteArray(ccm::types::float128 value) +template std::array ldoubleToByteArray(ccm::types::float128 value) { std::array byteArray{}; std::memcpy(byteArray.data(), &value, sizeof(ccm::types::float128)); @@ -309,6 +312,8 @@ TEST(CcmathBasicTests, Nan_LDouble128bit) #else TEST(CcmathBasicTests, Nan_LDoubleUnknownBits) -{ FAIL() << "We do not know how to handle long doubles with an unknown number of bits. Please report this if you see this failure."; } +{ + FAIL() << "We do not know how to handle long doubles with an unknown number of bits. Please report this if you see this failure."; +} #endif #endif // !(defined(SYCL_LANGUAGE_VERSION) || defined(__INTEL_LLVM_COMPILER)) diff --git a/tests/src/math/basic/remainder_test.cpp b/tests/src/math/basic/remainder_test.cpp index 69da67f4..3de18746 100644 --- a/tests/src/math/basic/remainder_test.cpp +++ b/tests/src/math/basic/remainder_test.cpp @@ -20,11 +20,15 @@ namespace { constexpr double make_remainder(double x, double y) - { return ccm::remainder(x, y); } + { + return ccm::remainder(x, y); + } // Pulls the result of remainder(finite, inf) into a constant context. constexpr double remainder_by_inf(double x) - { return ccm::remainder(x, std::numeric_limits::infinity()); } + { + return ccm::remainder(x, std::numeric_limits::infinity()); + } } // namespace TEST(CcmathBasicTests, RemainderCompileTime) @@ -118,14 +122,20 @@ TEST(CcmathBasicTests, Remainder) { -1.0, 0.0 }, { 5.0, 3.0 }, { 7.5, 2.0 }, { -5.0, 3.0 }, { 5.0, -3.0 }, { -7.5, 2.0 }, { 9.0, 4.0 }, { 2.0, 1.0 }, { 3.0, 2.0 }, { 10.5, 3.25 }, { -10.5, 3.25 }, { 123.456, 7.0 }, { 0.1, 0.03 }, { -0.1, 0.03 }, }; - for (const auto & c : doubles) { ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); } + for (const auto & c : doubles) + { + ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); + } // A spread of normal values, both signs, for float. const float floats[][2] = { { 1.0F, 1.0F }, { 5.0F, 3.0F }, { 7.5F, 2.0F }, { -5.0F, 3.0F }, { 5.0F, -3.0F }, { 9.0F, 4.0F }, { 2.0F, 1.0F }, { 10.5F, 3.25F }, { 123.456F, 7.0F }, { 0.1F, 0.03F }, }; - for (const auto & c : floats) { ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); } + for (const auto & c : floats) + { + ccm::test::ExpectBinaryMatchesStd(c[0], c[1], ccm::remainder, static_cast(std::remainder)); + } // Subnormal operands. constexpr double subnormal_dividend = -5.0166534782602e-198; diff --git a/tests/src/math/constexpr_smoke_test.cpp b/tests/src/math/constexpr_smoke_test.cpp index 1d86b842..78e11170 100644 --- a/tests/src/math/constexpr_smoke_test.cpp +++ b/tests/src/math/constexpr_smoke_test.cpp @@ -185,6 +185,12 @@ TEST(CcmathMathConstexprSmokeTest, SincosInfinityAndNanPayload) std::feclearexcept(FE_ALL_EXCEPT); errno = 0; EXPECT_TRUE(ccm::isnan(ccm::gen::sin_gen(inf))); - if ((handling & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) { EXPECT_NE(std::fetestexcept(FE_INVALID), 0); } - if ((handling & get_mode(ccm_math_err_mode::eErrno)) != 0) { EXPECT_EQ(errno, EDOM); } + if ((handling & get_mode(ccm_math_err_mode::eErrnoExcept)) != 0) + { + EXPECT_NE(std::fetestexcept(FE_INVALID), 0); + } + if ((handling & get_mode(ccm_math_err_mode::eErrno)) != 0) + { + EXPECT_EQ(errno, EDOM); + } } diff --git a/tests/src/math/expo/exp2_test.cpp b/tests/src/math/expo/exp2_test.cpp index c38e1ffe..fe99cae7 100644 --- a/tests/src/math/expo/exp2_test.cpp +++ b/tests/src/math/expo/exp2_test.cpp @@ -19,7 +19,10 @@ TEST(CcmathExponentialTests, Exp2_Double_ValidInput) { constexpr double inputs[] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0 }; - for (double input : inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); } + for (double input : inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); + } // Test that exp2 is mathematically the same as exp( X * log(2) ) ccm::test::ExpectSameAsStd(ccm::exp2(4.0), std::exp(4.0 * std::log(2.0))); @@ -39,13 +42,19 @@ TEST(CcmathExponentialTests, Exp2_Double_EdgeCases) std::numeric_limits::denorm_min(), -std::numeric_limits::denorm_min(), std::numeric_limits::quiet_NaN() }; - for (double input : inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); } + for (double input : inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); + } } TEST(CcmathExponentialTests, Exp2_Float) { constexpr float inputs[] = { 1.0F, 2.0F, 4.0F, 8.0F, 16.0F, 32.0F, 64.0F, 128.0F, 256.0F, 512.0F, 1024.0F, 2048.0F, 4096.0F }; - for (float input : inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); } + for (float input : inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); + } } TEST(CcmathExponentialTests, Exp2_Float_EdgeCases) @@ -56,5 +65,8 @@ TEST(CcmathExponentialTests, Exp2_Float_EdgeCases) -std::numeric_limits::infinity(), -1.038470636961323e34F, std::numeric_limits::quiet_NaN() }; - for (float input : inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); } + for (float input : inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp2, static_cast(std::exp2)); + } } diff --git a/tests/src/math/expo/exp_test.cpp b/tests/src/math/expo/exp_test.cpp index 1296c5aa..1df6a67b 100644 --- a/tests/src/math/expo/exp_test.cpp +++ b/tests/src/math/expo/exp_test.cpp @@ -23,7 +23,10 @@ TEST(CcmathExponentialTests, Exp) static_assert(ccm::exp(1.0) == ccm::numbers::e, "ccm::exp is not working with static_assert!"); constexpr double double_inputs[] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0 }; - for (double input : double_inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); } + for (double input : double_inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); + } /* * For some reason with MSVC on Windows the following tests fails with the output: * ccm::exp(128.0) @@ -43,15 +46,24 @@ TEST(CcmathExponentialTests, Exp) constexpr double double_edges[] = { 0.0, -0.0, std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN() }; - for (double input : double_edges) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); } + for (double input : double_edges) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); + } // Float overloads. constexpr float float_inputs[] = { 1.0F, 2.0F, 4.0F, 8.0F, 16.0F, 32.0F, 64.0F, 256.0F, 512.0F, 1024.0F, 2048.0F, 4096.0F }; - for (float input : float_inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); } + for (float input : float_inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); + } // EXPECT_EQ(ccm::exp(128.0F), std::exp(128.0F)); // See above. constexpr float float_edges[] = { 0.0F, -0.0F, std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN() }; - for (float input : float_edges) { ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); } + for (float input : float_edges) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::exp, static_cast(std::exp)); + } } diff --git a/tests/src/math/expo/expo_ulp_accuracy_test.cpp b/tests/src/math/expo/expo_ulp_accuracy_test.cpp index 87aa00ad..0a1d63d0 100644 --- a/tests/src/math/expo/expo_ulp_accuracy_test.cpp +++ b/tests/src/math/expo/expo_ulp_accuracy_test.cpp @@ -18,34 +18,54 @@ #include TEST(CcmathExponentialUlpTests, ExpDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::exp, static_cast(std::exp)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::exp, static_cast(std::exp)); +} TEST(CcmathExponentialUlpTests, Exp2Double) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::exp2, static_cast(std::exp2)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::exp2, static_cast(std::exp2)); +} TEST(CcmathExponentialUlpTests, LogDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log, static_cast(std::log)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log, static_cast(std::log)); +} TEST(CcmathExponentialUlpTests, Log2Double) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log2, static_cast(std::log2)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log2, static_cast(std::log2)); +} TEST(CcmathExponentialUlpTests, Log10Double) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log10, static_cast(std::log10)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoDouble, ccm::log10, static_cast(std::log10)); +} TEST(CcmathExponentialUlpTests, ExpFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::exp, static_cast(std::exp)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::exp, static_cast(std::exp)); +} TEST(CcmathExponentialUlpTests, Exp2Float) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::exp2, static_cast(std::exp2)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::exp2, static_cast(std::exp2)); +} TEST(CcmathExponentialUlpTests, LogFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log, static_cast(std::log)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log, static_cast(std::log)); +} TEST(CcmathExponentialUlpTests, Log2Float) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log2, static_cast(std::log2)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log2, static_cast(std::log2)); +} TEST(CcmathExponentialUlpTests, Log10Float) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log10, static_cast(std::log10)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kExpoFloat, ccm::log10, static_cast(std::log10)); +} TEST(CcmathExponentialUlpTests, DomainEdgeCasesMatchLibm) { diff --git a/tests/src/math/expo/log2_test.cpp b/tests/src/math/expo/log2_test.cpp index cc8abf09..d3799b79 100644 --- a/tests/src/math/expo/log2_test.cpp +++ b/tests/src/math/expo/log2_test.cpp @@ -21,11 +21,17 @@ TEST(CcmathExponentialTests, Log2) static_assert(ccm::log2(1.0) == 0.0, "log2 has failed testing that it is static_assert-able!"); // Check that we can use static_assert with this function constexpr double double_inputs[] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0 }; - for (double input : double_inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::log2, static_cast(std::log2)); } + for (double input : double_inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::log2, static_cast(std::log2)); + } // Test floating point precision constexpr float float_inputs[] = { 1.0F, 2.0F, 4.0F, 8.0F, 16.0F, 32.0F, 64.0F, 128.0F, 256.0F, 512.0F, 1024.0F, 2048.0F, 4096.0F }; - for (float input : float_inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::log2, static_cast(std::log2)); } + for (float input : float_inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::log2, static_cast(std::log2)); + } // Test edge cases ccm::test::ExpectUnaryMatchesStd(0.0, ccm::log2, static_cast(std::log2)); diff --git a/tests/src/math/expo/log_test.cpp b/tests/src/math/expo/log_test.cpp index 6f579172..caec9c5d 100644 --- a/tests/src/math/expo/log_test.cpp +++ b/tests/src/math/expo/log_test.cpp @@ -26,7 +26,10 @@ TEST(CcmathExponentialTests, Log) constexpr double inputs[] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0, 131072.0, 262144.0, 524288.0, 1048576.0 }; - for (double input : inputs) { ccm::test::ExpectUnaryMatchesStd(input, ccm::log, static_cast(std::log)); } + for (double input : inputs) + { + ccm::test::ExpectUnaryMatchesStd(input, ccm::log, static_cast(std::log)); + } // Check for edge cases ccm::test::ExpectUnaryMatchesStd(std::numeric_limits::quiet_NaN(), ccm::log, static_cast(std::log)); diff --git a/tests/src/math/fmanip/ldexp_test.cpp b/tests/src/math/fmanip/ldexp_test.cpp index 879f4a84..30f95384 100644 --- a/tests/src/math/fmanip/ldexp_test.cpp +++ b/tests/src/math/fmanip/ldexp_test.cpp @@ -18,16 +18,13 @@ // NOLINTBEGIN -template -class CcmathFmanipTests : public ::testing::Test -{ -}; +template class CcmathFmanipTests : public ::testing::Test +{}; class FPNameGenerator { public: - template - static std::string GetName(float) + template static std::string GetName(float) { if constexpr (std::is_same_v) return "float"; if constexpr (std::is_same_v) return "double"; diff --git a/tests/src/math/fmanip/nexttoward_test.cpp b/tests/src/math/fmanip/nexttoward_test.cpp index 6f20967f..409616f9 100644 --- a/tests/src/math/fmanip/nexttoward_test.cpp +++ b/tests/src/math/fmanip/nexttoward_test.cpp @@ -16,4 +16,6 @@ #include TEST(CcmathFmanipTests, Nexttoward) -{ EXPECT_EQ(ccm::nexttoward(1.0, 2.0), std::nexttoward(1.0, 2.0)); } +{ + EXPECT_EQ(ccm::nexttoward(1.0, 2.0), std::nexttoward(1.0, 2.0)); +} diff --git a/tests/src/math/internal/simd/pp_simd_test.cpp b/tests/src/math/internal/simd/pp_simd_test.cpp index e596dad4..5df708cf 100644 --- a/tests/src/math/internal/simd/pp_simd_test.cpp +++ b/tests/src/math/internal/simd/pp_simd_test.cpp @@ -32,19 +32,22 @@ namespace using namespace ccm::pp; // Fill a buffer with a deterministic, non-degenerate pattern. - template - void fill_a(T * p, int n) + template void fill_a(T * p, int n) { - for (int i = 0; i < n; ++i) { p[i] = static_cast(i + 1); } + for (int i = 0; i < n; ++i) + { + p[i] = static_cast(i + 1); + } } - template - void fill_b(T * p, int n) + template void fill_b(T * p, int n) { - for (int i = 0; i < n; ++i) { p[i] = static_cast((i % 3) + 1); } + for (int i = 0; i < n; ++i) + { + p[i] = static_cast((i % 3) + 1); + } } - template - void check_construction() + template void check_construction() { using T = typename V::value_type; constexpr int N = V::size(); @@ -55,15 +58,24 @@ namespace // load constructor (vector aligned) + store roundtrip (element aligned) V va(a, vector_aligned); va.copy_to(out, element_aligned); - for (int i = 0; i < N; ++i) { EXPECT_EQ(out[i], a[i]); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(out[i], a[i]); + } // broadcast V vb(static_cast(7)); - for (int i = 0; i < N; ++i) { EXPECT_EQ(vb[i], static_cast(7)); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(vb[i], static_cast(7)); + } // generator V vg([](auto i) { return static_cast(static_cast(i) * 2); }); - for (int i = 0; i < N; ++i) { EXPECT_EQ(vg[i], static_cast(i * 2)); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(vg[i], static_cast(i * 2)); + } // mutable lane proxy va[0] = static_cast(42); @@ -72,11 +84,13 @@ namespace // copy_from V vc; vc.copy_from(a, element_aligned); - for (int i = 0; i < N; ++i) { EXPECT_EQ(vc[i], a[i]); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(vc[i], a[i]); + } } - template - void check_arithmetic() + template void check_arithmetic() { using T = typename V::value_type; constexpr int N = V::size(); @@ -105,11 +119,13 @@ namespace vc -= vb; vc *= vb; vc /= vb; - for (int i = 0; i < N; ++i) { EXPECT_EQ(vc[i], static_cast(static_cast(a[i] * b[i]) / b[i])); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(vc[i], static_cast(static_cast(a[i] * b[i]) / b[i])); + } } - template - void check_masks() + template void check_masks() { using T = typename V::value_type; constexpr int N = V::size(); @@ -151,8 +167,7 @@ namespace EXPECT_TRUE(all_of(either)); } - template - void check_where() + template void check_where() { using T = typename V::value_type; constexpr int N = V::size(); @@ -164,7 +179,10 @@ namespace auto m = va < vb; V blended = simd_select(m, va, vb); // where m: va else vb - for (int i = 0; i < N; ++i) { EXPECT_EQ(blended[i], (a[i] < b[i]) ? a[i] : b[i]); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(blended[i], (a[i] < b[i]) ? a[i] : b[i]); + } V w = vb; where(m, w) = static_cast(0); @@ -176,8 +194,7 @@ namespace } } - template - void check_all() + template void check_all() { check_construction(); check_arithmetic(); @@ -186,12 +203,15 @@ namespace } inline void expect_close(float got, float ref) - { EXPECT_FLOAT_EQ(got, ref); } + { + EXPECT_FLOAT_EQ(got, ref); + } inline void expect_close(double got, double ref) - { EXPECT_DOUBLE_EQ(got, ref); } + { + EXPECT_DOUBLE_EQ(got, ref); + } - template - void check_integer_ops() + template void check_integer_ops() { using V = basic_simd; constexpr int N = V::size(); @@ -237,18 +257,23 @@ namespace } } - template - void check_conversions() + template void check_conversions() { using T = typename V::value_type; using I = std::conditional_t; constexpr int N = V::size(); alignas(64) T x[16]; - for (int i = 0; i < N; ++i) { x[i] = static_cast(i - N / 2) + static_cast(0.5); } + for (int i = 0; i < N; ++i) + { + x[i] = static_cast(i - N / 2) + static_cast(0.5); + } V vx(x, element_aligned); auto vi = static_simd_cast(vx); // truncation toward zero - for (int i = 0; i < N; ++i) { EXPECT_EQ(vi[i], static_cast(x[i])); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(vi[i], static_cast(x[i])); + } auto vb = simd_bit_cast(vx); // bit reinterpret for (int i = 0; i < N; ++i) @@ -259,11 +284,13 @@ namespace } auto back = static_simd_cast(vi); - for (int i = 0; i < N; ++i) { expect_close(back[i], static_cast(static_cast(x[i]))); } + for (int i = 0; i < N; ++i) + { + expect_close(back[i], static_cast(static_cast(x[i]))); + } } - template - void check_math() + template void check_math() { using T = typename V::value_type; constexpr int N = V::size(); @@ -307,14 +334,16 @@ namespace } } - template - void check_reduce() + template void check_reduce() { using T = typename V::value_type; using M = typename V::mask_type; constexpr int N = V::size(); alignas(64) T a[16]; - for (int i = 0; i < N; ++i) { a[i] = static_cast((i * 7 % 13) + 1); } + for (int i = 0; i < N; ++i) + { + a[i] = static_cast((i * 7 % 13) + 1); + } V v(a, element_aligned); T sum = 0, prod = 1, mn = a[0], mx = a[0]; @@ -343,8 +372,7 @@ namespace { mmn = mmx = a[i]; any = true; - } - else + } else { mmn = a[i] < mmn ? a[i] : mmn; mmx = a[i] > mmx ? a[i] : mmx; @@ -356,8 +384,7 @@ namespace expect_close(reduce_max(v, m), mmx); } - template - void check_masked_memory() + template void check_masked_memory() { using T = typename V::value_type; using M = typename V::mask_type; @@ -368,44 +395,80 @@ namespace M m = M::from_bits(0x5555555555555555ull); // even lanes alignas(64) T dst[16]; - for (int i = 0; i < N; ++i) { dst[i] = static_cast(-1); } + for (int i = 0; i < N; ++i) + { + dst[i] = static_cast(-1); + } where(m, v).copy_to(dst); - for (int i = 0; i < N; ++i) { EXPECT_EQ(dst[i], (i % 2 == 0) ? a[i] : static_cast(-1)); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(dst[i], (i % 2 == 0) ? a[i] : static_cast(-1)); + } alignas(64) T src[16]; - for (int i = 0; i < N; ++i) { src[i] = static_cast(100 + i); } + for (int i = 0; i < N; ++i) + { + src[i] = static_cast(100 + i); + } V w(static_cast(0)); where(m, w).copy_from(src); - for (int i = 0; i < N; ++i) { EXPECT_EQ(w[i], (i % 2 == 0) ? src[i] : static_cast(0)); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(w[i], (i % 2 == 0) ? src[i] : static_cast(0)); + } alignas(64) T dst2[16]; - for (int i = 0; i < N; ++i) { dst2[i] = static_cast(-1); } + for (int i = 0; i < N; ++i) + { + dst2[i] = static_cast(-1); + } V const cw = v; where(m, cw).copy_to(dst2); // const_where_expression - for (int i = 0; i < N; ++i) { EXPECT_EQ(dst2[i], (i % 2 == 0) ? a[i] : static_cast(-1)); } + for (int i = 0; i < N; ++i) + { + EXPECT_EQ(dst2[i], (i % 2 == 0) ? a[i] : static_cast(-1)); + } } } // namespace TEST(PpSimdTest, ScalarAbiFloat) -{ check_all>(); } +{ + check_all>(); +} TEST(PpSimdTest, ScalarAbiDouble) -{ check_all>(); } +{ + check_all>(); +} TEST(PpSimdTest, ScalarAbiInt32) -{ check_all>(); } +{ + check_all>(); +} TEST(PpSimdTest, VecAbiFloat4) -{ check_all>>(); } +{ + check_all>>(); +} TEST(PpSimdTest, VecAbiFloat8) -{ check_all>>(); } +{ + check_all>>(); +} TEST(PpSimdTest, VecAbiDouble2) -{ check_all>>(); } +{ + check_all>>(); +} TEST(PpSimdTest, VecAbiInt32x4) -{ check_all>>(); } +{ + check_all>>(); +} TEST(PpSimdTest, NativeFloat) -{ check_all>(); } +{ + check_all>(); +} TEST(PpSimdTest, NativeDouble) -{ check_all>(); } +{ + check_all>(); +} TEST(PpSimdTest, AliasesAndTraits) { @@ -430,11 +493,17 @@ TEST(PpSimdTest, AliasesAndTraits) } TEST(PpSimdTest, IntegerOpsScalar) -{ check_integer_ops(); } +{ + check_integer_ops(); +} TEST(PpSimdTest, IntegerOpsVec4) -{ check_integer_ops>(); } +{ + check_integer_ops>(); +} TEST(PpSimdTest, IntegerOpsVec8) -{ check_integer_ops>(); } +{ + check_integer_ops>(); +} TEST(PpSimdTest, ConversionsFloat) { @@ -505,7 +574,10 @@ TEST(PpSimdTest, CatSplit) auto cat = simd_cat(lo, hi); static_assert(decltype(cat)::size() == 8, ""); - for (int i = 0; i < 8; ++i) { EXPECT_EQ(cat[i], a[i]); } + for (int i = 0; i < 8; ++i) + { + EXPECT_EQ(cat[i], a[i]); + } auto parts = simd_split(cat); EXPECT_EQ(parts.size(), 2u); diff --git a/tests/src/math/internal/support/math_support_test.cpp b/tests/src/math/internal/support/math_support_test.cpp index 2a638dbb..fb0a1968 100644 --- a/tests/src/math/internal/support/math_support_test.cpp +++ b/tests/src/math/internal/support/math_support_test.cpp @@ -19,31 +19,43 @@ namespace { - template - [[nodiscard]] constexpr T trailing_ones_ref(std::size_t count) + template [[nodiscard]] constexpr T trailing_ones_ref(std::size_t count) { constexpr std::size_t digits = std::numeric_limits::digits; - if (count == 0) { return T(0); } - if (count == digits) { return std::numeric_limits::max(); } + if (count == 0) + { + return T(0); + } + if (count == digits) + { + return std::numeric_limits::max(); + } return static_cast((T(1) << count) - T(1)); } - template - [[nodiscard]] constexpr T leading_ones_ref(std::size_t count) + template [[nodiscard]] constexpr T leading_ones_ref(std::size_t count) { constexpr std::size_t digits = std::numeric_limits::digits; - if (count == 0) { return T(0); } - if (count == digits) { return std::numeric_limits::max(); } + if (count == 0) + { + return T(0); + } + if (count == digits) + { + return std::numeric_limits::max(); + } return static_cast(~trailing_ones_ref(digits - count)); } - template - void expect_masks_for_all_counts() + template void expect_masks_for_all_counts() { EXPECT_EQ((ccm::support::mask_trailing_ones()), trailing_ones_ref(Count)); EXPECT_EQ((ccm::support::mask_leading_ones()), leading_ones_ref(Count)); - if constexpr (Count < std::numeric_limits::digits) { expect_masks_for_all_counts(); } + if constexpr (Count < std::numeric_limits::digits) + { + expect_masks_for_all_counts(); + } } } // namespace @@ -91,7 +103,10 @@ TEST(CcmathInternalSupportTests, AddOverflowInt8MatchesReferenceExhaustively) const bool expected_overflow = expected < std::numeric_limits::min() || expected > std::numeric_limits::max(); EXPECT_EQ(overflow, expected_overflow); - if (!expected_overflow) { EXPECT_EQ(out, static_cast(expected)); } + if (!expected_overflow) + { + EXPECT_EQ(out, static_cast(expected)); + } } } } @@ -108,7 +123,10 @@ TEST(CcmathInternalSupportTests, SubOverflowInt8MatchesReferenceExhaustively) const bool expected_overflow = expected < std::numeric_limits::min() || expected > std::numeric_limits::max(); EXPECT_EQ(overflow, expected_overflow); - if (!expected_overflow) { EXPECT_EQ(out, static_cast(expected)); } + if (!expected_overflow) + { + EXPECT_EQ(out, static_cast(expected)); + } } } } @@ -175,7 +193,7 @@ TEST(CcmathInternalSupportTests, AddWithCarryUint64MatchesBoundaryCases) { 0x8000000000000000ULL, 0x8000000000000000ULL, 0ULL, 0ULL, 1ULL }, } }; - for (const auto& test_case : cases) + for (const auto & test_case : cases) { std::uint64_t carry_out = 0; const std::uint64_t actual = ccm::support::add_with_carry(test_case.lhs, test_case.rhs, test_case.carry_in, carry_out); @@ -207,7 +225,7 @@ TEST(CcmathInternalSupportTests, SubWithBorrowUint64MatchesBoundaryCases) { 0x7FFFFFFFFFFFFFFFULL, 0x8000000000000000ULL, 0ULL, std::numeric_limits::max(), 1ULL }, } }; - for (const auto& test_case : cases) + for (const auto & test_case : cases) { std::uint64_t borrow_out = 0; const std::uint64_t actual = ccm::support::sub_with_borrow(test_case.lhs, test_case.rhs, test_case.borrow_in, borrow_out); diff --git a/tests/src/math/internal/types/big_int_test.cpp b/tests/src/math/internal/types/big_int_test.cpp index 8559f9c3..6634a5aa 100644 --- a/tests/src/math/internal/types/big_int_test.cpp +++ b/tests/src/math/internal/types/big_int_test.cpp @@ -26,8 +26,7 @@ namespace { - template - struct RefUint + template struct RefUint { static_assert(Bits > 0 && Bits % 32 == 0, "RefUint requires a 32-bit limb width"); @@ -36,7 +35,10 @@ namespace std::array limbs{}; - [[nodiscard]] static RefUint zero() { return {}; } + [[nodiscard]] static RefUint zero() + { + return {}; + } [[nodiscard]] static RefUint one() { @@ -55,20 +57,31 @@ namespace [[nodiscard]] static RefUint from_u64(std::uint64_t value) { RefUint out; - if constexpr (limb_count > 0) { out.limbs[0] = static_cast(value); } - if constexpr (limb_count > 1) { out.limbs[1] = static_cast(value >> 32U); } + if constexpr (limb_count > 0) + { + out.limbs[0] = static_cast(value); + } + if constexpr (limb_count > 1) + { + out.limbs[1] = static_cast(value >> 32U); + } return out; } - template - [[nodiscard]] static RefUint from_words64(const std::array& words) + template [[nodiscard]] static RefUint from_words64(const std::array & words) { RefUint out; for (std::size_t i = 0; i < WordCount; ++i) { const std::size_t lo_index = i * 2; - if (lo_index < limb_count) { out.limbs[lo_index] = static_cast(words[i]); } - if (lo_index + 1 < limb_count) { out.limbs[lo_index + 1] = static_cast(words[i] >> 32U); } + if (lo_index < limb_count) + { + out.limbs[lo_index] = static_cast(words[i]); + } + if (lo_index + 1 < limb_count) + { + out.limbs[lo_index + 1] = static_cast(words[i] >> 32U); + } } return out; } @@ -77,14 +90,20 @@ namespace { for (std::uint32_t limb : limbs) { - if (limb != 0U) { return false; } + if (limb != 0U) + { + return false; + } } return true; } [[nodiscard]] bool get_bit(std::size_t bit) const { - if (bit >= Bits) { return false; } + if (bit >= Bits) + { + return false; + } const std::size_t limb_index = bit / limb_bits; const std::size_t bit_index = bit % limb_bits; return ((limbs[limb_index] >> bit_index) & 1U) != 0U; @@ -104,27 +123,42 @@ namespace limbs[limb_index] &= ~(1U << bit_index); } - [[nodiscard]] int compare(const RefUint& other) const + [[nodiscard]] int compare(const RefUint & other) const { for (std::size_t i = limb_count; i-- > 0;) { - if (limbs[i] < other.limbs[i]) { return -1; } - if (limbs[i] > other.limbs[i]) { return 1; } + if (limbs[i] < other.limbs[i]) + { + return -1; + } + if (limbs[i] > other.limbs[i]) + { + return 1; + } } return 0; } - [[nodiscard]] bool operator==(const RefUint& other) const { return limbs == other.limbs; } - [[nodiscard]] bool operator!=(const RefUint& other) const { return !(*this == other); } + [[nodiscard]] bool operator==(const RefUint & other) const + { + return limbs == other.limbs; + } + [[nodiscard]] bool operator!=(const RefUint & other) const + { + return !(*this == other); + } [[nodiscard]] RefUint bitwise_not() const { RefUint out; - for (std::size_t i = 0; i < limb_count; ++i) { out.limbs[i] = ~limbs[i]; } + for (std::size_t i = 0; i < limb_count; ++i) + { + out.limbs[i] = ~limbs[i]; + } return out; } - [[nodiscard]] std::pair add(const RefUint& other) const + [[nodiscard]] std::pair add(const RefUint & other) const { RefUint out; std::uint64_t carry = 0; @@ -137,7 +171,7 @@ namespace return { out, carry != 0 }; } - [[nodiscard]] std::pair sub(const RefUint& other) const + [[nodiscard]] std::pair sub(const RefUint & other) const { RefUint out; std::uint64_t borrow = 0; @@ -151,8 +185,7 @@ namespace return { out, borrow != 0 }; } - template - [[nodiscard]] RefUint full_mul(const RefUint& other) const + template [[nodiscard]] RefUint full_mul(const RefUint & other) const { RefUint out; for (std::size_t i = 0; i < limb_count; ++i) @@ -162,9 +195,9 @@ namespace { const std::size_t out_index = i + j; const std::uint64_t accum = static_cast(out.limbs[out_index]) + - static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; - out.limbs[out_index] = static_cast(accum); - carry = accum >> limb_bits; + static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; + out.limbs[out_index] = static_cast(accum); + carry = accum >> limb_bits; } for (std::size_t out_index = i + RefUint::limb_count; carry != 0 && out_index < RefUint::limb_count; ++out_index) { @@ -178,7 +211,10 @@ namespace [[nodiscard]] RefUint shift_left(std::size_t shift) const { - if (shift >= Bits) { return {}; } + if (shift >= Bits) + { + return {}; + } RefUint out; const std::size_t limb_shift = shift / limb_bits; @@ -186,20 +222,32 @@ namespace for (std::size_t i = 0; i < limb_count; ++i) { - if (limbs[i] == 0U) { continue; } + if (limbs[i] == 0U) + { + continue; + } const std::size_t out_index = i + limb_shift; - if (out_index >= limb_count) { continue; } + if (out_index >= limb_count) + { + continue; + } const std::uint64_t wide = static_cast(limbs[i]) << bit_shift; out.limbs[out_index] |= static_cast(wide); - if (bit_shift != 0 && out_index + 1 < limb_count) { out.limbs[out_index + 1] |= static_cast(wide >> limb_bits); } + if (bit_shift != 0 && out_index + 1 < limb_count) + { + out.limbs[out_index + 1] |= static_cast(wide >> limb_bits); + } } return out; } [[nodiscard]] RefUint shift_right(std::size_t shift) const { - if (shift >= Bits) { return {}; } + if (shift >= Bits) + { + return {}; + } RefUint out; const std::size_t limb_shift = shift / limb_bits; @@ -228,16 +276,22 @@ namespace } } - [[nodiscard]] std::pair divmod(const RefUint& divider) const + [[nodiscard]] std::pair divmod(const RefUint & divider) const { RefUint quotient; RefUint remainder; - if (divider.is_zero()) { return { quotient, remainder }; } + if (divider.is_zero()) + { + return { quotient, remainder }; + } for (std::size_t bit = Bits; bit-- > 0;) { remainder.shift_left_one_in_place(); - if (get_bit(bit)) { remainder.limbs[0] |= 1U; } + if (get_bit(bit)) + { + remainder.limbs[0] |= 1U; + } if (remainder.compare(divider) >= 0) { remainder = remainder.sub(divider).first; @@ -247,22 +301,26 @@ namespace return { quotient, remainder }; } - template - [[nodiscard]] RefUint truncate_low() const + template [[nodiscard]] RefUint truncate_low() const { RefUint out; const std::size_t max_limbs = std::min(limb_count, RefUint::limb_count); - for (std::size_t i = 0; i < max_limbs; ++i) { out.limbs[i] = limbs[i]; } + for (std::size_t i = 0; i < max_limbs; ++i) + { + out.limbs[i] = limbs[i]; + } return out; } - template - [[nodiscard]] RefUint slice(std::size_t bit_offset) const + template [[nodiscard]] RefUint slice(std::size_t bit_offset) const { RefUint out; for (std::size_t bit = 0; bit < NewBits; ++bit) { - if (get_bit(bit_offset + bit)) { out.set_bit(bit); } + if (get_bit(bit_offset + bit)) + { + out.set_bit(bit); + } } return out; } @@ -271,7 +329,10 @@ namespace { for (std::size_t i = 2; i < limb_count; ++i) { - if (limbs[i] != 0U) { return false; } + if (limbs[i] != 0U) + { + return false; + } } return true; } @@ -279,8 +340,14 @@ namespace [[nodiscard]] std::uint64_t to_u64() const { std::uint64_t out = 0; - if constexpr (limb_count > 0) { out |= limbs[0]; } - if constexpr (limb_count > 1) { out |= static_cast(limbs[1]) << 32U; } + if constexpr (limb_count > 0) + { + out |= limbs[0]; + } + if constexpr (limb_count > 1) + { + out |= static_cast(limbs[1]) << 32U; + } return out; } @@ -293,26 +360,33 @@ namespace { if (!started) { - if (limbs[i] == 0U && i != 0) { continue; } + if (limbs[i] == 0U && i != 0) + { + continue; + } out << std::hex << std::uppercase << limbs[i]; started = true; - } - else + } else { out << std::hex << std::uppercase << std::setfill('0') << std::setw(8) << limbs[i]; } } - if (!started) { out << '0'; } + if (!started) + { + out << '0'; + } return out.str(); } }; - template - struct RefInt + template struct RefInt { RefUint raw{}; - [[nodiscard]] static RefInt zero() { return {}; } + [[nodiscard]] static RefInt zero() + { + return {}; + } [[nodiscard]] static RefInt one() { @@ -349,24 +423,39 @@ namespace const std::uint64_t raw_bits = static_cast(value); for (std::size_t bit = 0; bit < std::min(Bits, 64); ++bit) { - if (((raw_bits >> bit) & 1ULL) != 0ULL) { out.raw.set_bit(bit); } + if (((raw_bits >> bit) & 1ULL) != 0ULL) + { + out.raw.set_bit(bit); + } } if (value < 0) { - for (std::size_t bit = 64; bit < Bits; ++bit) { out.raw.set_bit(bit); } + for (std::size_t bit = 64; bit < Bits; ++bit) + { + out.raw.set_bit(bit); + } } return out; } - [[nodiscard]] bool is_negative() const { return raw.get_bit(Bits - 1); } + [[nodiscard]] bool is_negative() const + { + return raw.get_bit(Bits - 1); + } - [[nodiscard]] int compare(const RefInt& other) const + [[nodiscard]] int compare(const RefInt & other) const { - if (is_negative() != other.is_negative()) { return is_negative() ? -1 : 1; } + if (is_negative() != other.is_negative()) + { + return is_negative() ? -1 : 1; + } return raw.compare(other.raw); } - [[nodiscard]] bool operator==(const RefInt& other) const { return raw == other.raw; } + [[nodiscard]] bool operator==(const RefInt & other) const + { + return raw == other.raw; + } [[nodiscard]] RefInt negated() const { @@ -375,30 +464,51 @@ namespace return out; } - [[nodiscard]] RefInt add(const RefInt& other) const { return RefInt{ raw.add(other.raw).first }; } - [[nodiscard]] RefInt sub(const RefInt& other) const { return RefInt{ raw.sub(other.raw).first }; } - [[nodiscard]] RefInt mul(const RefInt& other) const { return RefInt{ raw.full_mul(other.raw).template truncate_low() }; } + [[nodiscard]] RefInt add(const RefInt & other) const + { + return RefInt{ raw.add(other.raw).first }; + } + [[nodiscard]] RefInt sub(const RefInt & other) const + { + return RefInt{ raw.sub(other.raw).first }; + } + [[nodiscard]] RefInt mul(const RefInt & other) const + { + return RefInt{ raw.full_mul(other.raw).template truncate_low() }; + } - [[nodiscard]] RefUint abs_unsigned() const { return is_negative() ? negated().raw : raw; } + [[nodiscard]] RefUint abs_unsigned() const + { + return is_negative() ? negated().raw : raw; + } [[nodiscard]] RefInt arithmetic_shift_right(std::size_t shift) const { - if (shift >= Bits) { return is_negative() ? minus_one() : zero(); } + if (shift >= Bits) + { + return is_negative() ? minus_one() : zero(); + } RefInt out; out.raw = raw.shift_right(shift); if (is_negative()) { - for (std::size_t bit = Bits - shift; bit < Bits; ++bit) { out.raw.set_bit(bit); } + for (std::size_t bit = Bits - shift; bit < Bits; ++bit) + { + out.raw.set_bit(bit); + } } return out; } - [[nodiscard]] std::pair divmod(const RefInt& divider) const + [[nodiscard]] std::pair divmod(const RefInt & divider) const { RefInt quotient; RefInt remainder; - if (divider.raw.is_zero()) { return { quotient, remainder }; } + if (divider.raw.is_zero()) + { + return { quotient, remainder }; + } const RefUint lhs_mag = abs_unsigned(); const RefUint rhs_mag = divider.abs_unsigned(); @@ -407,62 +517,75 @@ namespace quotient.raw = uq; remainder.raw = ur; - if (is_negative() != divider.is_negative()) { quotient = quotient.negated(); } - if (is_negative()) { remainder = remainder.negated(); } + if (is_negative() != divider.is_negative()) + { + quotient = quotient.negated(); + } + if (is_negative()) + { + remainder = remainder.negated(); + } return { quotient, remainder }; } - [[nodiscard]] std::string hex_string() const { return raw.hex_string(); } + [[nodiscard]] std::string hex_string() const + { + return raw.hex_string(); + } }; - template - [[nodiscard]] Big to_bigint(const RefUint& ref) + template [[nodiscard]] Big to_bigint(const RefUint & ref) { Big out{}; for (std::size_t bit = 0; bit < Bits; ++bit) { - if (ref.get_bit(bit)) { out.set_bit(bit); } + if (ref.get_bit(bit)) + { + out.set_bit(bit); + } } return out; } - template - [[nodiscard]] Big to_bigint(const RefInt& ref) - { return to_bigint(ref.raw); } + template [[nodiscard]] Big to_bigint(const RefInt & ref) + { + return to_bigint(ref.raw); + } template - [[nodiscard]] RefUint to_ref_unsigned(const ccm::types::BigInt& value) + [[nodiscard]] RefUint to_ref_unsigned(const ccm::types::BigInt & value) { RefUint out; for (std::size_t bit = 0; bit < Bits; ++bit) { - if (value.get_bit(bit)) { out.set_bit(bit); } + if (value.get_bit(bit)) + { + out.set_bit(bit); + } } return out; } - template - [[nodiscard]] RefInt to_ref_signed(const ccm::types::BigInt& value) - { return RefInt{ to_ref_unsigned(value) }; } + template [[nodiscard]] RefInt to_ref_signed(const ccm::types::BigInt & value) + { + return RefInt{ to_ref_unsigned(value) }; + } - template - [[nodiscard]] RefUint repeat_limb(std::uint32_t value) + template [[nodiscard]] RefUint repeat_limb(std::uint32_t value) { RefUint out; out.limbs.fill(value); return out; } - template - [[nodiscard]] RefUint single_bit(std::size_t bit) + template [[nodiscard]] RefUint single_bit(std::size_t bit) { RefUint out; out.set_bit(bit); return out; } - template - [[nodiscard]] RefUint generated_case(std::uint64_t seed) + template [[nodiscard]] RefUint generated_case(std::uint64_t seed) { RefUint out; std::uint64_t state = seed; @@ -477,8 +600,7 @@ namespace return out; } - template - [[nodiscard]] std::vector> build_unsigned_cases() + template [[nodiscard]] std::vector> build_unsigned_cases() { std::vector> out; out.push_back(RefUint::zero()); @@ -527,8 +649,7 @@ namespace return out; } - template - [[nodiscard]] std::vector> build_signed_cases() + template [[nodiscard]] std::vector> build_signed_cases() { std::vector> out = { RefInt::zero(), @@ -552,67 +673,74 @@ namespace return out; } - template - [[nodiscard]] int countl_zero_ref(const RefUint& value) + template [[nodiscard]] int countl_zero_ref(const RefUint & value) { int count = 0; for (std::size_t bit = Bits; bit-- > 0;) { - if (value.get_bit(bit)) { break; } + if (value.get_bit(bit)) + { + break; + } ++count; } return count; } - template - [[nodiscard]] int countr_zero_ref(const RefUint& value) + template [[nodiscard]] int countr_zero_ref(const RefUint & value) { int count = 0; for (std::size_t bit = 0; bit < Bits; ++bit) { - if (value.get_bit(bit)) { break; } + if (value.get_bit(bit)) + { + break; + } ++count; } return count; } - template - [[nodiscard]] int countl_one_ref(const RefUint& value) + template [[nodiscard]] int countl_one_ref(const RefUint & value) { int count = 0; for (std::size_t bit = Bits; bit-- > 0;) { - if (!value.get_bit(bit)) { break; } + if (!value.get_bit(bit)) + { + break; + } ++count; } return count; } - template - [[nodiscard]] int countr_one_ref(const RefUint& value) + template [[nodiscard]] int countr_one_ref(const RefUint & value) { int count = 0; for (std::size_t bit = 0; bit < Bits; ++bit) { - if (!value.get_bit(bit)) { break; } + if (!value.get_bit(bit)) + { + break; + } ++count; } return count; } - template - [[nodiscard]] int bit_width_ref(const RefUint& value) - { return value.is_zero() ? 0 : static_cast(Bits) - countl_zero_ref(value); } + template [[nodiscard]] int bit_width_ref(const RefUint & value) + { + return value.is_zero() ? 0 : static_cast(Bits) - countl_zero_ref(value); + } - template - void expect_bigint_equals_ref(const Big& actual, const Ref& expected) + template void expect_bigint_equals_ref(const Big & actual, const Ref & expected) { const auto actual_ref = to_ref_unsigned(actual); EXPECT_TRUE(actual_ref == expected) << "actual=" << actual_ref.hex_string() << " expected=" << expected.hex_string(); } - template - void expect_signed_bigint_equals_ref(const Big& actual, const RefInt& expected) + template void expect_signed_bigint_equals_ref(const Big & actual, const RefInt & expected) { const auto actual_ref = to_ref_signed(actual); EXPECT_TRUE(actual_ref == expected) << "actual=" << actual_ref.hex_string() << " expected=" << expected.hex_string(); @@ -687,7 +815,7 @@ TEST(CcmathInternalTypesTests, BigIntMultiwordMul2MatchesReference) { 0xFEDCBA9876543210ULL, 0x0123456789ABCDEFULL }, } }; - for (const auto& [lhs, rhs] : cases) + for (const auto & [lhs, rhs] : cases) { SCOPED_TRACE(lhs); SCOPED_TRACE(rhs); @@ -707,7 +835,7 @@ TEST(CcmathInternalTypesTests, BigIntUnsignedShiftsAndCountHelpersMatchReference const auto cases = build_unsigned_cases<256>(); const std::array shifts = { 0, 1, 7, 31, 32, 33, 63, 64, 65, 95, 127, 128, 129, 191, 255 }; - for (const auto& value : cases) + for (const auto & value : cases) { const Big big = to_bigint(value); SCOPED_TRACE(value.hex_string()); @@ -732,9 +860,9 @@ TEST(CcmathInternalTypesTests, BigIntUnsignedArithmeticMatchesReference) using Big = ccm::types::BigInt<256, false, std::uint64_t>; const auto cases = build_unsigned_cases<256>(); - for (const auto& lhs_ref : cases) + for (const auto & lhs_ref : cases) { - for (const auto& rhs_ref : cases) + for (const auto & rhs_ref : cases) { const Big lhs = to_bigint(lhs_ref); const Big rhs = to_bigint(rhs_ref); @@ -764,9 +892,9 @@ TEST(CcmathInternalTypesTests, BigIntQuickMulHiRespectsReferenceErrorBound) using Big = ccm::types::BigInt<256, false, std::uint64_t>; const auto cases = build_unsigned_cases<256>(); - for (const auto& lhs_ref : cases) + for (const auto & lhs_ref : cases) { - for (const auto& rhs_ref : cases) + for (const auto & rhs_ref : cases) { const Big lhs = to_bigint(lhs_ref); const Big rhs = to_bigint(rhs_ref); @@ -802,7 +930,10 @@ TEST(CcmathInternalTypesTests, BigIntPowNMatchesReference) std::uint64_t exp = exponent; while (exp > 0) { - if ((exp & 1ULL) != 0ULL) { expected = expected.full_mul(power).template truncate_low<128>(); } + if ((exp & 1ULL) != 0ULL) + { + expected = expected.full_mul(power).template truncate_low<128>(); + } exp >>= 1U; power = power.full_mul(power).template truncate_low<128>(); } @@ -826,7 +957,7 @@ TEST(CcmathInternalTypesTests, BigIntDivHalfWordTimesPowerOfTwoMatchesReference) const std::array divisors = { 1U, 3U, 5U, 0x7FFFFFFFU, 0x80000001U, 0xFFFFFFFFU }; const std::array shifts = { 0, 1, 7, 31, 32, 33, 63, 64, 95, 127, 191, 320 }; - for (const auto& numerator_ref : cases) + for (const auto & numerator_ref : cases) { for (HalfWord divisor : divisors) { @@ -860,7 +991,7 @@ TEST(CcmathInternalTypesTests, BigIntSignedArithmeticAndDivisionMatchReference) const auto cases = build_signed_cases<128>(); const std::array shifts = { 0, 1, 2, 31, 32, 33, 63, 64, 65, 127 }; - for (const auto& lhs_ref : cases) + for (const auto & lhs_ref : cases) { const Big lhs = to_bigint(lhs_ref); SCOPED_TRACE(lhs_ref.hex_string()); @@ -872,7 +1003,7 @@ TEST(CcmathInternalTypesTests, BigIntSignedArithmeticAndDivisionMatchReference) expect_signed_bigint_equals_ref(lhs << shift, RefInt<128>{ lhs_ref.raw.shift_left(shift) }); } - for (const auto& rhs_ref : cases) + for (const auto & rhs_ref : cases) { const Big rhs = to_bigint(rhs_ref); @@ -890,7 +1021,10 @@ TEST(CcmathInternalTypesTests, BigIntSignedArithmeticAndDivisionMatchReference) expect_signed_bigint_equals_ref(lhs * rhs, lhs_ref.mul(rhs_ref)); } - if (rhs_ref.raw.is_zero()) { continue; } + if (rhs_ref.raw.is_zero()) + { + continue; + } const auto [expected_q, expected_r] = lhs_ref.divmod(rhs_ref); { @@ -948,7 +1082,10 @@ TEST(CcmathInternalTypesTests, BigIntCrossWordSignedAndUnsignedConversionsPreser RefInt<192> wide_signed_expected{ signed_ref.raw.template slice<192>(0) }; if (signed_ref.is_negative()) { - for (std::size_t bit = 96; bit < 192; ++bit) { wide_signed_expected.raw.set_bit(bit); } + for (std::size_t bit = 96; bit < 192; ++bit) + { + wide_signed_expected.raw.set_bit(bit); + } } const SmallSigned small_s = to_bigint(signed_ref); @@ -963,17 +1100,21 @@ TEST(CcmathInternalSupportTests, BigIntMaskHelpersMatchReference) { using Big = ccm::types::BigInt<256, false, std::uint64_t>; - const auto expect_trailing = [](const auto& actual, std::size_t count) - { + const auto expect_trailing = [](const auto & actual, std::size_t count) { RefUint<256> expected; - for (std::size_t bit = 0; bit < count; ++bit) { expected.set_bit(bit); } + for (std::size_t bit = 0; bit < count; ++bit) + { + expected.set_bit(bit); + } expect_bigint_equals_ref(actual, expected); }; - const auto expect_leading = [](const auto& actual, std::size_t count) - { + const auto expect_leading = [](const auto & actual, std::size_t count) { RefUint<256> expected; - for (std::size_t bit = 256 - count; bit < 256; ++bit) { expected.set_bit(bit); } + for (std::size_t bit = 256 - count; bit < 256; ++bit) + { + expected.set_bit(bit); + } expect_bigint_equals_ref(actual, expected); }; diff --git a/tests/src/math/internal/types/dyadic_float_test.cpp b/tests/src/math/internal/types/dyadic_float_test.cpp index e37bee11..cee7c988 100644 --- a/tests/src/math/internal/types/dyadic_float_test.cpp +++ b/tests/src/math/internal/types/dyadic_float_test.cpp @@ -21,7 +21,9 @@ namespace constexpr bool kNoFpExceptions = false; double to_double(const Float128 & value) - { return value.template as(); } + { + return value.template as(); + } } // namespace TEST(CcmathInternalTypesTests, DyadicFloatQuickSubAndNegation) diff --git a/tests/src/math/internal/types/normalized_float_test.cpp b/tests/src/math/internal/types/normalized_float_test.cpp index 88986172..8178527f 100644 --- a/tests/src/math/internal/types/normalized_float_test.cpp +++ b/tests/src/math/internal/types/normalized_float_test.cpp @@ -46,7 +46,9 @@ TEST(CcmathInternalSupportTests, ExceptValuesLookupTowardZero) } TEST(CcmathInternalSupportTests, ExceptValuesLookupMissReturnsNullopt) -{ EXPECT_FALSE(kFloatExcepts.lookup(0x40000000U).has_value()); } +{ + EXPECT_FALSE(kFloatExcepts.lookup(0x40000000U).has_value()); +} TEST(CcmathInternalTypesTests, DyadicFloatRoundedDivNearOne) { diff --git a/tests/src/math/misc/gamma_test.cpp b/tests/src/math/misc/gamma_test.cpp index 783c3725..238364e8 100644 --- a/tests/src/math/misc/gamma_test.cpp +++ b/tests/src/math/misc/gamma_test.cpp @@ -20,12 +20,14 @@ namespace { - template - void ExpectRelativeNearStd(T input, CcmFn ccm_fn, StdFn std_fn, T rel_tol) + template void ExpectRelativeNearStd(T input, CcmFn ccm_fn, StdFn std_fn, T rel_tol) { const T actual = ccm_fn(input); const T expected = std_fn(input); - if (actual == expected) { return; } + if (actual == expected) + { + return; + } const T scale = std::fabs(expected) > T(1) ? std::fabs(expected) : T(1); EXPECT_NEAR(actual, expected, rel_tol * scale); } @@ -56,7 +58,10 @@ TEST(CcmathMiscTests, GammaMatchesLibmDouble) for (double x : ccm::test::samples::kGammaProbeDouble) { SCOPED_TRACE(x); - if (x < 0.0 && x == std::trunc(x)) { continue; } + if (x < 0.0 && x == std::trunc(x)) + { + continue; + } #if defined(_MSC_VER) && !defined(__clang__) ExpectRelativeNearStd(x, ccm::tgamma, static_cast(std::tgamma), 1e-6); #else @@ -70,7 +75,10 @@ TEST(CcmathMiscTests, GammaMatchesLibmFloat) for (float x : ccm::test::samples::kGammaProbeFloat) { SCOPED_TRACE(x); - if (x < 0.0F && x == std::trunc(x)) { continue; } + if (x < 0.0F && x == std::trunc(x)) + { + continue; + } ccm::test::ExpectUlpUnaryVsStd(x, ccm::tgamma, static_cast(std::tgamma)); } } diff --git a/tests/src/math/misc/lerp_test_std.cpp b/tests/src/math/misc/lerp_test_std.cpp index 37f27a34..ead9fbb0 100644 --- a/tests/src/math/misc/lerp_test_std.cpp +++ b/tests/src/math/misc/lerp_test_std.cpp @@ -18,7 +18,9 @@ #include TEST(CcmathBasicTests, LerpStaticAssert) -{ static_assert(ccm::lerp(1, 2, 0.5) == 1.5, "lerp has failed testing that it is static_assert-able!"); } +{ + static_assert(ccm::lerp(1, 2, 0.5) == 1.5, "lerp has failed testing that it is static_assert-able!"); +} TEST(CcmathBasicTests, LerpFloat) { diff --git a/tests/src/math/misc/lgamma_test.cpp b/tests/src/math/misc/lgamma_test.cpp index eaa5bdb3..ba1be896 100644 --- a/tests/src/math/misc/lgamma_test.cpp +++ b/tests/src/math/misc/lgamma_test.cpp @@ -20,12 +20,14 @@ namespace { - template - void ExpectRelativeNearStd(T input, CcmFn ccm_fn, StdFn std_fn, T rel_tol) + template void ExpectRelativeNearStd(T input, CcmFn ccm_fn, StdFn std_fn, T rel_tol) { const T actual = ccm_fn(input); const T expected = std_fn(input); - if (actual == expected) { return; } + if (actual == expected) + { + return; + } const T scale = std::fabs(expected) > T(1) ? std::fabs(expected) : T(1); EXPECT_NEAR(actual, expected, rel_tol * scale); } diff --git a/tests/src/math/nearest/ceil_round_rint_test.cpp b/tests/src/math/nearest/ceil_round_rint_test.cpp index d298cfa8..5e5fdd8e 100644 --- a/tests/src/math/nearest/ceil_round_rint_test.cpp +++ b/tests/src/math/nearest/ceil_round_rint_test.cpp @@ -30,15 +30,13 @@ namespace using testing::TestWithParam; using testing::ValuesIn; - template - struct NearestUnaryParams + template struct NearestUnaryParams { T input{}; T expected{}; }; - template - const std::vector>& BasicNearestParams() + template const std::vector> & BasicNearestParams() { static const std::vector> params{ { static_cast(1.0), StdFn(static_cast(1.0)) }, @@ -61,15 +59,12 @@ namespace return params; } - template - class NearestUnaryTest : public TestWithParam> - { - }; + template class NearestUnaryTest : public TestWithParam> + {}; #define INSTANTIATE_NEAREST_UNARY_TEST(SuiteName, Type, StdFn, CcmFn) \ class SuiteName##Type##Tests : public NearestUnaryTest \ - { \ - }; \ + {}; \ INSTANTIATE_TEST_SUITE_P(SuiteName##Type, SuiteName##Type##Tests, ValuesIn(BasicNearestParams())); \ TEST_P(SuiteName##Type##Tests, MatchesStd) \ { \ @@ -88,8 +83,7 @@ namespace #undef INSTANTIATE_NEAREST_UNARY_TEST - template - void ExpectNearestNanPropagation() + template void ExpectNearestNanPropagation() { ccm::test::ExpectUnaryMatchesStd(std::numeric_limits::quiet_NaN(), CcmFn, StdFn); ccm::test::ExpectUnaryMatchesStd(-std::numeric_limits::quiet_NaN(), CcmFn, StdFn); diff --git a/tests/src/math/nearest/floor_test.cpp b/tests/src/math/nearest/floor_test.cpp index dc7d2259..97a6af28 100644 --- a/tests/src/math/nearest/floor_test.cpp +++ b/tests/src/math/nearest/floor_test.cpp @@ -58,8 +58,7 @@ namespace } // namespace class CcmathFloorTests : public TestWithParam -{ -}; +{}; INSTANTIATE_TEST_SUITE_P(FloorTests, CcmathFloorTests, ValuesIn(kFloorTestsParams)); diff --git a/tests/src/math/nearest/nearbyint_test.cpp b/tests/src/math/nearest/nearbyint_test.cpp index 8bfeea53..4e87caf6 100644 --- a/tests/src/math/nearest/nearbyint_test.cpp +++ b/tests/src/math/nearest/nearbyint_test.cpp @@ -29,8 +29,7 @@ namespace using testing::TestWithParam; using testing::ValuesIn; - template - struct NearbyIntTestParams + template struct NearbyIntTestParams { InputType input{}; OutputType expected{}; @@ -167,20 +166,16 @@ namespace } // namespace class CcmathNearbyIntIntegerTests : public TestWithParam> -{ -}; +{}; class CcmathNearbyIntDoubleTests : public TestWithParam> -{ -}; +{}; class CcmathNearbyIntFloatTests : public TestWithParam> -{ -}; +{}; class CcmathNearbyIntLongDoubleTests : public TestWithParam> -{ -}; +{}; INSTANTIATE_TEST_SUITE_P(NearestIntIntegerTests, CcmathNearbyIntIntegerTests, ValuesIn(kNearbyIntIntegerTestParams)); INSTANTIATE_TEST_SUITE_P(NearestIntDoubleTests, CcmathNearbyIntDoubleTests, ValuesIn(kNearbyIntDoubleTestParams)); diff --git a/tests/src/math/nearest/trunc_test.cpp b/tests/src/math/nearest/trunc_test.cpp index 76f6a5c3..4f18e41e 100644 --- a/tests/src/math/nearest/trunc_test.cpp +++ b/tests/src/math/nearest/trunc_test.cpp @@ -29,8 +29,7 @@ namespace using testing::TestWithParam; using testing::ValuesIn; - template - struct TruncTestParams + template struct TruncTestParams { T input{}; T expected{}; @@ -130,14 +129,11 @@ namespace // TODO(IanP): Add coverage for compiler-specific trunc dispatch paths when they diverge. class CcmathTruncDoubleTests : public TestWithParam> -{ -}; +{}; class CcmathTruncFloatTests : public TestWithParam> -{ -}; +{}; class CcmathTruncLongDoubleTests : public TestWithParam> -{ -}; +{}; INSTANTIATE_TEST_SUITE_P(TruncDoubleTests, CcmathTruncDoubleTests, ValuesIn(kTruncDoubleTestsParams)); INSTANTIATE_TEST_SUITE_P(TruncFloatTests, CcmathTruncFloatTests, ValuesIn(kTruncFloatTestParams)); diff --git a/tests/src/math/power/pow_correct_rounding_test.cpp b/tests/src/math/power/pow_correct_rounding_test.cpp index 7d16ab0c..5dbb7a5f 100644 --- a/tests/src/math/power/pow_correct_rounding_test.cpp +++ b/tests/src/math/power/pow_correct_rounding_test.cpp @@ -81,14 +81,14 @@ namespace { 0x3b5fffffffffffffULL, 0x402c000000000000ULL, { 0x000ffffffffffff9ULL, 0x000ffffffffffff9ULL, 0x000ffffffffffffaULL, 0x000ffffffffffff9ULL } }, }; - constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; - constexpr std::array kNames = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; + constexpr std::array kModes = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; + constexpr std::array kNames = { "FE_TONEAREST", "FE_TOWARDZERO", "FE_UPWARD", "FE_DOWNWARD" }; } // namespace TEST(CcmathPowCorrectRounding, BinaryDoubleAllModes) { const int saved = std::fegetround(); - for (const auto& c : kCases) + for (const auto & c : kCases) { const double base = from_bits(c.base_bits); const double exp = from_bits(c.exp_bits); diff --git a/tests/src/math/power/pow_deterministic_test.cpp b/tests/src/math/power/pow_deterministic_test.cpp index bff90281..654957c8 100644 --- a/tests/src/math/power/pow_deterministic_test.cpp +++ b/tests/src/math/power/pow_deterministic_test.cpp @@ -23,8 +23,7 @@ namespace { - template - bool same_bits(T a, T b) noexcept + template bool same_bits(T a, T b) noexcept { using U = std::conditional_t; return ccm::support::bit_cast(a) == ccm::support::bit_cast(b); @@ -93,6 +92,8 @@ TEST(PowDeterministic, CompileTimeUsesGenericKernel) #else TEST(PowDeterministic, DisabledWithoutFlag) -{ GTEST_SKIP() << "CCMATH_ENABLE_DETERMINISTIC is OFF; deterministic guarantees are not in effect."; } +{ + GTEST_SKIP() << "CCMATH_ENABLE_DETERMINISTIC is OFF; deterministic guarantees are not in effect."; +} #endif // CCM_CONFIG_DETERMINISTIC diff --git a/tests/src/math/power/pow_integer_boundary_test.cpp b/tests/src/math/power/pow_integer_boundary_test.cpp index feb8dfe4..b1e83596 100644 --- a/tests/src/math/power/pow_integer_boundary_test.cpp +++ b/tests/src/math/power/pow_integer_boundary_test.cpp @@ -20,15 +20,13 @@ namespace { - template - void ExpectParityResult(T exponent, T expected) + template void ExpectParityResult(T exponent, T expected) { const T actual = ccm::pow(static_cast(-1.0), exponent); ccm::test::ExpectSameFloatingAsStd(actual, expected, 0); } - template - void ExpectNegativeBaseDomainError(T exponent) + template void ExpectNegativeBaseDomainError(T exponent) { const T actual = ccm::pow(static_cast(-2.0), exponent); const T expected = static_cast(std::pow(static_cast(-2.0), exponent)); @@ -36,15 +34,13 @@ namespace ccm::test::ExpectSameFloatingAsStd(actual, expected); } - template - void ExpectGenParityResult(T exponent, T expected) + template void ExpectGenParityResult(T exponent, T expected) { const T actual = ccm::gen::pow_gen(static_cast(-1.0), exponent); ccm::test::ExpectSameFloatingAsStd(actual, expected, 0); } - template - void ExpectGenNegativeBaseDomainError(T exponent) + template void ExpectGenNegativeBaseDomainError(T exponent) { const T actual = ccm::gen::pow_gen(static_cast(-2.0), exponent); const T expected = static_cast(std::pow(static_cast(-2.0), exponent)); diff --git a/tests/src/math/power/pow_simd_test.cpp b/tests/src/math/power/pow_simd_test.cpp index 2bedc78c..73746771 100644 --- a/tests/src/math/power/pow_simd_test.cpp +++ b/tests/src/math/power/pow_simd_test.cpp @@ -45,7 +45,10 @@ namespace // NaN payloads are not part of the contract, so any NaN matches any NaN. bool bit_equal(double a, double b) { - if (std::isnan(a) && std::isnan(b)) { return true; } + if (std::isnan(a) && std::isnan(b)) + { + return true; + } return double_bits(a) == double_bits(b); } @@ -53,8 +56,7 @@ namespace { std::vector xs; std::vector ys; - const auto add = [&](double x, double y) - { + const auto add = [&](double x, double y) { xs.push_back(x); ys.push_back(y); }; @@ -74,25 +76,37 @@ namespace }; for (double b : special_bases) { - for (double e : special_exps) { add(b, e); } + for (double e : special_exps) + { + add(b, e); + } } // Normal range grid through the vector fast path. for (double b = 0.001; b <= 1000.0; b *= 1.7) { - for (double e = -150.0; e <= 150.0; e += 1.7) { add(b, e + 0.123456789); } + for (double e = -150.0; e <= 150.0; e += 1.7) + { + add(b, e + 0.123456789); + } } // Hard near-one region with large exponents (stresses the double-double pipeline). for (double b = 0.95; b <= 1.05; b += 0.00037) { - for (double e : { -4000.1, -750.3, -13.7, 13.7, 750.3, 4000.1 }) { add(b, e); } + for (double e : { -4000.1, -750.3, -13.7, 13.7, 750.3, 4000.1 }) + { + add(b, e); + } } // Integer exponents (exact ipow path) including negative bases. for (double b = -10.0; b <= 10.0; b += 0.375) { - for (int e = -24; e <= 24; ++e) { add(b, static_cast(e)); } + for (int e = -24; e <= 24; ++e) + { + add(b, static_cast(e)); + } } // Half-integer exponents (exact sqrt path) and just-off neighbors (vector path). @@ -121,7 +135,10 @@ namespace // Subnormal bases. for (int e2 = -1074; e2 <= -1000; e2 += 7) { - for (double e : { -2.5, -1.1, 0.7, 1.3, 2.9 }) { add(std::ldexp(1.2345, e2), e); } + for (double e : { -2.5, -1.1, 0.7, 1.3, 2.9 }) + { + add(std::ldexp(1.2345, e2), e); + } } // Huge exponents straddling the clamp threshold 0x43d74910d52d3052. @@ -138,13 +155,15 @@ namespace std::mt19937_64 rng(20260612ULL); std::uniform_real_distribution base_dist(1.0e-12, 1.0e12); std::uniform_real_distribution exp_dist(-600.0, 600.0); - for (int i = 0; i < 40000; ++i) { add(base_dist(rng), exp_dist(rng)); } + for (int i = 0; i < 40000; ++i) + { + add(base_dist(rng), exp_dist(rng)); + } return { std::move(xs), std::move(ys) }; } - template - void expect_matches_scalar(const std::vector& xs, const std::vector& ys, const char* tag) + template void expect_matches_scalar(const std::vector & xs, const std::vector & ys, const char * tag) { constexpr int width = static_cast(V::size()); ASSERT_EQ(xs.size(), ys.size()); @@ -175,9 +194,10 @@ namespace } } - template - double scalar_lane(double x, double y) - { return ccm::gen::impl::pow_simd(V(x), V(y))[0]; } + template double scalar_lane(double x, double y) + { + return ccm::gen::impl::pow_simd(V(x), V(y))[0]; + } struct FenvObservation { @@ -188,8 +208,7 @@ namespace int err; }; - template - FenvObservation observe_fenv(F&& run) + template FenvObservation observe_fenv(F && run) { std::feclearexcept(FE_ALL_EXCEPT); errno = 0; @@ -226,8 +245,7 @@ TEST(CcmathPowSimd, BitIdenticalToScalarKernelWidth8) { const auto [xs, ys] = build_inputs(); expect_matches_scalar>(xs, ys, "w8"); - } - else + } else { GTEST_SKIP() << "8-wide double simd not available on this target"; } @@ -292,7 +310,7 @@ TEST(CcmathPowSimd, FenvAndErrnoParityWithScalarKernel) { 1.0000001, 1e15 }, // overflow from a near-one base }; - for (const auto& c : cases) + for (const auto & c : cases) { volatile double simd_out = 0.0; volatile double scal_out = 0.0; diff --git a/tests/src/math/power/pow_test.cpp b/tests/src/math/power/pow_test.cpp index a0776905..bbab352d 100644 --- a/tests/src/math/power/pow_test.cpp +++ b/tests/src/math/power/pow_test.cpp @@ -33,9 +33,10 @@ namespace { using ccm::test::runtime_value; - template - void consume(T value) - { [[maybe_unused]] volatile T sink = value; } + template void consume(T value) + { + [[maybe_unused]] volatile T sink = value; + } } // namespace // [cmath.syn]: Synopsis-level overloads are constexpr entry points. @@ -624,18 +625,16 @@ TEST(CcmathPowerTests, PowDoubleUnderflowScalePathBoundary) TEST(CcmathPowerTests, PowRoundingModeConformance) { - ccm::test::ForEachRoundingModeOrSkip( - [](int mode) - { - SCOPED_TRACE(ccm::test::RoundingModeName(mode)); + ccm::test::ForEachRoundingModeOrSkip([](int mode) { + SCOPED_TRACE(ccm::test::RoundingModeName(mode)); - // Special cases must hold regardless of rounding mode - ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(1.0), runtime_value(42.0)), std::pow(1.0, 42.0)); - ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(2.0), runtime_value(0.0)), std::pow(2.0, 0.0)); - ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(0.0), runtime_value(3.0)), std::pow(0.0, 3.0)); + // Special cases must hold regardless of rounding mode + ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(1.0), runtime_value(42.0)), std::pow(1.0, 42.0)); + ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(2.0), runtime_value(0.0)), std::pow(2.0, 0.0)); + ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(0.0), runtime_value(3.0)), std::pow(0.0, 3.0)); - // Representative numeric cases - ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(2.0), runtime_value(10.0)), std::pow(2.0, 10.0)); - ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(3.0), runtime_value(7.0)), std::pow(3.0, 7.0)); - }); + // Representative numeric cases + ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(2.0), runtime_value(10.0)), std::pow(2.0, 10.0)); + ccm::test::ExpectSameFloatingAsStd(ccm::pow(runtime_value(3.0), runtime_value(7.0)), std::pow(3.0, 7.0)); + }); } diff --git a/tests/src/math/power/power_ulp_accuracy_test.cpp b/tests/src/math/power/power_ulp_accuracy_test.cpp index 8a4ee48c..2b84be95 100644 --- a/tests/src/math/power/power_ulp_accuracy_test.cpp +++ b/tests/src/math/power/power_ulp_accuracy_test.cpp @@ -22,10 +22,14 @@ #include TEST(CcmathPowerUlpTests, SqrtDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kSqrtDouble, ccm::sqrt, static_cast(std::sqrt)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kSqrtDouble, ccm::sqrt, static_cast(std::sqrt)); +} TEST(CcmathPowerUlpTests, SqrtFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kSqrtFloat, ccm::sqrt, static_cast(std::sqrt)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kSqrtFloat, ccm::sqrt, static_cast(std::sqrt)); +} // [cmath.syn] and [c.math]/1: validates the primary double overload against the C library semantics used by the standard. TEST(CcmathPowerUlpTests, PowDouble) @@ -36,7 +40,10 @@ TEST(CcmathPowerUlpTests, PowDouble) { SCOPED_TRACE(base); SCOPED_TRACE(exp); - if (std::isnan(std::pow(base, exp))) { continue; } + if (std::isnan(std::pow(base, exp))) + { + continue; + } ccm::test::ExpectUlpBinaryVsStd(base, exp, ccm::pow, static_cast(std::pow)); } } @@ -51,7 +58,10 @@ TEST(CcmathPowerUlpTests, PowGenDouble) { SCOPED_TRACE(base); SCOPED_TRACE(exp); - if (std::isnan(std::pow(base, exp))) { continue; } + if (std::isnan(std::pow(base, exp))) + { + continue; + } ccm::test::ExpectSameFloatingAsStd(ccm::gen::pow_gen(base, exp), std::pow(base, exp), 1); } } @@ -96,7 +106,10 @@ TEST(CcmathPowerUlpTests, PowFloat) const float exp = ccm::test::samples::kPowFloatPairsExp[i]; SCOPED_TRACE(base); SCOPED_TRACE(exp); - if (std::isnan(std::pow(base, exp))) { continue; } + if (std::isnan(std::pow(base, exp))) + { + continue; + } ccm::test::ExpectUlpBinaryVsStd(base, exp, ccm::powf, static_cast(std::pow)); } } @@ -137,7 +150,10 @@ TEST(CcmathPowerUlpTests, PowGenDoubleAllMantissaBuckets) const double base = 1.0 + static_cast(k) / 128.0; for (double exp : ccm::test::samples::kPowExpGrid) { - if (std::isnan(std::pow(base, exp))) { continue; } + if (std::isnan(std::pow(base, exp))) + { + continue; + } SCOPED_TRACE(base); SCOPED_TRACE(exp); ccm::test::ExpectSameFloatingAsStd(ccm::gen::pow_gen(base, exp), std::pow(base, exp), 1); @@ -153,7 +169,10 @@ TEST(CcmathPowerUlpTests, PowGenFloatAllMantissaBuckets) const float base = 1.0F + static_cast(k) / 128.0F; for (float exp : ccm::test::samples::kPowExpGridFloat) { - if (std::isnan(std::pow(base, exp))) { continue; } + if (std::isnan(std::pow(base, exp))) + { + continue; + } SCOPED_TRACE(base); SCOPED_TRACE(exp); ccm::test::ExpectSameFloatingAsStd(ccm::gen::pow_gen(base, exp), std::pow(base, exp), 1); @@ -181,7 +200,10 @@ TEST(CcmathPowerUlpTests, DISABLED_PowGenFloatExhaustiveMantissa) for (float exp : exponents) { const float expected = std::pow(base, exp); - if (std::isnan(expected)) { continue; } + if (std::isnan(expected)) + { + continue; + } const float actual = ccm::gen::pow_gen(base, exp); if (ulp_difference(actual, expected) > ccm::test::kMaxAllowedUlp) { @@ -352,10 +374,14 @@ TEST(CcmathPowerUlpTests, PowfZivFallbackRegressionCases) } TEST(CcmathPowerUlpTests, CbrtDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kCbrtDouble, ccm::cbrt, static_cast(std::cbrt)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kCbrtDouble, ccm::cbrt, static_cast(std::cbrt)); +} TEST(CcmathPowerUlpTests, CbrtFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kCbrtFloat, ccm::cbrt, static_cast(std::cbrt)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kCbrtFloat, ccm::cbrt, static_cast(std::cbrt)); +} TEST(CcmathPowerUlpTests, HypotDouble) { diff --git a/tests/src/math/power/powf_simd_test.cpp b/tests/src/math/power/powf_simd_test.cpp index 1d29ea47..d547cfd0 100644 --- a/tests/src/math/power/powf_simd_test.cpp +++ b/tests/src/math/power/powf_simd_test.cpp @@ -42,7 +42,10 @@ namespace // NaN payloads are not part of the contract, so any NaN matches any NaN. bool bit_equal(float a, float b) { - if (std::isnan(a) && std::isnan(b)) { return true; } + if (std::isnan(a) && std::isnan(b)) + { + return true; + } return float_bits(a) == float_bits(b); } @@ -50,8 +53,7 @@ namespace { std::vector xs; std::vector ys; - const auto add = [&](float x, float y) - { + const auto add = [&](float x, float y) { xs.push_back(x); ys.push_back(y); }; @@ -65,25 +67,37 @@ namespace const float special_exps[] = { 0.0f, -0.0f, 1.0f, -1.0f, 2.0f, 3.0f, -3.0f, 0.5f, -0.5f, 2.5f, inf, -inf, qnan, 127.0f, -149.0f, 0.3333333f }; for (float b : special_bases) { - for (float e : special_exps) { add(b, e); } + for (float e : special_exps) + { + add(b, e); + } } // Normal range grid. for (float b = 0.125f; b <= 64.0f; b *= 1.3f) { - for (float e = -18.0f; e <= 18.0f; e += 0.7f) { add(b, e); } + for (float e = -18.0f; e <= 18.0f; e += 0.7f) + { + add(b, e); + } } // Hard near-one region with large exponents (stresses the Ziv resolution). for (float b = 0.9f; b <= 1.1f; b += 0.0007f) { - for (float e : { -240.0f, -50.0f, -7.5f, 7.5f, 50.0f, 240.0f }) { add(b, e); } + for (float e : { -240.0f, -50.0f, -7.5f, 7.5f, 50.0f, 240.0f }) + { + add(b, e); + } } // Integer exponents (exact integer power fast paths) and negative bases. for (float b = -12.0f; b <= 12.0f; b += 0.5f) { - for (int e = -16; e <= 16; ++e) { add(b, static_cast(e)); } + for (int e = -16; e <= 16; ++e) + { + add(b, static_cast(e)); + } } // Overflow and underflow edges. @@ -101,13 +115,15 @@ namespace std::mt19937 rng(20240611U); std::uniform_real_distribution base_dist(1.0e-6f, 1.0e6f); std::uniform_real_distribution exp_dist(-60.0f, 60.0f); - for (int i = 0; i < 40000; ++i) { add(base_dist(rng), exp_dist(rng)); } + for (int i = 0; i < 40000; ++i) + { + add(base_dist(rng), exp_dist(rng)); + } return { std::move(xs), std::move(ys) }; } - template - void expect_matches_scalar(const std::vector& xs, const std::vector& ys, const char* tag) + template void expect_matches_scalar(const std::vector & xs, const std::vector & ys, const char * tag) { constexpr int width = static_cast(V::size()); ASSERT_EQ(xs.size(), ys.size()); @@ -138,9 +154,10 @@ namespace } } - template - float scalar_lane(float x, float y) - { return ccm::gen::impl::powf_simd(V(x), V(y))[0]; } + template float scalar_lane(float x, float y) + { + return ccm::gen::impl::powf_simd(V(x), V(y))[0]; + } } // namespace TEST(CcmathPowfSimd, BitIdenticalToScalarKernelNativeWidth) @@ -161,8 +178,7 @@ TEST(CcmathPowfSimd, BitIdenticalToScalarKernelWidth8) { const auto [xs, ys] = build_inputs(); expect_matches_scalar>(xs, ys, "w8"); - } - else + } else { GTEST_SKIP() << "8-wide float/double simd not available on this target"; } diff --git a/tests/src/math/power/powl_fallback_policy_test.cpp b/tests/src/math/power/powl_fallback_policy_test.cpp index 9409d946..7273da00 100644 --- a/tests/src/math/power/powl_fallback_policy_test.cpp +++ b/tests/src/math/power/powl_fallback_policy_test.cpp @@ -27,7 +27,9 @@ namespace using ccm::test::oracle::PowlImplementationPath; void ExpectPath(long double base, long double exponent, PowlImplementationPath expected) - { EXPECT_EQ(classify_powl_gen_path(base, exponent), expected) << "base=" << base << " exponent=" << exponent; } + { + EXPECT_EQ(classify_powl_gen_path(base, exponent), expected) << "base=" << base << " exponent=" << exponent; + } } // namespace TEST(PowlFallbackPolicy, ReportsFallbackPolicyState) @@ -38,7 +40,10 @@ TEST(PowlFallbackPolicy, ReportsFallbackPolicyState) TEST(PowlFallbackPolicy, Ld64AliasIsNotReducedPrecision) { - if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::Double) { GTEST_SKIP() << "double-shaped long double required"; } + if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::Double) + { + GTEST_SKIP() << "double-shaped long double required"; + } ExpectPath(2.0L, 0.5L, PowlImplementationPath::Ld64DoubleAlias); EXPECT_FALSE(powl_path_is_reduced_precision(PowlImplementationPath::Ld64DoubleAlias)); @@ -87,8 +92,7 @@ TEST(PowlFallbackPolicy, UnsupportedTierPolicy) const auto path = classify_powl_gen_path(base, exponent); EXPECT_TRUE(path == PowlImplementationPath::Ld128ReducedPrecisionFallback || path == PowlImplementationPath::UnknownReducedPrecisionFallback); EXPECT_TRUE(powl_path_is_reduced_precision(path)); - } - else + } else { const auto path = classify_powl_gen_path(base, exponent); EXPECT_TRUE(path == PowlImplementationPath::Ld128Unsupported || path == PowlImplementationPath::UnknownUnsupported); diff --git a/tests/src/math/power/powl_ld64_alias_test.cpp b/tests/src/math/power/powl_ld64_alias_test.cpp index 469ccd29..c5bba6a6 100644 --- a/tests/src/math/power/powl_ld64_alias_test.cpp +++ b/tests/src/math/power/powl_ld64_alias_test.cpp @@ -51,6 +51,9 @@ TEST(PowlLd64Alias, PublicPowlMatchesGenPath) const std::array exponents = { -2.0L, 0.5L, 2.0L, 3.0L }; for (long double base : bases) { - for (long double exponent : exponents) { EXPECT_EQ(ccm::powl(base, exponent), ccm::gen::pow_gen(base, exponent)); } + for (long double exponent : exponents) + { + EXPECT_EQ(ccm::powl(base, exponent), ccm::gen::pow_gen(base, exponent)); + } } } diff --git a/tests/src/math/power/powl_ld80_bounded_int_test.cpp b/tests/src/math/power/powl_ld80_bounded_int_test.cpp index 2b634756..1f81ac95 100644 --- a/tests/src/math/power/powl_ld80_bounded_int_test.cpp +++ b/tests/src/math/power/powl_ld80_bounded_int_test.cpp @@ -23,7 +23,10 @@ namespace #define REQUIRE_POWL_LD80() \ do \ { \ - if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) { GTEST_SKIP() << "x87 80-bit long double required"; } \ + if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) \ + { \ + GTEST_SKIP() << "x87 80-bit long double required"; \ + } \ } while (false) void ExpectPowlMatchesStd(long double base, long double exponent) diff --git a/tests/src/math/power/powl_ld80_general_finite_test.cpp b/tests/src/math/power/powl_ld80_general_finite_test.cpp index c69297ba..041af9a5 100644 --- a/tests/src/math/power/powl_ld80_general_finite_test.cpp +++ b/tests/src/math/power/powl_ld80_general_finite_test.cpp @@ -24,7 +24,10 @@ namespace #define REQUIRE_POWL_LD80() \ do \ { \ - if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) { GTEST_SKIP() << "x87 80-bit long double required"; } \ + if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) \ + { \ + GTEST_SKIP() << "x87 80-bit long double required"; \ + } \ } while (false) void ExpectPowlMatchesStd(long double base, long double exponent) @@ -81,7 +84,10 @@ TEST(PowlLd80GeneralFinite, RandomFinitePositiveBase) { const long double base = base_dist(rng); const long double exponent = exp_dist(rng); - if (base == 1.0L) { continue; } + if (base == 1.0L) + { + continue; + } ExpectPowlMatchesStd(base, exponent); } } diff --git a/tests/src/math/power/powl_ld80_integer_class_test.cpp b/tests/src/math/power/powl_ld80_integer_class_test.cpp index 0a94145a..d8bb8fa8 100644 --- a/tests/src/math/power/powl_ld80_integer_class_test.cpp +++ b/tests/src/math/power/powl_ld80_integer_class_test.cpp @@ -22,13 +22,19 @@ namespace #define REQUIRE_POWL_LD80() \ do \ { \ - if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) { GTEST_SKIP() << "x87 80-bit long double required"; } \ + if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) \ + { \ + GTEST_SKIP() << "x87 80-bit long double required"; \ + } \ } while (false) void ExpectInteger(long double value, bool is_integer, bool is_odd = false) { EXPECT_EQ(ccm::gen::internal::powl_bits::is_integer(value), is_integer) << "value=" << value; - if (is_integer) { EXPECT_EQ(ccm::gen::internal::powl_bits::is_odd_integer(value), is_odd) << "value=" << value; } + if (is_integer) + { + EXPECT_EQ(ccm::gen::internal::powl_bits::is_odd_integer(value), is_odd) << "value=" << value; + } } } // namespace @@ -67,8 +73,7 @@ TEST(PowlLd80IntegerClass, NegativeBaseParityMatchesStdPow) { REQUIRE_POWL_LD80(); - const auto expect_parity = [](long double exponent, long double expected) - { + const auto expect_parity = [](long double exponent, long double expected) { const long double actual = ccm::gen::pow_gen(-1.0L, exponent); EXPECT_EQ(actual, expected) << "exponent=" << exponent; }; diff --git a/tests/src/math/power/powl_ld80_special_cases_test.cpp b/tests/src/math/power/powl_ld80_special_cases_test.cpp index 5e3d1c1a..414169c1 100644 --- a/tests/src/math/power/powl_ld80_special_cases_test.cpp +++ b/tests/src/math/power/powl_ld80_special_cases_test.cpp @@ -23,7 +23,10 @@ namespace #define REQUIRE_POWL_LD80() \ do \ { \ - if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) { GTEST_SKIP() << "x87 80-bit long double required"; } \ + if (ccm::config::detect_long_double_format() != ccm::config::LongDoubleFormat::X87Extended) \ + { \ + GTEST_SKIP() << "x87 80-bit long double required"; \ + } \ } while (false) void ExpectPowlMatchesStd(long double base, long double exponent) diff --git a/tests/src/math/power/powl_unsupported_tier_test.cpp b/tests/src/math/power/powl_unsupported_tier_test.cpp index 19a1da0b..b5be29a6 100644 --- a/tests/src/math/power/powl_unsupported_tier_test.cpp +++ b/tests/src/math/power/powl_unsupported_tier_test.cpp @@ -58,8 +58,7 @@ TEST(PowlUnsupportedTier, PolicySelectsFallbackOrUnsupported) const long double actual = ccm::gen::pow_gen(base, exponent); const long double expected = static_cast(ccm::gen::pow_gen(static_cast(base), static_cast(exponent))); EXPECT_EQ(actual, expected); - } - else + } else { EXPECT_TRUE(path == ccm::test::oracle::PowlImplementationPath::Ld128Unsupported || path == ccm::test::oracle::PowlImplementationPath::UnknownUnsupported); diff --git a/tests/src/math/power/sqrt_test.cpp b/tests/src/math/power/sqrt_test.cpp index fc0041ad..4fc9f0f8 100644 --- a/tests/src/math/power/sqrt_test.cpp +++ b/tests/src/math/power/sqrt_test.cpp @@ -20,10 +20,14 @@ #include TEST(CcmathPowerTests, Sqrt_StaticAssert) -{ static_assert(ccm::sqrt(2.0) == ccm::sqrt(2.0), "ccm::sqrt is not a compile time constant!"); } +{ + static_assert(ccm::sqrt(2.0) == ccm::sqrt(2.0), "ccm::sqrt is not a compile time constant!"); +} TEST(CcmathPowerTests, Sqrt_Double_CheckCCM) -{ EXPECT_EQ(ccm::sqrt(0.0), ccm::sqrt(0.0)); } +{ + EXPECT_EQ(ccm::sqrt(0.0), ccm::sqrt(0.0)); +} TEST(CcmathPowerTests, Sqrt_Double_CheckCCM_static) { @@ -32,7 +36,9 @@ TEST(CcmathPowerTests, Sqrt_Double_CheckCCM_static) } TEST(CcmathPowerTests, Sqrt_Double_CheckSTD) -{ EXPECT_EQ(std::sqrt(0.0), std::sqrt(0.0)); } +{ + EXPECT_EQ(std::sqrt(0.0), std::sqrt(0.0)); +} TEST(CcmathPowerTests, Sqrt_Double) { diff --git a/tests/src/math/system_math_header_coexistence_test.cpp b/tests/src/math/system_math_header_coexistence_test.cpp index e1a2ed78..510a6132 100644 --- a/tests/src/math/system_math_header_coexistence_test.cpp +++ b/tests/src/math/system_math_header_coexistence_test.cpp @@ -48,8 +48,7 @@ namespace { // Force a genuine runtime call so the runtime dispatch is exercised rather than constant folded. - template - T runtime(T value) + template T runtime(T value) { volatile T sink = value; return sink; diff --git a/tests/src/math/trig/trig_ulp_accuracy_test.cpp b/tests/src/math/trig/trig_ulp_accuracy_test.cpp index b937b832..67430ac8 100644 --- a/tests/src/math/trig/trig_ulp_accuracy_test.cpp +++ b/tests/src/math/trig/trig_ulp_accuracy_test.cpp @@ -18,15 +18,17 @@ namespace { - template - void ExpectTanAwayFromPole(T input, CcmFn ccm_fn, StdFn std_fn, T pole_guard) + template void ExpectTanAwayFromPole(T input, CcmFn ccm_fn, StdFn std_fn, T pole_guard) { - if (std::abs(std::cos(input)) < pole_guard) { return; } + if (std::abs(std::cos(input)) < pole_guard) + { + return; + } ccm::test::ExpectUlpUnaryVsStd(input, ccm_fn, std_fn); } template - void ExpectTanOver(const std::array& inputs, CcmFn ccm_fn, StdFn std_fn, T pole_guard) + void ExpectTanOver(const std::array & inputs, CcmFn ccm_fn, StdFn std_fn, T pole_guard) { for (T input : inputs) { @@ -37,34 +39,56 @@ namespace } // namespace TEST(CcmathTrigonometricUlpTests, SinFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigFloat, ccm::sin, static_cast(std::sin)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigFloat, ccm::sin, static_cast(std::sin)); +} TEST(CcmathTrigonometricUlpTests, CosFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigFloat, ccm::cos, static_cast(std::cos)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigFloat, ccm::cos, static_cast(std::cos)); +} TEST(CcmathTrigonometricUlpTests, TanFloat) -{ ExpectTanOver(ccm::test::samples::kTrigFloat, ccm::tan, static_cast(std::tan), 1e-6F); } +{ + ExpectTanOver(ccm::test::samples::kTrigFloat, ccm::tan, static_cast(std::tan), 1e-6F); +} TEST(CcmathTrigonometricUlpTests, SinDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigDouble, ccm::sin, static_cast(std::sin)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigDouble, ccm::sin, static_cast(std::sin)); +} TEST(CcmathTrigonometricUlpTests, CosDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigDouble, ccm::cos, static_cast(std::cos)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kTrigDouble, ccm::cos, static_cast(std::cos)); +} TEST(CcmathTrigonometricUlpTests, TanDouble) -{ ExpectTanOver(ccm::test::samples::kTrigDouble, ccm::tan, static_cast(std::tan), 1e-12); } +{ + ExpectTanOver(ccm::test::samples::kTrigDouble, ccm::tan, static_cast(std::tan), 1e-12); +} TEST(CcmathTrigonometricUlpTests, AsinFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitFloat, ccm::asin, static_cast(std::asin)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitFloat, ccm::asin, static_cast(std::asin)); +} TEST(CcmathTrigonometricUlpTests, AcosFloat) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitFloat, ccm::acos, static_cast(std::acos)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitFloat, ccm::acos, static_cast(std::acos)); +} TEST(CcmathTrigonometricUlpTests, AsinDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitDouble, ccm::asin, static_cast(std::asin)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitDouble, ccm::asin, static_cast(std::asin)); +} TEST(CcmathTrigonometricUlpTests, AcosDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitDouble, ccm::acos, static_cast(std::acos)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kInvTrigUnitDouble, ccm::acos, static_cast(std::acos)); +} TEST(CcmathTrigonometricUlpTests, AtanDouble) -{ ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kAtanDouble, ccm::atan, static_cast(std::atan)); } +{ + ccm::test::ExpectUlpUnaryOver(ccm::test::samples::kAtanDouble, ccm::atan, static_cast(std::atan)); +} diff --git a/tools/apply_format.sh b/tools/apply_format.sh old mode 100644 new mode 100755 diff --git a/tools/asmlab/accuracy/candidate_accuracy_powf_impl.cpp b/tools/asmlab/accuracy/candidate_accuracy_powf_impl.cpp index a09d474b..f262b573 100644 --- a/tools/asmlab/accuracy/candidate_accuracy_powf_impl.cpp +++ b/tools/asmlab/accuracy/candidate_accuracy_powf_impl.cpp @@ -34,18 +34,41 @@ namespace float parse_float(const char * text) { - if (std::strcmp(text, "nan") == 0 || std::strcmp(text, "NaN") == 0) { return std::numeric_limits::quiet_NaN(); } - if (std::strcmp(text, "inf") == 0 || std::strcmp(text, "+inf") == 0) { return std::numeric_limits::infinity(); } - if (std::strcmp(text, "-inf") == 0) { return -std::numeric_limits::infinity(); } - if (std::strcmp(text, "-0") == 0 || std::strcmp(text, "-0.0") == 0) { return -0.0F; } - if (std::strcmp(text, "denorm_min") == 0) { return std::numeric_limits::denorm_min(); } - if (std::strcmp(text, "min") == 0) { return std::numeric_limits::min(); } - if (std::strcmp(text, "max") == 0) { return std::numeric_limits::max(); } + if (std::strcmp(text, "nan") == 0 || std::strcmp(text, "NaN") == 0) + { + return std::numeric_limits::quiet_NaN(); + } + if (std::strcmp(text, "inf") == 0 || std::strcmp(text, "+inf") == 0) + { + return std::numeric_limits::infinity(); + } + if (std::strcmp(text, "-inf") == 0) + { + return -std::numeric_limits::infinity(); + } + if (std::strcmp(text, "-0") == 0 || std::strcmp(text, "-0.0") == 0) + { + return -0.0F; + } + if (std::strcmp(text, "denorm_min") == 0) + { + return std::numeric_limits::denorm_min(); + } + if (std::strcmp(text, "min") == 0) + { + return std::numeric_limits::min(); + } + if (std::strcmp(text, "max") == 0) + { + return std::numeric_limits::max(); + } return static_cast(std::strtod(text, nullptr)); } bool same_signed_zero(float a, float b) - { return a == 0.0F && b == 0.0F && std::signbit(a) == std::signbit(b); } + { + return a == 0.0F && b == 0.0F && std::signbit(a) == std::signbit(b); + } bool check_ulp_vs_std(float base, float exp, int64_t max_ulp) { @@ -70,7 +93,10 @@ namespace } return true; } - if (same_signed_zero(actual, expected)) { return true; } + if (same_signed_zero(actual, expected)) + { + return true; + } const auto dist = ccm::test::ulp::distance_or_max(actual, expected); if (dist > static_cast(max_ulp)) @@ -104,8 +130,14 @@ namespace } return true; } - if (same_signed_zero(actual, expected)) { return true; } - if (actual == expected) { return true; } + if (same_signed_zero(actual, expected)) + { + return true; + } + if (actual == expected) + { + return true; + } std::cerr << "special mismatch actual=" << actual << " expected=" << expected << "\n"; return false; } @@ -137,17 +169,32 @@ int main(int argc, char ** argv) for (int i = 1; i < argc; ++i) { - if (std::strcmp(argv[i], "--check") == 0 && i + 1 < argc) { check = argv[++i]; } - else if (std::strcmp(argv[i], "--base") == 0 && i + 1 < argc) { base = parse_float(argv[++i]); } - else if (std::strcmp(argv[i], "--exp") == 0 && i + 1 < argc) { exp = parse_float(argv[++i]); } - else if (std::strcmp(argv[i], "--max-ulp") == 0 && i + 1 < argc) { max_ulp = std::strtoll(argv[++i], nullptr, 10); } + if (std::strcmp(argv[i], "--check") == 0 && i + 1 < argc) + { + check = argv[++i]; + } else if (std::strcmp(argv[i], "--base") == 0 && i + 1 < argc) + { + base = parse_float(argv[++i]); + } else if (std::strcmp(argv[i], "--exp") == 0 && i + 1 < argc) + { + exp = parse_float(argv[++i]); + } else if (std::strcmp(argv[i], "--max-ulp") == 0 && i + 1 < argc) + { + max_ulp = std::strtoll(argv[++i], nullptr, 10); + } } bool ok = false; - if (check == "ulp") { ok = check_ulp_vs_std(base, exp, max_ulp); } - else if (check == "special") { ok = check_special_vs_std(base, exp); } - else if (check == "domain") { ok = check_domain_vs_std(base, exp); } - else + if (check == "ulp") + { + ok = check_ulp_vs_std(base, exp, max_ulp); + } else if (check == "special") + { + ok = check_special_vs_std(base, exp); + } else if (check == "domain") + { + ok = check_domain_vs_std(base, exp); + } else { std::cerr << "unknown check: " << check << "\n"; return 2; diff --git a/tools/asmlab/bench/golden_poly_bench.cpp b/tools/asmlab/bench/golden_poly_bench.cpp index 6c930fe3..f03c53b2 100644 --- a/tools/asmlab/bench/golden_poly_bench.cpp +++ b/tools/asmlab/bench/golden_poly_bench.cpp @@ -17,17 +17,23 @@ namespace } } // namespace -static void BM_golden_poly_horner(benchmark::State& state) +static void BM_golden_poly_horner(benchmark::State & state) { const float x = load_input(1.25f); - for (auto _ : state) { benchmark::DoNotOptimize(asmlab::golden::poly_horner::poly_horner(x)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(asmlab::golden::poly_horner::poly_horner(x)); + } } BENCHMARK(BM_golden_poly_horner); -static void BM_golden_poly_estrin(benchmark::State& state) +static void BM_golden_poly_estrin(benchmark::State & state) { const float x = load_input(1.25f); - for (auto _ : state) { benchmark::DoNotOptimize(asmlab::golden::poly_estrin::poly_estrin(x)); } + for (auto _ : state) + { + benchmark::DoNotOptimize(asmlab::golden::poly_estrin::poly_estrin(x)); + } } BENCHMARK(BM_golden_poly_estrin); diff --git a/tools/asmlab/bench/powf_impl_bench.cpp b/tools/asmlab/bench/powf_impl_bench.cpp index 2b7433f9..4ef886a5 100644 --- a/tools/asmlab/bench/powf_impl_bench.cpp +++ b/tools/asmlab/bench/powf_impl_bench.cpp @@ -38,7 +38,7 @@ namespace return out; } - static void bench_pair(benchmark::State& state, float base_lit, float exp_lit) + static void bench_pair(benchmark::State & state, float base_lit, float exp_lit) { for (auto _ : state) { @@ -48,37 +48,54 @@ namespace } } - static void bench_rand_pairs(benchmark::State& state, float bmin, float bmax, float emin, float emax) + static void bench_rand_pairs(benchmark::State & state, float bmin, float bmax, float emin, float emax) { std::vector bases(static_cast(state.range(0))); std::vector exps(static_cast(state.range(0))); std::mt19937 gen(0xA51AB); std::uniform_real_distribution bdist(bmin, bmax); std::uniform_real_distribution edist(emin, emax); - for (auto& b : bases) { b = bdist(gen); } - for (auto& e : exps) { e = edist(gen); } + for (auto & b : bases) + { + b = bdist(gen); + } + for (auto & e : exps) + { + e = edist(gen); + } while (state.KeepRunning()) { - for (std::size_t i = 0; i < bases.size(); ++i) { benchmark::DoNotOptimize(ASMLAB_POWF_IMPL_CALL(load_float(bases[i]), load_float(exps[i]))); } + for (std::size_t i = 0; i < bases.size(); ++i) + { + benchmark::DoNotOptimize(ASMLAB_POWF_IMPL_CALL(load_float(bases[i]), load_float(exps[i]))); + } } state.SetItemsProcessed(static_cast(state.iterations()) * static_cast(bases.size())); } } // namespace -static void BM_asmlab_powf_impl_positive_finite_general(benchmark::State& state) -{ bench_pair(state, 2.0f, 3.0f); } +static void BM_asmlab_powf_impl_positive_finite_general(benchmark::State & state) +{ + bench_pair(state, 2.0f, 3.0f); +} BENCHMARK(BM_asmlab_powf_impl_positive_finite_general); -static void BM_asmlab_powf_impl_near_one(benchmark::State& state) -{ bench_pair(state, 1.0001f, 1.5f); } +static void BM_asmlab_powf_impl_near_one(benchmark::State & state) +{ + bench_pair(state, 1.0001f, 1.5f); +} BENCHMARK(BM_asmlab_powf_impl_near_one); -static void BM_asmlab_powf_impl_integer_exponent(benchmark::State& state) -{ bench_pair(state, 2.0f, 4.0f); } +static void BM_asmlab_powf_impl_integer_exponent(benchmark::State & state) +{ + bench_pair(state, 2.0f, 4.0f); +} BENCHMARK(BM_asmlab_powf_impl_integer_exponent); -static void BM_asmlab_powf_impl_rand_positive_finite(benchmark::State& state) -{ bench_rand_pairs(state, 0.5f, 8.0f, 0.25f, 6.0f); } +static void BM_asmlab_powf_impl_rand_positive_finite(benchmark::State & state) +{ + bench_rand_pairs(state, 0.5f, 8.0f, 0.25f, 6.0f); +} BENCHMARK(BM_asmlab_powf_impl_rand_positive_finite)->RangeMultiplier(2)->Range(64, 64 << 4); BENCHMARK_MAIN(); diff --git a/tools/run_format.sh b/tools/run_format.sh old mode 100644 new mode 100755 From 27ab35739f984e083c198726bf0c3b6cbfed4c1d Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 28 Jun 2026 23:45:51 -0400 Subject: [PATCH 50/52] Format big_int_test.cpp --- tests/src/math/internal/types/big_int_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/math/internal/types/big_int_test.cpp b/tests/src/math/internal/types/big_int_test.cpp index 6634a5aa..e90ce219 100644 --- a/tests/src/math/internal/types/big_int_test.cpp +++ b/tests/src/math/internal/types/big_int_test.cpp @@ -195,9 +195,9 @@ namespace { const std::size_t out_index = i + j; const std::uint64_t accum = static_cast(out.limbs[out_index]) + - static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; - out.limbs[out_index] = static_cast(accum); - carry = accum >> limb_bits; + static_cast(limbs[i]) * static_cast(other.limbs[j]) + carry; + out.limbs[out_index] = static_cast(accum); + carry = accum >> limb_bits; } for (std::size_t out_index = i + RefUint::limb_count; carry != 0 && out_index < RefUint::limb_count; ++out_index) { From b6c99c3c38fa0b278adcda7487ec33def8ed37b9 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 28 Jun 2026 23:46:17 -0400 Subject: [PATCH 51/52] Assert spec-correct fdim(inf, inf) instead of host libm --- tests/src/math/basic/fdim_test.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/src/math/basic/fdim_test.cpp b/tests/src/math/basic/fdim_test.cpp index 78ccff50..4e5a4b2f 100644 --- a/tests/src/math/basic/fdim_test.cpp +++ b/tests/src/math/basic/fdim_test.cpp @@ -48,9 +48,8 @@ namespace // Edge cases { std::numeric_limits::infinity(), 1.0, std::fdim(std::numeric_limits::infinity(), 1.0) }, { 1.0, std::numeric_limits::infinity(), std::fdim(1.0, std::numeric_limits::infinity()) }, - { std::numeric_limits::infinity(), - std::numeric_limits::infinity(), - std::fdim(std::numeric_limits::infinity(), std::numeric_limits::infinity()) }, + // fdim(inf, inf) is +0 by the standard, the host libm oracle is non-conformant on some platforms + { std::numeric_limits::infinity(), std::numeric_limits::infinity(), +0.0 }, { std::numeric_limits::quiet_NaN(), 1.0, std::fdim(std::numeric_limits::quiet_NaN(), 1.0) }, { 1.0, std::numeric_limits::quiet_NaN(), std::fdim(1.0, std::numeric_limits::quiet_NaN()) }, { std::numeric_limits::max(), @@ -79,9 +78,8 @@ namespace // Edge cases { std::numeric_limits::infinity(), 1.0F, std::fdim(std::numeric_limits::infinity(), 1.0F) }, { 1.0F, std::numeric_limits::infinity(), std::fdim(1.0F, std::numeric_limits::infinity()) }, - { std::numeric_limits::infinity(), - std::numeric_limits::infinity(), - std::fdim(std::numeric_limits::infinity(), std::numeric_limits::infinity()) }, + // fdim(inf, inf) is +0 by the standard, the host libm oracle is non-conformant on some platforms + { std::numeric_limits::infinity(), std::numeric_limits::infinity(), +0.0F }, { std::numeric_limits::quiet_NaN(), 1.0F, std::fdim(std::numeric_limits::quiet_NaN(), 1.0F) }, { 1.0F, std::numeric_limits::quiet_NaN(), std::fdim(1.0F, std::numeric_limits::quiet_NaN()) }, { std::numeric_limits::max(), @@ -110,9 +108,8 @@ namespace // Edge cases { std::numeric_limits::infinity(), 1.0L, std::fdim(std::numeric_limits::infinity(), 1.0L) }, { 1.0L, std::numeric_limits::infinity(), std::fdim(1.0L, std::numeric_limits::infinity()) }, - { std::numeric_limits::infinity(), - std::numeric_limits::infinity(), - std::fdim(std::numeric_limits::infinity(), std::numeric_limits::infinity()) }, + // fdim(inf, inf) is +0 by the standard, the host libm oracle is non-conformant on some platforms + { std::numeric_limits::infinity(), std::numeric_limits::infinity(), +0.0L }, { std::numeric_limits::quiet_NaN(), 1.0L, std::fdim(std::numeric_limits::quiet_NaN(), 1.0L) }, { 1.0L, std::numeric_limits::quiet_NaN(), std::fdim(1.0L, std::numeric_limits::quiet_NaN()) }, { std::numeric_limits::max(), From 8bc261c0fef8f62d652957feec2ba6a0dcbdd8d5 Mon Sep 17 00:00:00 2001 From: ianpike Date: Sun, 5 Jul 2026 12:51:30 -0400 Subject: [PATCH 52/52] Document the NaN and FE_INVALID idiom in remquo --- .../generic/func/basic/remquo_impl/remquo_double_impl.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp index b4740906..00f9b9d6 100644 --- a/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp +++ b/include/ccmath/internal/math/generic/func/basic/remquo_impl/remquo_double_impl.hpp @@ -42,6 +42,10 @@ namespace ccm::internal y_i64 &= 0x7fffffffffffffffULL; // If y is zero. + // This is a common libm trick to produce a NaN and raise FE_INVALID at the same time + // without hard coding a NaN or pulling in feraiseexception. The idea is that for y == 0 + // it computes x * 0 then 0 / 0, which gives the NaN and sets the invalid flag. It also + // handles the case where x is infinite. The same idiom is reused below for the non-finite case. if (CCM_UNLIKELY(y_i64 == 0)) { return (x * y) / (x * y);