From c08228a5995a81ac6cc3576f09993241068f026f Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 9 May 2024 12:44:54 -0400 Subject: [PATCH] Support `QuantityPoint` in rounding functions (#237) Includes the `round`, `floor`, and `ceil` families. There's more subtlety here than for `Quantity`, because we can have additive offsets. However, since these functions all aim to behave like the standard library, and the standard library always uses floating point, then it's actually quite simple. The way we handle rounding a quantity point in different units than it's stored is to first perform the conversion, then do the rounding. As with quantities, we use `RoundingRep` to make sure we're using a sane floating point type to perform the conversion. Includes a bunch of new test cases, and doc updates. Fixes #221. --- au/math.hh | 105 +++++++++++++++++++++++--- au/math_test.cc | 162 +++++++++++++++++++++++++++++++++++++++++ docs/reference/math.md | 89 ++++++++++++++++++++-- 3 files changed, 340 insertions(+), 16 deletions(-) diff --git a/au/math.hh b/au/math.hh index feac9cb5..e9f333d3 100644 --- a/au/math.hh +++ b/au/math.hh @@ -110,6 +110,9 @@ struct RoundingRep, RoundingUnits> { static_assert(std::is_same::value, ""); static_assert(std::is_same::value, ""); }; +template +struct RoundingRep, RoundingUnits> + : RoundingRep, RoundingUnits> {}; } // namespace detail // The absolute value of a Quantity. @@ -414,129 +417,211 @@ auto remainder(Quantity q1, Quantity q2) { } // -// Round the value of this Quantity to the nearest integer in the given units. +// Round the value of this Quantity or QuantityPoint to the nearest integer in the given units. // // This is the "Unit-only" format (i.e., `round_in(rounding_units, q)`). // +// a) Version for Quantity. template auto round_in(RoundingUnits rounding_units, Quantity q) { using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; return std::round(q.template in(rounding_units)); } +// b) Version for QuantityPoint. +template +auto round_in(RoundingUnits rounding_units, QuantityPoint p) { + using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; + return std::round(p.template in(rounding_units)); +} // -// Round the value of this Quantity to the nearest integer in the given units, returning OutputRep. +// Round the value of this Quantity or QuantityPoint to the nearest integer in the given units, +// returning OutputRep. // // This is the "Explicit-Rep" format (e.g., `round_in(rounding_units, q)`). // +// a) Version for Quantity. template auto round_in(RoundingUnits rounding_units, Quantity q) { return static_cast(round_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto round_in(RoundingUnits rounding_units, QuantityPoint p) { + return static_cast(round_in(rounding_units, p)); +} // -// The integral-valued Quantity, in this unit, nearest to the input. +// The integral-valued Quantity or QuantityPoint, in this unit, nearest to the input. // // This is the "Unit-only" format (i.e., `round_as(rounding_units, q)`). // +// a) Version for Quantity. template auto round_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(round_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto round_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>( + round_in(rounding_units, p)); +} // -// The integral-valued Quantity, in this unit, nearest to the input, using the specified OutputRep. +// The integral-valued Quantity or QuantityPoint, in this unit, nearest to the input, using the +// specified OutputRep. // // This is the "Explicit-Rep" format (e.g., `round_as(rounding_units, q)`). // +// a) Version for Quantity. template auto round_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(round_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto round_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>( + round_in(rounding_units, p)); +} // // Return the largest integral value in `rounding_units` which is not greater than `q`. // // This is the "Unit-only" format (i.e., `floor_in(rounding_units, q)`). // +// a) Version for Quantity. template auto floor_in(RoundingUnits rounding_units, Quantity q) { using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; return std::floor(q.template in(rounding_units)); } +// b) Version for QuantityPoint. +template +auto floor_in(RoundingUnits rounding_units, QuantityPoint p) { + using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; + return std::floor(p.template in(rounding_units)); +} // // Return `OutputRep` with largest integral value in `rounding_units` which is not greater than `q`. // // This is the "Explicit-Rep" format (e.g., `floor_in(rounding_units, q)`). // +// a) Version for Quantity. template auto floor_in(RoundingUnits rounding_units, Quantity q) { return static_cast(floor_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto floor_in(RoundingUnits rounding_units, QuantityPoint p) { + return static_cast(floor_in(rounding_units, p)); +} // -// The largest integral-valued Quantity, in this unit, not greater than the input. +// The largest integral-valued Quantity or QuantityPoint, in this unit, not greater than the input. // // This is the "Unit-only" format (i.e., `floor_as(rounding_units, q)`). // +// a) Version for Quantity. template auto floor_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(floor_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto floor_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>( + floor_in(rounding_units, p)); +} // -// The largest integral-valued Quantity, in this unit, not greater than the input, using the -// specified `OutputRep`. +// The largest integral-valued Quantity or QuantityPoint, in this unit, not greater than the input, +// using the specified `OutputRep`. // // This is the "Explicit-Rep" format (e.g., `floor_as(rounding_units, q)`). // +// a) Version for Quantity. template auto floor_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(floor_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto floor_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>( + floor_in(rounding_units, p)); +} // // Return the smallest integral value in `rounding_units` which is not less than `q`. // // This is the "Unit-only" format (i.e., `ceil_in(rounding_units, q)`). // +// a) Version for Quantity. template auto ceil_in(RoundingUnits rounding_units, Quantity q) { using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; return std::ceil(q.template in(rounding_units)); } +// b) Version for QuantityPoint. +template +auto ceil_in(RoundingUnits rounding_units, QuantityPoint p) { + using OurRoundingRep = detail::RoundingRepT, RoundingUnits>; + return std::ceil(p.template in(rounding_units)); +} // // Return the smallest integral value in `rounding_units` which is not less than `q`. // // This is the "Explicit-Rep" format (e.g., `ceil_in(rounding_units, q)`). // +// a) Version for Quantity. template auto ceil_in(RoundingUnits rounding_units, Quantity q) { return static_cast(ceil_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto ceil_in(RoundingUnits rounding_units, QuantityPoint p) { + return static_cast(ceil_in(rounding_units, p)); +} // -// The smallest integral-valued Quantity, in this unit, not less than the input. +// The smallest integral-valued Quantity or QuantityPoint, in this unit, not less than the input. // // This is the "Unit-only" format (i.e., `ceil_as(rounding_units, q)`). // +// a) Version for Quantity. template auto ceil_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(ceil_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto ceil_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>(ceil_in(rounding_units, p)); +} // -// The smallest integral-valued Quantity, in this unit, not less than the input, using the specified -// `OutputRep`. +// The smallest integral-valued Quantity or QuantityPoint, in this unit, not less than the input, +// using the specified `OutputRep`. // // This is the "Explicit-Rep" format (e.g., `ceil_as(rounding_units, q)`). // +// a) Version for Quantity. template auto ceil_as(RoundingUnits rounding_units, Quantity q) { return make_quantity>(ceil_in(rounding_units, q)); } +// b) Version for QuantityPoint. +template +auto ceil_as(RoundingUnits rounding_units, QuantityPoint p) { + return make_quantity_point>( + ceil_in(rounding_units, p)); +} // Wrapper for std::sin() which accepts a strongly typed angle quantity. template diff --git a/au/math_test.cc b/au/math_test.cc index 7a8dd2da..d56e75ce 100644 --- a/au/math_test.cc +++ b/au/math_test.cc @@ -762,8 +762,19 @@ TEST(RoundAs, SameAsStdRoundForSameUnits) { EXPECT_THAT(round_as(meters, meters(3.14f)), SameTypeAndValue(meters(std::round(3.14f)))); EXPECT_THAT(round_as(meters, meters(3.14L)), SameTypeAndValue(meters(std::round(3.14L)))); + EXPECT_THAT(round_as(meters_pt, meters_pt(3)), SameTypeAndValue(meters_pt(std::round(3)))); + EXPECT_THAT(round_as(meters_pt, meters_pt(3.14)), + SameTypeAndValue(meters_pt(std::round(3.14)))); + EXPECT_THAT(round_as(meters_pt, meters_pt(3.14f)), + SameTypeAndValue(meters_pt(std::round(3.14f)))); + EXPECT_THAT(round_as(meters_pt, meters_pt(3.14L)), + SameTypeAndValue(meters_pt(std::round(3.14L)))); + EXPECT_THAT(round_as(meters, meters(INTEGER_TOO_BIG_FOR_DOUBLE)), SameTypeAndValue(meters(std::round(INTEGER_TOO_BIG_FOR_DOUBLE)))); + + EXPECT_THAT(round_as(meters_pt, meters_pt(INTEGER_TOO_BIG_FOR_DOUBLE)), + SameTypeAndValue(meters_pt(std::round(INTEGER_TOO_BIG_FOR_DOUBLE)))); } TEST(RoundAs, RoundsAsExpectedForDifferentUnits) { @@ -771,6 +782,14 @@ TEST(RoundAs, RoundsAsExpectedForDifferentUnits) { EXPECT_THAT(round_as(kilo(meters), meters(999.9)), SameTypeAndValue(kilo(meters)(1.0))); EXPECT_THAT(round_as(kilo(meters), meters(999.9f)), SameTypeAndValue(kilo(meters)(1.0f))); EXPECT_THAT(round_as(kilo(meters), meters(999.9L)), SameTypeAndValue(kilo(meters)(1.0L))); + + EXPECT_THAT(round_as(kilo(meters_pt), meters_pt(999)), SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(round_as(kilo(meters_pt), meters_pt(999.9)), + SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(round_as(kilo(meters_pt), meters_pt(999.9f)), + SameTypeAndValue(kilo(meters_pt)(1.0f))); + EXPECT_THAT(round_as(kilo(meters_pt), meters_pt(999.9L)), + SameTypeAndValue(kilo(meters_pt)(1.0L))); } TEST(RoundAs, SupportsDifferentOutputTypes) { @@ -781,6 +800,30 @@ TEST(RoundAs, SupportsDifferentOutputTypes) { EXPECT_THAT(round_as(kilo(meters), meters(999.9f)), SameTypeAndValue(kilo(meters)(1.0))); + + EXPECT_THAT(round_as(meters_pt, meters_pt(3)), + SameTypeAndValue(meters_pt(static_cast(std::round(3))))); + EXPECT_THAT(round_as(meters_pt, meters_pt(3.9)), + SameTypeAndValue(meters_pt(static_cast(std::round(3.9))))); + + EXPECT_THAT(round_as(kilo(meters_pt), meters_pt(999.9f)), + SameTypeAndValue(kilo(meters_pt)(1.0))); +} + +TEST(RoundAs, SupportsQuantityPointWithNontrivialOffset) { + EXPECT_THAT(round_as(kelvins_pt, celsius_pt(20.0f)), SameTypeAndValue(kelvins_pt(293.0f))); + EXPECT_THAT(round_as(kelvins_pt, celsius_pt(20.5f)), SameTypeAndValue(kelvins_pt(294.0f))); + + // Each degree Fahrenheit is 5/9 of a degree Celsius. Thus, moving away from an exact + // correspondence by one degree Fahrenheit will be enough to move to the next integer Celsius + // when we round, but moving by half a degree will not. + EXPECT_THAT(round_as(celsius_pt, fahrenheit_pt(31.0)), SameTypeAndValue(celsius_pt(-1))); + EXPECT_THAT(round_as(celsius_pt, fahrenheit_pt(31.5)), SameTypeAndValue(celsius_pt(0))); + + EXPECT_THAT(round_as(celsius_pt, fahrenheit_pt(32.0)), SameTypeAndValue(celsius_pt(0))); + + EXPECT_THAT(round_as(celsius_pt, fahrenheit_pt(32.5)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(round_as(celsius_pt, fahrenheit_pt(33.0)), SameTypeAndValue(celsius_pt(1))); } TEST(RoundIn, SameAsRoundAs) { @@ -788,10 +831,16 @@ TEST(RoundIn, SameAsRoundAs) { EXPECT_THAT(round_in(kilo(meters), meters(754.28)), SameTypeAndValue(1.0)); EXPECT_THAT(round_in(kilo(meters), meters(754.28f)), SameTypeAndValue(1.0f)); EXPECT_THAT(round_in(kilo(meters), meters(754.28L)), SameTypeAndValue(1.0L)); + + EXPECT_THAT(round_in(kilo(meters_pt), meters_pt(754)), SameTypeAndValue(1.0)); + EXPECT_THAT(round_in(kilo(meters_pt), meters_pt(754.28)), SameTypeAndValue(1.0)); + EXPECT_THAT(round_in(kilo(meters_pt), meters_pt(754.28f)), SameTypeAndValue(1.0f)); + EXPECT_THAT(round_in(kilo(meters_pt), meters_pt(754.28L)), SameTypeAndValue(1.0L)); } TEST(RoundIn, SupportsDifferentOutputTypes) { EXPECT_THAT(round_in(kilo(meters), meters(754.28f)), SameTypeAndValue(1.0L)); + EXPECT_THAT(round_in(kilo(meters_pt), meters_pt(754.28f)), SameTypeAndValue(1.0L)); } TEST(FloorAs, SameAsStdFloorForSameUnits) { @@ -800,8 +849,19 @@ TEST(FloorAs, SameAsStdFloorForSameUnits) { EXPECT_THAT(floor_as(meters, meters(3.14f)), SameTypeAndValue(meters(std::floor(3.14f)))); EXPECT_THAT(floor_as(meters, meters(3.14L)), SameTypeAndValue(meters(std::floor(3.14L)))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3)), SameTypeAndValue(meters_pt(std::floor(3)))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3.14)), + SameTypeAndValue(meters_pt(std::floor(3.14)))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3.14f)), + SameTypeAndValue(meters_pt(std::floor(3.14f)))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3.14L)), + SameTypeAndValue(meters_pt(std::floor(3.14L)))); + EXPECT_THAT(floor_as(meters, meters(INTEGER_TOO_BIG_FOR_DOUBLE)), SameTypeAndValue(meters(std::floor(INTEGER_TOO_BIG_FOR_DOUBLE)))); + + EXPECT_THAT(floor_as(meters_pt, meters_pt(INTEGER_TOO_BIG_FOR_DOUBLE)), + SameTypeAndValue(meters_pt(std::floor(INTEGER_TOO_BIG_FOR_DOUBLE)))); } TEST(FloorAs, RoundsDownAsExpectedForDifferentUnits) { @@ -810,10 +870,26 @@ TEST(FloorAs, RoundsDownAsExpectedForDifferentUnits) { EXPECT_THAT(floor_as(kilo(meters), meters(999.9f)), SameTypeAndValue(kilo(meters)(0.0f))); EXPECT_THAT(floor_as(kilo(meters), meters(999.9L)), SameTypeAndValue(kilo(meters)(0.0L))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(999)), SameTypeAndValue(kilo(meters_pt)(0.0))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(999.9)), + SameTypeAndValue(kilo(meters_pt)(0.0))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(999.9f)), + SameTypeAndValue(kilo(meters_pt)(0.0f))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(999.9L)), + SameTypeAndValue(kilo(meters_pt)(0.0L))); + EXPECT_THAT(floor_as(kilo(meters), meters(1001)), SameTypeAndValue(kilo(meters)(1.0))); EXPECT_THAT(floor_as(kilo(meters), meters(1000.1)), SameTypeAndValue(kilo(meters)(1.0))); EXPECT_THAT(floor_as(kilo(meters), meters(1000.1f)), SameTypeAndValue(kilo(meters)(1.0f))); EXPECT_THAT(floor_as(kilo(meters), meters(1000.1L)), SameTypeAndValue(kilo(meters)(1.0L))); + + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(1001)), SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(1000.1)), + SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(1000.1f)), + SameTypeAndValue(kilo(meters_pt)(1.0f))); + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(1000.1L)), + SameTypeAndValue(kilo(meters_pt)(1.0L))); } TEST(FloorAs, SupportsDifferentOutputTypes) { @@ -822,8 +898,32 @@ TEST(FloorAs, SupportsDifferentOutputTypes) { EXPECT_THAT(floor_as(meters, meters(3.9)), SameTypeAndValue(meters(static_cast(std::floor(3.9))))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3)), + SameTypeAndValue(meters_pt(static_cast(std::floor(3))))); + EXPECT_THAT(floor_as(meters_pt, meters_pt(3.9)), + SameTypeAndValue(meters_pt(static_cast(std::floor(3.9))))); + EXPECT_THAT(floor_as(kilo(meters), meters(1000.1f)), SameTypeAndValue(kilo(meters)(1.0))); + + EXPECT_THAT(floor_as(kilo(meters_pt), meters_pt(1000.1f)), + SameTypeAndValue(kilo(meters_pt)(1.0))); +} + +TEST(FloorAs, SupportsQuantityPointWithNontrivialOffset) { + EXPECT_THAT(floor_as(kelvins_pt, celsius_pt(20.0f)), SameTypeAndValue(kelvins_pt(293.0f))); + EXPECT_THAT(floor_as(kelvins_pt, celsius_pt(20.8f)), SameTypeAndValue(kelvins_pt(293.0f))); + EXPECT_THAT(floor_as(kelvins_pt, celsius_pt(20.9f)), SameTypeAndValue(kelvins_pt(294.0f))); + + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(31.0)), SameTypeAndValue(celsius_pt(-1))); + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(31.5)), SameTypeAndValue(celsius_pt(-1))); + + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(32.0)), SameTypeAndValue(celsius_pt(0))); + + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(32.5)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(33.0)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(33.5)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(floor_as(celsius_pt, fahrenheit_pt(34.0)), SameTypeAndValue(celsius_pt(1))); } TEST(FloorIn, SameAsFloorAs) { @@ -831,10 +931,17 @@ TEST(FloorIn, SameAsFloorAs) { EXPECT_THAT(floor_in(kilo(meters), meters(1154.28)), SameTypeAndValue(1.0)); EXPECT_THAT(floor_in(kilo(meters), meters(1154.28f)), SameTypeAndValue(1.0f)); EXPECT_THAT(floor_in(kilo(meters), meters(1154.28L)), SameTypeAndValue(1.0L)); + + EXPECT_THAT(floor_in(kilo(meters_pt), meters_pt(1154)), SameTypeAndValue(1.0)); + EXPECT_THAT(floor_in(kilo(meters_pt), meters_pt(1154.28)), SameTypeAndValue(1.0)); + EXPECT_THAT(floor_in(kilo(meters_pt), meters_pt(1154.28f)), SameTypeAndValue(1.0f)); + EXPECT_THAT(floor_in(kilo(meters_pt), meters_pt(1154.28L)), SameTypeAndValue(1.0L)); } TEST(FloorIn, SupportsDifferentOutputTypes) { EXPECT_THAT(floor_in(kilo(meters), meters(1154.28f)), SameTypeAndValue(1.0L)); + EXPECT_THAT(floor_in(kilo(meters_pt), meters_pt(1154.28f)), + SameTypeAndValue(1.0L)); } TEST(CeilAs, SameAsStdCeilForSameUnits) { @@ -843,8 +950,18 @@ TEST(CeilAs, SameAsStdCeilForSameUnits) { EXPECT_THAT(ceil_as(meters, meters(3.14f)), SameTypeAndValue(meters(std::ceil(3.14f)))); EXPECT_THAT(ceil_as(meters, meters(3.14L)), SameTypeAndValue(meters(std::ceil(3.14L)))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3)), SameTypeAndValue(meters_pt(std::ceil(3)))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3.14)), SameTypeAndValue(meters_pt(std::ceil(3.14)))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3.14f)), + SameTypeAndValue(meters_pt(std::ceil(3.14f)))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3.14L)), + SameTypeAndValue(meters_pt(std::ceil(3.14L)))); + EXPECT_THAT(ceil_as(meters, meters(INTEGER_TOO_BIG_FOR_DOUBLE)), SameTypeAndValue(meters(std::ceil(INTEGER_TOO_BIG_FOR_DOUBLE)))); + + EXPECT_THAT(ceil_as(meters_pt, meters_pt(INTEGER_TOO_BIG_FOR_DOUBLE)), + SameTypeAndValue(meters_pt(std::ceil(INTEGER_TOO_BIG_FOR_DOUBLE)))); } TEST(CeilAs, RoundsUpAsExpectedForDifferentUnits) { @@ -853,10 +970,25 @@ TEST(CeilAs, RoundsUpAsExpectedForDifferentUnits) { EXPECT_THAT(ceil_as(kilo(meters), meters(999.9f)), SameTypeAndValue(kilo(meters)(1.0f))); EXPECT_THAT(ceil_as(kilo(meters), meters(999.9L)), SameTypeAndValue(kilo(meters)(1.0L))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(999)), SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(999.9)), SameTypeAndValue(kilo(meters_pt)(1.0))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(999.9f)), + SameTypeAndValue(kilo(meters_pt)(1.0f))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(999.9L)), + SameTypeAndValue(kilo(meters_pt)(1.0L))); + EXPECT_THAT(ceil_as(kilo(meters), meters(1001)), SameTypeAndValue(kilo(meters)(2.0))); EXPECT_THAT(ceil_as(kilo(meters), meters(1000.1)), SameTypeAndValue(kilo(meters)(2.0))); EXPECT_THAT(ceil_as(kilo(meters), meters(1000.1f)), SameTypeAndValue(kilo(meters)(2.0f))); EXPECT_THAT(ceil_as(kilo(meters), meters(1000.1L)), SameTypeAndValue(kilo(meters)(2.0L))); + + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(1001)), SameTypeAndValue(kilo(meters_pt)(2.0))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(1000.1)), + SameTypeAndValue(kilo(meters_pt)(2.0))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(1000.1f)), + SameTypeAndValue(kilo(meters_pt)(2.0f))); + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(1000.1L)), + SameTypeAndValue(kilo(meters_pt)(2.0L))); } TEST(CeilAs, SupportsDifferentOutputTypes) { @@ -865,8 +997,32 @@ TEST(CeilAs, SupportsDifferentOutputTypes) { EXPECT_THAT(ceil_as(meters, meters(3.9)), SameTypeAndValue(meters(static_cast(std::ceil(3.9))))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3)), + SameTypeAndValue(meters_pt(static_cast(std::ceil(3))))); + EXPECT_THAT(ceil_as(meters_pt, meters_pt(3.9)), + SameTypeAndValue(meters_pt(static_cast(std::ceil(3.9))))); + EXPECT_THAT(ceil_as(kilo(meters), meters(1000.1f)), SameTypeAndValue(kilo(meters)(2.0))); + + EXPECT_THAT(ceil_as(kilo(meters_pt), meters_pt(1000.1f)), + SameTypeAndValue(kilo(meters_pt)(2.0))); +} + +TEST(CeilAs, SupportsQuantityPointWithNontrivialOffset) { + EXPECT_THAT(ceil_as(kelvins_pt, celsius_pt(20.0f)), SameTypeAndValue(kelvins_pt(294.0f))); + EXPECT_THAT(ceil_as(kelvins_pt, celsius_pt(20.8f)), SameTypeAndValue(kelvins_pt(294.0f))); + EXPECT_THAT(ceil_as(kelvins_pt, celsius_pt(20.9f)), SameTypeAndValue(kelvins_pt(295.0f))); + + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(30.0)), SameTypeAndValue(celsius_pt(-1))); + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(30.5)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(31.0)), SameTypeAndValue(celsius_pt(0))); + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(31.5)), SameTypeAndValue(celsius_pt(0))); + + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(32.0)), SameTypeAndValue(celsius_pt(0))); + + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(32.5)), SameTypeAndValue(celsius_pt(1))); + EXPECT_THAT(ceil_as(celsius_pt, fahrenheit_pt(33.0)), SameTypeAndValue(celsius_pt(1))); } TEST(CeilIn, SameAsCeilAs) { @@ -874,10 +1030,16 @@ TEST(CeilIn, SameAsCeilAs) { EXPECT_THAT(ceil_in(kilo(meters), meters(354.28)), SameTypeAndValue(1.0)); EXPECT_THAT(ceil_in(kilo(meters), meters(354.28f)), SameTypeAndValue(1.0f)); EXPECT_THAT(ceil_in(kilo(meters), meters(354.28L)), SameTypeAndValue(1.0L)); + + EXPECT_THAT(ceil_in(kilo(meters_pt), meters_pt(354)), SameTypeAndValue(1.0)); + EXPECT_THAT(ceil_in(kilo(meters_pt), meters_pt(354.28)), SameTypeAndValue(1.0)); + EXPECT_THAT(ceil_in(kilo(meters_pt), meters_pt(354.28f)), SameTypeAndValue(1.0f)); + EXPECT_THAT(ceil_in(kilo(meters_pt), meters_pt(354.28L)), SameTypeAndValue(1.0L)); } TEST(CeilIn, SupportsDifferentOutputTypes) { EXPECT_THAT(ceil_in(kilo(meters), meters(354.28f)), SameTypeAndValue(1.0L)); + EXPECT_THAT(ceil_in(kilo(meters_pt), meters_pt(354.28f)), SameTypeAndValue(1.0L)); } TEST(InverseAs, HandlesIntegerRepCorrectly) { diff --git a/docs/reference/math.md b/docs/reference/math.md index b8f0b187..359d54df 100644 --- a/docs/reference/math.md +++ b/docs/reference/math.md @@ -400,19 +400,31 @@ As with everything else in the library, `"as"` is a word that means "return a `Q ```cpp // -// round_as(): return a Quantity +// round_as(): return a Quantity or QuantityPoint (depending on the input type) // // 1. Unit-only version (including safety checks). Typical callsites look like: // `round_as(units, quantity)` + +// a) For `Quantity` inputs template auto round_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto round_as(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `round_as(units, quantity)` + +// a) For `Quantity` inputs template auto round_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto round_as(RoundingUnits rounding_units, QuantityPoint q); + // // round_in(): return a raw number @@ -420,17 +432,30 @@ auto round_as(RoundingUnits rounding_units, Quantity q); // 1. Unit-only version (including safety checks). Typical callsites look like: // `round_in(units, quantity)` + +// a) For `Quantity` inputs template auto round_in(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto round_in(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `round_in(units, quantity)` + +// a) For `Quantity` inputs template auto round_in(RoundingUnits rounding_units, Quantity q); + +// b) For `QuantityPoint` inputs +template +auto round_in(RoundingUnits rounding_units, QuantityPoint q); ``` -**Returns:** A `Quantity`, expressed in the requested units, which has an integer value in those -units. We return the nearest such quantity to the original input quantity. +**Returns:** A `Quantity` or `QuantityPoint` (depending on the input type), expressed in the +requested units, which has an integer value in those units. We return the nearest such quantity to +the original input quantity. The policy for the rep is consistent with [`std::round`](https://en.cppreference.com/w/cpp/numeric/math/round). The output rep is the same as @@ -454,19 +479,31 @@ As with everything else in the library, `"as"` is a word that means "return a `Q ```cpp // -// ceil_as(): return a Quantity +// ceil_as(): return a Quantity or QuantityPoint (depending on the input type) // // 1. Unit-only version (including safety checks). Typical callsites look like: // `ceil_as(units, quantity)` + +// a) For `Quantity` inputs template auto ceil_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto ceil_as(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `ceil_as(units, quantity)` + +// a) For `Quantity` inputs template auto ceil_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto ceil_as(RoundingUnits rounding_units, QuantityPoint q); + // // ceil_in(): return a raw number @@ -474,13 +511,25 @@ auto ceil_as(RoundingUnits rounding_units, Quantity q); // 1. Unit-only version (including safety checks). Typical callsites look like: // `ceil_in(units, quantity)` + +// a) For `Quantity` inputs template auto ceil_in(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto ceil_in(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `ceil_in(units, quantity)` + +// a) For `Quantity` inputs template auto ceil_in(RoundingUnits rounding_units, Quantity q); + +// b) For `QuantityPoint` inputs +template +auto ceil_in(RoundingUnits rounding_units, QuantityPoint q); ``` **Returns:** A `Quantity`, expressed in the requested units, which has an integer value in those @@ -508,19 +557,31 @@ As with everything else in the library, `"as"` is a word that means "return a `Q ```cpp // -// floor_as(): return a Quantity +// floor_as(): return a Quantity or QuantityPoint (depending on the input type) // // 1. Unit-only version (including safety checks). Typical callsites look like: // `floor_as(units, quantity)` + +// a) For `Quantity` inputs template auto floor_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto floor_as(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `floor_as(units, quantity)` + +// a) For `Quantity` inputs template auto floor_as(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto floor_as(RoundingUnits rounding_units, QuantityPoint q); + // // floor_in(): return a raw number @@ -528,13 +589,25 @@ auto floor_as(RoundingUnits rounding_units, Quantity q); // 1. Unit-only version (including safety checks). Typical callsites look like: // `floor_in(units, quantity)` + +// a) For `Quantity` inputs template auto floor_in(RoundingUnits rounding_units, Quantity q); +// b) For `QuantityPoint` inputs +template +auto floor_in(RoundingUnits rounding_units, QuantityPoint q); + // 2. Explicit-rep version (overriding; ignores safety checks). Typical callsites look like: // `floor_in(units, quantity)` + +// a) For `Quantity` inputs template auto floor_in(RoundingUnits rounding_units, Quantity q); + +// b) For `QuantityPoint` inputs +template +auto floor_in(RoundingUnits rounding_units, QuantityPoint q); ``` **Returns:** A `Quantity`, expressed in the requested units, which has an integer value in those @@ -640,8 +713,13 @@ Indicates whether the underlying value of a `Quantity` is a NaN ("not-a-number") **Signature:** ```cpp +// 1. `Quantity` inputs template constexpr bool isnan(Quantity q); + +// 2. `QuantityPoint` inputs +template +constexpr bool isnan(QuantityPoint q); ``` **Returns:** `true` if `q` is NaN; `false` otherwise. @@ -715,4 +793,3 @@ auto remainder(Quantity q1, Quantity q2); **Returns:** The remainder of `q1 / q2`, in the type `Quantity`, where `U` is the common unit of `U1` and `U2`, and `R` is the common type of `R1` and `R2`. -