| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #define EIGEN_RUNTIME_NO_MALLOC |
| #include "main.h" |
| #include <Eigen/Core> |
| #include <Eigen/Geometry> |
| #include "fp_control.h" |
| #define EIGEN_TEST_ANNOYING_SCALAR_DONT_THROW |
| #include "AnnoyingScalar.h" |
| #include "CustomComplex.h" |
| |
| template <typename Scalar, int Options> |
| void approx_comparisons_floating() { |
| using Real = typename NumTraits<Scalar>::Real; |
| using MatrixType = Matrix<Scalar, Dynamic, Dynamic, Options>; |
| const Real precision = Real(0.125); |
| MatrixType x(3, 5), y(3, 5); |
| for (const Real scale : |
| {Real(1), NumTraits<Real>::highest() / Real(32), (numext::numeric_limits<Real>::min)() * Real(32)}) { |
| x.setConstant(Scalar(scale)); |
| y = x * Real(2); |
| internal::set_is_malloc_allowed(false); |
| VERIFY(x.isApprox(x, precision)); |
| VERIFY(!x.isApprox(y, precision)); |
| VERIFY(!y.isApprox(x, precision)); |
| VERIFY(!x.array().isApprox(y.array(), precision)); |
| VERIFY(!x.transpose().isApprox(y.transpose(), precision)); |
| VERIFY(!x.isMuchSmallerThan(y, precision)); |
| VERIFY(x.isMuchSmallerThan(y, Real(0.5))); |
| VERIFY(!x.isMuchSmallerThan(scale, precision)); |
| VERIFY(x.isMuchSmallerThan(Real(4) * scale, Real(1))); |
| VERIFY(x.isApprox(y, Real(1))); |
| VERIFY(x.isApprox(y, Real(-1))); |
| VERIFY(x.isApprox(x, Real(0))); |
| VERIFY(!x.isApprox(y, Real(0))); |
| y = x * (Real(1) + precision / Real(2)); |
| VERIFY(x.isApprox(y, precision)); |
| VERIFY(x.array().isApprox(y.array(), precision)); |
| VERIFY((x.template block<2, 3>(0, 1).isApprox(y.template block<2, 3>(0, 1), precision))); |
| internal::set_is_malloc_allowed(true); |
| // The scaled path evaluates a lazy operand, which allocates for dynamic sizes. |
| VERIFY(!x.isApprox(MatrixType::Zero(3, 5), precision)); |
| Matrix<Scalar, Dynamic, Dynamic, Options == ColMajor ? RowMajor : ColMajor> other = y; |
| VERIFY(x.isApprox(other, precision)); |
| } |
| |
| // Norms and differences can exceed the scalar range even though every coefficient is finite. |
| x.setConstant(Scalar(NumTraits<Real>::highest())); |
| y = -x; |
| VERIFY(x.isApprox(x, Real(0))); |
| VERIFY(!x.isApprox(y, Real(1))); |
| VERIFY(x.isApprox(y, Real(2))); |
| VERIFY(!x.isMuchSmallerThan(x, precision)); |
| // The squared difference, but not the bound, can overflow the accumulator here. |
| const Real root = numext::ldexp(Real(1), (NumTraits<Real>::max_exponent() - 1) / 2); |
| x.setZero(); |
| x(0, 0) = Scalar(root); |
| y = -x; |
| VERIFY(!x.isApprox(y, Real(1.9))); |
| VERIFY(x.isApprox(y, Real(2))); |
| |
| // Squaring either the difference or the tolerance would underflow. |
| const Real tiny = (numext::numeric_limits<Real>::min)() * Real(16); |
| x.setZero(); |
| y.setZero(); |
| x(0, 0) = y(0, 0) = Scalar(1); |
| x(2, 4) = Scalar(tiny); |
| VERIFY(!x.isApprox(y, Real(0))); |
| VERIFY(!x.isApprox(y, tiny / Real(2))); |
| VERIFY(x.isApprox(y, tiny * Real(2))); |
| VERIFY(x.isApprox(y, NumTraits<Real>::highest())); |
| x.setZero(); |
| VERIFY(x.isMuchSmallerThan(y, Real(0))); |
| VERIFY(x.isMuchSmallerThan(Real(1), Real(0))); |
| |
| for (Index rows : {Index(0), Index(3)}) { |
| x.resize(rows, 0); |
| y.resize(rows, 0); |
| VERIFY(x.isApprox(y)); |
| VERIFY(x.isMuchSmallerThan(y)); |
| VERIFY(x.isMuchSmallerThan(Real(0))); |
| } |
| } |
| |
| // The scaled path views plain objects, maps and their blocks in place, whatever their strides, and evaluates other |
| // operands, on the stack when their size is bounded. |
| template <typename Scalar> |
| void approx_comparisons_strided_operands() { |
| using Real = typename NumTraits<Scalar>::Real; |
| using ColMatrix = Matrix<Scalar, Dynamic, Dynamic, ColMajor>; |
| using RowMatrix = Matrix<Scalar, Dynamic, Dynamic, RowMajor>; |
| using StridedMap = Map<const ColMatrix, 0, Stride<Dynamic, Dynamic>>; |
| const Real precision = Real(0.125); |
| // Squares of these coefficients underflow, which selects the scaled path. |
| const Real scale = (numext::numeric_limits<Real>::min)() * Real(32); |
| const ColMatrix x = ColMatrix::Random(6, 8) * scale; |
| const ColMatrix y = x * (Real(1) + precision / Real(2)); |
| const ColMatrix z = x * Real(2); |
| const RowMatrix xr = x, yr = y, zr = z; |
| const Stride<Dynamic, Dynamic> stride(12, 2); |
| const ColMatrix ys = StridedMap(y.data(), 3, 4, stride); |
| internal::set_is_malloc_allowed(false); |
| VERIFY(xr.isApprox(y, precision)); |
| VERIFY(!xr.isApprox(z, precision)); |
| VERIFY(x.row(2).isApprox(yr.row(2), precision)); |
| VERIFY(!x.row(2).isApprox(zr.row(2), precision)); |
| VERIFY(xr.col(3).isApprox(y.col(3), precision)); |
| VERIFY(!xr.col(3).isApprox(z.col(3), precision)); |
| VERIFY(StridedMap(x.data(), 3, 4, stride).isApprox(ys, precision)); |
| VERIFY(!StridedMap(x.data(), 3, 4, stride).isApprox(StridedMap(z.data(), 3, 4, stride), precision)); |
| VERIFY((Real(2) * x.template topLeftCorner<3, 3>()).isApprox(Real(2) * y.template topLeftCorner<3, 3>(), precision)); |
| VERIFY(!(Real(2) * x.template topLeftCorner<3, 3>()).isApprox(x.template topLeftCorner<3, 3>(), precision)); |
| internal::set_is_malloc_allowed(true); |
| // These have direct access, but their strides do not address every coefficient. |
| VERIFY(x(2, seq(0, 7, 2)).isApprox(yr(2, seq(0, 7, 2)), precision)); |
| VERIFY(xr(seq(0, 5, 2), 3).isApprox(y(seq(0, 5, 2), 3), precision)); |
| VERIFY(x.row(2).realView().isApprox(yr.row(2).realView(), precision)); |
| } |
| |
| template <typename Real> |
| void approx_comparisons_rounding_boundary() { |
| using Vector = Matrix<Real, 1, 1>; |
| const Real eps = NumTraits<Real>::epsilon(); |
| // One nonzero component and power-of-two scales make the relative difference exactly 8*eps. |
| for (int exponent : {0, NumTraits<Real>::min_exponent() + 4, NumTraits<Real>::max_exponent() - 8}) { |
| const Real scale = numext::ldexp(Real(1), exponent); |
| const Vector x = Vector::Constant(scale); |
| const Vector y = Vector::Constant(scale * (Real(1) + Real(8) * eps)); |
| VERIFY(!x.isApprox(y, Real(7) * eps)); |
| VERIFY(x.isApprox(y, Real(8) * eps)); |
| VERIFY(x.isApprox(y, Real(9) * eps)); |
| } |
| } |
| |
| template <typename Real> |
| void approx_comparisons_flush_to_zero() { |
| ScopedFlushToZero flushToZero; |
| using Vector = Matrix<Real, 2, 1>; |
| // The difference of these normal operands is subnormal. |
| const Real normalMin = (numext::numeric_limits<Real>::min)(); |
| const Vector x(Real(1.5) * normalMin, Real(0)), y(normalMin, Real(0)); |
| VERIFY(!x.isApprox(y, Real(0.25))); |
| VERIFY(!x.isApprox(y, Real(0))); |
| VERIFY(x.isApprox(y, Real(0.5))); |
| VERIFY_IS_EQUAL(test_relative_error(x, y), Real(0.5)); |
| approx_comparisons_rounding_boundary<Real>(); |
| } |
| |
| // The squared differences, 2^-118 and just below 2^-126, accumulate in float, where FTZ flushes the second. |
| template <typename = void> |
| void approx_comparisons_flush_to_zero_bfloat16() { |
| ScopedFlushToZero flushToZero; |
| using Vector = Matrix<bfloat16, 2, 1>; |
| // Opaque, so the compiler cannot square it with IEEE subnormals at compile time. |
| volatile float tail = numext::ldexp(1.9921875f, -64); |
| const Vector x(bfloat16(numext::ldexp(1.0f, -52)), bfloat16(0.0f)); |
| const Vector y(bfloat16(numext::ldexp(1.0f + 1.0f / 128, -52)), bfloat16(float(tail))); |
| VERIFY(!x.isApprox(y, bfloat16(1.0f / 128))); |
| } |
| |
| template <typename Real> |
| void approx_comparisons_special_values() { |
| using Vector = Matrix<Real, 2, 1>; |
| const Real inf = NumTraits<Real>::infinity(); |
| const Real nan = NumTraits<Real>::quiet_NaN(); |
| Vector x = Vector::Ones(), y = Vector::Ones(); |
| for (Index i = 0; i < 2; ++i) { |
| y = x; |
| y(i) = nan; |
| VERIFY(!x.isApprox(y)); |
| VERIFY(!y.isApprox(x)); |
| VERIFY(!y.isApprox(y)); |
| VERIFY(!x.isMuchSmallerThan(y)); |
| VERIFY(!y.isMuchSmallerThan(x)); |
| y(i) = inf; |
| VERIFY(!x.isApprox(y)); |
| VERIFY(!y.isApprox(y)); |
| VERIFY(!y.isMuchSmallerThan(y)); |
| VERIFY(x.isMuchSmallerThan(y)); |
| } |
| VERIFY(!x.isApprox(x, nan)); |
| VERIFY(!x.isMuchSmallerThan(x, nan)); |
| VERIFY(!x.isMuchSmallerThan(nan)); |
| VERIFY(!x.isMuchSmallerThan(inf, Real(0))); |
| |
| if (subnormalDivisionIsExact<Real>()) { |
| const Real tiny = (numext::numeric_limits<Real>::denorm_min)(); |
| x.setConstant(tiny); |
| y = Real(2) * x; |
| VERIFY(!x.isApprox(y)); |
| VERIFY(x.isApprox(x, Real(0))); |
| VERIFY(!x.isApprox(Vector::Zero())); |
| VERIFY(!x.isMuchSmallerThan(y)); |
| VERIFY(!x.isMuchSmallerThan(tiny)); |
| } |
| } |
| |
| template <typename Scalar> |
| void approx_comparisons_exact() { |
| using MatrixType = Matrix<Scalar, 2, 2>; |
| const MatrixType zero = MatrixType::Zero(); |
| const MatrixType one = MatrixType::Ones(); |
| VERIFY(zero.isApprox(zero)); |
| VERIFY(one.isApprox(one)); |
| VERIFY(!zero.isApprox(one)); |
| VERIFY(!one.isApprox(zero)); |
| VERIFY(zero.isMuchSmallerThan(one)); |
| VERIFY(zero.isMuchSmallerThan(Scalar(0))); |
| VERIFY(!one.isMuchSmallerThan(one)); |
| VERIFY(!one.isMuchSmallerThan(Scalar(1))); |
| } |
| |
| template <typename = void> |
| void approx_comparisons_expressions() { |
| Matrix2d x; |
| x << 1, 2, 3, 4; |
| VERIFY((x * x).isApprox((x * x).eval())); |
| VERIFY((x + x).isApprox(2 * x)); |
| VERIFY((x * x * 0).isMuchSmallerThan(x * x)); |
| VERIFY((x * x * 0).isMuchSmallerThan(1.0)); |
| VERIFY(!Matrix2f::Ones().isMuchSmallerThan(Matrix2d::Constant(1e200), 0.0f)); |
| VERIFY(Matrix2f::Ones().isMuchSmallerThan(Matrix2d::Constant(1e200))); |
| VERIFY(!Matrix2d::Ones().isMuchSmallerThan(Matrix2f::Constant(1e-30f))); |
| VERIFY(Matrix2d::Constant(1e-300).isMuchSmallerThan(Matrix2f::Constant(1e10f), 1e-300)); |
| VERIFY(!Matrix2f::Constant(1e-23f).isMuchSmallerThan(Matrix2d::Constant(1e-20), 1e-5f)); |
| VERIFY(Matrix2f::Constant(1e20f).isMuchSmallerThan(Matrix2d::Constant(1e30), 1e-5f)); |
| const Matrix2d tiny = Matrix2d::Constant(1e-200); |
| const Matrix2cd complexTiny = tiny.cast<std::complex<double>>(); |
| VERIFY(tiny.isApprox(complexTiny)); |
| VERIFY(complexTiny.isApprox(tiny)); |
| STATIC_CHECK(!internal::use_scaled_comparison<AnnoyingScalar>::value); |
| approx_comparisons_exact<AnnoyingScalar>(); |
| const Matrix<AnnoyingScalar, 2, 2> custom = Matrix<AnnoyingScalar, 2, 2>::Ones(); |
| VERIFY(test_isCwiseApprox(custom, custom, false)); |
| VERIFY(test_relative_error(custom, custom) == AnnoyingScalar(0)); |
| // A custom complex scalar over a built-in real has no dedicated coefficient overload. |
| using CustomComplexScalar = CustomComplex<double>; |
| STATIC_CHECK(!internal::use_scaled_comparison<CustomComplexScalar>::value); |
| const Matrix<CustomComplexScalar, 2, 2> customComplex = |
| Matrix<CustomComplexScalar, 2, 2>::Constant(CustomComplexScalar(1, 2)); |
| VERIFY(test_isCwiseApprox(customComplex, customComplex, true)); |
| VERIFY(test_isCwiseApprox(customComplex, customComplex, false)); |
| VERIFY(!test_isCwiseApprox(customComplex, (customComplex * CustomComplexScalar(2)).eval(), false)); |
| VERIFY_IS_EQUAL(test_relative_error(customComplex, customComplex), 0.0); |
| } |
| |
| template <typename Real> |
| void approx_comparisons_complex_components() { |
| using Complex = std::complex<Real>; |
| using Vector = Matrix<Complex, 2, 1>; |
| const Real big = NumTraits<Real>::highest(); |
| const Vector x = Vector::Constant(Complex(big, big)); |
| const Vector y = -x; |
| VERIFY(x.isApprox(x, Real(0))); |
| VERIFY(!x.isApprox(y, Real(1))); |
| VERIFY(x.isApprox(y, Real(2))); |
| VERIFY(!x.isMuchSmallerThan(y, Real(0.5))); |
| VERIFY(!x.isMuchSmallerThan(big, Real(1))); |
| } |
| |
| // nested_eval keeps these cheap expressions lazy, so they reach the scaled path without direct access. |
| template <typename Real> |
| void approx_comparisons_lazy_operands() { |
| using Complex = std::complex<Real>; |
| using Vector3 = Matrix<Real, 3, 1>; |
| using Vector4 = Matrix<Real, 4, 1>; |
| using Array4 = Array<Real, 4, 1>; |
| const Real precision = Real(0.125); |
| const Array4 base = Array4::LinSpaced(Real(1), Real(2)); |
| for (int exponent : {0, NumTraits<Real>::min_exponent() + 4, NumTraits<Real>::max_exponent() - 8}) { |
| const Real scale = numext::ldexp(Real(1), exponent); |
| const Vector3 x = Vector3::Constant(scale); |
| Vector4 y; |
| y << x, Real(1); |
| VERIFY(x.homogeneous().isApprox(y, precision)); |
| VERIFY(y.isApprox(x.homogeneous(), precision)); |
| VERIFY_IS_EQUAL(test_relative_error(x.homogeneous(), y), Real(0)); |
| VERIFY(!x.homogeneous().isMuchSmallerThan(y, precision)); |
| y *= Real(2); |
| VERIFY(!x.homogeneous().isApprox(y, precision)); |
| VERIFY(!y.isApprox(x.homogeneous(), precision)); |
| VERIFY(test_relative_error(x.homogeneous(), y) > precision); |
| |
| const Array4 a = base * scale; |
| VERIFY((a + a).isApprox(a * Real(2), precision)); |
| VERIFY(!(a + a).isApprox(a, precision)); |
| VERIFY_IS_EQUAL(test_relative_error(a + a, a * Real(2)), Real(0)); |
| } |
| // A unary functor whose result is complex but whose argument is real. |
| const Complex power(Real(2), Real(1)); |
| VERIFY(base.pow(power).isApprox(base.template cast<Complex>().pow(power), precision)); |
| VERIFY(test_relative_error(base.pow(power), base.template cast<Complex>().pow(power)) <= precision); |
| } |
| |
| template <typename Real> |
| void approx_comparisons_scale_invariance() { |
| using Vector = Matrix<Real, 17, 1>; |
| const Real precision = Real(0.125); |
| const long double precision2 = static_cast<long double>(precision) * static_cast<long double>(precision); |
| for (int repeat = 0; repeat < g_repeat; ++repeat) { |
| const Vector a = Vector::Random(); |
| for (int close = 0; close < 2; ++close) { |
| Vector b = Vector::Random(); |
| if (close) b = a + precision * Real(0.25) * b; |
| long double difference = 0, a2 = 0, b2 = 0; |
| for (Index i = 0; i < a.size(); ++i) { |
| const long double av = static_cast<long double>(a(i)); |
| const long double bv = static_cast<long double>(b(i)); |
| difference += (av - bv) * (av - bv); |
| a2 += av * av; |
| b2 += bv * bv; |
| } |
| const bool expected = difference <= precision2 * (std::min)(a2, b2); |
| for (int exponent : {0, NumTraits<Real>::min_exponent() + 4, NumTraits<Real>::max_exponent() - 8}) { |
| const Real scale = numext::ldexp(Real(1), exponent); |
| const Vector x = a * scale, y = b * scale; |
| VERIFY_IS_EQUAL(x.isApprox(y, precision), expected); |
| } |
| } |
| } |
| } |
| |
| EIGEN_DECLARE_TEST(approx_comparisons) { |
| CALL_SUBTEST_1((approx_comparisons_floating<float, ColMajor>())); |
| CALL_SUBTEST_1((approx_comparisons_floating<double, RowMajor>())); |
| CALL_SUBTEST_1(approx_comparisons_strided_operands<float>()); |
| CALL_SUBTEST_1(approx_comparisons_strided_operands<double>()); |
| CALL_SUBTEST_1(approx_comparisons_special_values<float>()); |
| CALL_SUBTEST_1(approx_comparisons_special_values<double>()); |
| CALL_SUBTEST_1(approx_comparisons_rounding_boundary<float>()); |
| CALL_SUBTEST_1(approx_comparisons_rounding_boundary<double>()); |
| CALL_SUBTEST_1(approx_comparisons_scale_invariance<float>()); |
| CALL_SUBTEST_1(approx_comparisons_scale_invariance<double>()); |
| CALL_SUBTEST_1(approx_comparisons_flush_to_zero<float>()); |
| CALL_SUBTEST_1(approx_comparisons_flush_to_zero<double>()); |
| CALL_SUBTEST_2((approx_comparisons_floating<std::complex<float>, RowMajor>())); |
| CALL_SUBTEST_2((approx_comparisons_floating<std::complex<double>, ColMajor>())); |
| CALL_SUBTEST_2(approx_comparisons_strided_operands<std::complex<float>>()); |
| CALL_SUBTEST_2(approx_comparisons_complex_components<float>()); |
| CALL_SUBTEST_2(approx_comparisons_complex_components<double>()); |
| CALL_SUBTEST_2(approx_comparisons_lazy_operands<float>()); |
| CALL_SUBTEST_2(approx_comparisons_lazy_operands<double>()); |
| CALL_SUBTEST_3((approx_comparisons_floating<half, ColMajor>())); |
| CALL_SUBTEST_3((approx_comparisons_floating<bfloat16, RowMajor>())); |
| CALL_SUBTEST_3((approx_comparisons_floating<long double, ColMajor>())); |
| CALL_SUBTEST_3(approx_comparisons_rounding_boundary<half>()); |
| CALL_SUBTEST_3(approx_comparisons_rounding_boundary<bfloat16>()); |
| CALL_SUBTEST_3(approx_comparisons_flush_to_zero<bfloat16>()); |
| CALL_SUBTEST_3(approx_comparisons_flush_to_zero_bfloat16<>()); |
| CALL_SUBTEST_4(approx_comparisons_exact<int>()); |
| CALL_SUBTEST_4(approx_comparisons_exact<unsigned int>()); |
| CALL_SUBTEST_4(approx_comparisons_exact<bool>()); |
| CALL_SUBTEST_4(approx_comparisons_expressions<>()); |
| } |