mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-05 21:24:27 +02:00
created sudo_cast<QP> overload, and merged shared computation into separate helper
This commit is contained in:
@@ -37,6 +37,45 @@ template<typename T, typename Other>
|
|||||||
using maybe_common_type = MP_UNITS_TYPENAME std::conditional_t<requires { typename std::common_type_t<T, Other>; },
|
using maybe_common_type = MP_UNITS_TYPENAME std::conditional_t<requires { typename std::common_type_t<T, Other>; },
|
||||||
get_common_type<T, Other>, std::type_identity<T>>::type;
|
get_common_type<T, Other>, std::type_identity<T>>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Details about the conversion from one quantity to another.
|
||||||
|
*
|
||||||
|
* This struct calculates the conversion factor that needs to be applied to a number,
|
||||||
|
* in order to convert from one quantity to another. In addition to that, it also
|
||||||
|
* helps to determine what representations to use at which step in the conversion process,
|
||||||
|
* in order to avoid overflow and underflow while not causing excessive computations.
|
||||||
|
*
|
||||||
|
* @note This is a low-level facility.
|
||||||
|
*
|
||||||
|
* @tparam To a target quantity type to cast to
|
||||||
|
* @tparam From a source quantity type to cast from
|
||||||
|
*/
|
||||||
|
template<Quantity To, Quantity From>
|
||||||
|
requires(castable(From::quantity_spec, To::quantity_spec))
|
||||||
|
struct magnitude_conversion_traits {
|
||||||
|
// scale the number
|
||||||
|
static constexpr Magnitude auto c_mag = get_canonical_unit(From::unit).mag / get_canonical_unit(To::unit).mag;
|
||||||
|
static constexpr Magnitude auto num = numerator(c_mag);
|
||||||
|
static constexpr Magnitude auto den = denominator(c_mag);
|
||||||
|
static constexpr Magnitude auto irr = c_mag * (den / num);
|
||||||
|
using c_rep_type = maybe_common_type<typename std::remove_reference_t<From>::rep, typename To::rep>;
|
||||||
|
using c_mag_type = common_magnitude_type<c_mag>;
|
||||||
|
using multiplier_type = conditional<
|
||||||
|
treat_as_floating_point<c_rep_type>,
|
||||||
|
// ensure that the multiplier is also floating-point
|
||||||
|
conditional<std::is_arithmetic_v<value_type_t<c_rep_type>>,
|
||||||
|
// reuse user's type if possible
|
||||||
|
std::common_type_t<c_mag_type, value_type_t<c_rep_type>>, std::common_type_t<c_mag_type, double>>,
|
||||||
|
c_mag_type>;
|
||||||
|
using c_type = maybe_common_type<c_rep_type, multiplier_type>;
|
||||||
|
static constexpr auto val(Magnitude auto m) { return get_value<multiplier_type>(m); };
|
||||||
|
static constexpr multiplier_type num_mult = val(num);
|
||||||
|
static constexpr multiplier_type den_mult = val(den);
|
||||||
|
static constexpr multiplier_type irr_mult = val(irr);
|
||||||
|
static constexpr multiplier_type ratio = num_mult / den_mult * irr_mult;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Explicit cast between different quantity types
|
* @brief Explicit cast between different quantity types
|
||||||
*
|
*
|
||||||
@@ -64,34 +103,77 @@ template<Quantity To, typename From>
|
|||||||
// warnings on conversions
|
// warnings on conversions
|
||||||
} else {
|
} else {
|
||||||
// scale the number
|
// scale the number
|
||||||
constexpr Magnitude auto c_mag = get_canonical_unit(q_unit).mag / get_canonical_unit(To::unit).mag;
|
using traits = magnitude_conversion_traits<To, std::remove_reference_t<From>>;
|
||||||
constexpr Magnitude auto num = numerator(c_mag);
|
if constexpr (std::is_floating_point_v<typename traits::multiplier_type>) {
|
||||||
constexpr Magnitude auto den = denominator(c_mag);
|
|
||||||
constexpr Magnitude auto irr = c_mag * (den / num);
|
|
||||||
using c_rep_type = maybe_common_type<typename std::remove_reference_t<From>::rep, typename To::rep>;
|
|
||||||
using c_mag_type = common_magnitude_type<c_mag>;
|
|
||||||
using multiplier_type = conditional<
|
|
||||||
treat_as_floating_point<c_rep_type>,
|
|
||||||
// ensure that the multiplier is also floating-point
|
|
||||||
conditional<std::is_arithmetic_v<value_type_t<c_rep_type>>,
|
|
||||||
// reuse user's type if possible
|
|
||||||
std::common_type_t<c_mag_type, value_type_t<c_rep_type>>, std::common_type_t<c_mag_type, double>>,
|
|
||||||
c_mag_type>;
|
|
||||||
using c_type = maybe_common_type<c_rep_type, multiplier_type>;
|
|
||||||
constexpr auto val = [](Magnitude auto m) { return get_value<multiplier_type>(m); };
|
|
||||||
if constexpr (std::is_floating_point_v<multiplier_type>) {
|
|
||||||
// this results in great assembly
|
// this results in great assembly
|
||||||
constexpr auto ratio = val(num) / val(den) * val(irr);
|
|
||||||
auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
|
auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
|
||||||
static_cast<c_type>(q.numerical_value_is_an_implementation_detail_) * ratio);
|
static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::ratio);
|
||||||
return {res, To::reference};
|
return {res, To::reference};
|
||||||
} else {
|
} else {
|
||||||
// this is slower but allows conversions like 2000 m -> 2 km without loosing data
|
// this is slower but allows conversions like 2000 m -> 2 km without loosing data
|
||||||
auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
|
auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
|
||||||
static_cast<c_type>(q.numerical_value_is_an_implementation_detail_) * val(num) / val(den) * val(irr));
|
static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::num_mult /
|
||||||
|
traits::den_mult * traits::irr_mult);
|
||||||
return {res, To::reference};
|
return {res, To::reference};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Explicit cast between different quantity_point types
|
||||||
|
*
|
||||||
|
* @note This is a low-level facility and is too powerful to be used by the users directly. They should either use
|
||||||
|
* `value_cast` or `quantity_cast`.
|
||||||
|
*
|
||||||
|
* @tparam ToQP a target quantity point type to which to cast to
|
||||||
|
*/
|
||||||
|
template<QuantityPoint ToQP, typename FromQP>
|
||||||
|
requires QuantityPoint<std::remove_cvref_t<FromQP>> &&
|
||||||
|
(castable(std::remove_reference_t<FromQP>::quantity_spec, ToQP::quantity_spec)) &&
|
||||||
|
(detail::same_absolute_point_origins(ToQP::point_origin, std::remove_reference_t<FromQP>::point_origin)) &&
|
||||||
|
((std::remove_reference_t<FromQP>::unit == ToQP::unit &&
|
||||||
|
std::constructible_from<typename ToQP::rep, typename std::remove_reference_t<FromQP>::rep>) ||
|
||||||
|
(std::remove_reference_t<FromQP>::unit != ToQP::unit))
|
||||||
|
[[nodiscard]] constexpr QuantityPoint auto sudo_cast(FromQP&& qp)
|
||||||
|
{
|
||||||
|
using qp_type = std::remove_reference_t<FromQP>;
|
||||||
|
if constexpr (is_same_v<std::remove_const_t<decltype(ToQP::point_origin)>,
|
||||||
|
std::remove_const_t<decltype(qp_type::point_origin)>>) {
|
||||||
|
return quantity_point{
|
||||||
|
sudo_cast<typename ToQP::quantity_type>(std::forward<FromQP>(qp).quantity_from(qp_type::point_origin)),
|
||||||
|
qp_type::point_origin};
|
||||||
|
} else {
|
||||||
|
// it's unclear how hard we should try to avoid truncation here. For now, the only corner case we cater for,
|
||||||
|
// is when the range of the quantity type of at most one of QP or ToQP doesn't cover the offset between the
|
||||||
|
// point origins. In that case, we need to be careful to ensure we use the quantity type with the larger range
|
||||||
|
// of the two to perform the point_origin conversion.
|
||||||
|
// Numerically, we'll potentially need to do three things:
|
||||||
|
// (a) cast the representation type
|
||||||
|
// (b) scale the numerical value
|
||||||
|
// (c) add/subtract the origin difference
|
||||||
|
// In the following, we carefully select the order of these three operations: each of (a) and (b) is scheduled
|
||||||
|
// either before or after (c), such that (c) acts on the largest range possible among all combination of source
|
||||||
|
// and target unit and represenation.
|
||||||
|
using traits = magnitude_conversion_traits<typename ToQP::quantity_type, typename qp_type::quantity_type>;
|
||||||
|
using c_rep_type = typename traits::c_rep_type;
|
||||||
|
if constexpr (traits::num_mult * traits::irr_mult > traits::den_mult) {
|
||||||
|
// original unit had a larger unit magnitude; if we first convert to the common representation but retain the
|
||||||
|
// unit, we obtain the largest possible range while not causing truncation of fractional values. This is optimal
|
||||||
|
// for the offset computation.
|
||||||
|
return sudo_cast<ToQP>(
|
||||||
|
sudo_cast<quantity_point<qp_type::reference, qp_type::point_origin, c_rep_type>>(std::forward<FromQP>(qp))
|
||||||
|
.point_for(ToQP::point_origin));
|
||||||
|
} else {
|
||||||
|
// new unit may have a larger unit magnitude; we first need to convert to the new unit (potentially causing
|
||||||
|
// truncation, but no more than if we did the conversion later), but make sure we keep the larger of the two
|
||||||
|
// representation types. Then, we can perform the offset computation.
|
||||||
|
return sudo_cast<ToQP>(sudo_cast<quantity_point<make_reference(qp_type::quantity_spec, ToQP::unit),
|
||||||
|
qp_type::point_origin, c_rep_type>>(std::forward<FromQP>(qp))
|
||||||
|
.point_for(ToQP::point_origin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace mp_units::detail
|
} // namespace mp_units::detail
|
||||||
|
@@ -185,10 +185,9 @@ template<Unit auto ToU, Representation ToRep, typename QP>
|
|||||||
* (e.g. non-truncating) conversion. In truncating cases an explicit cast have to be used.
|
* (e.g. non-truncating) conversion. In truncating cases an explicit cast have to be used.
|
||||||
*
|
*
|
||||||
* inline constexpr struct A : absolute_point_origin<A, isq::distance> A;
|
* inline constexpr struct A : absolute_point_origin<A, isq::distance> A;
|
||||||
* inline constexpr struct B : relative_point_origin<A + 1*m> B;
|
|
||||||
*
|
*
|
||||||
* using ToQP = quantity_point<mm, B, int>;
|
* using ToQ = quantity<mm, int>;
|
||||||
* auto qp = value_cast<ToQP>(quantity_point{1.23 * m});
|
* auto qp = value_cast<ToQ>(quantity_point{1.23 * m});
|
||||||
*
|
*
|
||||||
* Note that value_cast only changes the "representation aspects" (unit and representation
|
* Note that value_cast only changes the "representation aspects" (unit and representation
|
||||||
* type), but not the "meaning" (quantity type or the actual point that is being described).
|
* type), but not the "meaning" (quantity type or the actual point that is being described).
|
||||||
@@ -221,6 +220,16 @@ template<Quantity ToQ, typename QP>
|
|||||||
* type and point origin), but not the "meaning" (quantity type or the actual point that is
|
* type and point origin), but not the "meaning" (quantity type or the actual point that is
|
||||||
* being described).
|
* being described).
|
||||||
*
|
*
|
||||||
|
* Note also that changing the point origin bears risks regarding truncation and overflow
|
||||||
|
* similar to other casts that change representation (which is why we require a `value_cast`
|
||||||
|
* and disallow implicit conversions). This cast is guaranteed not to cause overflow of
|
||||||
|
* any intermediate representation type provided that the input quantity point is within
|
||||||
|
* the range of `ToQP`. Calling `value_cast<ToQP>(qp)` on a `qp` outside of the range of `ToQP`
|
||||||
|
* is potentially undefined behaviour.
|
||||||
|
* The implementation further attempts not to cause more than
|
||||||
|
* rounding error than approximately the sum of the resolution of `qp` as represented in `FromQP`,
|
||||||
|
* plust the resolution of `qp` as represented in `ToQP`.
|
||||||
|
*
|
||||||
* @tparam ToQP a target quantity point type to which to cast the representation of the point
|
* @tparam ToQP a target quantity point type to which to cast the representation of the point
|
||||||
*/
|
*/
|
||||||
template<QuantityPoint ToQP, typename QP>
|
template<QuantityPoint ToQP, typename QP>
|
||||||
@@ -231,52 +240,7 @@ template<QuantityPoint ToQP, typename QP>
|
|||||||
std::constructible_from<typename ToQP::rep, typename std::remove_reference_t<QP>::rep>
|
std::constructible_from<typename ToQP::rep, typename std::remove_reference_t<QP>::rep>
|
||||||
[[nodiscard]] constexpr QuantityPoint auto value_cast(QP&& qp)
|
[[nodiscard]] constexpr QuantityPoint auto value_cast(QP&& qp)
|
||||||
{
|
{
|
||||||
using qp_type = std::remove_reference_t<QP>;
|
return detail::sudo_cast<ToQP>(std::forward<QP>(qp));
|
||||||
if constexpr (is_same_v<std::remove_const_t<decltype(ToQP::point_origin)>,
|
|
||||||
std::remove_const_t<decltype(qp_type::point_origin)>>) {
|
|
||||||
return quantity_point{
|
|
||||||
value_cast<typename ToQP::quantity_type>(std::forward<QP>(qp).quantity_from(qp_type::point_origin)),
|
|
||||||
qp_type::point_origin};
|
|
||||||
} else {
|
|
||||||
// it's unclear how hard we should try to avoid truncation here. For now, the only corner case we cater for,
|
|
||||||
// is when the range of the quantity type of at most one of QP or ToQP doesn't cover the offset between the
|
|
||||||
// point origins. In that case, we need to be careful to ensure we use the quantity type with the larger range
|
|
||||||
// of the two to perform the point_origin conversion.
|
|
||||||
// Numerically, we'll potentially need to do three things:
|
|
||||||
// (a) cast the representation type
|
|
||||||
// (b) scale the numerical value
|
|
||||||
// (c) add/subtract the origin difference
|
|
||||||
// In the following, we carefully select the order of these three operations: each of (a) and (b) is scheduled
|
|
||||||
// either before or after (c), such that (c) acts on the largest range possible among all combination of source
|
|
||||||
// and target unit and represenation.
|
|
||||||
constexpr Magnitude auto c_mag = get_canonical_unit(qp_type::unit).mag / get_canonical_unit(ToQP::unit).mag;
|
|
||||||
constexpr Magnitude auto num = detail::numerator(c_mag);
|
|
||||||
constexpr Magnitude auto den = detail::denominator(c_mag);
|
|
||||||
constexpr Magnitude auto irr = c_mag * (den / num);
|
|
||||||
using c_rep_type = detail::maybe_common_type<typename ToQP::rep, typename qp_type::rep>;
|
|
||||||
using c_mag_type = detail::common_magnitude_type<c_mag>;
|
|
||||||
using multiplier_type = conditional<
|
|
||||||
treat_as_floating_point<c_rep_type>,
|
|
||||||
// ensure that the multiplier is also floating-point
|
|
||||||
conditional<std::is_arithmetic_v<value_type_t<c_rep_type>>,
|
|
||||||
// reuse user's type if possible
|
|
||||||
std::common_type_t<c_mag_type, value_type_t<c_rep_type>>, std::common_type_t<c_mag_type, double>>,
|
|
||||||
c_mag_type>;
|
|
||||||
constexpr auto val = [](Magnitude auto m) { return get_value<multiplier_type>(m); };
|
|
||||||
if constexpr (val(num) * val(irr) > val(den)) {
|
|
||||||
// original unit had a larger unit magnitude; if we first convert to the common representation but retain the
|
|
||||||
// unit, we obtain the largest possible range while not causing truncation of fractional values. This is optimal
|
|
||||||
// for the offset computation.
|
|
||||||
return value_cast<typename ToQP::quantity_type>(
|
|
||||||
value_cast<c_rep_type>(std::forward<QP>(qp)).point_for(ToQP::point_origin));
|
|
||||||
} else {
|
|
||||||
// new unit may have a larger unit magnitude; we first need to convert to the new unit (potentially causing
|
|
||||||
// truncation, but no more than if we did the conversion later), but make sure we keep the larger of the two
|
|
||||||
// representation types. Then, we can perform the offset computation.
|
|
||||||
return value_cast<typename ToQP::quantity_type>(
|
|
||||||
value_cast<ToQP::unit, c_rep_type>(std::forward<QP>(qp)).point_for(ToQP::point_origin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user