Skip to content

Commit

Permalink
feat: atan2 2-argument arctangent
Browse files Browse the repository at this point in the history
  • Loading branch information
nebkat committed Jan 20, 2024
1 parent 6e8a21a commit 057d659
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/users_guide/framework_basics/quantity_arithmetics.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ Among others, we can find there the following:
- `inverse()`,
- `hypot()`,
- `sin()`, `cos()`, `tan()`,
- `asin()`, `acos()`, `atan()`.
- `asin()`, `acos()`, `atan()`, `atan2()`.

In the library, we can also find _mp-units/random.h_ header file with all the pseudo-random number
generators working on quantity types.
20 changes: 20 additions & 0 deletions src/systems/include/mp-units/systems/angular/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,24 @@ template<ReferenceOf<dimensionless> auto R, typename Rep>
return quantity{atan(q.numerical_value_in(one)), radian};
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
requires requires(Rep1 v1, Rep2 v2) {
common_reference(R1, R2);
requires requires { atan2(v1, v2); } || requires { std::atan2(v1, v2); };
}
[[nodiscard]] inline QuantityOf<angle> auto atan2(const quantity<R1, Rep1>& y, const quantity<R2, Rep2>& x) noexcept
{
constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref);
using std::atan2;
if constexpr (!treat_as_floating_point<Rep1> || !treat_as_floating_point<Rep2>) {
// check what is the return type when called with the integral value
using rep = decltype(atan2(y.force_numerical_value_in(unit), x.force_numerical_value_in(unit)));
// use this type ahead of calling the function to prevent narrowing if a unit conversion is needed
return quantity{atan2(value_cast<rep>(y).numerical_value_in(unit), value_cast<rep>(x).numerical_value_in(unit)),
radian};
} else
return quantity{atan2(y.numerical_value_in(unit), x.numerical_value_in(unit)), radian};
}

} // namespace mp_units::angular
21 changes: 21 additions & 0 deletions src/systems/include/mp-units/systems/si/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,25 @@ template<ReferenceOf<dimensionless> auto R, typename Rep>
return quantity{atan(q.numerical_value_in(one)), radian};
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
requires requires(Rep1 v1, Rep2 v2) {
common_reference(R1, R2);
requires requires { atan2(v1, v2); } || requires { std::atan2(v1, v2); };
}
[[nodiscard]] inline QuantityOf<isq::angular_measure> auto atan2(const quantity<R1, Rep1>& y,
const quantity<R2, Rep2>& x) noexcept
{
constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref);
using std::atan2;
if constexpr (!treat_as_floating_point<Rep1> || !treat_as_floating_point<Rep2>) {
// check what is the return type when called with the integral value
using rep = decltype(atan2(y.force_numerical_value_in(unit), x.force_numerical_value_in(unit)));
// use this type ahead of calling the function to prevent narrowing if a unit conversion is needed
return quantity{atan2(value_cast<rep>(y).numerical_value_in(unit), value_cast<rep>(x).numerical_value_in(unit)),
radian};
} else
return quantity{atan2(y.numerical_value_in(unit), x.numerical_value_in(unit)), radian};
}

} // namespace mp_units::si
36 changes: 36 additions & 0 deletions test/runtime/math_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,22 @@ TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]")
}
}

TEST_CASE("SI atan2 functions", "[atan2][si]")
{
SECTION("atan2 should work on the same quantities")
{
REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg));
REQUIRE_THAT(si::atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * deg));
REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * deg));
}
SECTION("atan2 should work with different units of the same dimension")
{
REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * deg));
REQUIRE_THAT(si::atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * deg));
REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg));
}
}


TEST_CASE("Angle trigonometric functions", "[trig][angle]")
{
Expand Down Expand Up @@ -449,3 +465,23 @@ TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]")
REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg]));
}
}

TEST_CASE("Angle atan2 functions", "[atan2][angle]")
{
using namespace mp_units::angular;
using namespace mp_units::angular::unit_symbols;
using mp_units::angular::unit_symbols::deg;

SECTION("atan2 should work on the same quantities")
{
REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg]));
REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * angle[deg]));
}
SECTION("atan2 should work with different units of the same dimension")
{
REQUIRE_THAT(atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * angle[deg]));
REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg]));
REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg]));
}
}

0 comments on commit 057d659

Please sign in to comment.