| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #include "main.h" |
| |
| #include <cfenv> |
| #include <cmath> |
| #include <complex> |
| #include <limits> |
| #include <vector> |
| |
| // use_double_word<packet_traits<Scalar>::type> names a vector type such as __m512d as a template argument. |
| #if EIGEN_GNUC_STRICT_AT_LEAST(6, 0, 0) |
| #pragma GCC diagnostic ignored "-Wignored-attributes" |
| #endif |
| |
| // Integer powers of binary32 and binary64 bases, real and complex, are computed by repeated squaring in |
| // double-word arithmetic: correctly rounded up to the rare cases where the exact power lies within about |
| // 7 * n * u^2 of a rounding boundary. These tests check that bound against independent references, on the |
| // vectorized path and on the scalar path, together with the exceptional values and signed zeros. |
| |
| template <typename Scalar> |
| Scalar ulp_of(const Scalar& x) { |
| Scalar a = numext::abs(x); |
| return numext::nextafter(a, std::numeric_limits<Scalar>::infinity()) - a; |
| } |
| |
| // |a - b| in units of ulp(b), with non-finite and zero references matched exactly (including the sign of zero). |
| template <typename Scalar> |
| bool within_ulps(const Scalar& a, const Scalar& b, double ulps) { |
| if ((numext::isnan)(a) || (numext::isnan)(b)) return (numext::isnan)(a) && (numext::isnan)(b); |
| if ((numext::isinf)(b) || b == Scalar(0)) return a == b && bool(numext::signbit(a)) == bool(numext::signbit(b)); |
| if ((numext::isinf)(a)) return false; |
| return double(numext::abs(a - b)) <= ulps * double(ulp_of(b)); |
| } |
| |
| // Log-uniform magnitudes over [2^lo, 2^hi], both signs. |
| template <typename Scalar> |
| std::vector<Scalar> log_uniform_bases(int lo, int hi, int count) { |
| std::vector<Scalar> bases; |
| for (int i = 0; i < count; ++i) { |
| double exponent = internal::random<double>(double(lo), double(hi)); |
| Scalar x = Scalar(std::ldexp(internal::random<double>(1.0, 2.0), int(std::floor(exponent)))); |
| bases.push_back(i % 2 ? -x : x); |
| } |
| return bases; |
| } |
| |
| // y = x.pow(n), returning which of FE_INVALID and FE_OVERFLOW it raised on a target whose packets flush subnormals, |
| // where the results below the smallest normal are rebuilt from integer bits. Elsewhere it reports none: the double |
| // word can raise FE_INVALID for a zero base, which an AVX2 partial packet holds as padding. |
| template <typename ArrayType, typename Exponent> |
| int pow_exceptions(const ArrayType& x, const Exponent& n, ArrayType& y) { |
| using Packet = typename internal::packet_traits<typename ArrayType::Scalar>::type; |
| std::fenv_t saved_environment; |
| const bool environment_saved = |
| internal::unary_pow::flushes_subnormals<Packet>::value && std::feholdexcept(&saved_environment) == 0; |
| y = x.pow(n); |
| const int exceptions = environment_saved ? std::fetestexcept(FE_INVALID | FE_OVERFLOW) : 0; |
| if (environment_saved) std::fesetenv(&saved_environment); |
| return exceptions; |
| } |
| |
| // Reference: pow in the next wider format, so its own rounding is negligible for float; for double, the |
| // platform pow, which is within 1 ulp on every supported libm. |
| template <typename Scalar> |
| Scalar reference_pow(const Scalar& x, long n) { |
| using Wide = std::conditional_t<std::is_same<Scalar, float>::value, double, Scalar>; |
| return Scalar(std::pow(Wide(x), Wide(n))); |
| } |
| |
| template <typename Scalar, typename Exponent> |
| void check_real_pow(const std::vector<Scalar>& bases, const std::vector<long>& exponents, double ulps) { |
| Index size = 2 * internal::packet_traits<Scalar>::size + 1; |
| ArrayX<Scalar> x(size), y(size); |
| for (long n : exponents) { |
| Exponent exponent = Exponent(n); |
| for (const Scalar& base : bases) { |
| x.setConstant(base); |
| y = x.pow(exponent); |
| Scalar reference = reference_pow(base, n); |
| for (Index i = 0; i < size; ++i) { |
| bool ok = within_ulps(y(i), reference, ulps); |
| if (!ok) |
| std::cout << "pow(" << base << ", " << n << ") = " << y(i) << " != " << reference << " (" << ulps << " ulp)" |
| << std::endl; |
| VERIFY(ok); |
| } |
| } |
| } |
| } |
| |
| template <typename Scalar> |
| void real_pow_test() { |
| // Exponents on both sides of the squaring cutoffs: the plain-squaring range [-3, 7] of other scalar types, the |
| // fallback to generic_pow above 2^12 (float) or 2^20 (double), and integer exponents beyond the significand, |
| // which generic_pow could not represent. |
| long cutoff = long(internal::unary_pow::max_squaring_exponent<Scalar>()); |
| const std::vector<long> exponents = {2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 100, 127, 1000, |
| -2, -3, -4, -5, -7, -8, -9, -15, -100, -127, -1000, 4096, cutoff, -cutoff}; |
| const std::vector<long> huge_exponents = {cutoff + 1, |
| -cutoff - 1, |
| 1 << 24, |
| -(1 << 24), |
| (1 << 24) + 1, |
| -((1 << 24) + 1), |
| (std::numeric_limits<int>::max)(), |
| (std::numeric_limits<int>::min)()}; |
| constexpr int min_exponent = std::numeric_limits<Scalar>::min_exponent; |
| constexpr int max_exponent = std::numeric_limits<Scalar>::max_exponent; |
| |
| // Correctly rounded within 2 ulps only holds for double-word repeated squaring, whose fallback -- plain |
| // squaring -- is just "a few ulps" and exercised elsewhere. Only the MSA and HVX packets lack the path, so |
| // require it everywhere else rather than skipping on the trait that selects it: losing it has to fail here. |
| using Packet = typename internal::packet_traits<Scalar>::type; |
| constexpr bool has_double_word = internal::unary_pow::use_double_word<Packet>::value; |
| #if !defined(EIGEN_VECTORIZE_MSA) && !defined(EIGEN_VECTORIZE_HVX) |
| VERIFY(has_double_word); |
| #endif |
| if (has_double_word) { |
| for (long n : exponents) { |
| int limit = numext::mini((max_exponent - 2) / int(numext::abs(n)), max_exponent - 2); |
| const std::vector<Scalar> bases = log_uniform_bases<Scalar>(-limit, limit, 64); |
| check_real_pow<Scalar, int>(bases, {n}, 2.0); |
| check_real_pow<Scalar, Scalar>(bases, {n}, 2.0); |
| } |
| } |
| // Results near the underflow and overflow thresholds: the final scaling rounds once more into the subnormal |
| // range, and the reference rounds too. |
| { |
| const std::vector<Scalar> bases = log_uniform_bases<Scalar>(min_exponent - 4, 4, 32); |
| check_real_pow<Scalar, int>(bases, {2, 3, 8, -2, -3, -8}, 3.0); |
| check_real_pow<Scalar, Scalar>(bases, {2, 3, 8, -2, -3, -8}, 3.0); |
| const std::vector<Scalar> tiny = log_uniform_bases<Scalar>(min_exponent - 20, min_exponent / 8, 32); |
| check_real_pow<Scalar, int>(tiny, {8, 9, 100, -8, -9, -100}, 3.0); |
| check_real_pow<Scalar, Scalar>(tiny, {8, 9, 100, -8, -9, -100}, 3.0); |
| } |
| // x^-1 and x^2 round once, as 1/x and x * x do, also where their results are subnormal. |
| { |
| const Index size = 2 * internal::packet_traits<Scalar>::size + 1; |
| for (const Scalar& base : log_uniform_bases<Scalar>(max_exponent - 3, max_exponent, 64)) { |
| ArrayX<Scalar> y = ArrayX<Scalar>::Constant(size, base).pow(-1); |
| for (Index k = 0; k < size; ++k) VERIFY(within_ulps(y(k), Scalar(1) / base, 0.0)); |
| } |
| for (const Scalar& base : log_uniform_bases<Scalar>(min_exponent / 2 - 13, min_exponent / 2, 64)) { |
| ArrayX<Scalar> y = ArrayX<Scalar>::Constant(size, base).pow(2); |
| for (Index k = 0; k < size; ++k) VERIFY(within_ulps(y(k), base * base, 0.0)); |
| } |
| } |
| // A subnormal power beside normal ones, 2^-n and 1^n in one packet, raises neither FE_INVALID nor FE_OVERFLOW. |
| { |
| const int n = 1 - min_exponent + std::numeric_limits<Scalar>::digits / 2; |
| const Scalar tiny = Scalar(std::ldexp(1.0, -n)); |
| ArrayX<Scalar> x = ArrayX<Scalar>::Ones(2 * internal::packet_traits<Scalar>::size + 1), y; |
| x(0) = Scalar(0.5); |
| VERIFY_IS_EQUAL(pow_exceptions(x, n, y), 0); |
| for (Index k = 0; k < x.size(); ++k) VERIFY(within_ulps(y(k), k == 0 ? tiny : Scalar(1), 0.0)); |
| VERIFY_IS_EQUAL(pow_exceptions(x, Scalar(n), y), 0); |
| for (Index k = 0; k < x.size(); ++k) VERIFY(within_ulps(y(k), k == 0 ? tiny : Scalar(1), 0.0)); |
| } |
| // Exponents beyond the squaring cutoff, on bases within a few ulps of one, where the power stays finite for |
| // every exponent up to INT_MAX. The reference pow then carries the full rounding error of its own exponent |
| // conversion, which is why the double reference is allowed a wider margin. |
| { |
| std::vector<Scalar> bases; |
| for (int i = -16; i <= 16; ++i) bases.push_back(Scalar(1) + Scalar(i) * NumTraits<Scalar>::epsilon()); |
| check_real_pow<Scalar, int>(bases, huge_exponents, 3.0); |
| for (long n : huge_exponents) |
| if (n == long(Scalar(n))) check_real_pow<Scalar, Scalar>(bases, {n}, 3.0); |
| } |
| // Exceptional values and signed zeros, exactly as std::pow. |
| { |
| Scalar inf = std::numeric_limits<Scalar>::infinity(); |
| Scalar nan = std::numeric_limits<Scalar>::quiet_NaN(); |
| Scalar subnormal = std::numeric_limits<Scalar>::denorm_min() * Scalar(3); |
| const std::vector<Scalar> specials = {Scalar(0), |
| -Scalar(0), |
| inf, |
| -inf, |
| nan, |
| subnormal, |
| -subnormal, |
| (std::numeric_limits<Scalar>::max)(), |
| (std::numeric_limits<Scalar>::min)(), |
| Scalar(1), |
| Scalar(-1)}; |
| const std::vector<long> small = {1, 2, 3, 4, 5, 8, 9, 100, -1, -2, -3, -4, -5, -8, -9, -100}; |
| check_real_pow<Scalar, int>(specials, small, 1.0); |
| check_real_pow<Scalar, Scalar>(specials, small, 1.0); |
| } |
| } |
| |
| // Complex reference: componentwise double-word repeated multiplication, no scaling, so the bases are kept where |
| // nothing under- or overflows. |
| template <typename Real> |
| struct double_word { |
| Real hi, lo; |
| static double_word product(const double_word& a, const double_word& b) { |
| Real p = a.hi * b.hi; |
| Real e = numext::fma(a.hi, b.hi, -p) + (a.hi * b.lo + a.lo * b.hi); |
| Real s = p + e; |
| return {s, e - (s - p)}; |
| } |
| static double_word sum(const double_word& a, const double_word& b) { |
| Real s = a.hi + b.hi; |
| Real bb = s - a.hi; |
| Real e = ((a.hi - (s - bb)) + (b.hi - bb)) + a.lo + b.lo; |
| Real t = s + e; |
| return {t, e - (t - s)}; |
| } |
| static double_word negate(const double_word& a) { return {-a.hi, -a.lo}; } |
| }; |
| |
| template <typename Real> |
| std::complex<Real> reference_complex_pow(const std::complex<Real>& z, long n) { |
| using DW = double_word<Real>; |
| DW re{Real(1), Real(0)}, im{Real(0), Real(0)}; |
| DW zr{numext::real(z), Real(0)}, zi{numext::imag(z), Real(0)}; |
| for (long k = 0; k < numext::abs(n); ++k) { |
| DW new_re = DW::sum(DW::product(re, zr), DW::negate(DW::product(im, zi))); |
| im = DW::sum(DW::product(re, zi), DW::product(im, zr)); |
| re = new_re; |
| } |
| if (n < 0) { |
| // 1/(re + im i) = (re - im i)/(re^2 + im^2) in double-word arithmetic, then rounded. |
| using LongDouble = long double; |
| DW norm = DW::sum(DW::product(re, re), DW::product(im, im)); |
| LongDouble inv = 1.0L / (LongDouble(norm.hi) + LongDouble(norm.lo)); |
| return std::complex<Real>(Real((LongDouble(re.hi) + LongDouble(re.lo)) * inv), |
| Real(-(LongDouble(im.hi) + LongDouble(im.lo)) * inv)); |
| } |
| return std::complex<Real>(Real(re.hi + re.lo), Real(im.hi + im.lo)); |
| } |
| |
| // The reciprocal above rounds in long double, which is a reference only where that is wider than Real: not where |
| // long double is binary64 (MSVC, Apple arm64), for Real = double. |
| template <typename Real> |
| bool has_complex_reference(long n) { |
| return n >= 0 || sizeof(long double) > sizeof(Real); |
| } |
| |
| // The error of a complex result is measured against the magnitude of the reference: a component that nearly |
| // cancels is not required to be accurate on its own. |
| template <typename Real> |
| bool complex_within_ulps(const std::complex<Real>& a, const std::complex<Real>& b, double ulps) { |
| double magnitude = double(numext::abs(b)); |
| double error = double(numext::abs(a - b)); |
| return error <= ulps * double(ulp_of(Real(magnitude))); |
| } |
| |
| template <typename Real, typename Exponent> |
| void check_complex_pow(const std::vector<std::complex<Real>>& bases, const std::vector<long>& exponents, double ulps) { |
| using Complex = std::complex<Real>; |
| Index size = 2 * internal::packet_traits<Complex>::size + 1; |
| ArrayX<Complex> z(size), y(size); |
| for (long n : exponents) { |
| if (!has_complex_reference<Real>(n)) continue; |
| Exponent exponent = Exponent(n); |
| for (const Complex& base : bases) { |
| z.setConstant(base); |
| y = z.pow(exponent); |
| Complex reference = reference_complex_pow(base, n); |
| for (Index i = 0; i < size; ++i) { |
| bool ok = complex_within_ulps(y(i), reference, ulps); |
| if (!ok) |
| std::cout << "pow(" << base << ", " << n << ") = " << y(i) << " != " << reference << " (" << ulps << " ulp)" |
| << std::endl; |
| VERIFY(ok); |
| } |
| } |
| } |
| } |
| |
| template <typename Real> |
| void complex_pow_test() { |
| using Complex = std::complex<Real>; |
| Index size = 2 * internal::packet_traits<Complex>::size + 1; |
| |
| // Small integer inputs have exactly representable powers. |
| { |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Complex(3, 4)); |
| VERIFY((z.pow(3) == Complex(-117, 44)).all()); |
| VERIFY((z.pow(Real(3)) == Complex(-117, 44)).all()); |
| VERIFY((z.pow(4) == Complex(-527, -336)).all()); |
| z.setConstant(Complex(1, 1)); |
| VERIFY((z.pow(2) == Complex(0, 2)).all()); |
| VERIFY((z.pow(Real(2)) == Complex(0, 2)).all()); |
| VERIFY((z.pow(4) == Complex(-4, 0)).all()); |
| VERIFY((z.pow(-2) == Complex(0, Real(-0.5))).all()); |
| VERIFY((z.pow(-4) == Complex(Real(-0.25), 0)).all()); |
| VERIFY((z.pow(0) == Complex(1, 0)).all()); |
| VERIFY((z.pow(1) == Complex(1, 1)).all()); |
| // (1+i)^(8k+7) = 2^(4k+3) (1 - i): exact, with a magnitude of sqrt(2) times a finite component. The unscaled |
| // loop must not admit it once the exponent budget is exhausted, or its intermediates overflow. |
| constexpr int k = (std::numeric_limits<Real>::max_exponent - 8) / 4; |
| Real big = Real(std::ldexp(1.0, 4 * k + 3)); |
| VERIFY((z.pow(8 * k + 7) == Complex(big, -big)).all()); |
| VERIFY((z.pow(Real(8 * k + 7)) == Complex(big, -big)).all()); |
| } |
| // Components so far apart that the smaller one underflows when both take the larger one's scale, while the |
| // power's imaginary part, 2ab, is normal: each component is checked on its own, as the magnitude-relative check |
| // would not notice the imaginary part missing. |
| { |
| Real big = Real(std::ldexp(1.0, std::numeric_limits<Real>::max_exponent / 2 - 2)); |
| Real tiny = Real(std::ldexp(1.0, std::numeric_limits<Real>::min_exponent + std::numeric_limits<Real>::digits)); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Complex(big, tiny)); |
| ArrayX<Complex> y = z.pow(2); |
| for (Index k = 0; k < size; ++k) { |
| VERIFY(within_ulps(numext::real(y(k)), big * big, 1.0)); |
| VERIFY(within_ulps(numext::imag(y(k)), Real(2) * big * tiny, 1.0)); |
| } |
| } |
| // Powers with both components subnormal, exact: 2^(5s) times (1 + i)^5 = -4 (1 + i) and (1 + i)^-5 = -(1 - i) / 8. |
| // Compared coefficient by coefficient, as a vectorized comparison may flush the expected value to zero too. |
| { |
| const int s = (std::numeric_limits<Real>::min_exponent - 5) / 5; |
| const Complex tiny = Real(std::ldexp(1.0, 5 * s + 2)) * Complex(-1, -1); |
| const Complex tinier = Real(std::ldexp(1.0, 5 * s - 3)) * Complex(-1, 1); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Real(std::ldexp(1.0, s)) * Complex(1, 1)); |
| ArrayX<Complex> y = z.pow(5), y_real = z.pow(Real(5)); |
| for (Index k = 0; k < size; ++k) VERIFY(y(k) == tiny && y_real(k) == tiny); |
| z.setConstant(Real(std::ldexp(1.0, -s)) * Complex(1, 1)); |
| y = z.pow(-5); |
| y_real = z.pow(Real(-5)); |
| for (Index k = 0; k < size; ++k) VERIFY(y(k) == tinier && y_real(k) == tinier); |
| } |
| // A zero component beside nonzero ones, (1 + i)^2 = 2i and (1 + 2i)^2 = -3 + 4i in one packet, raises neither |
| // FE_INVALID nor FE_OVERFLOW. |
| { |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Complex(1, 2)), y; |
| z(0) = Complex(1, 1); |
| VERIFY_IS_EQUAL(pow_exceptions(z, 2, y), 0); |
| for (Index k = 0; k < size; ++k) VERIFY(y(k) == (k == 0 ? Complex(0, 2) : Complex(-3, 4))); |
| VERIFY_IS_EQUAL(pow_exceptions(z, Real(2), y), 0); |
| for (Index k = 0; k < size; ++k) VERIFY(y(k) == (k == 0 ? Complex(0, 2) : Complex(-3, 4))); |
| } |
| // Reciprocals of normal bases whose norm exceeds 1 / min, c (1 + i) with |z|^2 = 4.5 * 2^(2e), or whose smaller |
| // component is subnormal: 1/(2^p + 2^-30 i) = 2^-p - 2^(-30 - 2p) i to working precision. |
| { |
| const int e = (std::numeric_limits<Real>::max_exponent - 1) / 2 - 1; |
| const Real c = Real(std::ldexp(1.5, e)); |
| ArrayX<Complex> y = ArrayX<Complex>::Constant(size, Complex(c, c)).pow(-1); |
| for (Index k = 0; k < size; ++k) { |
| VERIFY(within_ulps(numext::real(y(k)), Real(1) / (Real(2) * c), 2.0)); |
| VERIFY(within_ulps(numext::imag(y(k)), Real(-1) / (Real(2) * c), 2.0)); |
| } |
| const int p = (std::numeric_limits<Real>::max_exponent - 1) / 2 - 13; |
| y = ArrayX<Complex>::Constant(size, Complex(Real(std::ldexp(1.0, p)), Real(std::ldexp(1.0, -30)))).pow(-1); |
| for (Index k = 0; k < size; ++k) { |
| VERIFY(within_ulps(numext::real(y(k)), Real(std::ldexp(1.0, -p)), 2.0)); |
| VERIFY(within_ulps(numext::imag(y(k)), Real(-std::ldexp(1.0, -30 - 2 * p)), 2.0)); |
| } |
| } |
| // Separated components near |z| = 1, with a large |n|: (a + bi)^n = a^n (1 + i n b/a) and |
| // (b + ai)^n = i^n a^n (1 - i n b/a) to working precision, both components to double-word accuracy. The smaller |
| // component is subnormal, or normal but small enough that a negative power, which shrinks between |
| // renormalizations, would take its residuals below the normal range. |
| const Complex i_powers[4] = {Complex(1, 0), Complex(0, 1), Complex(-1, 0), Complex(0, -1)}; |
| for (int b_exponent : {std::numeric_limits<Real>::min_exponent - 6, std::numeric_limits<Real>::min_exponent + 4}) { |
| Real a = Real(1) + Real(std::ldexp(1.0, -10)); |
| Real b = Real(std::ldexp(1.0, b_exponent)); |
| for (long n : {100L, 101L, 1000L, 1003L, -99L, -100L, -1000L, -1001L}) { |
| if (!has_complex_reference<Real>(n)) continue; |
| Real power = numext::real(reference_complex_pow(Complex(a), n)); |
| Real term = Real(n) * numext::real(reference_complex_pow(Complex(a), n - 1)) * b; |
| for (bool swapped : {false, true}) { |
| Complex expected = swapped ? i_powers[((n % 4) + 4) % 4] * Complex(power, -term) : Complex(power, term); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, swapped ? Complex(b, a) : Complex(a, b)); |
| ArrayX<Complex> y = z.pow(int(n)); |
| for (Index k = 0; k < size; ++k) { |
| VERIFY(within_ulps(numext::real(y(k)), numext::real(expected), 2.0)); |
| VERIFY(within_ulps(numext::imag(y(k)), numext::imag(expected), 2.0)); |
| } |
| } |
| } |
| } |
| // (1 + 2^-40 i)^n with n = 2^30 + 1, where n r = 2^-10 makes first order 8 ulp off for float: such an n |
| // renormalizes at every step instead. The reference is the binomial series in double, whose terms |
| // C(n, k) r^k fall by 2^-10 each. |
| EIGEN_IF_CONSTEXPR (std::is_same<Real, float>::value) { |
| const long n = (1L << 30) + 1; |
| const double r = std::ldexp(1.0, -40); |
| double re = 0, im = 0, term = 1; |
| for (int k = 0; k < 12; ++k) { |
| if (k > 0) term *= double(n - k + 1) / k * r; |
| double signed_term = (k / 2) % 2 == 0 ? term : -term; |
| (k % 2 == 0 ? re : im) += signed_term; |
| } |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Complex(1, Real(r))); |
| ArrayX<Complex> y = z.pow(int(n)); |
| for (Index k = 0; k < size; ++k) { |
| VERIFY(within_ulps(numext::real(y(k)), Real(re), 1.0)); |
| VERIFY(within_ulps(numext::imag(y(k)), Real(im), 1.0)); |
| } |
| } |
| // n = 2^digits + 1 is inexact in Real, and the first-order coefficient n b / a must not round it: the |
| // imaginary part of (1 + 1.5 * 2^e i)^(+-n) is +-(3 * 2^(digits - 1) + 1.5) 2^e, correctly rounded to |
| // +-(3 * 2^(digits - 1) + 2) 2^e, and the real part is 1. |
| { |
| constexpr int digits = std::numeric_limits<Real>::digits; |
| constexpr int e = std::numeric_limits<Real>::min_exponent - 2; |
| const long long n = (1LL << digits) + 1; |
| Real im = Real(std::ldexp(3.0 * std::ldexp(1.0, digits - 1) + 2.0, e)); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, Complex(1, Real(std::ldexp(1.5, e)))); |
| for (long long sign : {1LL, -1LL}) { |
| ArrayX<Complex> y = z.pow(sign * n); |
| for (Index k = 0; k < size; ++k) VERIFY(y(k) == Complex(1, Real(sign) * im)); |
| } |
| } |
| // An element's power does not depend on its packet neighbours, one of which here forces the scaled loop: the |
| // unscaled loop, whose residuals would underflow in the smaller component (here 8 a b^7, subnormal), requires |
| // both components in range, so this base takes the scaled loop either way. |
| { |
| constexpr int min_exponent = std::numeric_limits<Real>::min_exponent; |
| constexpr int e = min_exponent / 9; |
| for (int i = 0; i < 16; ++i) { |
| Real a = std::ldexp(internal::random<Real>(Real(1), Real(2)), min_exponent - 20 - 7 * e); |
| Real b = std::ldexp(internal::random<Real>(Real(-2), Real(-1)), e); |
| ArrayX<Complex> alone = ArrayX<Complex>::Constant(size, Complex(a, b)), mixed = alone; |
| for (Index k = 1; k < size; k += 2) mixed(k) = Complex((std::numeric_limits<Real>::max)() / 4, 1); |
| ArrayX<Complex> y_alone = alone.pow(8), y_mixed = mixed.pow(8); |
| for (Index k = 0; k < size; k += 2) VERIFY(y_alone(k) == y_mixed(k)); |
| } |
| } |
| // Random bases at magnitudes where the reference cannot under- or overflow; the exponent runs past the |
| // renormalization interval of the implementation. |
| { |
| const std::vector<long> exponents = {2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 40, -2, -3, -4, -5, -8, -9, -17, -40}; |
| std::vector<Complex> bases; |
| for (int i = 0; i < 64; ++i) { |
| Real magnitude = internal::random<Real>(Real(0.5), Real(2)); |
| Real angle = internal::random<Real>(Real(-3.14159), Real(3.14159)); |
| bases.push_back(Complex(magnitude * std::cos(angle), magnitude * std::sin(angle))); |
| } |
| check_complex_pow<Real, int>(bases, exponents, 2.0); |
| check_complex_pow<Real, Real>(bases, exponents, 2.0); |
| } |
| // Magnitudes across the range, against the same base scaled by a power of two, which only moves the exponent. |
| // The scale runs through the range in steps, ends included: at its extremes |z| exceeds what the reciprocal's |
| // norm can represent. |
| { |
| constexpr int max_exponent = std::numeric_limits<Real>::max_exponent; |
| for (long n : {1L, 3L, 8L, 100L, -1L, -2L, -3L, -8L, -100L}) { |
| int extreme = (max_exponent - 4) / int(numext::abs(n)) - 1; // |base|^n within 2^(+-|n|) stays finite |
| for (int i = 0; i <= 32; ++i) { |
| int e = -extreme + (2 * extreme * i) / 32; |
| Real scale = Real(std::ldexp(1.0, e)); |
| Real magnitude = internal::random<Real>(Real(0.5), Real(1.5)); |
| Real angle = internal::random<Real>(Real(-3.14159), Real(3.14159)); |
| Complex base(magnitude * std::cos(angle), magnitude * std::sin(angle)); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, base), w = ArrayX<Complex>::Constant(size, base * scale); |
| ArrayX<Complex> zn = z.pow(int(n)), wn = w.pow(int(n)); |
| Real power_of_scale = Real(std::pow(double(scale), double(n))); |
| for (Index k = 0; k < size; ++k) { |
| Complex expected = zn(k) * power_of_scale; |
| VERIFY(complex_within_ulps(wn(k), expected, 2.0)); |
| } |
| } |
| } |
| } |
| // Zero and infinite bases are the points 0 and infinity of the extended plane, any value with an infinite |
| // component being the infinity +inf + 0i of std::proj: a zero base gives +0 + 0i for n > 1 and +inf + 0i for n < 0, |
| // an infinite base the reverse, whatever the signs of their zeros; a NaN component without an infinite one gives |
| // NaN + NaN i. std::pow leaves all of these to the implementation. In a packet, in the scalar tail, and next to |
| // ordinary bases in the same packet, for integer and floating exponents; n = 1 returns the base. |
| { |
| Real inf = std::numeric_limits<Real>::infinity(); |
| Real nan = std::numeric_limits<Real>::quiet_NaN(); |
| auto same = [](const Complex& a, const Complex& b) { |
| auto same_real = [](const Real& x, const Real& y) { |
| return ((numext::isnan)(x) && (numext::isnan)(y)) || (x == y && std::signbit(x) == std::signbit(y)); |
| }; |
| return same_real(numext::real(a), numext::real(b)) && same_real(numext::imag(a), numext::imag(b)); |
| }; |
| std::vector<Complex> zeros = {Complex(0, 0), Complex(-Real(0), 0), Complex(0, -Real(0)), |
| Complex(-Real(0), -Real(0))}; |
| std::vector<Complex> infinities = {Complex(inf, 0), Complex(-inf, 2), Complex(0, inf), Complex(-2, -inf), |
| Complex(inf, inf), Complex(-inf, inf), Complex(inf, nan), Complex(nan, -inf)}; |
| std::vector<Complex> nans = {Complex(nan, 1), Complex(1, nan), Complex(nan, 0), Complex(nan, nan)}; |
| for (int n : {2, 3, 4, 5, -1, -2, -3, -4}) { |
| for (int kind = 0; kind < 3; ++kind) { |
| const std::vector<Complex>& bases = kind == 0 ? zeros : kind == 1 ? infinities : nans; |
| Complex expected = kind == 2 ? Complex(nan, nan) : (kind == 0) == (n > 0) ? Complex(0, 0) : Complex(inf, 0); |
| for (const Complex& base : bases) { |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, base); |
| for (Index k = 1; k < size; k += 2) z(k) = Complex(Real(0.75), Real(-1.25)); |
| ArrayX<Complex> y = z.pow(n), y_real = z.pow(Real(n)), ordinary = ArrayX<Complex>::Constant(1, z(1)).pow(n); |
| for (Index k = 0; k < size; k += 2) VERIFY(same(y(k), expected) && same(y_real(k), expected)); |
| for (Index k = 1; k < size; k += 2) VERIFY(same(y(k), ordinary(0))); |
| VERIFY(same(ArrayX<Complex>::Constant(1, base).pow(1)(0), base)); |
| } |
| } |
| } |
| } |
| // A base with one zero component is the limit of a base whose component is tiny and of the same sign, and the |
| // zero component of its power takes the sign of that limit: here of the power of the perturbed base, computed |
| // in double by repeated multiplication, where the tiny component cannot cancel. |
| for (Real a : {Real(2), Real(-2), Real(0.5), Real(-3)}) { |
| for (Real s : {Real(0), -Real(0)}) { |
| for (bool swapped : {false, true}) { |
| for (int n = -12; n <= 12; ++n) { |
| if (n == 0) continue; |
| double tiny = std::copysign(std::ldexp(1.0, -60), double(s)); |
| std::complex<double> w = swapped ? std::complex<double>(tiny, a) : std::complex<double>(a, tiny); |
| if (n < 0) w = std::conj(w) / std::norm(w); |
| std::complex<double> reference = w; |
| for (int k = 1; k < numext::abs(n); ++k) reference *= w; |
| bool real_is_zero = std::abs(reference.real()) < std::abs(reference.imag()); |
| double zero_sign = real_is_zero ? reference.real() : reference.imag(); |
| ArrayX<Complex> z = ArrayX<Complex>::Constant(size, swapped ? Complex(s, a) : Complex(a, s)); |
| ArrayX<Complex> y = z.pow(n); |
| for (Index k = 0; k < size; ++k) { |
| Real zero = real_is_zero ? numext::real(y(k)) : numext::imag(y(k)); |
| VERIFY(zero == Real(0) && std::signbit(zero) == std::signbit(zero_sign)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // half and bfloat16 keep plain repeated squaring, whose error grows like n ulp, for integer exponents and small |
| // floating-point ones, and generic_pow (pow through float) beyond; with F16C their packets take the vectorized |
| // path. Check both against float. |
| template <typename Scalar> |
| void narrow_pow_test() { |
| Index size = 2 * internal::packet_traits<Scalar>::size + 1; |
| ArrayX<Scalar> x(size), y(size); |
| for (long n : {1L, 2L, 3L, 5L, 7L, 8L, 16L, 100L, 4097L, -1L, -2L, -3L, -8L, -100L}) { |
| // Powers within the normal range of the type: |x|^n in 2^(+-(max_exponent - 2)). |
| double limit = double(std::numeric_limits<Scalar>::max_exponent - 2) / double(numext::abs(n)); |
| for (int i = 0; i < 32; ++i) { |
| float base = float(std::exp2(internal::random<double>(-limit, limit))); |
| x.setConstant(Scalar(i % 2 ? -base : base)); |
| Scalar reference = Scalar(std::pow(float(x(0)), float(n))); |
| double ulps = 2.0 + double(numext::abs(n)); |
| y = x.pow(int(n)); |
| for (Index k = 0; k < size; ++k) { |
| if (!within_ulps(y(k), reference, ulps)) |
| std::cout << "pow(" << x(0) << ", " << n << ") = " << y(k) << " != " << reference << std::endl; |
| VERIFY(within_ulps(y(k), reference, ulps)); |
| } |
| if (n == long(Scalar(float(n)))) { |
| y = x.pow(Scalar(float(n))); |
| for (Index k = 0; k < size; ++k) VERIFY(within_ulps(y(k), reference, ulps)); |
| } |
| } |
| } |
| } |
| |
| // complex<long double> keeps plain squaring; its reciprocal must stay range-safe, as the double-word path's |
| // conj(z)/|z|^2 would not be for long double's wider exponent range. |
| void complex_long_double_range_test() { |
| using Complex = std::complex<long double>; |
| constexpr int max_exponent = std::numeric_limits<long double>::max_exponent; |
| if (max_exponent <= std::numeric_limits<double>::max_exponent) return; |
| ArrayX<Complex> z(9), y(9); |
| auto close = [](const Complex& a, const Complex& b, long double ulps) { |
| return std::abs(a - b) <= ulps * std::numeric_limits<long double>::epsilon() * std::abs(b); |
| }; |
| // Built with ldexp rather than written as literals: 1e3000L and the like are out of range, and so a |
| // compile-time error, wherever long double is binary64, which the run-time guard above cannot prevent. |
| for (int e : {(4 * max_exponent) / 5, -(4 * max_exponent) / 5, (2 * max_exponent) / 5, -(2 * max_exponent) / 5}) { |
| long double magnitude = std::ldexp(1.0L, e); |
| z.setConstant(Complex(magnitude, magnitude / 3)); |
| y = z.pow(-1); |
| Complex expected = Complex(1) / z(0); |
| for (Index k = 0; k < 9; ++k) VERIFY(close(y(k), expected, 4.0L)); |
| if (2 * numext::abs(e) >= max_exponent) continue; // the square would leave the range |
| y = z.pow(-2); |
| expected = Complex(1) / (z(0) * z(0)); |
| for (Index k = 0; k < 9; ++k) VERIFY(close(y(k), expected, 8.0L)); |
| } |
| } |
| |
| EIGEN_DECLARE_TEST(unary_pow) { |
| for (int i = 0; i < g_repeat; i++) { |
| CALL_SUBTEST_1(real_pow_test<float>()); |
| CALL_SUBTEST_2(real_pow_test<double>()); |
| CALL_SUBTEST_3(complex_pow_test<float>()); |
| CALL_SUBTEST_4(complex_pow_test<double>()); |
| CALL_SUBTEST_5(narrow_pow_test<half>()); |
| CALL_SUBTEST_6(narrow_pow_test<bfloat16>()); |
| CALL_SUBTEST_7(complex_long_double_range_test()); |
| } |
| } |