|
morph 0.1.0
Asynchronous bridge between GUI thread and business models
|
Exact rational arithmetic for morph model/action values. More...
#include <cassert>#include <cmath>#include <compare>#include <concepts>#include <cstdint>#include <expected>#include <format>#include <glaze/glaze.hpp>#include <limits>#include <morph/core/logger.hpp>#include <morph/core/payload_shape_tag.hpp>#include <numeric>#include <string>#include <string_view>#include <type_traits>#include <utility>Go to the source code of this file.
Classes | |
| struct | morph::math::DecimalPlaces |
| Strong type for a decimal-precision count. More... | |
| struct | morph::math::Numerator |
| Strong type for a Rational numerator. More... | |
| struct | morph::math::Denominator |
| Strong type for a Rational denominator. More... | |
| class | morph::math::WireClampScope |
| Observes whether any Rational had to be clamped while decoding inside this scope. More... | |
| struct | morph::math::Rational |
| Exact rational number with a runtime decimal-precision tag. More... | |
| struct | morph::math::Rational::Wire |
| Flat JSON representation used by the Glaze codec below. More... | |
| struct | morph::model::PayloadShapeTag< morph::math::Rational > |
| Stable shape tag for Rational, so a payload fingerprint can tell a Rational field from a Timestamp or a Quantity one. More... | |
Enumerations | |
| enum class | morph::math::RationalError : std::uint8_t { DivisionByZero , NotFinite , Overflow } |
| Error states reachable through Rational operations. More... | |
| enum class | morph::math::RoundingMode : std::uint8_t { HalfAwayFromZero , HalfEven } |
| Rule for resolving a value sitting exactly halfway between the two nearest results when rounding to a decimal scale. More... | |
Functions | |
| constexpr Rational | morph::math::abs (const Rational &value) noexcept |
| Absolute value of a rational. Precision is preserved. | |
| constexpr std::int64_t | morph::math::ceil (const Rational &value) noexcept |
| Rounds toward positive infinity. | |
| constexpr std::int64_t | morph::math::floor (const Rational &value) noexcept |
| Rounds toward negative infinity. | |
| constexpr std::int64_t | morph::math::trunc (const Rational &value) noexcept |
| Truncates toward zero. | |
| constexpr Rational | morph::math::roundToDecimalPlaces (const Rational &value, DecimalPlaces places, RoundingMode mode=RoundingMode::HalfAwayFromZero) noexcept |
| Rounds to a fixed number of decimal places exactly, without leaving the Rational domain – the operation ceil/floor/trunc do not offer, since those return std::int64_t and so only ever round to zero decimal places. | |
| constexpr Rational | morph::math::operator+ (const Rational &lhs, const Rational &rhs) noexcept |
| Adds two Rationals. Result precision is max of the two. | |
| constexpr Rational | morph::math::operator- (const Rational &lhs, const Rational &rhs) noexcept |
| Subtracts two Rationals. Result precision is max of the two. | |
| constexpr Rational | morph::math::operator* (const Rational &lhs, const Rational &rhs) noexcept |
| Multiplies two Rationals. Result precision is max of the two. | |
| constexpr std::expected< Rational, RationalError > | morph::math::operator/ (const Rational &lhs, const Rational &rhs) noexcept |
| Divides two Rationals. Result precision is max of the two. | |
| constexpr std::expected< Rational, RationalError > | morph::math::checkedAdd (const Rational &lhs, const Rational &rhs) noexcept |
| Adds two Rationals, reporting overflow instead of saturating. | |
| constexpr std::expected< Rational, RationalError > | morph::math::checkedSub (const Rational &lhs, const Rational &rhs) noexcept |
| Subtracts two Rationals, reporting overflow instead of saturating. | |
| constexpr std::expected< Rational, RationalError > | morph::math::checkedMul (const Rational &lhs, const Rational &rhs) noexcept |
| Multiplies two Rationals, reporting overflow instead of saturating. | |
| constexpr std::expected< Rational, RationalError > | morph::math::checkedDiv (const Rational &lhs, const Rational &rhs) noexcept |
| Divides two Rationals, reporting overflow instead of saturating. | |
| template<typename Left, typename Right> requires (detail::RationalLike<Left> || detail::RationalLike<Right>) && (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) && (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>) | |
| detail::ExpectedRational | morph::math::operator+ (const Left &lhs, const Right &rhs) noexcept |
| Mixed-type addition with automatic expected-propagation. | |
| template<typename Left, typename Right> requires (detail::RationalLike<Left> || detail::RationalLike<Right>) && (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) && (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>) | |
| detail::ExpectedRational | morph::math::operator- (const Left &lhs, const Right &rhs) noexcept |
| Mixed-type subtraction with automatic expected-propagation. | |
| template<typename Left, typename Right> requires (detail::RationalLike<Left> || detail::RationalLike<Right>) && (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) && (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>) | |
| detail::ExpectedRational | morph::math::operator* (const Left &lhs, const Right &rhs) noexcept |
| Mixed-type multiplication with automatic expected-propagation. | |
| template<typename Left, typename Right> requires (detail::RationalLike<Left> || detail::RationalLike<Right>) && (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) && (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>) | |
| detail::ExpectedRational | morph::math::operator/ (const Left &lhs, const Right &rhs) noexcept |
| Mixed-type division with automatic expected-propagation. | |
Variables | |
| constexpr std::uint32_t | morph::math::kMaxDecimalPlaces = 18 |
| Largest decimal precision the type supports: 10^18 is the greatest power of ten that fits in std::int64_t. | |
Exact rational arithmetic for morph model/action values.
morph::math::Rational is a small, value-semantic, trivially-copyable struct representing the rational number numerator/denominator with std::int64_t components. It carries a runtime decimal-precision tag, decimalPlaces, as a strong type (DecimalPlaces). Arithmetic is exact — sums, differences, products, and quotients are reduced to canonical form with no floating-point rounding error. The precision tag affects only decimal scaling (Rational::fromFloat) and rounding (Rational::toDouble, formatting); it never changes a stored value.
Adapted from LASTRADA JPMath/Rational.hpp, with the boxed strong-type dependency replaced by a self-contained DecimalPlaces and a Glaze wire codec added so the type round-trips through the morph JSON wire with its invariants restored on read.
The precision is supplied at construction. There is intentionally no default — every call site states the precision it intends, e.g. Rational{1, 3, DecimalPlaces{9}}. Precision is capped at kMaxDecimalPlaces (18, the largest k for which 10^k fits in int64_t); out-of-range values assert in debug and clamp into [0, kMaxDecimalPlaces] in release. Zero decimal places is a legal precision — a whole-number quantity (e.g. a JPY/KRW amount) carries no fractional digit at all.
All sign lives in the numerator.
Overflow is not one of the conditions those channels report: all four operations saturate at ±INT64_MAX/1 and log, and operator/'s expected reports a zero divisor only — an overflowing quotient still comes back as a success holding a clamped value. checkedAdd, checkedSub, checkedMul and checkedDiv are the exact-or-nothing forms for callers that must branch on it.
Definition in file rational.hpp.
|
strong |
Error states reachable through Rational operations.
| Enumerator | |
|---|---|
| DivisionByZero | Divisor numerator is zero (operator/, reciprocal, From). |
| NotFinite | Floating-point input was NaN or +/-Inf (fromFloat only). |
| Overflow | Result or an intermediate term exceeds int64_t range (fromFloat, and the checked* arithmetic helpers checkedAdd/checkedSub/checkedMul/checkedDiv). Never produced by dividedBy/operator/, which saturate on overflow and report only DivisionByZero. |
Definition at line 170 of file rational.hpp.
|
strong |
Rule for resolving a value sitting exactly halfway between the two nearest results when rounding to a decimal scale.
morph's decimal display path (morph::units::detail::formatRationalDecimal) rounds half away from zero, and that is why it is the default here: a value reconciled to its declared precision with HalfAwayFromZero renders as the same digits the submitter was shown before sending it. HalfEven exists for a caller bound to banker's rounding (the default in IEEE 754-2008 decimal, Python decimal and JSR-354, because half-away-from-zero biases a long sum upward) and knowingly trades that agreement away.
| Enumerator | |
|---|---|
| HalfAwayFromZero | Ties go to the neighbour of larger magnitude: 1.25 -> 1.3, -1.25 -> -1.3. |
| HalfEven | Ties go to the neighbour whose last kept digit is even: 1.25 -> 1.2, 1.35 -> 1.4. |
Definition at line 190 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Absolute value of a rational. Precision is preserved.
| value | Value to take the absolute value of. |
Definition at line 976 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Rounds toward positive infinity.
| value | Value to round. |
Definition at line 985 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Rounds toward negative infinity.
| value | Value to round. |
Definition at line 997 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Truncates toward zero.
| value | Value to truncate. |
Definition at line 1009 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Rounds to a fixed number of decimal places exactly, without leaving the Rational domain – the operation ceil/floor/trunc do not offer, since those return std::int64_t and so only ever round to zero decimal places.
This changes the value, not merely the decimalPlaces tag: the result is the multiple of 10^-places nearest value, ties resolved by mode, and it is tagged at places so display and storage agree digit for digit. A value already representable at places decimals is returned unchanged except for that tag, so the common case is exact and cannot overflow.
Saturates rather than overflowing, like every other Rational arithmetic path: rounding scales by 10^places, so a value whose scaled form exceeds int64 range clamps to +/-INT64_MAX and logs at error (see operator*=). A caller who must detect that has to range-check first – there is no checkedRound.
| value | Value to round. |
| places | Target decimal places; silently clamped to [0, kMaxDecimalPlaces]. |
| mode | Tie-breaking rule. Defaults to half away from zero, matching morph's display path. |
places. Definition at line 1380 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Adds two Rationals. Result precision is max of the two.
| lhs | Left operand. |
| rhs | Right operand. |
Definition at line 1046 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Subtracts two Rationals. Result precision is max of the two.
| lhs | Left operand. |
| rhs | Right operand. |
Definition at line 1056 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Multiplies two Rationals. Result precision is max of the two.
| lhs | Left operand. |
| rhs | Right operand. |
Definition at line 1066 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Divides two Rationals. Result precision is max of the two.
Saturates on overflow and still reports success; see Rational::dividedBy, and checkedDiv for the exact-or-nothing form.
| lhs | Dividend. |
| rhs | Divisor. |
rhs is zero. Definition at line 1080 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Adds two Rationals, reporting overflow instead of saturating.
operator+ saturates and logs when the exact sum does not fit, so it is never undefined – but it is also silently inexact. This returns the overflow instead, for callers that must not absorb it: a ledger totalling rows needs to stop, not to carry on with a clamped balance.
Every intermediate is checked before any is formed, since detecting signed overflow by performing it is itself undefined. Note the cross-terms are the tighter bound: they can overflow while the final result would have been perfectly representable, which is exactly the case a caller cannot spot by inspecting the answer.
| lhs | Left addend. |
| rhs | Right addend. |
Definition at line 1101 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Subtracts two Rationals, reporting overflow instead of saturating.
The checkedAdd counterpart; see that function.
| lhs | Minuend. |
| rhs | Subtrahend. |
Definition at line 1117 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Multiplies two Rationals, reporting overflow instead of saturating.
Checks the cross-cancelled factors operator* actually multiplies, not the raw operands – see Rational::mulWouldOverflow.
| lhs | Left factor. |
| rhs | Right factor. |
Definition at line 1134 of file rational.hpp.
|
nodiscardconstexprnoexcept |
Divides two Rationals, reporting overflow instead of saturating.
The escape hatch division was missing (morph#206). dividedBy and operator/ already return std::expected, but they spend it on the zero divisor alone: an overflowing quotient saturates and comes back as a successful expected holding a clamp, so a caller doing the right thing and checking the result is told the division succeeded. This reports both failure modes through the one channel.
Division is multiplication by the divisor's reciprocal, so this is exactly checkedMul against rhs.reciprocal() — the same operand pair dividedBy forms internally, checked by the same mulWouldOverflow predicate the operator uses. The operators and the checked forms therefore cannot disagree about which quotients fit.
| lhs | Dividend. |
| rhs | Divisor. |
rhs is zero; unexpected(RationalError::Overflow) if the quotient is not representable. Definition at line 1164 of file rational.hpp.
|
inlinenodiscardnoexcept |
Mixed-type addition with automatic expected-propagation.
| lhs | Left operand (Rational, expected-Rational, or floating point). |
| rhs | Right operand (Rational, expected-Rational, or floating point). |
Definition at line 1274 of file rational.hpp.
|
inlinenodiscardnoexcept |
Mixed-type subtraction with automatic expected-propagation.
| lhs | Left operand (Rational, expected-Rational, or floating point). |
| rhs | Right operand (Rational, expected-Rational, or floating point). |
Definition at line 1290 of file rational.hpp.
|
inlinenodiscardnoexcept |
Mixed-type multiplication with automatic expected-propagation.
| lhs | Left operand (Rational, expected-Rational, or floating point). |
| rhs | Right operand (Rational, expected-Rational, or floating point). |
Definition at line 1306 of file rational.hpp.
|
inlinenodiscardnoexcept |
Mixed-type division with automatic expected-propagation.
| lhs | Dividend (Rational, expected-Rational, or floating point). |
| rhs | Divisor (Rational, expected-Rational, or floating point). |
Definition at line 1322 of file rational.hpp.
|
inlineconstexpr |
Largest decimal precision the type supports: 10^18 is the greatest power of ten that fits in std::int64_t.
Definition at line 167 of file rational.hpp.