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

## The `snap` requirement (and `real` as a fast storage option)
## The `snap` requirement (and `f64` as a fast storage option)

> **Naming:** the double-backed storage flag is now spelled **`f64`**; **`real`
> is a deprecated alias** kept for one release. The two are bit-identical, so the
> examples below (and existing code) compile under either spelling. New code
> should prefer `f64`.

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`,
rounding** — i.e. the **`snap`** bit (`snap`, any `round_*` mode, or `f64`,
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:
`f64` 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
Expand Down
8 changes: 4 additions & 4 deletions docs/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ is proven elsewhere. `clamp`, `wrap`, and `sentinel` are mutually exclusive
| `round_half_even` | banker's rounding — half to even (implies `snap`) |
| `ignore_zero` | skip the divide-by-zero check — `a / 0` / `a % 0` is UB (binary `div`/`mod`); compound `/= 0` / `%= 0` no-op |
| `ignore_domain` | suppress the runtime domain check |
| `real` / `exact` / `direct` / `indexed` | **representation flags** — select how the raw value is stored; see the next section |
| `f64` / `exact` / `direct` / `indexed` | **representation flags** — select how the raw value is stored; see the next section (`real` is a deprecated alias of `f64`) |

## Representation flags

Expand All @@ -59,7 +59,7 @@ Besides the *behavior* flags above, four flags select the **representation**

| Flag | Forces | Grid requirement | Notes |
|---|---|---|---|
| `real` | IEEE-754 `double` raw (the value itself, snapped to the grid) | dyadic **and** double-exact (every value fits `double`'s 53-bit significand) | bundles `round_nearest`; the math-operand flag — see [math.md](math.md#the-real-policy-requirement). Arithmetic drops `real` to an exact representation when a result grid is too fine for `double`. Under `BND_MATH_FIXED` it falls back to integer storage. |
| `f64` | IEEE-754 `double` raw (the value itself, snapped to the grid) | dyadic **and** double-exact (every value fits `double`'s 53-bit significand) | bundles `round_nearest`; the fast math-storage flag. Arithmetic drops `f64` to an exact representation when a result grid is too fine for `double`. Under `BND_MATH_FIXED` it falls back to integer storage. **`real` is a deprecated alias of `f64`.** |
| `exact` | exact-fraction raw on **any** grid | none | no notch-count limit, no `double` anywhere; arithmetic is exact — on notched grids overflow is usually provably impossible and `+ − ×` return plain bounds (no `optional`) |
| `direct` | raw == value as a plain integer | `Notch == 1` | e.g. `bound<{5, 100}, direct>` stores 5..100, not index 0..95 — the raw equals the wire/debugger value |
| `indexed` | raw == 0-based notch index | `Notch != 0` | e.g. `bound<{-5, 5}, indexed>` stores 0..10 unsigned — dense layout for serialization |
Expand All @@ -73,8 +73,8 @@ using slot = bound<{-5, 5}, indexed>; // raw() == 0..10, dense unsigned

Binary arithmetic ORs the policies of both operands, so a result can carry
several representation flags; storage selection resolves them
**widest-wins**: `exact > real > direct > indexed > deduced`. An
`exact + real` sum is therefore exact, and a `real` math chain stays
**widest-wins**: `exact > f64 > direct > indexed > deduced`. An
`exact + f64` sum is therefore exact, and a `f64` math chain stays
double-backed end to end — no errors at mixed call sites.

> **API-boundary shorthand:** the modern idiom for "saturate-and-round into
Expand Down
14 changes: 10 additions & 4 deletions include/bound/policy_flag.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ namespace bnd
// Representation flags — select raw storage. Without one, storage is deduced
// from the grid (notch-0 → rational; unit notch at/below 0 → integer value;
// else 0-based index). Binary ops OR operand policies; storage resolves
// widest-wins: exact > real > direct > indexed > deduced.
// widest-wins: exact > f64 > direct > indexed > deduced.
//
// `real` — math operand. Double-backed storage under the default engine (value
// `f64` — math operand, binary64-backed storage under the default engine (value
// held as IEEE-754 double, notch nominal); an ordinary round_nearest integer
// bound under BND_MATH_FIXED. Power-of-2 notch + dyadic Lower required so
// on-grid values are exact in double.
inline static constexpr policy_flag real{(1ull << 37) | round_nearest};
// on-grid values are exact in double (see `double_exact`).
inline static constexpr policy_flag f64{(1ull << 37) | round_nearest};

// `real` — deprecated spelling of `f64`, kept as an alias for one release. New
// code should use `f64` (binary64 storage); a future `f32` brings binary32
// storage. The flag is purely a storage choice — transcendentals gate on
// `snap`, not on this (see cmath.hpp).
inline static constexpr policy_flag real = f64;

// `exact` — force rational raw storage on any grid. Values still obey the grid;
// exact fractions, no notch-count limit, no double. Slowest; overflow-checked
Expand Down
14 changes: 10 additions & 4 deletions single_include/bound/bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3405,13 +3405,19 @@ namespace bnd
// Representation flags — select raw storage. Without one, storage is deduced
// from the grid (notch-0 → rational; unit notch at/below 0 → integer value;
// else 0-based index). Binary ops OR operand policies; storage resolves
// widest-wins: exact > real > direct > indexed > deduced.
// widest-wins: exact > f64 > direct > indexed > deduced.
//
// `real` — math operand. Double-backed storage under the default engine (value
// `f64` — math operand, binary64-backed storage under the default engine (value
// held as IEEE-754 double, notch nominal); an ordinary round_nearest integer
// bound under BND_MATH_FIXED. Power-of-2 notch + dyadic Lower required so
// on-grid values are exact in double.
inline static constexpr policy_flag real{(1ull << 37) | round_nearest};
// on-grid values are exact in double (see `double_exact`).
inline static constexpr policy_flag f64{(1ull << 37) | round_nearest};

// `real` — deprecated spelling of `f64`, kept as an alias for one release. New
// code should use `f64` (binary64 storage); a future `f32` brings binary32
// storage. The flag is purely a storage choice — transcendentals gate on
// `snap`, not on this (see cmath.hpp).
inline static constexpr policy_flag real = f64;

// `exact` — force rational raw storage on any grid. Values still obey the grid;
// exact fractions, no notch-count limit, no double. Slowest; overflow-checked
Expand Down
18 changes: 18 additions & 0 deletions tests/test_storage_flags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@ TEST_CASE("representation flags resolve widest-wins", "[storage][policy]")
REQUIRE(DI{42}.raw() == 42);
}

TEST_CASE("f64 is the canonical double-backed flag; real is its alias",
"[storage][f64]")
{
// `real` was renamed `f64`; the alias is bit-identical, so old code compiles.
STATIC_REQUIRE(bnd::f64 == bnd::real);
STATIC_REQUIRE(has_flag(bnd::f64, round_nearest)); // still carries snap/round

#ifndef BND_MATH_FIXED
// f64 selects binary64-backed storage exactly as `real` did (storage is
// independent of the compute engine — true in the double AND float builds).
using F = bound<{{0, 4}, notch<1, 256>}, round_nearest | f64>;
using R = bound<{{0, 4}, notch<1, 256>}, round_nearest | real>;
STATIC_REQUIRE(std::is_same_v<F::raw_type, double>);
STATIC_REQUIRE(std::is_same_v<F::raw_type, R::raw_type>);
STATIC_REQUIRE(detail::real_raw<F>);
#endif
}

TEST_CASE("representation flags compose with behavior policies",
"[storage][policy]")
{
Expand Down
Loading