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
7 changes: 5 additions & 2 deletions docs/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,11 @@ so `flt` reads it, computes, and stores the result straight in `float` — no
`double` round-trip. On a single-precision-only FPU that keeps the whole path in
hardware float; with `f64`/rational storage the boundary marshalling goes through
`double` (soft-float on such targets). Because binary32 has only a 24-bit
significand, an `f32` result grid that a function would overflow (e.g. `exp` of a
large argument on a fine grid) is a compile error — widen the grid or use `f64`.
significand, an `f32` grid too fine for `float` but representable in `double`
**auto-widens its storage to `f64`** (the value stays exact) — so a deduced `f32`
output whose grid overflows binary32 (e.g. `exp` of a large argument on a fine
grid) stores its result in `double` instead of failing to compile. Only a grid
too fine for `double` as well is a hard error (`exact` is the escape hatch).

## Compiling without floating point (`BND_MATH_NO_FP`)

Expand Down
24 changes: 19 additions & 5 deletions include/bound/grid.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ namespace bnd
template <grid G>
inline constexpr bool float_exact = compute_float_exact<G>();

// Demote an fp STORAGE flag a result grid can't represent — for DEDUCED policies
// (cmath auto-outputs, which inherit the operand's storage flag), so a deduced
// f32 output whose grid overflows binary32 silently widens instead of hard-
// erroring. (A grid a user spells `f32` on directly still static_asserts in
// storage_pick — that's deliberate misuse, not deduction.) f32 needs float_exact,
// f64 needs double_exact (Notch == 0 continuous fits either). When the flag
// doesn't fit: widen f32→f64 if double holds the grid, else drop the fp flag so
// storage is deduced. The snap/round bits are preserved.
// Storage for a bound<G, P>: representation flags pick the raw type, widest-wins
// (exact > real > direct > indexed > deduced).
// exact → rational raw on any grid.
Expand Down Expand Up @@ -285,13 +293,19 @@ namespace bnd
else if constexpr (((P & bnd::f32) == bnd::f32)
&& (float_exact<G> || G.Notch == 0))
return float{};
else if constexpr (((P & bnd::f32) == bnd::f32) && double_exact<G>)
// `f32` requested on a grid too fine for float but representable in double:
// WIDEN the storage to binary64. This makes a deduced f32 output (a cmath
// result inheriting the operand's flag) whose grid overflows float store its
// value in double rather than hard-erroring — the value stays exact. The f32
// POLICY bit remains (harmless; storage is raw-driven via fp_raw).
return double{};
else if constexpr (((P & bnd::f32) == bnd::f32) && dyadic_grid<G>)
{
// `f32` requested on a dyadic grid float can't represent exactly. Arithmetic
// demotes f32→f64 (or drops it) before reaching here, so this is direct misuse.
static_assert(float_exact<G>,
"f32 storage: grid exceeds float's 24-bit significand — use `f64` for the "
"wider range, or `exact`");
// Too fine for double too → genuinely unrepresentable as fp storage.
static_assert(double_exact<G>,
"f32 storage: grid exceeds double's 53-bit significand — coarsen the "
"notch/range or use `exact`");
return float{}; // unreachable; fixes the deduced return type
}
#endif
Expand Down
24 changes: 19 additions & 5 deletions single_include/bound/bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3823,6 +3823,14 @@ namespace bnd
template <grid G>
inline constexpr bool float_exact = compute_float_exact<G>();

// Demote an fp STORAGE flag a result grid can't represent — for DEDUCED policies
// (cmath auto-outputs, which inherit the operand's storage flag), so a deduced
// f32 output whose grid overflows binary32 silently widens instead of hard-
// erroring. (A grid a user spells `f32` on directly still static_asserts in
// storage_pick — that's deliberate misuse, not deduction.) f32 needs float_exact,
// f64 needs double_exact (Notch == 0 continuous fits either). When the flag
// doesn't fit: widen f32→f64 if double holds the grid, else drop the fp flag so
// storage is deduced. The snap/round bits are preserved.
// Storage for a bound<G, P>: representation flags pick the raw type, widest-wins
// (exact > real > direct > indexed > deduced).
// exact → rational raw on any grid.
Expand Down Expand Up @@ -3853,13 +3861,19 @@ namespace bnd
else if constexpr (((P & bnd::f32) == bnd::f32)
&& (float_exact<G> || G.Notch == 0))
return float{};
else if constexpr (((P & bnd::f32) == bnd::f32) && double_exact<G>)
// `f32` requested on a grid too fine for float but representable in double:
// WIDEN the storage to binary64. This makes a deduced f32 output (a cmath
// result inheriting the operand's flag) whose grid overflows float store its
// value in double rather than hard-erroring — the value stays exact. The f32
// POLICY bit remains (harmless; storage is raw-driven via fp_raw).
return double{};
else if constexpr (((P & bnd::f32) == bnd::f32) && dyadic_grid<G>)
{
// `f32` requested on a dyadic grid float can't represent exactly. Arithmetic
// demotes f32→f64 (or drops it) before reaching here, so this is direct misuse.
static_assert(float_exact<G>,
"f32 storage: grid exceeds float's 24-bit significand — use `f64` for the "
"wider range, or `exact`");
// Too fine for double too → genuinely unrepresentable as fp storage.
static_assert(double_exact<G>,
"f32 storage: grid exceeds double's 53-bit significand — coarsen the "
"notch/range or use `exact`");
return float{}; // unreachable; fixes the deduced return type
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions tests/test_storage_flags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ TEST_CASE("math output lands in f32 storage (flt engine pairs with f32)",
STATIC_REQUIRE(detail::f32_raw<decltype(r)>);
REQUIRE(rational{s} == 0);
REQUIRE(rational{r} == 2);

// Auto-demote: an f32 input whose result grid overflows binary32 (exp's range
// e^20 ≈ 4.85e8 > 2^24, still < 2^53) widens its OUTPUT to f64 storage rather
// than hard-erroring — the deduced output never static_asserts on f32 overflow.
using Big = bound<{{0, 20}, notch<1, 256>}, round_nearest | f32>;
auto e = math::flt::exp(Big{2});
STATIC_REQUIRE(detail::f64_raw<decltype(e)>); // demoted f32 → f64
REQUIRE(rational{e} > rational{7}); // ≈ 7.39
}
#endif // !BND_MATH_FIXED

Expand Down
Loading