blob: 785a630b22d9e7a9391a3bf5adab9a3d1ea14f59 [file] [edit]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2006-2008 Benoit Jacob <jacob.benoit.1@gmail.com>
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_APPROX_COMPARISONS_H
#define EIGEN_APPROX_COMPARISONS_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
namespace internal {
// Keep custom scalars on the algebraic path: exponent scaling requires binary floating-point arithmetic.
template <typename Scalar>
struct use_scaled_comparison
: bool_constant<(std::is_floating_point<Scalar>::value && std::numeric_limits<Scalar>::radix == 2) ||
std::is_same<Scalar, half>::value || std::is_same<Scalar, bfloat16>::value> {};
template <typename RealScalar>
struct use_scaled_comparison<std::complex<RealScalar>> : use_scaled_comparison<RealScalar> {};
// A nonnegative magnitude fraction * 2^exponent, including norms larger than the scalar range. Positive finite
// magnitudes order lexicographically by (exponent, fraction), the member order.
template <typename RealScalar>
struct comparison_magnitude {
int exponent = 0;
RealScalar fraction;
EIGEN_DEVICE_FUNC explicit comparison_magnitude(const RealScalar& value) : fraction(value) {
if (value > RealScalar(0) && value <= NumTraits<RealScalar>::highest()) {
EIGEN_USING_STD(frexp);
fraction = frexp(value, &exponent);
}
}
template <typename OtherRealScalar>
EIGEN_DEVICE_FUNC explicit comparison_magnitude(const comparison_magnitude<OtherRealScalar>& value)
: exponent(value.exponent), fraction(RealScalar(value.fraction)) {}
EIGEN_DEVICE_FUNC void multiply(const RealScalar& value) {
const comparison_magnitude factor(value);
const comparison_magnitude product(fraction * factor.fraction);
fraction = product.fraction;
exponent += factor.exponent + product.exponent;
}
EIGEN_DEVICE_FUNC bool isFinite() const { return fraction <= RealScalar(1); }
};
template <typename RealScalar>
EIGEN_DEVICE_FUNC bool operator<=(const comparison_magnitude<RealScalar>& x,
const comparison_magnitude<RealScalar>& y) {
if (!x.isFinite()) return false;
// Zero and non-finite magnitudes carry no exponent.
if (!y.isFinite() || x.fraction == RealScalar(0) || y.fraction == RealScalar(0)) return x.fraction <= y.fraction;
return x.exponent < y.exponent || (x.exponent == y.exponent && x.fraction <= y.fraction);
}
template <typename RealScalar, typename OtherRealScalar>
EIGEN_DEVICE_FUNC bool operator<=(const comparison_magnitude<RealScalar>& x,
const comparison_magnitude<OtherRealScalar>& y) {
using Common = std::common_type_t<RealScalar, OtherRealScalar>;
return comparison_magnitude<Common>(x) <= comparison_magnitude<Common>(y);
}
// Types whose data(), rowStride() and colStride() address every coefficient. DirectAccessBit alone does not promise
// this: a single-row IndexedView of a column-major matrix and the RealView of a strided complex block report strides
// that do not.
template <typename X>
struct scaled_comparison_viewable : std::false_type {};
template <typename Scalar, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
struct scaled_comparison_viewable<Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>> : std::true_type {};
template <typename Scalar, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
struct scaled_comparison_viewable<Array<Scalar, Rows, Cols, Options, MaxRows, MaxCols>> : std::true_type {};
template <typename PlainObjectType, int Options, typename StrideType>
struct scaled_comparison_viewable<Map<PlainObjectType, Options, StrideType>> : std::true_type {};
template <typename PlainObjectType, int Options, typename StrideType>
struct scaled_comparison_viewable<Ref<PlainObjectType, Options, StrideType>> : std::true_type {};
template <typename XprType, int BlockRows, int BlockCols, bool InnerPanel>
struct scaled_comparison_viewable<Block<XprType, BlockRows, BlockCols, InnerPanel>>
: scaled_comparison_viewable<std::remove_const_t<XprType>> {};
template <typename XprType>
struct scaled_comparison_viewable<Transpose<XprType>> : scaled_comparison_viewable<std::remove_const_t<XprType>> {};
template <typename XprType>
struct scaled_comparison_viewable<ArrayWrapper<XprType>> : scaled_comparison_viewable<std::remove_const_t<XprType>> {};
template <typename XprType>
struct scaled_comparison_viewable<MatrixWrapper<XprType>> : scaled_comparison_viewable<std::remove_const_t<XprType>> {};
// The scaled path reduces each operand several times, so it runs on at most three view types per scalar type rather
// than being instantiated for every operand expression. A viewable operand with direct access is viewed in place,
// keeping packet access when its inner stride is one; any other operand, including a lazy expression or a shape that
// hides operator*(Scalar) as Homogeneous does, is evaluated first, which allocates only for dynamic sizes and for
// fixed sizes above EIGEN_STACK_ALLOCATION_LIMIT.
template <typename X, bool DirectAccess = has_direct_access<X>::value && scaled_comparison_viewable<X>::value,
bool UnitInnerStride = inner_stride_at_compile_time<X>::value == 1>
struct scaled_comparison_operand {
using View =
Map<const Matrix<typename X::Scalar, Dynamic, Dynamic, X::IsRowMajor ? RowMajor : ColMajor>, 0, OuterStride<>>;
EIGEN_DEVICE_FUNC explicit scaled_comparison_operand(const X& x)
: view(x.data(), x.rows(), x.cols(), OuterStride<>(x.outerStride())) {}
View view;
};
template <typename X>
struct scaled_comparison_operand<X, true, false> {
using View = Map<const Matrix<typename X::Scalar, Dynamic, Dynamic>, 0, Stride<Dynamic, Dynamic>>;
EIGEN_DEVICE_FUNC explicit scaled_comparison_operand(const X& x)
: view(x.data(), x.rows(), x.cols(), Stride<Dynamic, Dynamic>(x.colStride(), x.rowStride())) {}
View view;
};
template <typename X, bool UnitInnerStride>
struct scaled_comparison_operand<X, false, UnitInnerStride> {
// A fixed capacity the stack limit would reject makes the comparison fail to compile for an operand the caller
// never stores, so the temporary takes it only where DenseStorage accepts it.
static constexpr bool FixedCapacity =
EIGEN_STACK_ALLOCATION_LIMIT == 0 ||
(X::MaxSizeAtCompileTime != Dynamic &&
std::ptrdiff_t(X::MaxSizeAtCompileTime) * std::ptrdiff_t(sizeof(typename X::Scalar)) <=
std::ptrdiff_t(EIGEN_STACK_ALLOCATION_LIMIT));
// Column-major unless it holds at most one row, where Matrix requires row-major storage.
using Plain =
Matrix<typename X::Scalar, Dynamic, Dynamic,
X::MaxRowsAtCompileTime == 1 && X::MaxColsAtCompileTime != 1 ? RowMajor : ColMajor,
FixedCapacity ? X::MaxRowsAtCompileTime : Dynamic, FixedCapacity ? X::MaxColsAtCompileTime : Dynamic>;
using View = typename scaled_comparison_operand<Plain>::View;
EIGEN_DEVICE_FUNC explicit scaled_comparison_operand(const X& x)
: value(x.matrix()), view(scaled_comparison_operand<Plain>(value).view) {}
Plain value;
View view;
};
template <typename Components>
EIGEN_DEVICE_FUNC typename Components::Scalar scaled_comparison_max_coeff(const Components& components) {
using RealScalar = typename Components::Scalar;
if (components.size() == 0) return RealScalar(0);
return safe_scaling<RealScalar>::recover_flushed_max_coeff(components,
components.cwiseAbs().template maxCoeff<PropagateNaN>());
}
template <typename Derived>
EIGEN_DEVICE_FUNC comparison_magnitude<typename stable_norm_accumulator<typename Derived::RealScalar>::type>
scaled_comparison_norm_impl(const MatrixBase<Derived>& matrix) {
using RealScalar = typename stable_norm_accumulator<typename Derived::RealScalar>::type;
const auto& realComponents = matrix.realView();
const auto& components = realComponents.template cast<RealScalar>();
const RealScalar scale = scaled_comparison_max_coeff(components);
// Classify first so NaNs do not reach the ordered comparison.
if (!(numext::isfinite)(scale) || !(scale > RealScalar(0))) return comparison_magnitude<RealScalar>(scale);
RealScalar squaredNorm = RealScalar(0);
const auto factors = safe_scaling<RealScalar>::with_scaled(
components, scale, [&](const auto& scaled) { squaredNorm = scaled.squaredNorm(); });
comparison_magnitude<RealScalar> result(numext::sqrt(squaredNorm));
result.multiply(factors.scale);
return result;
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC comparison_magnitude<typename stable_norm_accumulator<typename X::RealScalar>::type>
scaled_comparison_distance_impl(const MatrixBase<X>& matrixX, const MatrixBase<Y>& matrixY) {
using Accumulator = typename stable_norm_accumulator<typename X::RealScalar>::type;
using WideScalar =
std::conditional_t<NumTraits<typename X::Scalar>::IsComplex || NumTraits<typename Y::Scalar>::IsComplex,
std::complex<Accumulator>, Accumulator>;
const auto& wideX = matrixX.template cast<WideScalar>();
const auto& wideY = matrixY.template cast<WideScalar>();
// FTZ flushes a subnormal difference of normal operands. Scaling a maximum M < 1 up by a power of two first loses
// only differences below M * min; scaling larger operands down could underflow their small components.
const Accumulator maxCoeff = numext::mini(
Accumulator(1),
numext::maxi(scaled_comparison_max_coeff(wideX.realView()), scaled_comparison_max_coeff(wideY.realView())));
const safe_scaling_factors<Accumulator> factors = supports_power_of_two_scaling<Accumulator>::value
? safe_scaling<Accumulator>::compute_floor_factors(maxCoeff)
: safe_scaling_factors<Accumulator>();
// Both calls share one expression type, so scaled_comparison_norm_impl is instantiated once.
auto difference = scaled_comparison_norm_impl(wideX * factors.invScale - wideY * factors.invScale);
if (!difference.isFinite()) {
// Finite operands can overflow on subtraction; halving first keeps every component representable.
const Accumulator halfInvScale = factors.invScale * Accumulator(0.5);
difference = scaled_comparison_norm_impl(wideX * halfInvScale - wideY * halfInvScale);
difference.multiply(Accumulator(2));
}
difference.multiply(factors.scale);
return difference;
}
template <typename Derived>
EIGEN_DEVICE_FUNC comparison_magnitude<typename stable_norm_accumulator<typename Derived::RealScalar>::type>
scaled_comparison_norm(const Derived& x) {
return scaled_comparison_norm_impl(scaled_comparison_operand<Derived>(x).view);
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC comparison_magnitude<typename stable_norm_accumulator<typename X::RealScalar>::type>
scaled_comparison_distance(const X& x, const Y& y) {
return scaled_comparison_distance_impl(scaled_comparison_operand<X>(x).view, scaled_comparison_operand<Y>(y).view);
}
// Coefficients widened to the stable-norm accumulator, in which ordinary comparisons square and sum. The cast is the
// identity for float and double; half and bfloat16 widen exactly to float, where no half square underflows.
template <typename X>
using approx_comparison_wide_t =
std::conditional_t<NumTraits<typename X::Scalar>::IsComplex,
std::complex<typename stable_norm_accumulator<typename X::RealScalar>::type>,
typename stable_norm_accumulator<typename X::RealScalar>::type>;
template <typename Scalar, bool = use_scaled_comparison<Scalar>::value>
struct approx_comparison_impl {
using RealScalar = typename NumTraits<Scalar>::Real;
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static bool isApprox(const X& x, const Y& y, const RealScalar& prec) {
return (x.matrix() - y.matrix()).cwiseAbs2().sum() <=
prec * prec * numext::mini(x.cwiseAbs2().sum(), y.cwiseAbs2().sum());
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static bool isMuchSmallerThan(const X& x, const Y& y, const RealScalar& prec) {
return x.cwiseAbs2().sum() <= numext::abs2(prec) * y.cwiseAbs2().sum();
}
template <typename X>
EIGEN_DEVICE_FUNC static bool isMuchSmallerThan(const X& x, const RealScalar& y, const RealScalar& prec) {
return x.cwiseAbs2().sum() <= numext::abs2(prec * y);
}
};
template <typename Scalar>
struct approx_comparison_impl<Scalar, true> {
using RealScalar = typename NumTraits<Scalar>::Real;
using Accumulator = typename stable_norm_accumulator<RealScalar>::type;
template <typename Y>
using CommonAccumulator =
std::common_type_t<Accumulator, typename stable_norm_accumulator<typename Y::RealScalar>::type>;
template <typename ValueScalar>
EIGEN_DEVICE_FUNC static typename stable_norm_accumulator<ValueScalar>::type squared_norm_lower_bound(Index size) {
using ValueAccumulator = typename stable_norm_accumulator<ValueScalar>::type;
// Squares accumulate in ValueAccumulator; below n * min / epsilon, flushed squares can affect the comparison.
return stable_normalization_normal_min<ValueAccumulator, ValueAccumulator>::run() /
NumTraits<ValueAccumulator>::epsilon() * ValueAccumulator(size);
}
template <typename BoundScalar>
EIGEN_DEVICE_FUNC static bool safe_squared_norm(const BoundScalar& value, Index size) {
using Common = std::common_type_t<Accumulator, BoundScalar>;
return Common(value) >= Common(squared_norm_lower_bound<RealScalar>(size)) &&
Common(value) <= Common(NumTraits<Accumulator>::highest());
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static bool isApprox(const X& x, const Y& y, const RealScalar& prec) {
// Widening must not admit operands that cannot be subtracted, such as half and float.
EIGEN_CHECK_BINARY_COMPATIBILITY(scalar_difference_op<typename X::Scalar EIGEN_COMMA typename Y::Scalar>,
typename X::Scalar, typename Y::Scalar)
const auto& wideX = x.template cast<approx_comparison_wide_t<X>>();
const auto& wideY = y.template cast<approx_comparison_wide_t<Y>>();
const Accumulator x2 = wideX.cwiseAbs2().sum();
const Accumulator y2 = wideY.cwiseAbs2().sum();
const Accumulator minimum = numext::mini(x2, y2);
const Accumulator precision2 = Accumulator(prec) * Accumulator(prec);
const Accumulator bound = precision2 * minimum;
// Only the smaller norm enters the bound; overflow of the larger norm is harmless.
if (safe_squared_norm(bound, x.size()) && minimum >= squared_norm_lower_bound<RealScalar>(x.size()) &&
precision2 >= squared_norm_lower_bound<RealScalar>(1))
return (wideX.matrix() - wideY.matrix()).cwiseAbs2().sum() <= bound;
return isApprox_scaled(x, y, prec);
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static bool isMuchSmallerThan(const X& x, const Y& y, const RealScalar& prec) {
using Common = CommonAccumulator<Y>;
typename nested_eval<X, 2>::type nested(x);
typename nested_eval<Y, 2>::type otherNested(y);
const Accumulator x2 = nested.template cast<approx_comparison_wide_t<X>>().cwiseAbs2().sum();
const auto y2 = otherNested.template cast<approx_comparison_wide_t<Y>>().cwiseAbs2().sum();
const Accumulator precision2 = numext::abs2(Accumulator(prec));
const Common bound = Common(precision2) * Common(y2);
// A finite bound above the flushing error makes overflow/underflow of x2 harmless.
if (safe_squared_norm(bound, x.size()) &&
Common(y2) >= Common(squared_norm_lower_bound<typename Y::RealScalar>(y.size())) &&
precision2 >= squared_norm_lower_bound<RealScalar>(1))
return Common(x2) <= bound;
return isMuchSmallerThan_scaled(nested, otherNested, prec);
}
template <typename X>
EIGEN_DEVICE_FUNC static bool isMuchSmallerThan(const X& x, const RealScalar& y, const RealScalar& prec) {
typename nested_eval<X, 2>::type nested(x);
const Accumulator x2 = nested.template cast<approx_comparison_wide_t<X>>().cwiseAbs2().sum();
const Accumulator bound = numext::abs2(Accumulator(prec) * Accumulator(y));
if (safe_squared_norm(bound, x.size())) return x2 <= bound;
return isMuchSmallerThan_scaled(nested, y, prec);
}
private:
// Keep exponent scaling from inhibiting inlining of ordinary comparisons.
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static EIGEN_DONT_INLINE bool isApprox_scaled(const X& xExpr, const Y& yExpr,
const RealScalar& prec) {
const scaled_comparison_operand<X> x(xExpr);
const scaled_comparison_operand<Y> y(yExpr);
const auto nx = scaled_comparison_norm_impl(x.view);
const auto ny = scaled_comparison_norm_impl(y.view);
if (!nx.isFinite() || !ny.isFinite()) return false;
auto tolerance = nx <= ny ? nx : ny;
tolerance.multiply(numext::abs(Accumulator(prec)));
return scaled_comparison_distance_impl(x.view, y.view) <= tolerance;
}
template <typename X, typename Y>
EIGEN_DEVICE_FUNC static EIGEN_DONT_INLINE bool isMuchSmallerThan_scaled(const X& x, const Y& y,
const RealScalar& prec) {
using Common = CommonAccumulator<Y>;
comparison_magnitude<Common> tolerance(scaled_comparison_norm(y));
tolerance.multiply(numext::abs(Common(prec)));
return scaled_comparison_norm(x) <= tolerance;
}
template <typename X>
EIGEN_DEVICE_FUNC static EIGEN_DONT_INLINE bool isMuchSmallerThan_scaled(const X& x, const RealScalar& y,
const RealScalar& prec) {
comparison_magnitude<Accumulator> tolerance(numext::abs(Accumulator(y)));
tolerance.multiply(numext::abs(Accumulator(prec)));
return scaled_comparison_norm(x) <= tolerance;
}
};
// Exponent scaling needs binary floating-point on both sides.
template <typename Derived, typename OtherDerived>
using approx_comparison_impl_t =
approx_comparison_impl<typename Derived::Scalar, use_scaled_comparison<typename Derived::Scalar>::value &&
use_scaled_comparison<typename OtherDerived::Scalar>::value>;
template <typename Derived, typename OtherDerived, bool is_integer = NumTraits<typename Derived::Scalar>::IsInteger>
struct isApprox_selector {
EIGEN_DEVICE_FUNC static bool run(const Derived& x, const OtherDerived& y, const typename Derived::RealScalar& prec) {
typename internal::nested_eval<Derived, 2>::type nested(x);
typename internal::nested_eval<OtherDerived, 2>::type otherNested(y);
return approx_comparison_impl_t<Derived, OtherDerived>::isApprox(nested, otherNested, prec);
}
};
template <typename Derived, typename OtherDerived>
struct isApprox_selector<Derived, OtherDerived, true> {
EIGEN_DEVICE_FUNC static bool run(const Derived& x, const OtherDerived& y, const typename Derived::RealScalar&) {
return x.matrix() == y.matrix();
}
};
template <typename Derived, typename OtherDerived, bool is_integer = NumTraits<typename Derived::Scalar>::IsInteger>
struct isMuchSmallerThan_object_selector {
EIGEN_DEVICE_FUNC static bool run(const Derived& x, const OtherDerived& y, const typename Derived::RealScalar& prec) {
return approx_comparison_impl_t<Derived, OtherDerived>::isMuchSmallerThan(x, y, prec);
}
};
template <typename Derived, typename OtherDerived>
struct isMuchSmallerThan_object_selector<Derived, OtherDerived, true> {
EIGEN_DEVICE_FUNC static bool run(const Derived& x, const OtherDerived&, const typename Derived::RealScalar&) {
return x.matrix() == Derived::Zero(x.rows(), x.cols()).matrix();
}
};
template <typename Derived, bool is_integer = NumTraits<typename Derived::Scalar>::IsInteger>
struct isMuchSmallerThan_scalar_selector {
EIGEN_DEVICE_FUNC static bool run(const Derived& x, const typename Derived::RealScalar& y,
const typename Derived::RealScalar& prec) {
return approx_comparison_impl<typename Derived::Scalar>::isMuchSmallerThan(x, y, prec);
}
};
template <typename Derived>
struct isMuchSmallerThan_scalar_selector<Derived, true>
: isMuchSmallerThan_object_selector<Derived, typename Derived::RealScalar, true> {};
} // end namespace internal
/** \returns \c true if \c *this is approximately equal to \a other, within the precision
* determined by \a prec.
*
* \note This is a relative norm comparison. Two vectors \f$ v \f$ and \f$ w \f$
* are considered to be approximately equal within precision \f$ p \f$ if
* \f[ \Vert v - w \Vert \leqslant p\,\min(\Vert v\Vert, \Vert w\Vert). \f]
* For matrices, the comparison is done using the Hilbert-Schmidt norm (aka Frobenius norm
* L2 norm).
*
* \a prec is a relative tolerance chosen by the caller, not a bound on floating-point rounding error.
* The default, NumTraits<Scalar>::dummy_precision(), does not account for the operation, dimensions,
* or conditioning of the problem. Choose an explicit tolerance when checking numerical accuracy.
* A norm comparison also does not bound the relative error of each coefficient.
*
* \note Because of the multiplicativeness of this comparison, one can't use this function
* to check whether \c *this is approximately equal to the zero matrix or vector.
* Indeed, \c isApprox(zero) returns false unless \c *this itself is exactly the zero matrix
* or vector. If you want to test whether \c *this is small relative to a reference norm,
* use isMuchSmallerThan() instead.
*
* \sa isMuchSmallerThan(), isZero()
*/
template <typename Derived>
template <typename OtherDerived>
EIGEN_DEVICE_FUNC constexpr bool DenseBase<Derived>::isApprox(const DenseBase<OtherDerived>& other,
const RealScalar& prec) const {
return internal::isApprox_selector<Derived, OtherDerived>::run(derived(), other.derived(), prec);
}
/** \returns \c true if the norm of \c *this is much smaller than \a other,
* within the precision determined by \a prec.
*
* \note This is a relative norm comparison. A vector \f$ v \f$ is
* considered to be much smaller than \f$ x \f$ within precision \f$ p \f$ if
* \f[ \Vert v \Vert \leqslant p\,\vert x\vert. \f]
*
* For matrices, the comparison is done using the Hilbert-Schmidt norm. For this reason,
* the value of the reference scalar \a other should come from the Hilbert-Schmidt norm
* of a reference matrix of same dimensions.
*
* \sa isApprox(), isMuchSmallerThan(const DenseBase<OtherDerived>&, RealScalar) const
*/
template <typename Derived>
EIGEN_DEVICE_FUNC constexpr bool DenseBase<Derived>::isMuchSmallerThan(const typename NumTraits<Scalar>::Real& other,
const RealScalar& prec) const {
return internal::isMuchSmallerThan_scalar_selector<Derived>::run(derived(), other, prec);
}
/** \returns \c true if the norm of \c *this is much smaller than the norm of \a other,
* within the precision determined by \a prec.
*
* \note This is a relative norm comparison. A vector \f$ v \f$ is
* considered to be much smaller than a vector \f$ w \f$ within precision \f$ p \f$ if
* \f[ \Vert v \Vert \leqslant p\,\Vert w\Vert. \f]
* For matrices, the comparison is done using the Hilbert-Schmidt norm.
*
* \sa isApprox(), isMuchSmallerThan(const RealScalar&, RealScalar) const
*/
template <typename Derived>
template <typename OtherDerived>
EIGEN_DEVICE_FUNC constexpr bool DenseBase<Derived>::isMuchSmallerThan(const DenseBase<OtherDerived>& other,
const RealScalar& prec) const {
return internal::isMuchSmallerThan_object_selector<Derived, OtherDerived>::run(derived(), other.derived(), prec);
}
} // end namespace Eigen
#endif // EIGEN_APPROX_COMPARISONS_H