blob: ddc3b83f6901baca16e0f0dc86b3462cf03aeedd [file] [edit]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_SAFE_SCALING_H
#define EIGEN_SAFE_SCALING_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
namespace internal {
// Select factors for a positive finite scale. Supported binary floating-point scalars use normal powers of two;
// other scalar types use division for matrix scaling and can request a clamped reciprocal for stableNorm(). General
// scaling rounds down so it does not discard a representable tail that division by the original value would preserve.
// Stable reductions separately round up to keep scaled magnitudes at most one. At the upper exponent boundary, the
// scale is clamped to keep its reciprocal normal; for the standard binary formats this leaves magnitudes below four.
template <typename Scalar>
struct supports_power_of_two_scaling
: bool_constant<(std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value) &&
std::numeric_limits<Scalar>::is_iec559 && std::numeric_limits<Scalar>::radix == 2 &&
(sizeof(Scalar) == sizeof(numext::uint32_t) || sizeof(Scalar) == sizeof(numext::uint64_t))> {};
#if !defined(EIGEN_GPU_COMPILE_PHASE)
template <>
struct supports_power_of_two_scaling<long double> : bool_constant<std::numeric_limits<long double>::radix == 2> {};
#endif
template <>
struct supports_power_of_two_scaling<half> : true_type {};
template <>
struct supports_power_of_two_scaling<bfloat16> : true_type {};
template <typename Scalar, bool = supports_power_of_two_scaling<Scalar>::value>
struct safe_scaling;
template <typename Scalar>
struct safe_scaling_factors {
Scalar scale = Scalar(1);
// Division-only arithmetic scaling leaves this unused reciprocal at one.
Scalar invScale = Scalar(1);
};
// value * 2^exponent through the integer significand of the value with bit pattern valueBits, so FTZ/DAZ hardware and
// ARMv7 NEON can flush neither a subnormal input nor a subnormal result. Exact wherever the result is representable;
// a subnormal result rounds to nearest, ties to even, as IEEE 754 multiplication does; below denorm_min / 2 the result
// is a signed zero and above the largest finite value a signed infinity. Zeros, infinities and NaNs pass through. Keep
// the integer-only ABI so compilers cannot replace the reconstruction with FTZ/DAZ-sensitive floating-point arithmetic.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE typename binary_floating_point_traits<Scalar>::Bits scale_binary_bits_by_exponent(
const typename binary_floating_point_traits<Scalar>::Bits valueBits, int exponent) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
constexpr int kInfinityExponent = int(Binary::kExponentMask >> Binary::kFractionBits);
const Bits valueExponentBits = valueBits & Binary::kExponentMask;
const Bits fraction = valueBits & Binary::kFractionMask;
if (valueExponentBits == Binary::kExponentMask || (valueExponentBits == 0 && fraction == 0)) return valueBits;
const Bits sign = valueBits & Binary::kSignBit;
Bits significand = fraction;
int resultExponent = int(valueExponentBits >> Binary::kFractionBits);
if (resultExponent == 0) {
resultExponent = 1;
while (significand < Binary::kExponentUnit) {
significand <<= 1;
--resultExponent;
}
}
// Every finite value saturates beyond this magnitude of exponent, so the clamp changes no result.
constexpr int kSaturatingExponent = kInfinityExponent + Binary::kFractionBits + 2;
resultExponent += numext::mini(numext::maxi(exponent, -kSaturatingExponent), kSaturatingExponent);
if (resultExponent >= kInfinityExponent) return sign | Binary::kExponentMask;
if (resultExponent > 0)
return sign | (Bits(resultExponent) << Binary::kFractionBits) | (significand & Binary::kFractionMask);
// Subnormal result m * 2^(resultExponent - 1) denorm_min, m = significand | kExponentUnit < 2 * kExponentUnit: round
// to nearest, ties to even, as IEEE 754 multiplication does. A carry into kExponentUnit encodes the smallest normal;
// for shift > kFractionBits + 1 the result is below denorm_min / 2 and rounds to a signed zero.
const int shift = 1 - resultExponent;
if (shift > Binary::kFractionBits + 1) return sign;
const Bits mantissa = significand | Binary::kExponentUnit;
const Bits halfway = Bits(1) << (shift - 1);
const Bits remainder = mantissa & ((halfway << 1) - 1);
Bits rounded = mantissa >> shift;
if (remainder > halfway || (remainder == halfway && (rounded & Bits(1)) != 0)) ++rounded;
return sign | rounded;
}
// The same scaling by a positive normal power of two given as the bit pattern of its exponent field.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename binary_floating_point_traits<Scalar>::Bits
scale_binary_bits_by_power_of_two(const typename binary_floating_point_traits<Scalar>::Bits valueBits,
const typename binary_floating_point_traits<Scalar>::Bits factorExponentBits) {
using Binary = binary_floating_point_traits<Scalar>;
return scale_binary_bits_by_exponent<Scalar>(
valueBits, int(factorExponentBits >> Binary::kFractionBits) - Binary::kExponentBias);
}
// value * 2^exponent for the binary floating-point scalars, with the rounding and saturation of
// scale_binary_bits_by_exponent(): the ldexp that FTZ/DAZ hardware cannot flush, for any int exponent.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar scale_binary_by_exponent(const Scalar& value, int exponent) {
using Binary = binary_floating_point_traits<Scalar>;
return numext::bit_cast<Scalar>(scale_binary_bits_by_exponent<Scalar>(Binary::bits(value), exponent));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE std::complex<Scalar> scale_binary_by_exponent(const std::complex<Scalar>& value,
int exponent) {
return std::complex<Scalar>(scale_binary_by_exponent(value.real(), exponent),
scale_binary_by_exponent(value.imag(), exponent));
}
// frexp's exponent e, 2^(e - 1) <= |value| < 2^e, read from the representation of a finite value so that FTZ/DAZ
// hardware cannot flush a subnormal; 0 for a zero. Infinities and NaNs are not classified: they come back one above
// the largest finite exponent. The classification takes the magnitude bits out of line, like
// scale_binary_bits_by_exponent(): inlined, a compiler folds an integer test on the bit pattern of a float back into
// a floating-point comparison (`magnitude == 0` into `value == 0`), which FTZ/DAZ hardware flushes exactly like the
// arithmetic the bits were meant to bypass.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE int binary_frexp_exponent_of_magnitude(
const typename binary_floating_point_traits<Scalar>::Bits magnitude) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
if (magnitude == 0) return 0;
const Bits exponentBits = magnitude & Binary::kExponentMask;
if (exponentBits != 0) return int(exponentBits >> Binary::kFractionBits) - Binary::kExponentBias + 1;
// A subnormal is its fraction times 2^(min_exponent - digits).
return log_2_impl<Bits>::run_floor(magnitude) + 1 + std::numeric_limits<Scalar>::min_exponent -
std::numeric_limits<Scalar>::digits;
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE int binary_frexp_exponent(const Scalar& value) {
return binary_frexp_exponent_of_magnitude<Scalar>(binary_floating_point_traits<Scalar>::magnitude(value));
}
// The larger of two magnitude bit patterns, out of line for the same reason.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE typename binary_floating_point_traits<Scalar>::Bits larger_magnitude_bits(
const typename binary_floating_point_traits<Scalar>::Bits a,
const typename binary_floating_point_traits<Scalar>::Bits b) {
return a < b ? b : a;
}
// |value| < the smallest normal from the magnitude bits, out of line for the same reason.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE bool is_below_normal_magnitude_bits(
const typename binary_floating_point_traits<Scalar>::Bits magnitude) {
return magnitude < binary_floating_point_traits<Scalar>::kExponentUnit;
}
// frexp through the representation: the significand in [0.5, 1), with the sign of value, and its exponent.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar binary_frexp(const Scalar& value, int& exponent) {
exponent = binary_frexp_exponent(value);
return scale_binary_by_exponent(value, -exponent);
}
// Multiplication by a positive normal power of two through integer significands, so FTZ/DAZ cannot flush a subnormal
// input or result; a subnormal result rounds as IEEE 754 multiplication does, and a result above the largest finite
// value saturates to infinity.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar scale_binary_by_power_of_two(const Scalar& value, const Scalar& factor) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
const Bits factorExponentBits = Binary::bits(factor);
eigen_internal_assert(factorExponentBits != 0 && factorExponentBits < Binary::kExponentMask &&
(factorExponentBits & ~Binary::kExponentMask) == 0);
return numext::bit_cast<Scalar>(scale_binary_bits_by_power_of_two<Scalar>(Binary::bits(value), factorExponentBits));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE std::complex<Scalar> scale_binary_by_power_of_two(
const std::complex<Scalar>& value, const Scalar& factor) {
return std::complex<Scalar>(scale_binary_by_power_of_two(value.real(), factor),
scale_binary_by_power_of_two(value.imag(), factor));
}
// Coefficient-wise value * 2^exponent through scale_binary_by_exponent(). Scalar only: the integer path is the point.
template <typename Scalar>
struct scale_by_exponent_op {
EIGEN_DEVICE_FUNC explicit scale_by_exponent_op(int exponent) : m_exponent(exponent) {}
template <typename CoeffScalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE CoeffScalar operator()(const CoeffScalar& value) const {
return scale_binary_by_exponent(value, m_exponent);
}
int m_exponent;
};
template <typename Scalar>
struct functor_traits<scale_by_exponent_op<Scalar>> {
static constexpr int Cost = 10 * NumTraits<Scalar>::MulCost;
static constexpr bool PacketAccess = false;
static constexpr bool IsRepeatable = true;
};
template <typename FactorScalar>
struct scale_by_power_of_two_op {
EIGEN_DEVICE_FUNC explicit scale_by_power_of_two_op(const FactorScalar& factor) : m_factor(factor) {}
template <typename CoeffScalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE CoeffScalar operator()(const CoeffScalar& value) const {
return scale_binary_by_power_of_two(value, m_factor);
}
FactorScalar m_factor;
};
template <typename FactorScalar>
struct functor_traits<scale_by_power_of_two_op<FactorScalar>> {
// Integer significand scaling is scalar and calls an out-of-line helper.
static constexpr int Cost = 10 * NumTraits<FactorScalar>::MulCost;
static constexpr bool PacketAccess = false;
static constexpr bool IsRepeatable = true;
};
template <typename FactorScalar, typename CoeffScalar>
struct use_subnormal_preserving_scaling
: bool_constant<
(std::is_same<FactorScalar, float>::value &&
(std::is_same<CoeffScalar, float>::value || std::is_same<CoeffScalar, std::complex<float>>::value)) ||
(std::is_same<FactorScalar, double>::value &&
(std::is_same<CoeffScalar, double>::value || std::is_same<CoeffScalar, std::complex<double>>::value))> {};
// Subnormal-magnitude tests read the representation for float and double, where a comparison under DAZ
// reads a subnormal as zero as well; the other scalars compare arithmetically.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_subnormal_magnitude_impl(const Scalar& value, true_type) {
// The smallest normal, 2^(min_exponent - 1), has frexp exponent min_exponent.
const int exponent = binary_frexp_exponent(value);
return exponent != 0 && exponent < std::numeric_limits<Scalar>::min_exponent;
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_subnormal_magnitude_impl(const Scalar& value, false_type) {
const Scalar magnitude = numext::abs(value);
return magnitude > Scalar(0) && magnitude < (std::numeric_limits<Scalar>::min)();
}
// 0 < |value| < the smallest normal.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_subnormal_magnitude(const Scalar& value) {
return is_subnormal_magnitude_impl(value, bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
// |value| < the smallest normal, zero included: a maximum that a flushing comparison may have produced. A scalar
// reduction under FTZ/DAZ does not always return a zero: where max(a, b) is a compare and a select (Arm FZ with
// scalar VFP or fcsel, ARMv7's double, which has no packet), every comparison is false and the running maximum keeps
// the bits of the first operand.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_zero_or_subnormal_magnitude_impl(const Scalar& value, true_type) {
return is_below_normal_magnitude_bits<Scalar>(binary_floating_point_traits<Scalar>::magnitude(value));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_zero_or_subnormal_magnitude_impl(const Scalar& value, false_type) {
return numext::abs(value) < (std::numeric_limits<Scalar>::min)();
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_zero_or_subnormal_magnitude(const Scalar& value) {
return is_zero_or_subnormal_magnitude_impl(value,
bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
// The larger of two non-negative values, such as two recovered maxima, read from the representation for float and
// double: an arithmetic maximum (a compiler may lower it to fmaxnm) flushes subnormal operands to zero under FTZ/DAZ.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar max_preserving_subnormals_impl(const Scalar& a, const Scalar& b,
true_type) {
using Binary = binary_floating_point_traits<Scalar>;
return numext::bit_cast<Scalar>(larger_magnitude_bits<Scalar>(Binary::magnitude(a), Binary::magnitude(b)));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar max_preserving_subnormals_impl(const Scalar& a, const Scalar& b,
false_type) {
return numext::maxi(a, b);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar max_preserving_subnormals(const Scalar& a, const Scalar& b) {
return max_preserving_subnormals_impl(a, b, bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
// |value| and value < 0 of a finite real scalar, from the representation for float and double: a comparison reads a
// negative subnormal as zero under DAZ, and an abs() that widens (MSVC 19.29 takes abs(float) through double) flushes
// a subnormal result under FTZ when it narrows back.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar abs_preserving_subnormals_impl(const Scalar& value, true_type) {
return numext::bit_cast<Scalar>(binary_floating_point_traits<Scalar>::magnitude(value));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar abs_preserving_subnormals_impl(const Scalar& value, false_type) {
return numext::abs(value);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar abs_preserving_subnormals(const Scalar& value) {
return abs_preserving_subnormals_impl(value,
bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_negative_preserving_subnormals_impl(const Scalar& value, true_type) {
using Binary = binary_floating_point_traits<Scalar>;
return (Binary::bits(value) & Binary::kSignBit) != 0 && !is_zero_magnitude_bits<Scalar>(Binary::magnitude(value));
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_negative_preserving_subnormals_impl(const Scalar& value, false_type) {
return value < Scalar(0);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE bool is_negative_preserving_subnormals(const Scalar& value) {
return is_negative_preserving_subnormals_impl(
value, bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
// The position of the largest magnitude of a real vector with at least one coefficient, read from the representation
// and out of line like the other bit classifications.
template <typename Scalar, typename Derived>
EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE Index index_of_largest_magnitude(const DenseBase<Derived>& src) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
const evaluator<Derived> coeffs(src.derived());
Index position = 0;
Bits largest = Binary::magnitude(coeffs.coeff(0));
for (Index i = 1; i < src.size(); ++i) {
const Bits magnitude = Binary::magnitude(coeffs.coeff(i));
if (magnitude > largest) {
largest = magnitude;
position = i;
}
}
return position;
}
// ldexp and frexp's exponent for a real scalar: float and double through the representation, which FTZ/DAZ cannot
// reach, the other scalars through the C library. frexp's exponent is e with 2^(e - 1) <= |value| < 2^e, 0 for a
// zero.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar ldexp_preserving_subnormals_impl(const Scalar& value, int exponent,
true_type) {
return scale_binary_by_exponent(value, exponent);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar ldexp_preserving_subnormals_impl(const Scalar& value, int exponent,
false_type) {
return numext::ldexp(value, exponent);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar ldexp_preserving_subnormals(const Scalar& value, int exponent) {
return ldexp_preserving_subnormals_impl(value, exponent,
bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE int frexp_exponent_preserving_subnormals_impl(const Scalar& value, true_type) {
return binary_frexp_exponent(value);
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE int frexp_exponent_preserving_subnormals_impl(const Scalar& value, false_type) {
int exponent = 0;
EIGEN_USING_STD(frexp);
frexp(value, &exponent);
return exponent;
}
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE int frexp_exponent_preserving_subnormals(const Scalar& value) {
return frexp_exponent_preserving_subnormals_impl(
value, bool_constant<use_subnormal_preserving_scaling<Scalar, Scalar>::value>());
}
// The significand in [0.5, 1), with the sign of value, and its exponent.
template <typename Scalar>
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar frexp_preserving_subnormals(const Scalar& value, int& exponent) {
exponent = frexp_exponent_preserving_subnormals(value);
return ldexp_preserving_subnormals(value, -exponent);
}
template <typename Scalar, bool IsPowerOfTwo_>
struct safe_scaling_operations {
private:
using Factors = safe_scaling_factors<Scalar>;
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool needs_subnormal_recovery(const Scalar& value) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
constexpr Bits kThreshold = Bits(subnormal_recovery_exponent() + Binary::kExponentBias) << Binary::kFractionBits;
return Binary::magnitude(value) - Bits(1) < kThreshold - Bits(1);
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool is_identity(const Factors& factors) {
return factors.scale == Scalar(1) && factors.invScale == Scalar(1);
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool has_normal_reciprocal(const Scalar&, const Scalar&, false_type) {
return false;
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool has_normal_reciprocal(const Scalar& value, const Scalar& normalMin,
true_type) {
return value >= normalMin && value <= Scalar(1) / normalMin;
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool is_positive_finite(const Scalar& value, false_type) {
return value > Scalar(0) && value <= NumTraits<Scalar>::highest();
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool is_positive_finite(const Scalar& value, true_type) {
using Binary = binary_floating_point_traits<Scalar>;
const typename Binary::Bits bits = Binary::bits(value);
return bits > 0 && bits < Binary::kExponentMask;
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Factors select_factors(const Scalar& maxCoeff) {
using IsIeeeBinary = bool_constant<std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value>;
if (!is_positive_finite(maxCoeff, IsIeeeBinary())) return Factors{};
return safe_scaling<Scalar, IsPowerOfTwo_>::compute_floor_factors(maxCoeff);
}
template <typename Src, typename Func>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void with_scaled_impl(const Src& src, const Scalar&,
const Factors& factors, const Func& func,
false_type) {
if (is_identity(factors)) {
// Keep the copy distinct from multiplication by one, which flushes subnormals under FTZ.
const Src* unscaled = &src;
EIGEN_OPTIMIZATION_BARRIER(unscaled);
func(*unscaled);
} else EIGEN_IF_CONSTEXPR (IsPowerOfTwo_)
func(src * factors.invScale);
else
func(src / factors.scale);
}
template <typename Src, typename Func>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void with_scaled_impl(const Src& src, const Scalar& maxCoeff,
const Factors& factors, const Func& func,
true_type) {
if (!is_identity(factors) && needs_subnormal_recovery(maxCoeff))
func(src.unaryExpr(scale_by_power_of_two_op<Scalar>(factors.invScale)));
else
with_scaled_impl(src, maxCoeff, factors, func, false_type());
}
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_to_impl(Dest& dest, const Src& src, const Scalar&,
const Factors& factors, false_type) {
unscale_to(dest, src, factors);
}
// Below the recovery threshold, unscaling rounds coefficients that are significant relative to maxCoeff into the
// subnormal range, where FTZ/DAZ arithmetic would zero them.
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_to_impl(Dest& dest, const Src& src, const Scalar& maxCoeff,
const Factors& factors, true_type) {
if (!is_identity(factors) && needs_subnormal_recovery(maxCoeff))
dest = src.unaryExpr(scale_by_power_of_two_op<Scalar>(factors.scale));
else
unscale_to_impl(dest, src, maxCoeff, factors, false_type());
}
template <typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar recover_flushed_max_coeff_impl(const Src&, const Scalar& maxCoeff,
false_type) {
return maxCoeff;
}
template <typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar recover_flushed_max_coeff_impl(const Src& src,
const Scalar& maxCoeff,
true_type) {
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
if (!is_zero_or_subnormal_magnitude(maxCoeff)) return maxCoeff;
// A product expression has no coefficient access; its evaluator materializes it.
const evaluator<Src> coeffs(src);
Bits maxBits = 0;
for (Index col = 0; col < src.cols(); ++col) {
for (Index row = 0; row < src.rows(); ++row) {
const typename Src::Scalar coeff = coeffs.coeff(row, col);
maxBits = numext::maxi(
maxBits, numext::maxi(Binary::magnitude(numext::real(coeff)), Binary::magnitude(numext::imag(coeff))));
}
}
return numext::bit_cast<Scalar>(maxBits);
}
template <typename Derived>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Index recover_flushed_max_coeff_index_impl(const DenseBase<Derived>&,
Index position, false_type) {
return position;
}
template <typename Derived>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Index recover_flushed_max_coeff_index_impl(const DenseBase<Derived>& src,
Index, true_type) {
return index_of_largest_magnitude<Scalar>(src);
}
public:
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Factors
compute_ceiling_factors_with_normal_reciprocal(const Scalar& value) {
Factors factors = safe_scaling<Scalar, IsPowerOfTwo_>::compute_ceiling_factors(value);
EIGEN_IF_CONSTEXPR (!IsPowerOfTwo_) {
if (factors.invScale > NumTraits<Scalar>::highest()) {
factors.invScale = NumTraits<Scalar>::highest();
factors.scale = Scalar(1) / factors.invScale;
}
}
return factors;
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE bool try_compute_ceiling_factors_with_normal_reciprocal(
const Scalar& value, const Scalar& normalMin, Factors& factors) {
if (!IsPowerOfTwo_ &&
!has_normal_reciprocal(value, normalMin, bool_constant<std::is_floating_point<Scalar>::value>()))
return false;
factors = compute_ceiling_factors_with_normal_reciprocal(value);
return true;
}
// Dispatch once per expression; ordinary scaling retains packet access. The callback consumes the lazy
// expression before its operands expire, without requiring a common C++ type for the different paths.
template <typename Src, typename Func>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Factors with_scaled(const Src& src, const Scalar& maxCoeff,
const Func& func) {
const Factors factors = select_factors(maxCoeff);
constexpr bool kPreserveSubnormalInputs =
IsPowerOfTwo_ && use_subnormal_preserving_scaling<Scalar, typename Src::Scalar>::value;
with_scaled_impl(src, maxCoeff, factors, func, bool_constant<kPreserveSubnormalInputs>());
return factors;
}
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_to(Dest&& dest, const Src& src, const Factors& factors) {
if (factors.scale == Scalar(1)) {
const Src* unscaled = &src;
EIGEN_OPTIMIZATION_BARRIER(unscaled);
dest = *unscaled;
return;
}
dest = src * factors.scale;
}
template <typename ValueType>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_in_place(ValueType& value, const Factors& factors) {
if (factors.scale == Scalar(1)) return;
value *= factors.scale;
}
template <typename MatrixType>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_in_place(MatrixType& matrix, const Scalar& maxCoeff,
const Factors& factors) {
if (factors.scale == Scalar(1)) return;
unscale_to(matrix, matrix, maxCoeff, factors);
}
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void unscale_to(Dest&& dest, const Src& src, const Scalar& maxCoeff,
const Factors& factors) {
constexpr bool kPreserveSubnormalOutputs =
IsPowerOfTwo_ && use_subnormal_preserving_scaling<Scalar, typename Src::Scalar>::value;
unscale_to_impl(dest, src, maxCoeff, factors, bool_constant<kPreserveSubnormalOutputs>());
}
// Below 2^subnormal_recovery_exponent(), a coefficient within a factor 2^-kFractionBits of the largest one can be
// subnormal, so a scaling that must survive FTZ/DAZ goes through integer significands (scale_by_exponent_op,
// scale_by_power_of_two_op) instead of packet multiplication.
EIGEN_DEVICE_FUNC static constexpr int subnormal_recovery_exponent() {
return std::numeric_limits<Scalar>::min_exponent + std::numeric_limits<Scalar>::digits - 2;
}
// A SIMD unit that flushes subnormal inputs (ARMv7 NEON, Arm FZ, DAZ) reduces an all-subnormal matrix to a zero
// maximum, and a flushed scalar reduction to whichever subnormal it held first, so rescan a zero or subnormal
// maxCoeff from the representation. Any subnormal maximum, including the largest real or imaginary magnitude of a
// complex matrix, selects the same factors as the true one.
template <typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar recover_flushed_max_coeff(const Src& src,
const Scalar& maxCoeff) {
constexpr bool kPreserveSubnormalInputs =
IsPowerOfTwo_ && use_subnormal_preserving_scaling<Scalar, typename Src::Scalar>::value;
return recover_flushed_max_coeff_impl(src, maxCoeff, bool_constant<kPreserveSubnormalInputs>());
}
// The position of the largest magnitude in a real vector whose maxCoeff(&position) read zero or subnormal,
// rescanned from the representation like recover_flushed_max_coeff(); the position maxCoeff() reported where there
// is no representation path.
template <typename Derived>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Index recover_flushed_max_coeff_index(const DenseBase<Derived>& src,
Index position) {
constexpr bool kPreserveSubnormalInputs =
IsPowerOfTwo_ && use_subnormal_preserving_scaling<Scalar, typename Derived::Scalar>::value;
return recover_flushed_max_coeff_index_impl(src, position, bool_constant<kPreserveSubnormalInputs>());
}
template <typename MatrixType>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Factors scale_in_place(MatrixType& matrix, const Scalar& maxCoeff) {
const Factors factors = select_factors(maxCoeff);
scale_in_place(matrix, maxCoeff, factors);
return factors;
}
template <typename MatrixType>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void scale_in_place(MatrixType& matrix, const Scalar& maxCoeff,
const Factors& factors) {
if (!is_identity(factors)) scale_to(matrix, matrix, maxCoeff, factors);
}
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE void scale_to(Dest& dest, const Src& src, const Scalar& maxCoeff,
const Factors& factors) {
constexpr bool kPreserveSubnormalInputs =
IsPowerOfTwo_ && use_subnormal_preserving_scaling<Scalar, typename Src::Scalar>::value;
with_scaled_impl(
src, maxCoeff, factors, [&](const auto& scaled) { dest = scaled; }, bool_constant<kPreserveSubnormalInputs>());
}
template <typename Dest, typename Src>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Factors scale_to(Dest& dest, const Src& src, const Scalar& maxCoeff) {
const Factors factors = select_factors(maxCoeff);
scale_to(dest, src, maxCoeff, factors);
return factors;
}
};
template <typename Scalar, bool>
struct safe_scaling : safe_scaling_operations<Scalar, false> {
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE safe_scaling_factors<Scalar> compute_floor_factors(const Scalar& value) {
return {value, Scalar(1)};
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE safe_scaling_factors<Scalar> compute_ceiling_factors(
const Scalar& value) {
return {value, Scalar(1) / value};
}
};
template <typename Scalar>
struct safe_scaling<Scalar, true> : safe_scaling_operations<Scalar, true> {
private:
using Binary = binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
template <bool RoundUp>
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE safe_scaling_factors<Scalar> compute_power_of_two_factors(
const Scalar& value) {
constexpr Bits kMaxScale = Binary::kMaxFiniteExponentBits - Binary::kExponentUnit;
Bits scaleBits = RoundUp ? numext::bit_cast<Bits>(numext::ceil_power_of_two(value))
: Binary::bits(value) & Binary::kExponentMask;
if (scaleBits < Binary::kExponentUnit) scaleBits = Binary::kExponentUnit;
if (scaleBits > kMaxScale) scaleBits = kMaxScale;
const Bits invScaleBits = Bits(Binary::kMaxFiniteExponentBits - scaleBits);
return {numext::bit_cast<Scalar>(scaleBits), numext::bit_cast<Scalar>(invScaleBits)};
}
public:
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE safe_scaling_factors<Scalar> compute_floor_factors(const Scalar& value) {
return compute_power_of_two_factors<false>(value);
}
EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE safe_scaling_factors<Scalar> compute_ceiling_factors(
const Scalar& value) {
return compute_power_of_two_factors<true>(value);
}
};
#if !defined(EIGEN_GPU_COMPILE_PHASE)
template <>
struct safe_scaling<long double, true> : safe_scaling_operations<long double, true> {
static EIGEN_STRONG_INLINE safe_scaling_factors<long double> compute_floor_factors(const long double& value) {
EIGEN_USING_STD(frexp);
int exponent = 0;
frexp(value, &exponent);
return compute_factors_from_exponent(exponent - 1);
}
static EIGEN_STRONG_INLINE safe_scaling_factors<long double> compute_ceiling_factors(const long double& value) {
EIGEN_USING_STD(frexp);
int exponent = 0;
const long double fraction = frexp(value, &exponent);
if (numext::equal_strict(fraction, 0.5L)) --exponent;
return compute_factors_from_exponent(exponent);
}
private:
static EIGEN_STRONG_INLINE safe_scaling_factors<long double> compute_factors_from_exponent(int exponent) {
constexpr int kMinNormalExponent = std::numeric_limits<long double>::min_exponent - 1;
constexpr int kMaxNormalExponent = std::numeric_limits<long double>::max_exponent - 1;
constexpr int kMinScaleExponent =
kMinNormalExponent > -kMaxNormalExponent ? kMinNormalExponent : -kMaxNormalExponent;
constexpr int kMaxScaleExponent =
kMaxNormalExponent < -kMinNormalExponent ? kMaxNormalExponent : -kMinNormalExponent;
if (exponent < kMinScaleExponent) exponent = kMinScaleExponent;
if (exponent > kMaxScaleExponent) exponent = kMaxScaleExponent;
EIGEN_USING_STD(ldexp);
const long double scale = ldexp(1.0L, exponent);
return {scale, 1.0L / scale};
}
};
#endif
} // namespace internal
} // namespace Eigen
#endif // EIGEN_SAFE_SCALING_H