refactor: less, ceil, and round refactored and improved + more unit tests for round

Resolves #672
This commit is contained in:
Mateusz Pusz
2025-02-05 12:10:36 +01:00
parent 0b14d69539
commit 8d2dddb8b6
2 changed files with 62 additions and 75 deletions

View File

@ -333,32 +333,17 @@ template<Representation Rep, Reference R>
*/
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<detail::clone_reference_with<To>(R), Rep> floor(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires(Rep v) { floor(v); } || requires(Rep v) { std::floor(v); }) &&
(equivalent(To, get_unit(R)) || requires {
q.force_in(To);
representation_values<Rep>::one();
})
requires requires { q.force_in(To); } &&
(treat_as_floating_point<Rep> && (requires(Rep v) { floor(v); } || requires(Rep v) { std::floor(v); })) ||
(!treat_as_floating_point<Rep> && requires { representation_values<Rep>::one(); })
{
const auto handle_signed_results = [&]<typename T>(const T& res) {
if (res > q) {
return res - representation_values<Rep>::one() * T::reference;
}
return res;
};
const quantity res = q.force_in(To);
if constexpr (treat_as_floating_point<Rep>) {
using std::floor;
if constexpr (equivalent(To, get_unit(R))) {
return {static_cast<Rep>(floor(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)};
} else {
return handle_signed_results(
quantity{static_cast<Rep>(floor(q.force_numerical_value_in(To))), detail::clone_reference_with<To>(R)});
}
return {static_cast<Rep>(floor(res.numerical_value_ref_in(res.unit))), res.reference};
} else {
if constexpr (equivalent(To, get_unit(R))) {
return q.force_in(To);
} else {
return handle_signed_results(q.force_in(To));
}
if (res > q) return res - representation_values<Rep>::one() * res.reference;
return res;
}
}
@ -370,74 +355,48 @@ template<Unit auto To, auto R, typename Rep>
*/
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<detail::clone_reference_with<To>(R), Rep> ceil(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires(Rep v) { ceil(v); } || requires(Rep v) { std::ceil(v); }) &&
(equivalent(To, get_unit(R)) || requires {
q.force_in(To);
representation_values<Rep>::one();
})
requires requires { q.force_in(To); } &&
(treat_as_floating_point<Rep> && (requires(Rep v) { ceil(v); } || requires(Rep v) { std::ceil(v); })) ||
(!treat_as_floating_point<Rep> && requires { representation_values<Rep>::one(); })
{
const auto handle_signed_results = [&]<typename T>(const T& res) {
if (res < q) {
return res + representation_values<Rep>::one() * T::reference;
}
return res;
};
const quantity res = q.force_in(To);
if constexpr (treat_as_floating_point<Rep>) {
using std::ceil;
if constexpr (equivalent(To, get_unit(R))) {
return {static_cast<Rep>(ceil(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)};
} else {
return handle_signed_results(
quantity{static_cast<Rep>(ceil(q.force_numerical_value_in(To))), detail::clone_reference_with<To>(R)});
}
return {static_cast<Rep>(ceil(res.numerical_value_ref_in(res.unit))), res.reference};
} else {
if constexpr (equivalent(To, get_unit(R))) {
return q.force_in(To);
} else {
return handle_signed_results(q.force_in(To));
}
if (res < q) return res + representation_values<Rep>::one() * res.reference;
return res;
}
}
/**
* @brief Computes the nearest quantity with integer representation and unit type To to q
* @brief Computes the nearest quantity with integer representation and unit type `To` to `q`
*
* Rounding halfway cases away from zero, regardless of the current rounding mode.
* Returns the value `res` representable in `To` unit that is the closest to `q`. If there are two
* such values, returns the even value (that is, the value `res` such that `res % 2 == 0`).
*
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type To
* @return Quantity The quantity rounded to the nearest unit `To`, rounding to even in halfway
* cases.
*/
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<detail::clone_reference_with<To>(R), Rep> round(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires(Rep v) { round(v); } || requires(Rep v) { std::round(v); }) &&
(equivalent(To, get_unit(R)) || requires {
::mp_units::floor<To>(q);
representation_values<Rep>::one();
})
requires requires {
mp_units::floor<To>(q);
representation_values<Rep>::one();
} && std::constructible_from<std::int64_t, Rep>
{
if constexpr (equivalent(To, get_unit(R))) {
if constexpr (treat_as_floating_point<Rep>) {
using std::round;
return {static_cast<Rep>(round(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)};
} else {
return q.force_in(To);
}
} else {
const auto res_low = mp_units::floor<To>(q);
const auto res_high = res_low + representation_values<Rep>::one() * res_low.reference;
const auto diff0 = q - res_low;
const auto diff1 = res_high - q;
if (diff0 == diff1) {
if (static_cast<int>(res_low.numerical_value_ref_in(To)) & 1) {
return res_high;
}
return res_low;
}
if (diff0 < diff1) {
return res_low;
}
return res_high;
}
const auto res_low = mp_units::floor<To>(q);
const auto res_high = res_low + representation_values<Rep>::one() * res_low.reference;
const auto diff0 = q - res_low;
const auto diff1 = res_high - q;
if (diff0 == diff1) {
// TODO How to extend this to custom representation types?
if (static_cast<std::int64_t>(res_low.numerical_value_ref_in(To)) & 1) return res_high;
return res_low;
} else if (diff0 < diff1)
return res_low;
return res_high;
}
/**

View File

@ -189,11 +189,15 @@ static_assert(compare(round<si::second>(1001 * ms), 1 * s));
static_assert(compare(round<si::second>(1499 * ms), 1 * s));
static_assert(compare(round<si::second>(1500 * ms), 2 * s));
static_assert(compare(round<si::second>(1999 * ms), 2 * s));
static_assert(compare(round<si::second>(2500 * ms), 2 * s));
static_assert(compare(round<si::second>(3500 * ms), 4 * s));
static_assert(compare(round<si::second>(-1000 * ms), -1 * s));
static_assert(compare(round<si::second>(-1001 * ms), -1 * s));
static_assert(compare(round<si::second>(-1499 * ms), -1 * s));
static_assert(compare(round<si::second>(-1500 * ms), -2 * s));
static_assert(compare(round<si::second>(-1999 * ms), -2 * s));
static_assert(compare(round<si::second>(-2500 * ms), -2 * s));
static_assert(compare(round<si::second>(-3500 * ms), -4 * s));
static_assert(compare(round<si::second>(1 * isq::time[s]), 1 * isq::time[s]));
static_assert(compare(round<si::second>(1000 * isq::time[ms]), 1 * isq::time[s]));
@ -201,38 +205,62 @@ static_assert(compare(round<si::second>(1001 * isq::time[ms]), 1 * isq::time[s])
static_assert(compare(round<si::second>(1499 * isq::time[ms]), 1 * isq::time[s]));
static_assert(compare(round<si::second>(1500 * isq::time[ms]), 2 * isq::time[s]));
static_assert(compare(round<si::second>(1999 * isq::time[ms]), 2 * isq::time[s]));
static_assert(compare(round<si::second>(2500 * isq::time[ms]), 2 * isq::time[s]));
static_assert(compare(round<si::second>(3500 * isq::time[ms]), 4 * isq::time[s]));
static_assert(compare(round<si::second>(-1000 * isq::time[ms]), -1 * isq::time[s]));
static_assert(compare(round<si::second>(-1001 * isq::time[ms]), -1 * isq::time[s]));
static_assert(compare(round<si::second>(-1499 * isq::time[ms]), -1 * isq::time[s]));
static_assert(compare(round<si::second>(-1500 * isq::time[ms]), -2 * isq::time[s]));
static_assert(compare(round<si::second>(-1999 * isq::time[ms]), -2 * isq::time[s]));
static_assert(compare(round<si::second>(-2500 * isq::time[ms]), -2 * isq::time[s]));
static_assert(compare(round<si::second>(-3500 * isq::time[ms]), -4 * isq::time[s]));
// floating-point
static_assert(compare(round<si::second>(1.3 * s), 1. * s));
static_assert(compare(round<si::second>(1.5 * s), 2. * s));
static_assert(compare(round<si::second>(2.5 * s), 2. * s));
static_assert(compare(round<si::second>(3.5 * s), 4. * s));
static_assert(compare(round<si::second>(-1.3 * s), -1. * s));
static_assert(compare(round<si::second>(-1.5 * s), -2. * s));
static_assert(compare(round<si::second>(-2.5 * s), -2. * s));
static_assert(compare(round<si::second>(-3.5 * s), -4. * s));
static_assert(compare(round<si::second>(1000. * ms), 1. * s));
static_assert(compare(round<si::second>(1001. * ms), 1. * s));
static_assert(compare(round<si::second>(1499. * ms), 1. * s));
static_assert(compare(round<si::second>(1500. * ms), 2. * s));
static_assert(compare(round<si::second>(1999. * ms), 2. * s));
static_assert(compare(round<si::second>(2500. * ms), 2. * s));
static_assert(compare(round<si::second>(3500. * ms), 4. * s));
static_assert(compare(round<si::second>(-1000. * ms), -1. * s));
static_assert(compare(round<si::second>(-1001. * ms), -1. * s));
static_assert(compare(round<si::second>(-1499. * ms), -1. * s));
static_assert(compare(round<si::second>(-1500. * ms), -2. * s));
static_assert(compare(round<si::second>(-1999. * ms), -2. * s));
static_assert(compare(round<si::second>(-2500. * ms), -2. * s));
static_assert(compare(round<si::second>(-3500. * ms), -4. * s));
static_assert(compare(round<si::second>(1.3 * isq::time[s]), 1. * isq::time[s]));
static_assert(compare(round<si::second>(1.5 * isq::time[s]), 2. * isq::time[s]));
static_assert(compare(round<si::second>(2.5 * isq::time[s]), 2. * isq::time[s]));
static_assert(compare(round<si::second>(3.5 * isq::time[s]), 4. * isq::time[s]));
static_assert(compare(round<si::second>(-1.3 * isq::time[s]), -1. * isq::time[s]));
static_assert(compare(round<si::second>(-1.5 * isq::time[s]), -2. * isq::time[s]));
static_assert(compare(round<si::second>(-2.5 * isq::time[s]), -2. * isq::time[s]));
static_assert(compare(round<si::second>(-3.5 * isq::time[s]), -4. * isq::time[s]));
static_assert(compare(round<si::second>(1000. * isq::time[ms]), 1. * isq::time[s]));
static_assert(compare(round<si::second>(1001. * isq::time[ms]), 1. * isq::time[s]));
static_assert(compare(round<si::second>(1499. * isq::time[ms]), 1. * isq::time[s]));
static_assert(compare(round<si::second>(1500. * isq::time[ms]), 2. * isq::time[s]));
static_assert(compare(round<si::second>(1999. * isq::time[ms]), 2. * isq::time[s]));
static_assert(compare(round<si::second>(2500. * isq::time[ms]), 2. * isq::time[s]));
static_assert(compare(round<si::second>(3500. * isq::time[ms]), 4. * isq::time[s]));
static_assert(compare(round<si::second>(-1000. * isq::time[ms]), -1. * isq::time[s]));
static_assert(compare(round<si::second>(-1001. * isq::time[ms]), -1. * isq::time[s]));
static_assert(compare(round<si::second>(-1499. * isq::time[ms]), -1. * isq::time[s]));
static_assert(compare(round<si::second>(-1500. * isq::time[ms]), -2. * isq::time[s]));
static_assert(compare(round<si::second>(-1999. * isq::time[ms]), -2. * isq::time[s]));
static_assert(compare(round<si::second>(-2500. * isq::time[ms]), -2. * isq::time[s]));
static_assert(compare(round<si::second>(-3500. * isq::time[ms]), -4. * isq::time[s]));
#endif