From 11fe374ff09319a582344227dd4743da9b62e00c Mon Sep 17 00:00:00 2001 From: Justin Kottinger <168748737+justin-kottinger@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:49:16 -0700 Subject: [PATCH] Unit-Safe Implementation of `std::hypot` (#384) ## Summary Implements a unit-safe version of `std::hypot` within the AU library. This implementation ensures dimensional consistency when calculating the hypotenuse/Euclidean norm while preserving the underlying units. ## Implementation Details - Extends the existing AU library with a unit-safe wrapper around `std::hypot` - Maintains dimensional analysis correctness throughout calculations - Returns results in the common unit and underlying representation as the input parameters Related to #383 --------- Co-authored-by: Chip Hogg --- au/code/au/math.hh | 7 +++++++ au/code/au/math_test.cc | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/au/code/au/math.hh b/au/code/au/math.hh index 90d61dfc..671123e3 100644 --- a/au/code/au/math.hh +++ b/au/code/au/math.hh @@ -33,6 +33,7 @@ using std::abs; using std::copysign; using std::cos; using std::fmod; +using std::hypot; using std::isnan; using std::max; using std::min; @@ -172,6 +173,12 @@ constexpr auto clamp(QuantityPoint v, return (v < lo) ? ResultT{lo} : (hi < v) ? ResultT{hi} : ResultT{v}; } +template +auto hypot(Quantity x, Quantity y) { + using U = CommonUnitT; + return make_quantity(std::hypot(x.in(U{}), y.in(U{}))); +} + // Copysign where the magnitude has units. template constexpr auto copysign(Quantity mag, T sgn) { diff --git a/au/code/au/math_test.cc b/au/code/au/math_test.cc index 4c2c7f8a..dda4c821 100644 --- a/au/code/au/math_test.cc +++ b/au/code/au/math_test.cc @@ -159,6 +159,35 @@ TEST(clamp, SupportsZeroForMultipleArguments) { EXPECT_THAT(clamp(feet(6), ZERO, ZERO), SameTypeAndValue(feet(0))); } +TEST(hypot, QuantityConsistentWithStdHypotWhenTypesAreIdentical) { + auto expect_consistent_with_std_hypot = [](auto u, auto v) { + const auto expected = ohms(std::hypot(u, v)); + const auto actual = hypot(ohms(u), ohms(v)); + EXPECT_THAT(actual, SameTypeAndValue(expected)); + }; + + // Rep: `int`. + expect_consistent_with_std_hypot(-1, 0); + expect_consistent_with_std_hypot(0, 0); + expect_consistent_with_std_hypot(1, 0); + expect_consistent_with_std_hypot(2, 0); + expect_consistent_with_std_hypot(4, 2); + + // Rep: `double`. + expect_consistent_with_std_hypot(-1.0, 0.0); + expect_consistent_with_std_hypot(0.0, 0.0); + expect_consistent_with_std_hypot(1.0, 0.0); + expect_consistent_with_std_hypot(2.0, 0.0); + expect_consistent_with_std_hypot(4.0, 2.0); +} + +TEST(hypot, QuantityProducesResultsInCommonUnitOfInputs) { + EXPECT_THAT(hypot(centi(meters)(30), milli(meters)(400)), + SameTypeAndValue(milli(meters)(500.0))); + + EXPECT_THAT(hypot(inches(5.f), feet(1.f)), SameTypeAndValue(inches(13.f))); +} + TEST(copysign, ReturnsSameTypesAsStdCopysignForSameUnitInputs) { auto expect_consistent_with_std_copysign = [](auto mag, auto raw_sgn) { for (const auto test_sgn : {-1, 0, +1}) {