blob: 3f28a1b337c68afb43fc219de3b2f6c230784e86 [file] [edit]
// 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<>());
}