diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 339a760..6765f7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: run: ctest --test-dir build --output-on-failure -L example - name: Single-header smokes - run: cmake --build build --target single_header_smoke single_header_freestanding_smoke + run: cmake --build build --target single_header_smoke single_header_freestanding_smoke single_header_nofp_smoke # bound_fuzz and bench are large, slow-to-compile EXCLUDE_FROM_ALL targets, # so they are exercised only in Release on representative cells (one per diff --git a/CMakeLists.txt b/CMakeLists.txt index 1503e90..e55d8c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,24 @@ if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_options(single_header_freestanding_smoke PRIVATE -Wl,--allow-multiple-definition) endif() +# FP-free smoke: compiles the single header with BND_MATH_NO_FP and a poison +# shim placed FIRST on the include path, so the build hard-errors if any +# is pulled in. Proves the single header honors the FP-free guard (only +# the integer/CORDIC engine remains). GNU/Clang only — the proof relies on -I +# ordering for angle-bracket includes; bare-metal toolchains are GCC/Clang. +# EXCLUDE_FROM_ALL — build with `--target single_header_nofp_smoke`. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_executable(single_header_nofp_smoke EXCLUDE_FROM_ALL + tests/single_header_nofp_smoke.cpp) + # The poison dir must precede single_include AND the system dirs. BEFORE keeps + # it ahead of single_include; angle-bracket search already places -I dirs ahead + # of the system headers, so resolves to the poison shim. + target_include_directories(single_header_nofp_smoke BEFORE PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/tests/nofp_poison" + "${CMAKE_CURRENT_SOURCE_DIR}/single_include") + target_compile_definitions(single_header_nofp_smoke PRIVATE BND_MATH_NO_FP) +endif() + #--------------------------------------------------------------------------- # Install / package config # diff --git a/docs/math.md b/docs/math.md index c4e68c5..06fdaba 100644 --- a/docs/math.md +++ b/docs/math.md @@ -214,3 +214,29 @@ Use it when you need bit-identical results across heterogeneous targets FPU-free build. The default double engine is the right choice everywhere else: it carries the same grid guarantees and is reproducible across IEEE-754 platforms compiled without `-ffast-math`. + +## Compiling without floating point (`BND_MATH_NO_FP`) + +On a target with no hardware FPU and no ``, define **`BND_MATH_NO_FP`** +(any value). It compiles the double engine — and its `#include ` — out +**entirely**, leaving the always-present integer/CORDIC engine to serve the full +`bnd::math` API. The public surface, output grids, and types are unchanged: only +the compute backend differs. + +- **No ``, no `std::fma`/`std::sqrt`** are referenced anywhere in the + library when the macro is set — this holds for the modular headers **and** the + amalgamated single header (the `` include is emitted under the same + guard). A CI smoke (`single_header_nofp_smoke`) compiles the single header with + a *poison* `` shim first on the include path, so the build fails if any + `` is pulled in. +- **Auto-enabled** when `__STDC_HOSTED__ == 0` (i.e. `-ffreestanding`) and + **implied by `BND_MATH_FIXED`** — selecting the integer engine is itself an + FP-free build. +- All transcendentals are `constexpr` under `BND_MATH_NO_FP` (the integer engine), + so they evaluate at compile time as well as runtime. + +```bash +# bare-metal: integer engine, no , single header +g++ -std=c++23 -ffreestanding -I single_include my_app.cpp # NO_FP auto-on +g++ -std=c++23 -DBND_MATH_NO_FP -I single_include my_app.cpp # or force it +``` diff --git a/include/bound/cmath.hpp b/include/bound/cmath.hpp index 5ac9f25..77aa8cb 100644 --- a/include/bound/cmath.hpp +++ b/include/bound/cmath.hpp @@ -19,7 +19,11 @@ // std::nearbyint; feature macro __cpp_lib_constexpr_cmath). That branch is // inert (and untested) until such a toolchain exists. Decision 2026-06-12: // no compile-time softfloat emulation — wait for the standard. -#if defined(BND_MATH_FIXED) \ +// BND_MATH_NO_FP (resolved in cmath_double.hpp, included above) selects the +// integer/CORDIC engine and is implied by BND_MATH_FIXED — so the integer engine +// is constexpr here. The double engine becomes constexpr only on a C++26 toolchain +// with constexpr (P1383); that branch is inert until such a toolchain. +#if defined(BND_MATH_NO_FP) \ || (defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L) # define BND_MATH_FN constexpr #else @@ -37,6 +41,11 @@ // * `BND_MATH_FIXED` — integer/CORDIC engine (this file): FPU-free, constexpr, // UNCONDITIONALLY bit-identical (any platform/flags). For embedded/portability. // +// `BND_MATH_NO_FP` (implied by `BND_MATH_FIXED`, auto-enabled when +// `__STDC_HOSTED__ == 0`) compiles the double engine and its `` out +// entirely, leaving the integer engine — so the library, including the single +// header, builds with no hardware floating point. The dispatch below keys on it. +// // ===================================================================== // INTEGER (CORDIC) ENGINE — BIT-EXACT REPRODUCIBILITY CONTRACT // ===================================================================== @@ -1177,7 +1186,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sqrt(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sqrt_impl>(x); #else return dbl::sqrt_core>(x); @@ -1193,7 +1202,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::sqrt_signed_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sqrt_signed_impl(x); #else double v = static_cast(x); @@ -1207,7 +1216,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto exp2(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return exp2_impl>(x); #else return dbl::exp2_core>(x); @@ -1222,7 +1231,7 @@ namespace bnd::math // engine's *_impl: the double engine's log_core has no singularity check, // so log2(x<=0) would silently store finite garbage (e.g. log2(0) ≈ -7). static_assert(Lower > 0, "bnd::math::log2: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log2_impl>(x); #else return dbl::log2_core>(x); @@ -1233,7 +1242,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto exp(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return exp_impl>(x); #else return dbl::exp_core>(x); @@ -1245,7 +1254,7 @@ namespace bnd::math { static_assert(detail::require_snap()); static_assert(Lower > 0, "bnd::math::log: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log_impl>(x); #else return dbl::log_core>(x); @@ -1257,7 +1266,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::pow_base_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return pow_base_impl(x); #else return dbl::store(dbl::detail::d_pow(static_cast(Base), static_cast(x))); @@ -1308,7 +1317,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sin(In angle) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sin_impl>(angle); #else return dbl::sin_core>(angle); @@ -1319,7 +1328,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cos(In angle) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cos_impl>(angle); #else return dbl::cos_core>(angle); @@ -1330,7 +1339,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto atan2(In y, In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return atan2_impl>(y, x); #else return dbl::atan2_core>(y, x); @@ -1342,7 +1351,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::tan_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return tan_impl(angle); #else double x = static_cast(angle); @@ -1453,7 +1462,7 @@ namespace bnd::math BND_MATH_FN void sin(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); out = detail::sin_slot(bnd::detail::raw_imax(angle)); @@ -1467,7 +1476,7 @@ namespace bnd::math BND_MATH_FN void cos(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); out = detail::sin_slot(bnd::detail::raw_imax(angle) + M / 4); @@ -1483,7 +1492,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN bool tan(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); imax i = bnd::detail::raw_imax(angle); @@ -1842,7 +1851,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto atan(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return atan_impl>(x); #else return dbl::atan_core>(x); @@ -1853,7 +1862,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto asin(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return asin_impl>(x); #else return dbl::asin_core>(x); @@ -1864,7 +1873,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto acos(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return acos_impl>(x); #else return dbl::acos_core>(x); @@ -1875,7 +1884,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sinh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sinh_impl>(x); #else return dbl::sinh_core>(x); @@ -1886,7 +1895,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cosh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cosh_impl>(x); #else return dbl::cosh_core>(x); @@ -1897,7 +1906,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto tanh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return tanh_impl>(x); #else return dbl::tanh_core>(x); @@ -1909,7 +1918,7 @@ namespace bnd::math { static_assert(detail::require_snap()); static_assert(Lower > 0, "bnd::math::log10: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log10_impl>(x); #else return dbl::log10_core>(x); @@ -1920,7 +1929,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cbrt(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cbrt_impl>(x); #else return dbl::cbrt_core>(x); @@ -1931,7 +1940,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto hypot(InX x, InY y) noexcept { static_assert(detail::require_snap() && detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return hypot_impl>(x, y); #else return dbl::hypot_core>(x, y); @@ -1944,7 +1953,7 @@ namespace bnd::math { static_assert(detail::require_snap() && detail::require_snap()); using Out = detail::pow_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return pow_impl(base, exp); #else double b = static_cast(base); diff --git a/include/bound/cmath_double.hpp b/include/bound/cmath_double.hpp index 165c228..ab4b6f0 100644 --- a/include/bound/cmath_double.hpp +++ b/include/bound/cmath_double.hpp @@ -15,6 +15,22 @@ #include "bound/math.hpp" // bnd::detail::ldexp (constexpr, reproducible) #include "bound/bound.hpp" // complete bound/rational + has_flag/BoundPolicy/real (store<>) +// BND_MATH_NO_FP — resolved here (the lowest math header) so both this file and +// cmath.hpp see it. When defined, the library uses NO hardware floating point and +// NO : the double (FP) engine below compiles out and the always-present +// integer/CORDIC engine carries every transcendental. Define it (any value) to +// force the FP-free path; it is auto-enabled on freestanding targets +// (__STDC_HOSTED__ == 0) and whenever the integer engine is selected as the +// default (BND_MATH_FIXED). The integer engine is constexpr and bit-exact, so the +// public API and grid deduction are unchanged — only the compute backend differs. +#if !defined(BND_MATH_NO_FP) +# if defined(BND_MATH_FIXED) || (defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 0) +# define BND_MATH_NO_FP +# endif +#endif + +#ifndef BND_MATH_NO_FP // ===== FP engine present (needs + an FPU) ===== + #include // std::fma, std::sqrt, std::nearbyint ONLY // `BND_DBL_FN`: the engine cores become `constexpr` on C++26 toolchains with @@ -254,4 +270,6 @@ namespace bnd::math::dbl { return store(detail::d_hypot(static_cast(x), static_cast(y))); } } // namespace bnd::math::dbl +#endif // !BND_MATH_NO_FP + #endif // BNDcmathdoubleHPP diff --git a/single_include/bound/bound.hpp b/single_include/bound/bound.hpp index 374ceaa..1d2535b 100644 --- a/single_include/bound/bound.hpp +++ b/single_include/bound/bound.hpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -7708,6 +7707,23 @@ namespace bnd //--------------------------------------------------------------------------- +// BND_MATH_NO_FP — resolved here (the lowest math header) so both this file and +// cmath.hpp see it. When defined, the library uses NO hardware floating point and +// NO : the double (FP) engine below compiles out and the always-present +// integer/CORDIC engine carries every transcendental. Define it (any value) to +// force the FP-free path; it is auto-enabled on freestanding targets +// (__STDC_HOSTED__ == 0) and whenever the integer engine is selected as the +// default (BND_MATH_FIXED). The integer engine is constexpr and bit-exact, so the +// public API and grid deduction are unchanged — only the compute backend differs. +#if !defined(BND_MATH_NO_FP) +# if defined(BND_MATH_FIXED) || (defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 0) +# define BND_MATH_NO_FP +# endif +#endif + +#ifndef BND_MATH_NO_FP // ===== FP engine present (needs + an FPU) ===== + +#include // std::fma, std::sqrt, std::nearbyint ONLY // `BND_DBL_FN`: the engine cores become `constexpr` on C++26 toolchains with // constexpr (P1383). Inert otherwise — see BND_MATH_FN in cmath.hpp. @@ -7946,6 +7962,8 @@ namespace bnd::math::dbl { return store(detail::d_hypot(static_cast(x), static_cast(y))); } } // namespace bnd::math::dbl +#endif // !BND_MATH_NO_FP + @@ -7956,7 +7974,11 @@ namespace bnd::math::dbl // std::nearbyint; feature macro __cpp_lib_constexpr_cmath). That branch is // inert (and untested) until such a toolchain exists. Decision 2026-06-12: // no compile-time softfloat emulation — wait for the standard. -#if defined(BND_MATH_FIXED) \ +// BND_MATH_NO_FP (resolved in cmath_double.hpp, included above) selects the +// integer/CORDIC engine and is implied by BND_MATH_FIXED — so the integer engine +// is constexpr here. The double engine becomes constexpr only on a C++26 toolchain +// with constexpr (P1383); that branch is inert until such a toolchain. +#if defined(BND_MATH_NO_FP) \ || (defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L) # define BND_MATH_FN constexpr #else @@ -7974,6 +7996,11 @@ namespace bnd::math::dbl // * `BND_MATH_FIXED` — integer/CORDIC engine (this file): FPU-free, constexpr, // UNCONDITIONALLY bit-identical (any platform/flags). For embedded/portability. // +// `BND_MATH_NO_FP` (implied by `BND_MATH_FIXED`, auto-enabled when +// `__STDC_HOSTED__ == 0`) compiles the double engine and its `` out +// entirely, leaving the integer engine — so the library, including the single +// header, builds with no hardware floating point. The dispatch below keys on it. +// // ===================================================================== // INTEGER (CORDIC) ENGINE — BIT-EXACT REPRODUCIBILITY CONTRACT // ===================================================================== @@ -9114,7 +9141,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sqrt(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sqrt_impl>(x); #else return dbl::sqrt_core>(x); @@ -9130,7 +9157,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::sqrt_signed_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sqrt_signed_impl(x); #else double v = static_cast(x); @@ -9144,7 +9171,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto exp2(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return exp2_impl>(x); #else return dbl::exp2_core>(x); @@ -9159,7 +9186,7 @@ namespace bnd::math // engine's *_impl: the double engine's log_core has no singularity check, // so log2(x<=0) would silently store finite garbage (e.g. log2(0) ≈ -7). static_assert(Lower > 0, "bnd::math::log2: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log2_impl>(x); #else return dbl::log2_core>(x); @@ -9170,7 +9197,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto exp(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return exp_impl>(x); #else return dbl::exp_core>(x); @@ -9182,7 +9209,7 @@ namespace bnd::math { static_assert(detail::require_snap()); static_assert(Lower > 0, "bnd::math::log: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log_impl>(x); #else return dbl::log_core>(x); @@ -9194,7 +9221,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::pow_base_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return pow_base_impl(x); #else return dbl::store(dbl::detail::d_pow(static_cast(Base), static_cast(x))); @@ -9245,7 +9272,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sin(In angle) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sin_impl>(angle); #else return dbl::sin_core>(angle); @@ -9256,7 +9283,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cos(In angle) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cos_impl>(angle); #else return dbl::cos_core>(angle); @@ -9267,7 +9294,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto atan2(In y, In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return atan2_impl>(y, x); #else return dbl::atan2_core>(y, x); @@ -9279,7 +9306,7 @@ namespace bnd::math { static_assert(detail::require_snap()); using Out = detail::tan_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return tan_impl(angle); #else double x = static_cast(angle); @@ -9390,7 +9417,7 @@ namespace bnd::math BND_MATH_FN void sin(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); out = detail::sin_slot(bnd::detail::raw_imax(angle)); @@ -9404,7 +9431,7 @@ namespace bnd::math BND_MATH_FN void cos(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); out = detail::sin_slot(bnd::detail::raw_imax(angle) + M / 4); @@ -9420,7 +9447,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN bool tan(DEG angle, AMP& out) noexcept { static_assert(detail::valid_circle()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP constexpr imax M = detail::circle_slots; constexpr int W = detail::working_bits(); imax i = bnd::detail::raw_imax(angle); @@ -9779,7 +9806,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto atan(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return atan_impl>(x); #else return dbl::atan_core>(x); @@ -9790,7 +9817,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto asin(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return asin_impl>(x); #else return dbl::asin_core>(x); @@ -9801,7 +9828,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto acos(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return acos_impl>(x); #else return dbl::acos_core>(x); @@ -9812,7 +9839,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto sinh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return sinh_impl>(x); #else return dbl::sinh_core>(x); @@ -9823,7 +9850,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cosh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cosh_impl>(x); #else return dbl::cosh_core>(x); @@ -9834,7 +9861,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto tanh(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return tanh_impl>(x); #else return dbl::tanh_core>(x); @@ -9846,7 +9873,7 @@ namespace bnd::math { static_assert(detail::require_snap()); static_assert(Lower > 0, "bnd::math::log10: input must be strictly positive"); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return log10_impl>(x); #else return dbl::log10_core>(x); @@ -9857,7 +9884,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto cbrt(In x) noexcept { static_assert(detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return cbrt_impl>(x); #else return dbl::cbrt_core>(x); @@ -9868,7 +9895,7 @@ namespace bnd::math [[nodiscard]] BND_MATH_FN auto hypot(InX x, InY y) noexcept { static_assert(detail::require_snap() && detail::require_snap()); -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return hypot_impl>(x, y); #else return dbl::hypot_core>(x, y); @@ -9881,7 +9908,7 @@ namespace bnd::math { static_assert(detail::require_snap() && detail::require_snap()); using Out = detail::pow_auto_t; -#ifdef BND_MATH_FIXED +#ifdef BND_MATH_NO_FP return pow_impl(base, exp); #else double b = static_cast(base); diff --git a/tests/nofp_poison/cmath b/tests/nofp_poison/cmath new file mode 100644 index 0000000..3cbe243 --- /dev/null +++ b/tests/nofp_poison/cmath @@ -0,0 +1,5 @@ +// Poison shim for the single_header_nofp_smoke target. This directory is placed +// FIRST on the include path, so `#include ` resolves here. Under +// BND_MATH_NO_FP the library must pull NO — if it (or a minimal TU) does, +// this hard-errors and the FP-free guarantee is proven broken at compile time. +#error " was included under BND_MATH_NO_FP — the FP-free guard is broken" diff --git a/tests/single_header_nofp_smoke.cpp b/tests/single_header_nofp_smoke.cpp new file mode 100644 index 0000000..5958760 --- /dev/null +++ b/tests/single_header_nofp_smoke.cpp @@ -0,0 +1,47 @@ +// FP-free smoke test: compiles the amalgamated single header with BND_MATH_NO_FP, +// the no-hardware-floating-point path. Proves two things at compile time: +// 1. The single header builds with NO — a poison shim (placed +// first on the include path by CMake) hard-errors if anything pulls it in. +// 2. With the double engine compiled out, the always-present integer/CORDIC +// engine still serves the full bnd::math transcendental API. +// Built on demand via the `single_header_nofp_smoke` target (EXCLUDE_FROM_ALL). + +#include "bound/bound.hpp" // the single header (bnd::math amalgamated in too) + +#include + +#ifndef BND_MATH_NO_FP +# error "single_header_nofp_smoke must be built with BND_MATH_NO_FP defined" +#endif + +// The FP engine namespace must be gone; only the integer engine remains. (We do +// not name bnd::math::dbl here — it does not exist under BND_MATH_NO_FP.) +static_assert(noexcept(true)); + +int main() +{ + using namespace bnd; + + // Core arithmetic — no FP anywhere. + bound<{0, 100}, clamp> a{200}; // -> 100 + bound<{0, 9}, wrap> w{13}; // -> 3 + + // Transcendentals via the integer/CORDIC engine on a snap grid. These are + // constexpr under BND_MATH_NO_FP, so evaluate at compile time too. + using Ang = bound<{{-8, 8}, notch<1, 16384>}, round_nearest>; + constexpr auto s0 = math::sin(Ang{0}); + constexpr auto c0 = math::cos(Ang{0}); + static_assert(detail::rational{s0} == 0); + static_assert(detail::rational{c0} == 1); + + auto s1 = math::sin(Ang{1}); // runtime, integer engine + (void)s1; + + std::printf("a=%d w=%d s0=%d c0=%d\n", + (int)detail::to_value(a), + (int)detail::to_value(w), + (int)detail::to_value(s0), + (int)detail::to_value(c0)); + + return (detail::rational{c0} == 1) ? 0 : 1; +}