| // 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 |