Skip to content

CCMath 0.3.0 Release Candidate - #149

Closed
Rinzii wants to merge 63 commits into
review-base/v0.2.0from
release/0.3.x
Closed

Rinzii wants to merge 63 commits into
review-base/v0.2.0from
release/0.3.x

Conversation

@Rinzii

@Rinzii Rinzii commented Jun 14, 2026 •

Copy link
Copy Markdown
Owner

CCMath 0.3.0 Release Candidate

This is the review PR for v0.3.0. The goal is to catch any remaining blockers before the
release is cut.

It puts the entire v0.2.0 to v0.3.0 change set in one diff. It is review-only and will not be
merged. The base branch review-base/v0.2.0 is a frozen snapshot of the v0.2.0 tag, so the
diff is exactly what landed since the last release. The content itself already lives on main
and on release/0.3.x.

How this review works: comments here are the review record. Fixes land as normal PRs into
main, after which release/0.3.x is fast-forwarded. Record your approval as a review here.
When review wraps up, the PR is closed and review-base/v0.2.0 is deleted. Treat it as a diff
viewer and discussion thread.

The user-facing summary is in the release notes. What follows is for reviewers: what changed,
where the risk is, what is validated, and what would block the release.

Heads up on size: 891 files, about +59.6k / -9.8k. Most of it is build, tooling, test, and
doc work rather than runtime math kernels. The part that can produce silently wrong numbers
though is small and it is listed below first.

What counts as blocking

Reasons to hold the release. Mark these clearly so they stand out from nits:

  • A numeric result that violates the documented contract or a release claim, in any rounding
    mode, on a supported path.
  • Wrong dispatch or format selection (for example a powl call routed to the wrong kernel).
  • A public API contract problem in the new ext or fmanip functions (signature, NaN and
    infinity behavior, constexpr-ness).
  • A claim in the release notes the code does not actually support.
  • A portability break on a supported compiler or standard mode (C++17 through C++26).

Most other items can be handled as nits or follow-ups unless they change the release risk.
Items under Known limitations are out of scope by design, not blockers, unless you can show
one breaks a supported path.

Where to focus review

These are the surfaces where a mistake means wrong math, so they earn the closest reading.
Validation has been run on most of them (see Validation status), but that lowers the
uncertainty rather than removing the need for attention.

Trigonometry (double sin, cos, tan)

  • sincos_payne_hanek.hpp plus the
    sin, cos, and tan kernels.
  • What to check: the Payne-Hanek large-argument reduction, the binary64 constant values, the
    quadrant selection, sign-of-zero handling, and the inverse-trig edge cases (asin, acos,
    atan, atan2). The earlier failure mode was large arguments falling outside the
    reduction, so large-input behavior is the thing to primarily probe.

Power (double pow, long double powl)

  • The double-double constant pairs in the pow path. The headline bug class fixed this
    release was hi and lo halves stored swapped, which is harmless under round-to-nearest but
    wrong in directed modes, so check each DD constant against its intended value and ordering.
  • The new 80-bit powl kernel:
    powl_ld80_kernel.hpp
    and its generated
    powl_ld80_tables.hpp.
    It compiles only where long double is the 80-bit x87 extended format, so the double and
    IEEE binary128 long double platforms do not exercise it. This is preview quality (see
    Known limitations).
  • SIMD pow paths: pow_simd_impl.hpp and powf_simd_impl.hpp in the same directory. These
    run only under runtime SIMD (not deterministic mode), so the check is that they agree with
    the scalar path on the same inputs.

Configuration and dispatch

  • long_double_format.hpp and
    powl_policy.hpp decide which long double
    path is taken. A wrong detection sends every powl call down the wrong kernel, so the
    format-detection logic is worth careful eyes.
  • Deterministic mode (CCMATH_ENABLE_DETERMINISTIC): the claim is bit-identical float and
    double results across supported hardware. Worth confirming it forces the generic kernels, disables
    runtime SIMD, does not leak into a libm or compiler builtin on any path, and that constexpr
    and runtime agree. long double is not part of the deterministic claim.

New public API

  • The 15 new ccm::ext helpers under ext/ and the standard nextup
    and nextdown under fmanip/. Check signatures, NaN and
    infinity behavior, constexpr-ness, and that they build clean under -Wconversion. The ext
    helpers are opt-in behind CCMATH_ENABLE_EXTENSIONS, so also check the namespace placement
    and that they stay out of a default build.

Low-level support

  • support/fp/: cast.hpp, except_value_utils.hpp,
    fma.hpp. The changes here are small, but used very widely so mistake can easily propagate.

Lower-risk areas (skim)

  • ccm::pp SIMD layer under runtime/pp/: a
    standalone C++17 port of the C++26 std::simd interface. Not yet wired into dispatch, so it
    cannot change results this release. Review it on its own merits if you have the appetite,
    otherwise it can wait.
  • Tests under tests/: new conformance, rigorous, and proof harnesses. The useful
    question here is coverage gaps against the kernels above.
  • Fuzzing under fuzzing/: libFuzzer targets per family.
  • Tooling: asmlab under tools/asmlab/, the showcase comparisons, and the
    benchmarks. Build and dev infrastructure, not part of library runtime behavior.
  • Build systems: meson.build, premake5.lua, the CMake options, and the vendored-consumer
    integration test.
  • CI under .github/workflows/: the C++23 and C++26 matrix and the
    per-PR clang-format gate.
  • Docs and proofs: a partial POW_PROOF.tex (the full binary64 proof is still open) and the
    Sollya and Gappa scripts under docs/approximating_functions.

Validation status

What has been validated, and how. This lowers the uncertainty on these areas, it does not put
them out of bounds for review:

  • pow: CORE-MATH all-mode validation campaigns over the covered inputs, all four rounding
    modes. The campaign records live under tests/rigorous/ (see oracle_logs/ for the
    per-campaign summaries).
  • sin, cos, tan: full-range validation campaigns, large arguments included. This is
    representative coverage across the range, not an exhaustive sweep of every binary64 value.
  • fma: all-mode exact software fallback, with native runtime coverage validated on AArch64.
  • Differential fuzzing across the basic, compare, exponential, float-manipulation, misc,
    nearest, power, and trig families.
  • Builds under -Werror with aggressive warnings (-Wconversion and friends) across the CI
    matrix on Linux, macOS, and Windows.

Open validation work (not expected to be finished in this RC):

  • The full binary64 pow proof. The writeup in the tree is partial.
  • powf reduced-domain validation.
  • Independent validation of the 80-bit powl kernel.
  • Wider x86-64 fma validation.

Known limitations and intended scope

These are disclosed in the release notes and are intentionally out of scope for v0.3.0. Call
one out if you think it should block the release, otherwise treat it as known debt:

  • The 80-bit powl kernel is preview quality. Its generic fuzz lane is intentionally guarded
    off in power_fuzz.cpp until the kernel is finalized.
  • Deterministic mode is experimental.
  • Hyperbolic and special functions are still empty headers.

Building and validating locally

It is a header-only library, so consuming it is just an include. To exercise the validation
that matters for review:

cmake -S . -B build -DCCMATH_BUILD_RIGOROUS_TESTS=ON \
  -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON
cmake --build build --config Release
ctest --test-dir build --output-on-failure -C Release

On multi-config generators like MSVC the --config and -C Release flags matter, and they
are harmless elsewhere. Add -DCCMATH_ENABLE_MPFR_TESTS=ON and
-DCCMATH_ENABLE_COREMATH_TESTS=ON for the oracle-backed campaigns (these need MPFR and
CORE-MATH available, and the rigorous runs can take a while). Fuzzers build under Clang with
-DCCMATH_BUILD_FUZZING=ON. Run tools/ensure_format.sh for the format gate. The README
validation section and tools/asmlab/README.md cover the deeper tooling.

Where review is most valuable

Highest value first: numeric correctness and edge cases in the kernels above, then the public
API shape of the new ext and fmanip functions, then test coverage gaps against those
kernels. Style and tooling nits are welcome but lowest priority for an RC this size.

@Rinzii Rinzii self-assigned this Jun 14, 2026
@Rinzii Rinzii added the RC label Jun 14, 2026
@Rinzii Rinzii added this to the Road to v1.0.0 milestone Jun 14, 2026
@Rinzii
Rinzii requested a review from nekoflare June 14, 2026 00:55
Comment thread include/ccmath/ext/abs_diff.hpp Outdated
@Rinzii
Rinzii requested a review from Eczbek June 14, 2026 01:18
Comment thread include/ccmath/ext/ceil_div.hpp Outdated
Comment thread include/ccmath/ext/chgsign.hpp Outdated
Comment thread include/ccmath/ext/mix.hpp Outdated
Comment thread include/ccmath/ext/rcp.hpp Outdated
Comment thread include/ccmath/ext/remap.hpp Outdated
Comment thread include/ccmath/internal/math/generic/builtins/basic/fabs.hpp Outdated
Comment thread include/ccmath/internal/math/generic/func/basic/fmod_gen.hpp Outdated
Comment thread include/ccmath/internal/math/runtime/pp/assume_aligned.hpp Outdated
Comment thread include/ccmath/internal/math/runtime/pp/flags.hpp
Comment thread include/ccmath/internal/support/fp/cast.hpp
@Rinzii Rinzii removed the RC label Jun 15, 2026
@Rinzii
Rinzii requested a review from Eczbek June 16, 2026 01:45
@Rinzii Rinzii linked an issue Jun 19, 2026 that may be closed by this pull request
1 task done
@nekoflare

Copy link
Copy Markdown
Collaborator

I have an issue with this function:

    /**
     * @brief Determines if a signed 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.
     *
     * @note Prefer ccm::ext::safe::is_power_of_two for signed inputs to avoid signed
     * overflow on the minimum representable value.
     */
    template <typename T, std::enable_if_t<std::is_signed_v<T> && !std::is_same_v<T, bool>, bool> = true>
    constexpr bool is_power_of_two(T n) noexcept
    { return n && !(n & (n - T(1))); }

inside is_power_of_two.hpp file.
Because naturally negative numbers cannot be power of two.
Therefore you should add n > T(0)

Another issue being that for functions delta_angle, lerp_angle, and move_towards_angle inside the namespace ccm::ext you use degrees but everywhere else I can see you're using radians. What if user forgets to apply radians to degrees? I think you should add a suffix _deg to tell the user those functions operate on degrees rather than radians or rewrite those functions to use radians.

@Rinzii

Rinzii commented Jun 19, 2026 •

Copy link
Copy Markdown
Owner Author

I have an issue with this function:

    /**
     * @brief Determines if a signed 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.
     *
     * @note Prefer ccm::ext::safe::is_power_of_two for signed inputs to avoid signed
     * overflow on the minimum representable value.
     */
    template <typename T, std::enable_if_t<std::is_signed_v<T> && !std::is_same_v<T, bool>, bool> = true>
    constexpr bool is_power_of_two(T n) noexcept
    { return n && !(n & (n - T(1))); }

inside is_power_of_two.hpp file. Because naturally negative numbers cannot be power of two. Therefore you should add n > T(0)

Another issue being that for functions delta_angle, lerp_angle, and move_towards_angle inside the namespace ccm::ext you use degrees but everywhere else I can see you're using radians. What if user forgets to apply radians to degrees? I think you should add a suffix _deg to tell the user those functions operate on degrees rather than radians or rewrite those functions to use radians.

Good catch! The original intention was for the unqualified signed overload to be the lean bit-twiddle version and for ccm::ext::safe::is_power_of_two to be the guarded one that handles negatives and the minimum value correctly, the thinking being that the default should stay as cheap as possible.

After some analysis though I compared the codegen for both and there is honestly no performance difference between them that I can find. They come out branchless at the same instruction count on clang, and gcc emits the same shape with a single compare, so guarding against the minimum value should costs nothing. Given that, there is no reason I can think of to keep a default that is wrong at the minimum value, so I am going to replace the unsafe version with the safe one.

Thanks for flagging it!

@Rinzii

Rinzii commented Jun 19, 2026

Copy link
Copy Markdown
Owner Author

I have an issue with this function:

    /**
     * @brief Determines if a signed 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.
     *
     * @note Prefer ccm::ext::safe::is_power_of_two for signed inputs to avoid signed
     * overflow on the minimum representable value.
     */
    template <typename T, std::enable_if_t<std::is_signed_v<T> && !std::is_same_v<T, bool>, bool> = true>
    constexpr bool is_power_of_two(T n) noexcept
    { return n && !(n & (n - T(1))); }

inside is_power_of_two.hpp file. Because naturally negative numbers cannot be power of two. Therefore you should add n > T(0)

Another issue being that for functions delta_angle, lerp_angle, and move_towards_angle inside the namespace ccm::ext you use degrees but everywhere else I can see you're using radians. What if user forgets to apply radians to degrees? I think you should add a suffix _deg to tell the user those functions operate on degrees rather than radians or rewrite those functions to use radians.

Also fair point! These angle helpers borrow ideas from Unity and GLSL so that is where the original idea came from, but I agree that taking the approach of mirroring the rest of CCMath and the Standard by using radians is the right call here.

So I've decided we will key these off radians rather than add a _deg suffix. delta_angle, lerp_angle, and move_towards_angle now take and return radians, which keeps them in line with the trig functions. Also, while I was at it I decided to I flip the ext guard convention so the guarded behavior is now the default and the raw form lives under ccm::ext::unsafe rather than the opposite like before. I also went through the codegen on each unsafe variant and removed all the versions that did not produce better codegen.

@github-actions github-actions Bot added the S: Approved Has an approved review. label Jun 21, 2026

@ProfessionalMenace ProfessionalMenace left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found nothing major. Refactoring fixed a lot of the readability issues I had.

Comment thread docs/approximating_functions/sollya/sin_small_error.sollya
Comment thread include/ccmath/internal/math/runtime/func/fmanip/ilogb_rt.hpp
@Rinzii Rinzii closed this Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S: Approved Has an approved review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: constexpr math functions fail to compile on domain/special inputs

4 participants