105#include <glaze/glaze.hpp>
107#include <morph/core/logger.hpp>
111#include <string_view>
112#include <type_traits>
115namespace morph::math {
121struct DecimalPlaces {
129 constexpr explicit DecimalPlaces(std::uint32_t raw) noexcept :
value{raw} {}
134 [[nodiscard]]
constexpr auto operator<=>(
const DecimalPlaces& other)
const noexcept =
default;
144 constexpr Numerator() noexcept = default;
148 constexpr explicit Numerator(std::int64_t raw) noexcept :
value{raw} {}
162 constexpr explicit Denominator(std::int64_t raw) noexcept :
value{raw} {}
199[[nodiscard]]
inline std::size_t& wireClampCounter()
noexcept {
200 static thread_local std::size_t clamped = 0;
234 WireClampScope() noexcept : _saved{detail::wireClampCounter()} { detail::wireClampCounter() = 0; }
246 [[nodiscard]] std::size_t
clamped() const noexcept {
return detail::wireClampCounter(); }
261[[nodiscard]]
constexpr std::uint32_t clampWireDecimalPlaces(std::uint32_t rawDecimalPlaces)
noexcept {
262 if (rawDecimalPlaces > kMaxDecimalPlaces) {
263 return kMaxDecimalPlaces;
265 return rawDecimalPlaces;
270[[nodiscard]]
constexpr std::uint32_t clampDecimalPlaces(std::uint32_t rawDecimalPlaces)
noexcept {
271 assert(rawDecimalPlaces <= kMaxDecimalPlaces);
272 return clampWireDecimalPlaces(rawDecimalPlaces);
283[[nodiscard]]
constexpr bool addOverflows(std::int64_t lhs, std::int64_t rhs)
noexcept {
284 constexpr auto maxValue = std::numeric_limits<std::int64_t>::max();
285 constexpr auto minValue = std::numeric_limits<std::int64_t>::min();
286 if (rhs > 0 && lhs > maxValue - rhs) {
289 return rhs < 0 && lhs < minValue - rhs;
296[[nodiscard]]
constexpr bool subOverflows(std::int64_t lhs, std::int64_t rhs)
noexcept {
297 constexpr auto maxValue = std::numeric_limits<std::int64_t>::max();
298 constexpr auto minValue = std::numeric_limits<std::int64_t>::min();
299 if (rhs < 0 && lhs > maxValue + rhs) {
302 return rhs > 0 && lhs < minValue + rhs;
312[[nodiscard]]
constexpr bool mulOverflows(std::int64_t lhs, std::int64_t rhs)
noexcept {
313 constexpr auto maxValue = std::numeric_limits<std::int64_t>::max();
314 constexpr auto minValue = std::numeric_limits<std::int64_t>::min();
315 if (lhs == 0 || rhs == 0) {
321 return rhs == minValue;
324 return lhs == minValue;
327 return rhs > 0 ? lhs > maxValue / rhs : rhs < minValue / lhs;
329 return rhs > 0 ? lhs < minValue / rhs : lhs < maxValue / rhs;
336 [[nodiscard]]
constexpr auto operator<=>(
const U128&)
const noexcept =
default;
340[[nodiscard]]
constexpr U128 mulU64(std::uint64_t lhs, std::uint64_t rhs)
noexcept {
341#ifdef __SIZEOF_INT128__
342#if defined(__GNUC__) && !defined(__clang__)
343#pragma GCC diagnostic push
344#pragma GCC diagnostic ignored "-Wpedantic"
346 auto const product =
static_cast<unsigned __int128
>(lhs) * rhs;
347#if defined(__GNUC__) && !defined(__clang__)
348#pragma GCC diagnostic pop
350 return U128{.hi =
static_cast<std::uint64_t
>(product >> 64), .lo =
static_cast<std::uint64_t
>(product)};
353 constexpr std::uint64_t mask = 0xffffffffULL;
354 auto const lhsLow = lhs & mask;
355 auto const lhsHigh = lhs >> 32;
356 auto const rhsLow = rhs & mask;
357 auto const rhsHigh = rhs >> 32;
358 auto const lowLow = lhsLow * rhsLow;
359 auto const lowHigh = lhsLow * rhsHigh;
360 auto const highLow = lhsHigh * rhsLow;
361 auto const highHigh = lhsHigh * rhsHigh;
362 auto const mid = (lowLow >> 32) + (lowHigh & mask) + (highLow & mask);
363 return U128{.hi = highHigh + (lowHigh >> 32) + (highLow >> 32) + (mid >> 32), .lo = (mid << 32) | (lowLow & mask)};
368[[nodiscard]]
constexpr std::uint64_t absU64(std::int64_t value)
noexcept {
369 return value < 0 ? 0ULL -
static_cast<std::uint64_t
>(value) : static_cast<std::uint64_t>(value);
407 decimalPlaces{detail::clampDecimalPlaces(wantedPrecision.value)} {
416 [[nodiscard]]
static constexpr std::expected<Rational, RationalError>
from(
418 if (wantedDenominator.value == 0) {
421 return Rational{wantedNumerator, wantedDenominator, wantedPrecision};
429 [[nodiscard]]
static std::expected<Rational, RationalError>
fromFloat(
double value,
436 [[nodiscard]]
static std::expected<Rational, RationalError>
fromFloat(
float value,
443 [[nodiscard]]
static std::expected<Rational, RationalError>
fromFloat(
long double value,
484 [[nodiscard]]
double toDouble(std::uint32_t requestedDecimalPlaces)
const noexcept;
494 [[nodiscard]]
constexpr std::expected<Rational, RationalError>
reciprocal() const noexcept {
511 auto const leftSign =
static_cast<int>(
numerator > 0) -
static_cast<int>(
numerator < 0);
512 auto const rightSign =
static_cast<int>(other.numerator > 0) -
static_cast<int>(other.numerator < 0);
513 if (leftSign != rightSign) {
514 return leftSign <=> rightSign;
517 return std::strong_ordering::equal;
520 auto const leftProduct =
521 detail::mulU64(detail::absU64(
numerator),
static_cast<std::uint64_t
>(other.denominator));
522 auto const rightProduct =
523 detail::mulU64(detail::absU64(other.numerator),
static_cast<std::uint64_t
>(
denominator));
524 auto const magnitude = leftProduct <=> rightProduct;
528 if (std::is_lt(magnitude)) {
529 return std::strong_ordering::greater;
531 if (std::is_gt(magnitude)) {
532 return std::strong_ordering::less;
534 return std::strong_ordering::equal;
556 reportOverflow(
"operator+=",
"checkedAdd");
557 saturateToward(compareForSaturation(rhs,
true), rhs.decimalPlaces);
560 addAssignUnchecked(rhs);
571 reportOverflow(
"operator-=",
"checkedSub");
572 saturateToward(compareForSaturation(rhs,
false), rhs.decimalPlaces);
575 subAssignUnchecked(rhs);
586 mulAssignSaturating(rhs,
"operator*=",
"checkedMul");
603 [[nodiscard]]
constexpr std::expected<Rational, RationalError>
dividedBy(
const Rational& rhs)
const noexcept {
604 auto const divisorReciprocal = rhs.reciprocal();
605 if (!divisorReciprocal.has_value()) {
606 return std::unexpected(divisorReciprocal.error());
608 auto leftCopy = *
this;
612 leftCopy.mulAssignSaturating(*divisorReciprocal,
"dividedBy",
"checkedDiv");
637 [[nodiscard]]
constexpr bool validate() const noexcept {
643 std::in_range<std::int64_t>(detail::absU64(
den));
662 if (!wire.validate()) {
663 ++detail::wireClampCounter();
665 constexpr auto int64Min = std::numeric_limits<std::int64_t>::min();
666 constexpr auto negatableMin = -std::numeric_limits<std::int64_t>::max();
668 Denominator{wire.den == int64Min ? negatableMin : wire.den},
696 static constexpr void reportOverflow(std::string_view where, std::string_view remedy)
noexcept {
697 if (!std::is_constant_evaluated()) {
698 ::morph::log::logError(
699 "[Rational] {} overflowed int64 and saturated; the result is clamped, "
700 "not exact. Use {} to detect this instead.",
713 constexpr void mulAssignSaturating(
const Rational& rhs, std::string_view where, std::string_view remedy)
noexcept {
715 reportOverflow(where, remedy);
718 const bool negative = (
numerator < 0) != (rhs.numerator < 0);
719 saturateToward(negative ? -1 : 1, rhs.decimalPlaces);
722 mulAssignUnchecked(rhs);
730 static constexpr void reportClamp() noexcept {
731 if (!std::is_constant_evaluated()) {
732 ::morph::log::logError(
733 "[Rational] an INT64_MIN component was clamped to -INT64_MAX; "
734 "canonicalising it would require negating a value with no positive "
749 [[nodiscard]]
constexpr int compareForSaturation(
const Rational& rhs,
bool addition)
const noexcept {
750 const auto ordering = addition ? (*this <=> -rhs) : (*this <=> rhs);
754 if (std::is_lt(ordering)) {
757 return std::is_gt(ordering) ? 1 : 0;
773 constexpr void saturateToward(
int sign, DecimalPlaces other)
noexcept {
774 constexpr auto maxValue = std::numeric_limits<std::int64_t>::max();
775 numerator = sign == 0 ? 0 : (sign < 0 ? -maxValue : maxValue);
777 widenPrecisionTo(other);
782 struct DenominatorScale {
783 std::int64_t leftScaled;
784 std::int64_t rightScaled;
800 [[nodiscard]]
constexpr DenominatorScale scaleFactorsFor(
const Rational& rhs)
const noexcept {
801 auto const denominatorGcd = std::gcd(
denominator, rhs.denominator);
802 return DenominatorScale{
804 .rightScaled = rhs.denominator / denominatorGcd,
810 constexpr void addAssignUnchecked(
const Rational& rhs)
noexcept {
811 auto const scale = scaleFactorsFor(rhs);
814 widenPrecisionTo(rhs.decimalPlaces);
820 constexpr void subAssignUnchecked(
const Rational& rhs)
noexcept {
821 auto const scale = scaleFactorsFor(rhs);
824 widenPrecisionTo(rhs.decimalPlaces);
830 constexpr void mulAssignUnchecked(
const Rational& rhs)
noexcept {
832 auto const absoluteRightNumerator = rhs.numerator < 0 ? -rhs.numerator : rhs.numerator;
833 auto const crossDivisorOne = std::gcd(absoluteLeftNumerator, rhs.denominator);
834 auto const crossDivisorTwo = std::gcd(absoluteRightNumerator,
denominator);
835 auto const reducedLeftNumerator =
numerator / crossDivisorOne;
836 auto const reducedRightNumerator = rhs.numerator / crossDivisorTwo;
837 auto const reducedLeftDenominator =
denominator / crossDivisorTwo;
838 auto const reducedRightDenominator = rhs.denominator / crossDivisorOne;
839 numerator = reducedLeftNumerator * reducedRightNumerator;
840 denominator = reducedLeftDenominator * reducedRightDenominator;
841 widenPrecisionTo(rhs.decimalPlaces);
866 auto const scale = scaleFactorsFor(rhs);
867 if (detail::mulOverflows(
numerator, scale.rightScaled) ||
868 detail::mulOverflows(rhs.numerator, scale.leftScaled) ||
869 detail::mulOverflows(
denominator, scale.rightScaled)) {
872 return detail::addOverflows(
numerator * scale.rightScaled, rhs.numerator * scale.leftScaled);
879 auto const scale = scaleFactorsFor(rhs);
880 if (detail::mulOverflows(
numerator, scale.rightScaled) ||
881 detail::mulOverflows(rhs.numerator, scale.leftScaled) ||
882 detail::mulOverflows(
denominator, scale.rightScaled)) {
885 return detail::subOverflows(
numerator * scale.rightScaled, rhs.numerator * scale.leftScaled);
897 auto const absoluteRightNumerator = rhs.numerator < 0 ? -rhs.numerator : rhs.numerator;
898 auto const crossDivisorOne = std::gcd(absoluteLeftNumerator, rhs.denominator);
899 auto const crossDivisorTwo = std::gcd(absoluteRightNumerator,
denominator);
900 if (crossDivisorOne == 0 || crossDivisorTwo == 0) {
903 return detail::mulOverflows(
numerator / crossDivisorOne, rhs.numerator / crossDivisorTwo) ||
904 detail::mulOverflows(
denominator / crossDivisorTwo, rhs.denominator / crossDivisorOne);
911 constexpr void canonicalise() noexcept {
916 constexpr auto minValue = std::numeric_limits<std::int64_t>::min();
917 constexpr auto maxValue = std::numeric_limits<std::int64_t>::max();
958 constexpr void widenPrecisionTo(DecimalPlaces other)
noexcept {
966static_assert(std::is_trivially_copyable_v<Rational>);
967static_assert(std::is_standard_layout_v<Rational>);
977 return value.numerator < 0
985[[nodiscard]]
constexpr std::int64_t
ceil(
const Rational& value)
noexcept {
986 auto const quotient = value.numerator / value.denominator;
987 auto const remainder = value.numerator % value.denominator;
988 if (remainder != 0 && value.numerator > 0) {
998 auto const quotient = value.numerator / value.denominator;
999 auto const remainder = value.numerator % value.denominator;
1000 if (remainder != 0 && value.numerator < 0) {
1001 return quotient - 1;
1010 return value.numerator / value.denominator;
1035[[nodiscard]]
constexpr Rational roundToDecimalPlaces(
const Rational& value, DecimalPlaces places,
1036 RoundingMode mode = RoundingMode::HalfAwayFromZero)
noexcept;
1082 return lhs.dividedBy(rhs);
1103 if (lhs.addWouldOverflow(rhs)) {
1107 result.addAssignChecked(rhs);
1119 if (lhs.subWouldOverflow(rhs)) {
1123 result.subAssignChecked(rhs);
1136 if (lhs.mulWouldOverflow(rhs)) {
1140 result.mulAssignChecked(rhs);
1166 auto const divisorReciprocal = rhs.reciprocal();
1167 if (!divisorReciprocal.has_value()) {
1168 return std::unexpected(divisorReciprocal.error());
1179using ExpectedRational = std::expected<Rational, RationalError>;
1182template <
typename T>
1183concept IsRational = std::same_as<std::remove_cvref_t<T>, Rational>;
1186template <
typename T>
1187concept IsExpectedRational = std::same_as<std::remove_cvref_t<T>, ExpectedRational>;
1190template <
typename T>
1191concept IsFloat = std::floating_point<std::remove_cvref_t<T>>;
1194template <
typename T>
1195concept RationalLike = IsRational<T> || IsExpectedRational<T>;
1198template <
typename T>
1199concept LiftableOperand = RationalLike<T> || IsFloat<T>;
1202template <
typename T>
1203concept NeedsLifting = IsExpectedRational<T> || IsFloat<T>;
1206[[nodiscard]]
constexpr ExpectedRational lift(
const Rational& value, DecimalPlaces )
noexcept {
1207 return ExpectedRational{value};
1211[[nodiscard]]
constexpr ExpectedRational lift(
const ExpectedRational& value,
1212 DecimalPlaces )
noexcept {
1217template <std::
floating_po
int Float>
1218[[nodiscard]]
inline ExpectedRational lift(Float value, DecimalPlaces fallback)
noexcept {
1225template <
typename Left,
typename Right>
1226[[nodiscard]]
constexpr DecimalPlaces liftPrecision(
const Left& lhs,
const Right& rhs)
noexcept {
1227 if constexpr (IsRational<Left>) {
1228 return lhs.decimalPlaces;
1229 }
else if constexpr (IsExpectedRational<Left>) {
1230 return lhs ? lhs->decimalPlaces : DecimalPlaces{1};
1231 }
else if constexpr (IsRational<Right>) {
1232 return rhs.decimalPlaces;
1233 }
else if constexpr (IsExpectedRational<Right>) {
1234 return rhs ? rhs->decimalPlaces : DecimalPlaces{1};
1236 return DecimalPlaces{1};
1249template <
typename Left,
typename Right>
1250[[nodiscard]]
inline std::expected<std::pair<Rational, Rational>,
RationalError> liftBoth(
const Left& lhs,
1251 const Right& rhs)
noexcept {
1252 auto const precision = liftPrecision(lhs, rhs);
1253 auto const leftExpected = lift(lhs, precision);
1254 if (!leftExpected) {
1255 return std::unexpected(leftExpected.error());
1257 auto const rightExpected = lift(rhs, precision);
1258 if (!rightExpected) {
1259 return std::unexpected(rightExpected.error());
1261 return std::pair<Rational, Rational>{*leftExpected, *rightExpected};
1270template <
typename Left,
typename Right>
1271 requires(detail::RationalLike<Left> || detail::RationalLike<Right>) &&
1272 (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) &&
1273 (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>)
1274[[nodiscard]]
inline detail::ExpectedRational
operator+(
const Left& lhs,
const Right& rhs)
noexcept {
1275 auto const lifted = detail::liftBoth(lhs, rhs);
1277 return std::unexpected(lifted.error());
1279 return lifted->first + lifted->second;
1286template <
typename Left,
typename Right>
1287 requires(detail::RationalLike<Left> || detail::RationalLike<Right>) &&
1288 (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) &&
1289 (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>)
1290[[nodiscard]]
inline detail::ExpectedRational
operator-(
const Left& lhs,
const Right& rhs)
noexcept {
1291 auto const lifted = detail::liftBoth(lhs, rhs);
1293 return std::unexpected(lifted.error());
1295 return lifted->first - lifted->second;
1302template <
typename Left,
typename Right>
1303 requires(detail::RationalLike<Left> || detail::RationalLike<Right>) &&
1304 (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) &&
1305 (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>)
1306[[nodiscard]]
inline detail::ExpectedRational
operator*(
const Left& lhs,
const Right& rhs)
noexcept {
1307 auto const lifted = detail::liftBoth(lhs, rhs);
1309 return std::unexpected(lifted.error());
1311 return lifted->first * lifted->second;
1318template <
typename Left,
typename Right>
1319 requires(detail::RationalLike<Left> || detail::RationalLike<Right>) &&
1320 (detail::NeedsLifting<Left> || detail::NeedsLifting<Right>) &&
1321 (detail::LiftableOperand<Left> && detail::LiftableOperand<Right>)
1322[[nodiscard]]
inline detail::ExpectedRational
operator/(
const Left& lhs,
const Right& rhs)
noexcept {
1323 auto const lifted = detail::liftBoth(lhs, rhs);
1325 return std::unexpected(lifted.error());
1327 return lifted->first / lifted->second;
1337[[nodiscard]]
constexpr std::int64_t powerOfTen(std::uint32_t decimalPlaces)
noexcept {
1338 if (decimalPlaces > 18) {
1341 auto result = std::int64_t{1};
1342 for (std::uint32_t digit = 0; digit < decimalPlaces; ++digit) {
1349template <std::
floating_po
int Float>
1350[[nodiscard]]
inline ExpectedRational fromFloatImpl(Float value, DecimalPlaces wantedPrecision)
noexcept {
1351 if (!std::isfinite(value)) {
1352 return std::unexpected(RationalError::NotFinite);
1355 auto const rawPrecision = clampDecimalPlaces(wantedPrecision.value);
1358 auto const scale = powerOfTen(rawPrecision);
1360 auto const scaled =
static_cast<long double>(value) *
static_cast<long double>(scale);
1369 constexpr auto twoPow63 = 0x1p63L;
1370 if (scaled >= twoPow63 - 0.5L || scaled < -twoPow63) {
1371 return std::unexpected(RationalError::Overflow);
1374 auto const scaledNumerator =
static_cast<std::int64_t
>(std::llround(scaled));
1375 return Rational{Numerator{scaledNumerator}, Denominator{scale}, DecimalPlaces{rawPrecision}};
1381 auto const wanted = detail::clampWireDecimalPlaces(places.value);
1384 auto const scale = detail::powerOfTen(wanted);
1390 if (scale % value.denominator == 0) {
1397 auto const whole =
trunc(scaled);
1401 bool roundAway = std::is_gt(tie);
1402 if (std::is_eq(tie)) {
1408 auto stepped = whole;
1410 auto const step = scaled.numerator < 0 ? std::int64_t{-1} : std::int64_t{1};
1413 if (!detail::addOverflows(whole, step)) {
1414 stepped = whole + step;
1422 return detail::fromFloatImpl(value, wantedPrecision);
1427 return detail::fromFloatImpl(value, wantedPrecision);
1432 return detail::fromFloatImpl(value, wantedPrecision);
1437 auto const scale = detail::powerOfTen(requestedDecimalPlaces);
1441 auto const scaledDouble =
static_cast<double>(scale);
1442 return std::round(raw * scaledDouble) / scaledDouble;
1455 static constexpr auto value = glz::custom<&morph::math::Rational::setWire, &morph::math::Rational::getWire>;
1456 static constexpr std::string_view name =
"Rational";
1459namespace glz::detail {
1465struct to_json_schema<morph::math::Rational> {
1466 template <auto Opts>
1467 static void op(
auto& outSchema,
auto& defs) {
1468 to_json_schema<morph::math::Rational::Wire>::template op<Opts>(outSchema, defs);
1487 static constexpr std::string_view
name() noexcept {
return "rational"; }
1500 bool delegateToDouble{
false};
1501 std::formatter<double> doubleFormatter;
1503 constexpr auto parse(std::format_parse_context& ctx) {
1504 if (ctx.begin() == ctx.end() || *ctx.begin() ==
'}') {
1507 delegateToDouble =
true;
1508 return doubleFormatter.parse(ctx);
1511 auto format(
const morph::math::Rational& value, std::format_context& ctx)
const {
1512 if (delegateToDouble) {
1513 return doubleFormatter.format(value.
toDouble(), ctx);
1516 return std::format_to(ctx.out(),
"{}", value.
numerator);
Handle< std::string > format(AnyHandle const &pattern, std::vector< AnyHandle > args)
format: a template with {0}, {1}, … placeholders.
WireClampScope() noexcept
Starts a fresh count, saving any enclosing scope's.
~WireClampScope()
Folds this scope's count back into the enclosing one.
std::size_t clamped() const noexcept
How many Rational values were clamped so far in this scope.
Opt-in, compiler-independent shape tag for a type with a custom Glaze codec.
constexpr std::expected< Rational, RationalError > checkedMul(const Rational &lhs, const Rational &rhs) noexcept
Multiplies two Rationals, reporting overflow instead of saturating.
constexpr std::expected< Rational, RationalError > checkedAdd(const Rational &lhs, const Rational &rhs) noexcept
Adds two Rationals, reporting overflow instead of saturating.
constexpr Rational operator*(const Rational &lhs, const Rational &rhs) noexcept
Multiplies two Rationals. Result precision is max of the two.
constexpr std::int64_t trunc(const Rational &value) noexcept
Truncates toward zero.
constexpr Rational 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 operati...
constexpr std::int64_t ceil(const Rational &value) noexcept
Rounds toward positive infinity.
constexpr std::expected< Rational, RationalError > checkedDiv(const Rational &lhs, const Rational &rhs) noexcept
Divides two Rationals, reporting overflow instead of saturating.
RoundingMode
Rule for resolving a value sitting exactly halfway between the two nearest results when rounding to a...
@ HalfEven
Ties go to the neighbour whose last kept digit is even: 1.25 -> 1.2, 1.35 -> 1.4.
@ HalfAwayFromZero
Ties go to the neighbour of larger magnitude: 1.25 -> 1.3, -1.25 -> -1.3.
constexpr Rational operator-(const Rational &lhs, const Rational &rhs) noexcept
Subtracts two Rationals. Result precision is max of the two.
constexpr std::uint32_t kMaxDecimalPlaces
Largest decimal precision the type supports: 10^18 is the greatest power of ten that fits in std::int...
constexpr std::expected< Rational, RationalError > operator/(const Rational &lhs, const Rational &rhs) noexcept
Divides two Rationals. Result precision is max of the two.
constexpr std::expected< Rational, RationalError > checkedSub(const Rational &lhs, const Rational &rhs) noexcept
Subtracts two Rationals, reporting overflow instead of saturating.
RationalError
Error states reachable through Rational operations.
@ DivisionByZero
Divisor numerator is zero (operator/, reciprocal, From).
@ NotFinite
Floating-point input was NaN or +/-Inf (fromFloat only).
constexpr std::int64_t floor(const Rational &value) noexcept
Rounds toward negative infinity.
constexpr Rational operator+(const Rational &lhs, const Rational &rhs) noexcept
Adds two Rationals. Result precision is max of the two.
constexpr Rational abs(const Rational &value) noexcept
Absolute value of a rational. Precision is preserved.
Strong type for a decimal-precision count.
constexpr auto operator<=>(const DecimalPlaces &other) const noexcept=default
Value ordering/equality on the raw digit count.
std::uint32_t value
Raw digit count. Rational's invariant keeps it in [0, kMaxDecimalPlaces].
Strong type for a Rational denominator.
std::int64_t value
Raw signed integer value; must never be zero after canonicalisation.
Strong type for a Rational numerator.
std::int64_t value
Raw signed integer value.
Flat JSON representation used by the Glaze codec below.
constexpr bool validate() const noexcept
Whether these raw values decode without being clamped.
std::int64_t den
Denominator as sent/received; may be non-canonical.
std::uint32_t dp
Decimal-precision tag as sent/received.
std::int64_t num
Signed numerator as sent/received.
Exact rational number with a runtime decimal-precision tag.
constexpr bool isZero() const noexcept
Whether the value equals 0/1.
constexpr std::strong_ordering operator<=>(const Rational &other) const noexcept
Three-way comparison. Value-only: ignores decimalPlaces.
constexpr void subAssignChecked(const Rational &rhs) noexcept
Applies -='s arithmetic, having already proven it cannot overflow.
double toDouble() const noexcept
Converts to double, rounded to this value's decimalPlaces.
constexpr bool isNegative() const noexcept
Whether the value is strictly less than zero.
static constexpr std::expected< Rational, RationalError > from(Numerator wantedNumerator, Denominator wantedDenominator, DecimalPlaces wantedPrecision) noexcept
Validating factory.
constexpr std::expected< Rational, RationalError > dividedBy(const Rational &rhs) const noexcept
Non-throwing division. Result precision becomes max of the two.
constexpr bool mulWouldOverflow(const Rational &rhs) const noexcept
Whether *this * rhs would overflow, after cross-cancelling.
constexpr bool isInteger() const noexcept
Whether the value is an integer.
constexpr std::expected< Rational, RationalError > reciprocal() const noexcept
Multiplicative inverse.
constexpr bool addWouldOverflow(const Rational &rhs) const noexcept
Whether *this + rhs would overflow any intermediate or the result.
Wire getWire() const noexcept
Wire-codec exit (Glaze write side).
DecimalPlaces decimalPlaces
Decimal-precision tag. Invariant: 0 <= value <= kMaxDecimalPlaces.
static constexpr Rational zero(DecimalPlaces wantedPrecision) noexcept
Canonical zero (0/1) at the given precision.
constexpr DecimalPlaces getDecimalPlaces() const noexcept
The value's current decimal precision.
constexpr bool operator==(const Rational &other) const noexcept
Equality. Value-only: ignores decimalPlaces.
constexpr void addAssignChecked(const Rational &rhs) noexcept
Applies +='s arithmetic, having already proven it cannot overflow.
constexpr Rational(Numerator wantedNumerator, Denominator wantedDenominator, DecimalPlaces wantedPrecision) noexcept
Constructs from explicit numerator/denominator, then canonicalises.
constexpr Rational & operator*=(const Rational &rhs) noexcept
In-place multiplication. Result precision becomes max of the two. Cross-cancels common factors before...
static std::expected< Rational, RationalError > fromFloat(double value, DecimalPlaces wantedPrecision) noexcept
Converts a double to a Rational scaled to wantedPrecision.
constexpr Rational & operator-=(const Rational &rhs) noexcept
In-place subtraction. Result precision becomes max of the two.
constexpr bool subWouldOverflow(const Rational &rhs) const noexcept
Whether *this - rhs would overflow any intermediate or the result.
void setWire(Wire wire) noexcept
Wire-codec entry (Glaze read side): rebuilds through the canonicalising constructor,...
constexpr Rational operator-() const noexcept
Negates.
std::int64_t numerator
Signed numerator. Carries the sign of the rational value.
std::int64_t denominator
Strictly positive denominator. Never zero, never negative.
constexpr void mulAssignChecked(const Rational &rhs) noexcept
Applies *='s arithmetic, having already proven it cannot overflow.
static constexpr Rational one(DecimalPlaces wantedPrecision) noexcept
Canonical one (1/1) at the given precision.
constexpr Rational() noexcept=default
Default-constructs the canonical zero (0/1) at precision 1.
constexpr Rational & operator+=(const Rational &rhs) noexcept
In-place addition. Result precision becomes max of the two. Uses reduce-before-multiply to extend the...
static constexpr std::string_view name() noexcept
This type's stable shape name.
Declares a stable shape tag for a type T whose JSON shape payloadShape cannot decompose.