Skip to content

Commit

Permalink
fix: common_unit handling fixed for some corner cases
Browse files Browse the repository at this point in the history
  • Loading branch information
mpusz committed Oct 5, 2024
1 parent 07e4e79 commit 569f27a
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 12 deletions.
17 changes: 15 additions & 2 deletions src/core/include/mp-units/bits/get_associated_quantity.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
#include <mp-units/framework/quantity_spec.h>
#include <mp-units/framework/unit_concepts.h>

namespace mp_units::detail {
namespace mp_units {

template<Unit U1, Unit U2, Unit... Rest>
struct common_unit;

namespace detail {

template<AssociatedUnit U>
[[nodiscard]] consteval auto all_are_kinds(U);
Expand Down Expand Up @@ -61,6 +66,12 @@ template<AssociatedUnit U>
template<AssociatedUnit U>
using to_quantity_spec = decltype(get_associated_quantity_impl(U{}));

template<typename... Us>
[[nodiscard]] consteval auto get_associated_quantity_impl(common_unit<Us...>)
{
return get_common_quantity_spec(get_associated_quantity_impl(Us{})...);
}

template<AssociatedUnit U>
[[nodiscard]] consteval auto get_associated_quantity_impl(U u)
{
Expand All @@ -83,4 +94,6 @@ template<AssociatedUnit U>
return get_associated_quantity_impl(u);
}

} // namespace mp_units::detail
} // namespace detail

} // namespace mp_units
11 changes: 6 additions & 5 deletions src/core/include/mp-units/framework/quantity.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,17 @@ concept HaveCommonReferenceImpl = requires { get_common_reference(R1, R2); };
template<auto R1, auto R2>
concept HaveCommonReference = HaveCommonReferenceImpl<R1, R2>;

template<typename Func, Quantity Q1, Quantity Q2>
using common_quantity_for = quantity<get_common_reference(Q1::reference, Q2::reference),
std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>;

template<typename Func, typename Q1, typename Q2>
concept CommonlyInvocableQuantities =
Quantity<Q1> && Quantity<Q2> && HaveCommonReference<Q1::reference, Q2::reference> &&
std::convertible_to<Q1, common_quantity_for<Func, Q1, Q2>> &&
std::convertible_to<Q2, common_quantity_for<Func, Q1, Q2>> &&
InvocableQuantities<Func, Q1, Q2, get_common_quantity_spec(Q1::quantity_spec, Q2::quantity_spec).character>;

template<typename Func, Quantity Q1, Quantity Q2>
requires CommonlyInvocableQuantities<Func, Q1, Q2>
using common_quantity_for = quantity<get_common_reference(Q1::reference, Q2::reference),
std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>;

template<auto R1, auto R2, typename Rep1, typename Rep2>
concept SameValueAs = SameReference<R1, R2> && std::same_as<Rep1, Rep2>;

Expand Down
16 changes: 11 additions & 5 deletions src/core/include/mp-units/framework/unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ import std;

namespace mp_units {

template<Unit U1, Unit U2, Unit... Rest>
struct common_unit;

namespace detail {

template<Magnitude auto M, Unit U>
Expand Down Expand Up @@ -784,9 +781,18 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit_impl<M, U>& u, const u
template<typename... Us, Unit U>
[[nodiscard]] consteval Unit auto get_common_unit_in(common_unit<Us...>, U u)
{
auto get_magnitude = [&]() {
if constexpr (requires { common_unit<Us...>::mag; })
return common_unit<Us...>::mag;
else
return mag<1>;
};
constexpr auto canonical_u = get_canonical_unit(u);
constexpr Magnitude auto mag = common_unit<Us...>::mag / canonical_u.mag;
return scaled_unit<mag, U>{};
constexpr Magnitude auto cmag = get_magnitude() / canonical_u.mag;
if constexpr (cmag == mag<1>)
return u;
else
return scaled_unit<cmag, U>{};
}

template<typename CharT, std::output_iterator<CharT> Out, typename U, typename... Rest>
Expand Down
7 changes: 7 additions & 0 deletions test/static/concepts_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ static_assert(UnitOf<struct si::hertz, inverse(isq::time)>);
static_assert(UnitOf<struct one, dimensionless>);
static_assert(UnitOf<struct percent, dimensionless>);
static_assert(UnitOf<struct si::radian, isq::angular_measure>);
static_assert(UnitOf<struct si::degree, isq::angular_measure>);
static_assert(UnitOf<struct one, isq::angular_measure>);
static_assert(UnitOf<struct percent, isq::angular_measure>);
static_assert(!UnitOf<struct si::radian, dimensionless>);
Expand Down Expand Up @@ -247,9 +248,15 @@ static_assert(ReferenceOf<decltype(dimensionless[one]), dimensionless>);
static_assert(ReferenceOf<decltype(isq::rotation[one]), isq::rotation>);
static_assert(ReferenceOf<decltype(isq::rotation[one]), dimensionless>);
static_assert(ReferenceOf<struct si::radian, isq::angular_measure>);
static_assert(ReferenceOf<struct si::degree, isq::angular_measure>);
static_assert(ReferenceOf<decltype(get_common_unit(si::degree, si::radian)), isq::angular_measure>);
static_assert(!ReferenceOf<struct si::radian, dimensionless>);
static_assert(!ReferenceOf<struct si::degree, dimensionless>);
static_assert(!ReferenceOf<decltype(get_common_unit(si::degree, si::radian)), dimensionless>);
static_assert(ReferenceOf<decltype(isq::angular_measure[si::radian]), isq::angular_measure>);
static_assert(ReferenceOf<decltype(isq::angular_measure[si::degree]), isq::angular_measure>);
static_assert(!ReferenceOf<decltype(isq::angular_measure[si::radian]), dimensionless>);
static_assert(!ReferenceOf<decltype(isq::angular_measure[si::degree]), dimensionless>);
static_assert(ReferenceOf<struct one, isq::rotation>);
static_assert(ReferenceOf<struct one, isq::angular_measure>);
static_assert(!ReferenceOf<decltype(dimensionless[one]), isq::rotation>);
Expand Down
10 changes: 10 additions & 0 deletions test/static/quantity_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ static_assert(quantity<isq::length[m], int>(2000 * m).force_in(km).numerical_val
static_assert((15. * m).in(nm).numerical_value_in(m) == 15.);
static_assert((15'000. * nm).in(m).numerical_value_in(nm) == 15'000.);

// check if unit conversion works - don't bother about the actual result
static_assert((1. * rad + 1. * deg).in(rad) != 0 * rad);
static_assert((1. * rad + 1. * deg).in(deg) != 0 * deg);

#if MP_UNITS_HOSTED
using namespace std::complex_literals;
static_assert(((2. + 1i) * V).in(mV).numerical_value_in(mV) == 2000. + 1000i);
Expand Down Expand Up @@ -788,6 +792,12 @@ consteval bool invalid_arithmetic(Ts... ts)
static_assert(invalid_arithmetic(5 * isq::activity[Bq], 5 * isq::frequency[Hz]));
static_assert(invalid_arithmetic(5 * isq::activity[Bq], 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz]));

// irrational conversion factors require floating point representation
static_assert(invalid_arithmetic(1 * rad, 1 * deg));
static_assert(is_of_type<1. * rad + 1 * deg, quantity<common_unit<struct si::degree, struct si::radian>{}, double>>);
static_assert(is_of_type<1 * rad + 1. * deg, quantity<common_unit<struct si::degree, struct si::radian>{}, double>>);
static_assert(is_of_type<1. * rad + 1. * deg, quantity<common_unit<struct si::degree, struct si::radian>{}, double>>);

// Physical constants
static_assert(1 * si::si2019::speed_of_light_in_vacuum + 10 * isq::speed[m / s] == 299'792'468 * isq::speed[m / s]);

Expand Down
5 changes: 5 additions & 0 deletions test/static/unit_symbol_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include <mp-units/systems/angular.h>
#include <mp-units/systems/iau.h>
#include <mp-units/systems/iec.h>
#include <mp-units/systems/international.h>
Expand Down Expand Up @@ -209,6 +210,10 @@ static_assert(unit_symbol(get_common_unit(kilo<metre> / hour, metre / second) /
"EQUIV{[1/5 km/h], [1/18 m/s]}/s");
static_assert(unit_symbol(get_common_unit(kilo<metre> / hour, metre / second) * second) ==
"EQUIV{[1/5 km/h], [1/18 m/s]} s");
static_assert(unit_symbol(get_common_unit(radian, degree)) == "EQUIV{[1/𝜋°], [1/180 rad]}");
static_assert(unit_symbol(get_common_unit(angular::radian, angular::revolution)) == "EQUIV{rad, [2⁻¹ 𝜋⁻¹ rev]}");
static_assert(unit_symbol<usf{.solidus = always}>(get_common_unit(angular::radian, angular::revolution)) ==
"EQUIV{rad, [1/(2 𝜋) rev]}");

// derived units
static_assert(unit_symbol(one) == ""); // NOLINT(readability-container-size-empty)
Expand Down
4 changes: 4 additions & 0 deletions test/static/unit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ inline constexpr struct nu_second_ final : named_unit<"s"> {} nu_second;

// derived named units
inline constexpr struct radian_ final : named_unit<"rad", metre / metre> {} radian;
inline constexpr struct revolution_ final : named_unit<"rev", mag<2> * mag<pi> * radian> {} revolution;
inline constexpr struct steradian_ final : named_unit<"sr", square(metre) / square(metre)> {} steradian;
inline constexpr struct hertz_ final : named_unit<"Hz", inverse(second)> {} hertz;
inline constexpr struct becquerel_ final : named_unit<"Bq", inverse(second)> {} becquerel;
Expand Down Expand Up @@ -539,6 +540,9 @@ static_assert(is_of_type<get_common_unit(mile, yard), yard_>);
static_assert(
is_of_type<get_common_unit(speed_of_light_in_vacuum, metre / second), derived_unit<metre_, per<second_>>>);

static_assert(is_of_type<get_common_unit(radian, revolution), common_unit<radian_, revolution_>>);
static_assert(is_of_type<get_common_unit(radian, degree), common_unit<degree_, radian_>>);

// those should return instantiations of the `common_unit` class template
static_assert(is_of_type<get_common_unit(kilometre, mile), common_unit<kilo_<metre_>, mile_>>);
static_assert(is_of_type<get_common_unit(mile, kilometre), common_unit<kilo_<metre_>, mile_>>);
Expand Down

0 comments on commit 569f27a

Please sign in to comment.