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
37 changes: 23 additions & 14 deletions docs/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,29 @@ auto s = math::sin(angle{1}); // amplitude bound in [-1, 1]
auto h = math::hypot(s, s); // √(s²+s²), output grid auto-deduced
```

## The `real` policy requirement

Every transcendental operand must carry the **`real` representation flag**
(`bound<G, round_nearest | real>`); omitting it is a compile error. `real`
marks a bound as a math operand:

- Under the default engine it selects **double-backed storage** on the
bound's grid — the raw *is* the value, so input marshalling into the
engine is free (this is where the large speedup over integer-index I/O
comes from). Values still obey the grid: they snap to the notch on store.
Out-of-range stores run the same policy cascade as every other bound
(clamp / wrap / sentinel / checked report).
- Under `BND_MATH_FIXED` the same flag is an ordinary `round_nearest`
integer-backed bound — the source compiles unchanged.
## The `snap` requirement (and `real` as a fast storage option)

A transcendental result is irrational and must be **rounded onto the operand's
grid**, so every transcendental operand must carry a policy that **permits
rounding** — i.e. the **`snap`** bit (`snap`, any `round_*` mode, or `real`,
which implies `round_nearest`). Omitting it is a compile error. This is the only
hard requirement: `math::sin` etc. work on **any snap-capable grid**, including
plain integer grids and non-dyadic ones (e.g. a `notch<1,100>` money grid) —
the value is computed by the engine and snapped to the grid via exact rational
rounding.

`real` is **not** required — it is an optional **storage** flag that buys speed:

- Under the default engine `real` selects **double-backed storage** on the
bound's grid — the raw *is* the value, so input marshalling into the engine is
free (the large speedup over integer-index I/O). Values still obey the grid:
they snap to the notch on store. Out-of-range stores run the usual policy
cascade (clamp / wrap / sentinel / checked report).
- Without `real`, a snap-capable grid still works — the engine's `double`/integer
result is snapped to the grid through the assignment path (a touch slower; no
double fast path). Use `real` when the grid is dyadic and you want the speed.
- Under `BND_MATH_FIXED` `real` is an ordinary `round_nearest` integer-backed
bound — the source compiles unchanged.
- `real` requires a grid that is **exactly representable in `double`**: dyadic
(power-of-two notch and Lower) **and** within the 53-bit significand — writing
a value as `N·2^(−f)` with `f = log2(notch denominator)`, every on-grid value
Expand Down
67 changes: 34 additions & 33 deletions include/bound/cmath.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ namespace bnd::math
// and avoids the slow integer-I/O path. Pure grid ops (abs/floor/ceil/round/
// trunc/fmod) have no engine and don't require it.
template <boundable In>
consteval bool require_real() noexcept
consteval bool require_snap() noexcept
{
static_assert(has_flag(BoundPolicy<In>, real),
"bnd::math: transcendental operand must carry the `real` policy — "
"declare it as bound<G, real> (or add `| real` to its policy).");
static_assert(has_flag(BoundPolicy<In>, snap),
"bnd::math: a transcendental result is rounded onto the grid — its "
"operand must permit rounding. Declare it with `round_nearest` (or "
"`snap` / a `round_*` mode / `real`).");
return true;
}
}
Expand Down Expand Up @@ -1175,7 +1176,7 @@ namespace bnd::math
requires (Lower<In> == bnd::detail::rational{0})
[[nodiscard]] BND_MATH_FN auto sqrt(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return sqrt_impl<detail::sqrt_auto_t<In>>(x);
#else
Expand All @@ -1190,22 +1191,22 @@ namespace bnd::math
requires (Lower<In> < bnd::detail::rational{0})
[[nodiscard]] BND_MATH_FN auto sqrt(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
using Out = detail::sqrt_signed_auto_t<In>;
#ifdef BND_MATH_FIXED
return sqrt_signed_impl<Out>(x);
#else
double v = x;
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>{Out{dbl::detail::d_sqrt(v)}};
return slim::expected<Out, errc>{dbl::store<Out>(dbl::detail::d_sqrt(v))};
#endif
}

template <boundable In>
[[nodiscard]] BND_MATH_FN auto exp2(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return exp2_impl<detail::exp2_auto_t<In>>(x);
#else
Expand All @@ -1216,7 +1217,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto log2(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
// Domain guard belongs on the shared entry point, not just the fixed
// 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).
Expand All @@ -1231,7 +1232,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto exp(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return exp_impl<detail::exp_auto_t<In>>(x);
#else
Expand All @@ -1242,7 +1243,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto log(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::log: input must be strictly positive");
#ifdef BND_MATH_FIXED
return log_impl<detail::log_auto_t<In>>(x);
Expand All @@ -1254,12 +1255,12 @@ namespace bnd::math
template <imax Base, boundable In>
[[nodiscard]] BND_MATH_FN auto pow_base(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
using Out = detail::pow_base_auto_t<Base, In>;
#ifdef BND_MATH_FIXED
return pow_base_impl<Base, Out>(x);
#else
return Out{dbl::detail::d_pow(static_cast<double>(Base), x)};
return dbl::store<Out>(dbl::detail::d_pow(static_cast<double>(Base), static_cast<double>(x)));
#endif
}

Expand Down Expand Up @@ -1306,7 +1307,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto sin(In angle) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return sin_impl<detail::sin_auto_t<In>>(angle);
#else
Expand All @@ -1317,7 +1318,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto cos(In angle) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return cos_impl<detail::cos_auto_t<In>>(angle);
#else
Expand All @@ -1328,7 +1329,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto atan2(In y, In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return atan2_impl<detail::atan2_auto_t<In>>(y, x);
#else
Expand All @@ -1339,20 +1340,20 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto tan(In angle) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
using Out = detail::tan_auto_t<In>;
#ifdef BND_MATH_FIXED
return tan_impl<Out>(angle);
#else
double x = angle;
double x = static_cast<double>(angle);
double c = dbl::detail::d_cos(x);
if (c == 0.0)
return slim::expected<Out, errc>{slim::unexpected(errc::division_by_zero)};
double t = dbl::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>{Out{t}};
return slim::expected<Out, errc>{dbl::store<Out>(t)};
#endif
}

Expand Down Expand Up @@ -1840,7 +1841,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto atan(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return atan_impl<detail::atan_auto_t<In>>(x);
#else
Expand All @@ -1851,7 +1852,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto asin(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return asin_impl<detail::asin_auto_t<In>>(x);
#else
Expand All @@ -1862,7 +1863,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto acos(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return acos_impl<detail::acos_auto_t<In>>(x);
#else
Expand All @@ -1873,7 +1874,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto sinh(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return sinh_impl<detail::sinh_auto_t<In>>(x);
#else
Expand All @@ -1884,7 +1885,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto cosh(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return cosh_impl<detail::cosh_auto_t<In>>(x);
#else
Expand All @@ -1895,7 +1896,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto tanh(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return tanh_impl<detail::tanh_auto_t<In>>(x);
#else
Expand All @@ -1906,7 +1907,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto log10(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
static_assert(Lower<In> > 0, "bnd::math::log10: input must be strictly positive");
#ifdef BND_MATH_FIXED
return log10_impl<detail::log10_auto_t<In>>(x);
Expand All @@ -1918,7 +1919,7 @@ namespace bnd::math
template <boundable In>
[[nodiscard]] BND_MATH_FN auto cbrt(In x) noexcept
{
static_assert(detail::require_real<In>());
static_assert(detail::require_snap<In>());
#ifdef BND_MATH_FIXED
return cbrt_impl<detail::cbrt_auto_t<In>>(x);
#else
Expand All @@ -1929,7 +1930,7 @@ namespace bnd::math
template <boundable InX, boundable InY>
[[nodiscard]] BND_MATH_FN auto hypot(InX x, InY y) noexcept
{
static_assert(detail::require_real<InX>() && detail::require_real<InY>());
static_assert(detail::require_snap<InX>() && detail::require_snap<InY>());
#ifdef BND_MATH_FIXED
return hypot_impl<detail::hypot_auto_t<InX, InY>>(x, y);
#else
Expand All @@ -1941,19 +1942,19 @@ namespace bnd::math
requires (Lower<InB> > bnd::detail::rational{0})
[[nodiscard]] BND_MATH_FN auto pow(InB base, InE exp) noexcept
{
static_assert(detail::require_real<InB>() && detail::require_real<InE>());
static_assert(detail::require_snap<InB>() && detail::require_snap<InE>());
using Out = detail::pow_auto_t<InB, InE>;
#ifdef BND_MATH_FIXED
return pow_impl<Out>(base, exp);
#else
double b = base;
double b = static_cast<double>(base);
if (b <= 0.0)
return slim::expected<Out, errc>{slim::unexpected(errc::domain_error)};
double r = dbl::detail::d_pow(b, exp);
double r = dbl::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>{Out{r}};
return slim::expected<Out, errc>{dbl::store<Out>(r)};
#endif
}
}
Expand Down
44 changes: 27 additions & 17 deletions include/bound/cmath_double.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define BNDcmathdoubleHPP

#include "bound/math.hpp" // bnd::detail::ldexp (constexpr, reproducible)
#include "bound/bound.hpp" // complete bound/rational + has_flag/BoundPolicy/real (store<>)

#include <cmath> // std::fma, std::sqrt, std::nearbyint ONLY

Expand Down Expand Up @@ -206,42 +207,51 @@ namespace bnd::math::dbl
// The bound I/O is a plain double read/store (operator double / Out{double}),
// so the cost is the polynomial itself. These plug into the shared public
// surface as `fn_core` under the default build.
template <typename Out>
[[nodiscard]] BND_DBL_FN Out store(double d)
{
// `real` (double-backed) Out stores the double directly; a non-`real` snap grid
// assigns through the rational path, snapping via Out's round policy.
if constexpr (has_flag(BoundPolicy<Out>, real)) return Out{d};
else { Out o{}; o = bnd::detail::rational{d}; return o; }
}

template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out sin_core(In x) { return Out{detail::d_sin (x)}; }
[[nodiscard]] BND_DBL_FN Out sin_core(In x) { return store<Out>(detail::d_sin(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out cos_core(In x) { return Out{detail::d_cos (x)}; }
[[nodiscard]] BND_DBL_FN Out cos_core(In x) { return store<Out>(detail::d_cos(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out exp_core(In x) { return Out{detail::d_exp (x)}; }
[[nodiscard]] BND_DBL_FN Out exp_core(In x) { return store<Out>(detail::d_exp(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out sqrt_core(In x) { return Out{detail::d_sqrt(x)}; }
[[nodiscard]] BND_DBL_FN Out sqrt_core(In x) { return store<Out>(detail::d_sqrt(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out log_core(In x) { return Out{detail::d_log (x)}; }
[[nodiscard]] BND_DBL_FN Out log_core(In x) { return store<Out>(detail::d_log(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out exp2_core(In x) { return Out{detail::d_exp2(x)}; }
[[nodiscard]] BND_DBL_FN Out exp2_core(In x) { return store<Out>(detail::d_exp2(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out log2_core(In x) { return Out{detail::d_log2(x)}; }
[[nodiscard]] BND_DBL_FN Out log2_core(In x) { return store<Out>(detail::d_log2(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out log10_core(In x){ return Out{detail::d_log10(x)}; }
[[nodiscard]] BND_DBL_FN Out log10_core(In x){ return store<Out>(detail::d_log10(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out cbrt_core(In x) { return Out{detail::d_cbrt(x)}; }
[[nodiscard]] BND_DBL_FN Out cbrt_core(In x) { return store<Out>(detail::d_cbrt(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out sinh_core(In x) { return Out{detail::d_sinh(x)}; }
[[nodiscard]] BND_DBL_FN Out sinh_core(In x) { return store<Out>(detail::d_sinh(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out cosh_core(In x) { return Out{detail::d_cosh(x)}; }
[[nodiscard]] BND_DBL_FN Out cosh_core(In x) { return store<Out>(detail::d_cosh(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out tanh_core(In x) { return Out{detail::d_tanh(x)}; }
[[nodiscard]] BND_DBL_FN Out tanh_core(In x) { return store<Out>(detail::d_tanh(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out atan_core(In x) { return Out{detail::d_atan(x)}; }
[[nodiscard]] BND_DBL_FN Out atan_core(In x) { return store<Out>(detail::d_atan(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out asin_core(In x) { return Out{detail::d_asin(x)}; }
[[nodiscard]] BND_DBL_FN Out asin_core(In x) { return store<Out>(detail::d_asin(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out acos_core(In x) { return Out{detail::d_acos(x)}; }
[[nodiscard]] BND_DBL_FN Out acos_core(In x) { return store<Out>(detail::d_acos(static_cast<double>(x))); }
template <typename Out, typename In>
[[nodiscard]] BND_DBL_FN Out atan2_core(In y, In x)
{ return Out{detail::d_atan2(y, x)}; }
{ return store<Out>(detail::d_atan2(static_cast<double>(y), static_cast<double>(x))); }
template <typename Out, typename InX, typename InY>
[[nodiscard]] BND_DBL_FN Out hypot_core(InX x, InY y)
{ return Out{detail::d_hypot(x, y)}; }
{ return store<Out>(detail::d_hypot(static_cast<double>(x), static_cast<double>(y))); }
} // namespace bnd::math::dbl

#endif // BNDcmathdoubleHPP
Loading
Loading