From 4c49f07febb896a8276b4889ff2991dcbb886bf3 Mon Sep 17 00:00:00 2001 From: onalante-ebay <32563185-onalante-ebay@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 19:40:47 +0000 Subject: [PATCH] Introduce `numext::copysign` libeigen/eigen!2436 --- Eigen/src/Core/MathFunctions.h | 36 +++++++++++ test/numext.cpp | 113 +++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/Eigen/src/Core/MathFunctions.h b/Eigen/src/Core/MathFunctions.h index 100650c07..4b0ca975b 100644 --- a/Eigen/src/Core/MathFunctions.h +++ b/Eigen/src/Core/MathFunctions.h @@ -901,6 +901,37 @@ struct sign_retval { typedef Scalar type; }; +template ::IsComplex != 0), + bool IsInteger = (NumTraits::IsInteger != 0)> +struct copysign_impl { + EIGEN_DEVICE_FUNC static inline Scalar run(const Scalar& a, const Scalar& b) { + EIGEN_USING_STD(copysign); + return Scalar(copysign(a, b)); + } +}; + +template +struct copysign_impl { + EIGEN_DEVICE_FUNC static inline Scalar run(const Scalar& a, const Scalar& b) { + EIGEN_USING_STD(copysign); + return Scalar(copysign(numext::real(a), numext::real(b)), copysign(numext::imag(a), numext::imag(b))); + } +}; + +template +struct copysign_impl { + EIGEN_DEVICE_FUNC static inline Scalar run(const Scalar& a, const Scalar& b) { + EIGEN_IF_CONSTEXPR(!NumTraits::IsSigned) return a; + const Scalar abs_a = a < Scalar(0) ? -a : a; + return b < Scalar(0) ? -abs_a : abs_a; + } +}; + +template +struct copysign_retval { + typedef Scalar type; +}; + // suppress "unary minus operator applied to unsigned type, result still unsigned" warnings on MSVC // note: `0 - a` is distinct from `-a` when Scalar is a floating point type and `a` is zero @@ -1180,6 +1211,11 @@ EIGEN_DEVICE_FUNC inline EIGEN_MATHFUNC_RETVAL(sign, Scalar) sign(const Scalar& return EIGEN_MATHFUNC_IMPL(sign, Scalar)::run(x); } +template +EIGEN_DEVICE_FUNC inline EIGEN_MATHFUNC_RETVAL(copysign, Scalar) copysign(const Scalar& x, const Scalar& y) { + return EIGEN_MATHFUNC_IMPL(copysign, Scalar)::run(x, y); +} + template EIGEN_DEVICE_FUNC inline EIGEN_MATHFUNC_RETVAL(negate, Scalar) negate(const Scalar& x) { return EIGEN_MATHFUNC_IMPL(negate, Scalar)::run(x); diff --git a/test/numext.cpp b/test/numext.cpp index 32e9dcadc..a52987a3b 100644 --- a/test/numext.cpp +++ b/test/numext.cpp @@ -33,6 +33,101 @@ bool test_is_equal_or_nans(const T& actual, const U& expected) { #define VERIFY_IS_EQUAL_OR_NANS(a, b) VERIFY(test_is_equal_or_nans(a, b)) +template +struct check_copysign_impl { + static void run() { + const T pos_zero = T(0); + const T pos_one = T(1); + + // Tests valid for all types. + VERIFY_IS_EQUAL(numext::copysign(pos_one, pos_one), pos_one); + VERIFY_IS_EQUAL(numext::copysign(pos_zero, pos_one), pos_zero); + + // Tests valid for all signed types (integer and floating-point). + if (NumTraits::IsSigned) { + const T neg_one = numext::negate(pos_one); + VERIFY_IS_EQUAL(numext::copysign(pos_one, neg_one), neg_one); + VERIFY_IS_EQUAL(numext::copysign(neg_one, pos_one), pos_one); + VERIFY_IS_EQUAL(numext::copysign(neg_one, neg_one), neg_one); + } + + // Tests specific to floating-point types (negative zero, infinity, NaN). + if (!NumTraits::IsInteger) { + const T neg_zero = numext::negate(pos_zero); + const T neg_one = numext::negate(pos_one); + const T pos_inf = std::numeric_limits::infinity(); + const T neg_inf = numext::negate(pos_inf); + const T pos_nan = std::numeric_limits::quiet_NaN(); + const T neg_nan = numext::negate(pos_nan); + // Sign transferred from zero. + VERIFY_IS_EQUAL(numext::copysign(pos_one, pos_zero), pos_one); + VERIFY_IS_EQUAL(numext::copysign(pos_one, neg_zero), neg_one); + // Sign transferred from infinity. + VERIFY_IS_EQUAL(numext::copysign(pos_one, pos_inf), pos_one); + VERIFY_IS_EQUAL(numext::copysign(pos_one, neg_inf), neg_one); + // Sign transferred from NaN. + VERIFY_IS_EQUAL(numext::copysign(pos_one, pos_nan), pos_one); + VERIFY_IS_EQUAL(numext::copysign(pos_one, neg_nan), neg_one); + } + + for (int k = 0; k < 100; ++k) { + // For signed integers avoid lowest() so that abs(a) does not overflow. + const T a = (NumTraits::IsSigned && NumTraits::IsInteger) + ? internal::random(numext::negate(NumTraits::highest()), NumTraits::highest()) + : internal::random(); + const T b = internal::random(); + const T result = numext::copysign(a, b); + // Magnitude is preserved. + VERIFY_IS_EQUAL(numext::abs(result), numext::abs(a)); + // Sign matches sign source. Integers have no negative zero, so the sign + // of the result is only meaningful when a != 0. + if (!NumTraits::IsInteger || a != T(0)) { + VERIFY_IS_EQUAL(numext::copysign(pos_one, result), numext::copysign(pos_one, b)); + } + } + } +}; + +template +struct check_copysign_impl> { + static void run() { + typedef std::complex ComplexT; + const T pos_one = T(1); + const T neg_one = numext::negate(pos_one); + + // Complex copysign is applied component-wise. + VERIFY_IS_EQUAL(numext::copysign(ComplexT(pos_one, pos_one), ComplexT(pos_one, neg_one)), + ComplexT(pos_one, neg_one)); + VERIFY_IS_EQUAL(numext::copysign(ComplexT(neg_one, pos_one), ComplexT(pos_one, neg_one)), + ComplexT(pos_one, neg_one)); + VERIFY_IS_EQUAL(numext::copysign(ComplexT(pos_one, neg_one), ComplexT(neg_one, pos_one)), + ComplexT(neg_one, pos_one)); + + for (int k = 0; k < 100; ++k) { + const ComplexT a = internal::random(); + const ComplexT b = internal::random(); + const ComplexT result = numext::copysign(a, b); + // Each component is independently copysigned. + VERIFY_IS_EQUAL(numext::real(result), numext::copysign(numext::real(a), numext::real(b))); + VERIFY_IS_EQUAL(numext::imag(result), numext::copysign(numext::imag(a), numext::imag(b))); + } + } +}; + +template +void check_copysign() { + check_copysign_impl::run(); +} + +template <> +void check_copysign() { + for (bool a : {false, true}) { + for (bool b : {false, true}) { + VERIFY_IS_EQUAL(numext::copysign(a, b), a); + } + } +} + template void check_negate() { Index size = 1000; @@ -334,6 +429,24 @@ void check_shift() { EIGEN_DECLARE_TEST(numext) { for (int k = 0; k < g_repeat; ++k) { + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign>()); + CALL_SUBTEST(check_copysign>()); + + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_copysign()); + CALL_SUBTEST(check_negate()); CALL_SUBTEST(check_negate()); CALL_SUBTEST(check_negate());