Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,35 @@ 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`.

## Choosing an engine per call (`cordic::` / `dbl::`)

The unqualified `bnd::math::fn` uses the build's default engine. Both engines are
also reachable by name, **callable side-by-side in the same binary**:

| Namespace | Engine | Availability |
|---|---|---|
| `bnd::math::cordic::fn` | integer / CORDIC | **always** (constexpr, FPU-free) |
| `bnd::math::dbl::fn` | `double` | unless `BND_MATH_NO_FP` |
| `bnd::math::fn` | the default | alias of `cordic` under `BND_MATH_FIXED`/`BND_MATH_NO_FP`, else `dbl` |

The qualified entry points have the **same signatures, domains, auto-deduced
output grids, and domain `static_assert`s** as the unqualified one — only the
compute backend differs. This lets one program pick per call site:

```cpp
using A = bound<{{-8, 8}, notch<1, 16384>}, round_nearest | real>;

auto a = math::cordic::sin(A{1}); // bit-exact across every target — replay/sim
auto b = math::dbl::sin(A{1}); // ~2× faster — hot, accuracy-insensitive path
auto c = math::sin(A{1}); // whichever the build selected
```

Because the engines are independent approximations, `cordic::fn` and `dbl::fn`
can disagree by up to one notch on rounding ties (the table-maker's dilemma — see
[determinism.md](determinism.md)); algebraically-exact inputs (e.g. `sqrt(4)`,
`pow(2,4)`) land identically. Under `BND_MATH_NO_FP` the `dbl::` namespace is not
defined, so a `dbl::` call there is a compile error; `cordic::` always works.

## Compiling without floating point (`BND_MATH_NO_FP`)

On a target with no hardware FPU and no `<cmath>`, define **`BND_MATH_NO_FP`**
Expand Down
267 changes: 267 additions & 0 deletions include/bound/cmath.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,273 @@ namespace bnd::math
return slim::expected<Out, errc>{dbl::store<Out>(r)};
#endif
}

//===========================================================================
// Explicit engine namespaces — call a chosen engine regardless of the build
// default. `cordic::fn` (integer/CORDIC, ALWAYS present) and `dbl::fn` (the
// double engine, present unless BND_MATH_NO_FP) expose the SAME public-shaped
// API as the top-level `bnd::math::fn` — same signatures, domains, auto-deduced
// output grids, and domain static_asserts. The unqualified `bnd::math::fn` is
// an alias for whichever engine the build selects (see the #ifdef dispatch
// above); these let a single binary mix both — e.g. `cordic::sin` on a
// determinism-critical path and `dbl::sin` on a hot one.
//
// The engines are independent approximations: a grid-snapped value can differ
// between them by up to one notch on rounding ties (table-maker's dilemma).
// Don't compare outputs across engines — see determinism.md.
//===========================================================================
namespace cordic
{
template <boundable In>
requires (Lower<In> == bnd::detail::rational{0})
[[nodiscard]] constexpr auto sqrt(In x) noexcept
{ static_assert(detail::require_snap<In>()); return sqrt_impl<detail::sqrt_auto_t<In>>(x); }

template <boundable In>
requires (Lower<In> < bnd::detail::rational{0})
[[nodiscard]] constexpr auto sqrt(In x) noexcept
{ static_assert(detail::require_snap<In>()); return sqrt_signed_impl<detail::sqrt_signed_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto exp2(In x) noexcept
{ static_assert(detail::require_snap<In>()); return exp2_impl<detail::exp2_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto log2(In x) noexcept
{
static_assert(detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::cordic::log2: input must be strictly positive");
return log2_impl<detail::log2_auto_t<In>>(x);
}

template <boundable In>
[[nodiscard]] constexpr auto exp(In x) noexcept
{ static_assert(detail::require_snap<In>()); return exp_impl<detail::exp_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto log(In x) noexcept
{
static_assert(detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::cordic::log: input must be strictly positive");
return log_impl<detail::log_auto_t<In>>(x);
}

template <imax Base, boundable In>
[[nodiscard]] constexpr auto pow_base(In x) noexcept
{ static_assert(detail::require_snap<In>()); return pow_base_impl<Base, detail::pow_base_auto_t<Base, In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto sin(In angle) noexcept
{ static_assert(detail::require_snap<In>()); return sin_impl<detail::sin_auto_t<In>>(angle); }

template <boundable In>
[[nodiscard]] constexpr auto cos(In angle) noexcept
{ static_assert(detail::require_snap<In>()); return cos_impl<detail::cos_auto_t<In>>(angle); }

template <boundable In>
[[nodiscard]] constexpr auto tan(In angle) noexcept
{ static_assert(detail::require_snap<In>()); return tan_impl<detail::tan_auto_t<In>>(angle); }

template <boundable In>
[[nodiscard]] constexpr auto atan2(In y, In x) noexcept
{ static_assert(detail::require_snap<In>()); return atan2_impl<detail::atan2_auto_t<In>>(y, x); }

template <boundable In>
[[nodiscard]] constexpr auto atan(In x) noexcept
{ static_assert(detail::require_snap<In>()); return atan_impl<detail::atan_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto asin(In x) noexcept
{ static_assert(detail::require_snap<In>()); return asin_impl<detail::asin_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto acos(In x) noexcept
{ static_assert(detail::require_snap<In>()); return acos_impl<detail::acos_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto sinh(In x) noexcept
{ static_assert(detail::require_snap<In>()); return sinh_impl<detail::sinh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto cosh(In x) noexcept
{ static_assert(detail::require_snap<In>()); return cosh_impl<detail::cosh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto tanh(In x) noexcept
{ static_assert(detail::require_snap<In>()); return tanh_impl<detail::tanh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] constexpr auto log10(In x) noexcept
{
static_assert(detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::cordic::log10: input must be strictly positive");
return log10_impl<detail::log10_auto_t<In>>(x);
}

template <boundable In>
[[nodiscard]] constexpr auto cbrt(In x) noexcept
{ static_assert(detail::require_snap<In>()); return cbrt_impl<detail::cbrt_auto_t<In>>(x); }

template <boundable InX, boundable InY>
[[nodiscard]] constexpr auto hypot(InX x, InY y) noexcept
{
static_assert(detail::require_snap<InX>() && detail::require_snap<InY>());
return hypot_impl<detail::hypot_auto_t<InX, InY>>(x, y);
}

template <boundable InB, boundable InE>
requires (Lower<InB> > bnd::detail::rational{0})
[[nodiscard]] constexpr auto pow(InB base, InE exp) noexcept
{
static_assert(detail::require_snap<InB>() && detail::require_snap<InE>());
return pow_impl<detail::pow_auto_t<InB, InE>>(base, exp);
}
} // namespace cordic

#ifndef BND_MATH_NO_FP
namespace dbl
{
// Public-shaped double-engine entry points. `detail::` here would resolve to
// bnd::math::dbl::detail (the engine cores), so the shared deduction/helpers
// are qualified as `bnd::math::detail::`; the `*_core`/`store`/`d_*` names are
// this namespace's own. Absent under BND_MATH_NO_FP (no FP, no <cmath>).
template <boundable In>
requires (Lower<In> == bnd::detail::rational{0})
[[nodiscard]] BND_DBL_FN auto sqrt(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return sqrt_core<bnd::math::detail::sqrt_auto_t<In>>(x); }

template <boundable In>
requires (Lower<In> < bnd::detail::rational{0})
[[nodiscard]] BND_DBL_FN auto sqrt(In x) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
using Out = bnd::math::detail::sqrt_signed_auto_t<In>;
double v = static_cast<double>(x);
if (v < 0.0)
return slim::expected<Out, errc>{slim::unexpected(errc::domain_error)};
return slim::expected<Out, errc>{store<Out>(detail::d_sqrt(v))};
}

template <boundable In>
[[nodiscard]] BND_DBL_FN auto exp2(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return exp2_core<bnd::math::detail::exp2_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto log2(In x) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::dbl::log2: input must be strictly positive");
return log2_core<bnd::math::detail::log2_auto_t<In>>(x);
}

template <boundable In>
[[nodiscard]] BND_DBL_FN auto exp(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return exp_core<bnd::math::detail::exp_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto log(In x) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::dbl::log: input must be strictly positive");
return log_core<bnd::math::detail::log_auto_t<In>>(x);
}

template <imax Base, boundable In>
[[nodiscard]] BND_DBL_FN auto pow_base(In x) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
using Out = bnd::math::detail::pow_base_auto_t<Base, In>;
return store<Out>(detail::d_pow(static_cast<double>(Base), static_cast<double>(x)));
}

template <boundable In>
[[nodiscard]] BND_DBL_FN auto sin(In angle) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return sin_core<bnd::math::detail::sin_auto_t<In>>(angle); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto cos(In angle) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return cos_core<bnd::math::detail::cos_auto_t<In>>(angle); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto tan(In angle) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
using Out = bnd::math::detail::tan_auto_t<In>;
double x = static_cast<double>(angle);
double c = detail::d_cos(x);
if (c == 0.0)
return slim::expected<Out, errc>{slim::unexpected(errc::division_by_zero)};
double t = detail::d_sin(x) / c;
if constexpr (!has_flag(BoundPolicy<Out>, clamp)) // clamp Out: saturate below
if (t < static_cast<double>(Lower<Out>) || t > static_cast<double>(Upper<Out>))
return slim::expected<Out, errc>{slim::unexpected(errc::overflow)};
return slim::expected<Out, errc>{store<Out>(t)};
}

template <boundable In>
[[nodiscard]] BND_DBL_FN auto atan2(In y, In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return atan2_core<bnd::math::detail::atan2_auto_t<In>>(y, x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto atan(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return atan_core<bnd::math::detail::atan_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto asin(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return asin_core<bnd::math::detail::asin_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto acos(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return acos_core<bnd::math::detail::acos_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto sinh(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return sinh_core<bnd::math::detail::sinh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto cosh(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return cosh_core<bnd::math::detail::cosh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto tanh(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return tanh_core<bnd::math::detail::tanh_auto_t<In>>(x); }

template <boundable In>
[[nodiscard]] BND_DBL_FN auto log10(In x) noexcept
{
static_assert(bnd::math::detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::dbl::log10: input must be strictly positive");
return log10_core<bnd::math::detail::log10_auto_t<In>>(x);
}

template <boundable In>
[[nodiscard]] BND_DBL_FN auto cbrt(In x) noexcept
{ static_assert(bnd::math::detail::require_snap<In>()); return cbrt_core<bnd::math::detail::cbrt_auto_t<In>>(x); }

template <boundable InX, boundable InY>
[[nodiscard]] BND_DBL_FN auto hypot(InX x, InY y) noexcept
{
static_assert(bnd::math::detail::require_snap<InX>() && bnd::math::detail::require_snap<InY>());
return hypot_core<bnd::math::detail::hypot_auto_t<InX, InY>>(x, y);
}

template <boundable InB, boundable InE>
requires (Lower<InB> > bnd::detail::rational{0})
[[nodiscard]] BND_DBL_FN auto pow(InB base, InE exp) noexcept
{
static_assert(bnd::math::detail::require_snap<InB>() && bnd::math::detail::require_snap<InE>());
using Out = bnd::math::detail::pow_auto_t<InB, InE>;
double b = static_cast<double>(base);
if (b <= 0.0)
return slim::expected<Out, errc>{slim::unexpected(errc::domain_error)};
double r = detail::d_pow(b, static_cast<double>(exp));
if constexpr (!has_flag(BoundPolicy<Out>, clamp)) // clamp Out: saturate below
if (r < static_cast<double>(Lower<Out>) || r > static_cast<double>(Upper<Out>))
return slim::expected<Out, errc>{slim::unexpected(errc::overflow)};
return slim::expected<Out, errc>{store<Out>(r)};
}
} // namespace dbl
#endif // !BND_MATH_NO_FP
}

#endif
Loading
Loading