|
|
constexpr | Rational () |
| | Default: 0/1, the value-initialised T{} of the generic witnesses.
|
|
constexpr | Rational (Z num, Z den) |
|
constexpr | Rational (Z n) |
| | Embedding of an integer as a rational n/1. Implicit to allow Rational<Z>{Z_value} to flow into the (a + b) / T{2} midpoint expression that the IsDense concept (in order:completeness) requires. When T = Rational<Z>, T{2} resolves through the Rational(std::integral) overload below (int → Rational<Z> in one UDC), bypassing the two-UDC chain int → Z → Rational<Z> that C++ would otherwise reject. This Rational(Z) form remains the canonical embedding when the caller already has a Z value in hand.
|
template<std::integral S>
requires (!std::same_as<S, Z>) |
| constexpr | Rational (S n) |
| | Embedding of any standard integral as a rational n/1, in one user-defined conversion.
|
|
constexpr void | reject_non_finite () const |
| | #680: ℚ has no sentinels. On the saturating SignedCardinality a non-finite numerator/denominator (NaZ / ±ℵ_0) is not a rational, so reject it with std::domain_error (Honest Rejection at the carrier boundary, the same posture as division-by-zero, cf. the embed_𝕃_ℚ no-sentinels contract below). This also avoids euclidean_gcd's non-termination (NaZ % anything == NaZ) and the ordering incoherence a retained NaZ/1 would cause (<=> would read it EQUAL to 1/1 while == reads it unequal). Generic over the backing through RejectsNonFinite (the is_finite hook); a compile-time no-op for carriers without sentinels (plain integers).
|
| constexpr void | simplify () |
|
constexpr Z | num () const |
|
constexpr Z | den () const |
| constexpr Rational | inverse () const |
|
constexpr Rational | operator- () const |
template<IsInteger Z = default_integer>
class dedekind::numbers::Rational< Z >
The Field of Fractions over an Integral Domain Z.
The countably-infinite fiction. Over the saturating carrier SignedCardinality this type models ℚ as a faithful, strong-totally-ordered field over a countably infinite ℤ — even though no finite machine can hold arbitrarily large numerators / denominators. The carrier's sentinels (NaZ, ±ℵ_0) are not inhabitants of that ℚ; they are the out-of-memory boundary of the finite backing. Reaching one means the computation has run off the edge of the fiction, so a non-finite numerator / denominator is rejected (std::domain_error, see reject_non_finite) rather than admitted as a value — the OOM tripwire. This is what keeps operator<=> a genuine std::strong_ordering and preserves IsTotal / IsField (both load-bearing for the ℝ coat-hanger downstream); a retained NaZ/1 would force <=> to partial_ordering and quietly demote ℚ to a non-total order.
Comparison is a tripwire too. operator<=> cross-multiplies ( \(a/b \lessgtr c/d \iff ad \lessgtr cb\)), and on the saturating carrier the cross-products can overflow even for two finite canonical rationals (e.g. \(M/2\) vs \((M{-}1)/3\) near UINT64_MAX). Rather than let a saturated intermediate read them EQUAL while == reads them unequal, <=> rejects an overflowed cross-product (reject_non_finite_product) — the same OOM tripwire. So the strong-total-order guarantee is precise: every <=> that returns is a genuine strong-total verdict; a comparison that would leave the finite backing throws instead. Over the client-contracted finite subset the branch is never taken.
- Client contract.
- The fiction is deliberate: client code may pretend ℚ is truly infinite, at the risk of hitting the OOM tripwire (in construction or comparison). Robust client code constrains itself to a finite subset a priori — e.g. integers \(-\text{max} < x < \text{max}\), or the canonicalised rationals over that range — so the tripwire is never reached. The library does not silently clamp on the client's behalf; it throws, so the boundary stays visible (Honest Rejection, the same posture as division-by-zero).
template<IsInteger Z = default_integer>
template<std::integral S>
requires (!std::same_as<S, Z>)
Embedding of any standard integral as a rational n/1, in one user-defined conversion.
Without this constructor, building a Rational<Z> from an int literal would require two UDCs (int → Z → Rational<Z>), which C++ does not permit in implicit-conversion chains. This template collapses both steps into a single UDC, enabling:
Rational<Z>{2} for the (a + b) / T{2} midpoint expression in the IsDense concept (the T{2} reformulation, landed under this PR, replaces the previous bare-int 2 so the carrier is in control of its own value-2);
- generally, any context where an
int literal lifts into Rational<Z> across one UDC.
Canonical example post-retarget: Z = SignedExtensionalCardinal<>, whose S → SEC<> constructor is itself a one-UDC step.