Core: Fix numext::sign for complex values at the ends of the range libeigen/eigen!2989 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/MathFunctions.h b/Eigen/src/Core/MathFunctions.h index 26fb2b7..e8499d1 100644 --- a/Eigen/src/Core/MathFunctions.h +++ b/Eigen/src/Core/MathFunctions.h
@@ -838,8 +838,10 @@ EIGEN_USING_STD(abs); real_type aa = abs(a); if (aa == real_type(0)) return Scalar(0); - aa = real_type(1) / aa; - return Scalar(numext::real(a) * aa, numext::imag(a) * aa); + // Divide rather than multiply by 1/aa: the reciprocal overflows for subnormal aa and is itself + // subnormal, hence inexact, for aa near the top of the range. psign_impl for complex packets + // divides for the same reason. + return Scalar(numext::real(a) / aa, numext::imag(a) / aa); } };
diff --git a/test/numext.cpp b/test/numext.cpp index d1578b3..94b4f03 100644 --- a/test/numext.cpp +++ b/test/numext.cpp
@@ -234,6 +234,33 @@ } } +// numext::sign(z) = z / |z| for complex z. The interesting inputs are the ends of the range, where +// forming 1/|z| first would overflow (subnormal |z|) or itself be subnormal, hence inexact (|z| near max). +template <typename T> +void check_complex_sign() { + typedef typename NumTraits<T>::Real Real; + const Real zero(0), one(1); + + VERIFY_IS_EQUAL(numext::sign(T(zero, zero)), T(zero, zero)); + VERIFY_IS_EQUAL(numext::sign(T(one, zero)), T(one, zero)); + VERIFY_IS_EQUAL(numext::sign(T(zero, -one)), T(zero, -one)); + + for (Real r : {std::numeric_limits<Real>::denorm_min(), (std::numeric_limits<Real>::min)(), + (std::numeric_limits<Real>::max)()}) { + VERIFY_IS_EQUAL(numext::sign(T(r, zero)), T(one, zero)); + VERIFY_IS_EQUAL(numext::sign(T(-r, zero)), T(-one, zero)); + VERIFY_IS_EQUAL(numext::sign(T(zero, r)), T(zero, one)); + } + + // Off the axes the magnitude is only as accurate as abs() itself, which cannot resolve |z| for a z + // whose components are at the bottom of the subnormal range; from the smallest normal upwards it can. + for (Real r : {(std::numeric_limits<Real>::min)(), one, (std::numeric_limits<Real>::max)() / Real(2)}) { + const T s = numext::sign(T(r, r)); + VERIFY_IS_APPROX(numext::abs(s), one); + VERIFY_IS_EQUAL(numext::real(s), numext::imag(s)); + } +} + template <typename T> void check_arg() { typedef typename NumTraits<T>::Real Real; @@ -570,6 +597,9 @@ CALL_SUBTEST(check_abs<std::complex<float>>()); CALL_SUBTEST(check_abs<std::complex<double>>()); + CALL_SUBTEST(check_complex_sign<std::complex<float>>()); + CALL_SUBTEST(check_complex_sign<std::complex<double>>()); + CALL_SUBTEST(check_arg<std::complex<float>>()); CALL_SUBTEST(check_arg<std::complex<double>>());