Skip to content

Commit

Permalink
Support QuantityPoint in rounding functions (#237)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chiphogg committed May 9, 2024
1 parent 5339f61 commit c08228a
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 16 deletions.
105 changes: 95 additions & 10 deletions au/math.hh
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ struct RoundingRep<Quantity<U, R>, RoundingUnits> {
static_assert(std::is_same<decltype(std::floor(type{})), decltype(std::floor(R{}))>::value, "");
static_assert(std::is_same<decltype(std::ceil(type{})), decltype(std::ceil(R{}))>::value, "");
};
template <typename U, typename R, typename RoundingUnits>
struct RoundingRep<QuantityPoint<U, R>, RoundingUnits>
: RoundingRep<Quantity<U, R>, RoundingUnits> {};
} // namespace detail

// The absolute value of a Quantity.
Expand Down Expand Up @@ -414,129 +417,211 @@ auto remainder(Quantity<U1, R1> q1, Quantity<U2, R2> 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 <typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, Quantity<U, R> q) {
using OurRoundingRep = detail::RoundingRepT<Quantity<U, R>, RoundingUnits>;
return std::round(q.template in<OurRoundingRep>(rounding_units));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
using OurRoundingRep = detail::RoundingRepT<QuantityPoint<U, R>, RoundingUnits>;
return std::round(p.template in<OurRoundingRep>(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<int>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, Quantity<U, R> q) {
return static_cast<OutputRep>(round_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return static_cast<OutputRep>(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 <typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(round_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(
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<float>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(round_in<OutputRep>(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(
round_in<OutputRep>(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 <typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, Quantity<U, R> q) {
using OurRoundingRep = detail::RoundingRepT<Quantity<U, R>, RoundingUnits>;
return std::floor(q.template in<OurRoundingRep>(rounding_units));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
using OurRoundingRep = detail::RoundingRepT<QuantityPoint<U, R>, RoundingUnits>;
return std::floor(p.template in<OurRoundingRep>(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<int>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, Quantity<U, R> q) {
return static_cast<OutputRep>(floor_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return static_cast<OutputRep>(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 <typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(floor_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(
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<float>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(floor_in<OutputRep>(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(
floor_in<OutputRep>(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 <typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, Quantity<U, R> q) {
using OurRoundingRep = detail::RoundingRepT<Quantity<U, R>, RoundingUnits>;
return std::ceil(q.template in<OurRoundingRep>(rounding_units));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
using OurRoundingRep = detail::RoundingRepT<QuantityPoint<U, R>, RoundingUnits>;
return std::ceil(p.template in<OurRoundingRep>(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<int>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, Quantity<U, R> q) {
return static_cast<OutputRep>(ceil_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return static_cast<OutputRep>(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 <typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(ceil_in(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(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<float>(rounding_units, q)`).
//
// a) Version for Quantity.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, Quantity<U, R> q) {
return make_quantity<AssociatedUnitT<RoundingUnits>>(ceil_in<OutputRep>(rounding_units, q));
}
// b) Version for QuantityPoint.
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, QuantityPoint<U, R> p) {
return make_quantity_point<AssociatedUnitForPointsT<RoundingUnits>>(
ceil_in<OutputRep>(rounding_units, p));
}

// Wrapper for std::sin() which accepts a strongly typed angle quantity.
template <typename U, typename R>
Expand Down

0 comments on commit c08228a

Please sign in to comment.