blob: 3af70e4edba96a84b640d9ab54c2c4da3cc7d68a [file] [edit]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
#include "fp_control.h"
#define EIGEN_TEST_ANNOYING_SCALAR_DONT_THROW
#include "AnnoyingScalar.h"
template <typename T>
void check_power_of_two_scaling_factor() {
static_assert(internal::supports_power_of_two_scaling<T>::value, "power-of-two scaling must be enabled");
using Factors = internal::safe_scaling_factors<T>;
const auto check_factor = [](const T& value) {
const auto factors = internal::safe_scaling<T>::compute_floor_factors(value);
VERIFY(factors.scale >= (std::numeric_limits<T>::min)());
VERIFY(factors.invScale >= (std::numeric_limits<T>::min)());
VERIFY_IS_EQUAL(factors.scale * factors.invScale, T(1));
return factors;
};
const auto check_round_trip = [&](const T& value) {
const auto factors = check_factor(value);
Matrix<T, 1, 1> input;
input(0) = value;
Matrix<T, 1, 1> scaled;
const auto scaleToFactors = internal::safe_scaling<T>::scale_to(scaled, input, value);
VERIFY_IS_EQUAL(scaleToFactors.scale, factors.scale);
VERIFY_IS_EQUAL(scaleToFactors.invScale, factors.invScale);
Matrix<T, 1, 1> scaledInPlace;
scaledInPlace(0) = value;
internal::safe_scaling<T>::scale_in_place(scaledInPlace, value, factors);
VERIFY_IS_EQUAL(scaledInPlace, scaled);
scaledInPlace(0) = value;
const auto inPlaceFactors = internal::safe_scaling<T>::scale_in_place(scaledInPlace, value);
VERIFY_IS_EQUAL(inPlaceFactors.scale, factors.scale);
VERIFY_IS_EQUAL(inPlaceFactors.invScale, factors.invScale);
VERIFY_IS_EQUAL(scaledInPlace, scaled);
T restored;
internal::safe_scaling<T>::unscale_to(restored, scaled(0), factors);
VERIFY_IS_EQUAL(restored, value);
Matrix<T, 1, 1> restoredMatrix;
internal::safe_scaling<T>::unscale_to(restoredMatrix, scaled, value, factors);
VERIFY_IS_EQUAL(restoredMatrix, input);
internal::safe_scaling<T>::unscale_in_place(scaled, factors);
VERIFY_IS_EQUAL(scaled(0), value);
};
const auto denormFactors = check_factor(std::numeric_limits<T>::denorm_min());
VERIFY_IS_EQUAL(denormFactors.scale, (std::numeric_limits<T>::min)());
Factors normalReciprocalFactors;
VERIFY(internal::safe_scaling<T>::try_compute_ceiling_factors_with_normal_reciprocal(
std::numeric_limits<T>::denorm_min(), (std::numeric_limits<T>::min)(), normalReciprocalFactors));
VERIFY_IS_EQUAL(normalReciprocalFactors.scale, denormFactors.scale);
VERIFY_IS_EQUAL(normalReciprocalFactors.invScale, denormFactors.invScale);
// bfloat16 arithmetic widens to float, whose subnormal inputs may be flushed before scaling can recover them.
if (!std::is_same<T, bfloat16>::value || !ScopedFlushToZero::hardwareFlushesSubnormalInputs()) {
using InputScalar = std::conditional_t<std::is_floating_point<T>::value, T, float>;
volatile InputScalar denormInput = static_cast<InputScalar>(std::numeric_limits<T>::denorm_min());
const T denormMin = static_cast<T>(denormInput);
Matrix<T, 1, 1> denormInputMatrix;
denormInputMatrix(0) = denormMin;
Matrix<T, 1, 1> scaledDenorm;
const auto factors = internal::safe_scaling<T>::scale_to(scaledDenorm, denormInputMatrix, denormMin);
Matrix<T, 1, 1> scaledDenormInPlace;
scaledDenormInPlace(0) = denormMin;
internal::safe_scaling<T>::scale_in_place(scaledDenormInPlace, denormMin, factors);
VERIFY_IS_EQUAL(scaledDenormInPlace, scaledDenorm);
VERIFY(scaledDenorm(0) > T(0));
}
check_round_trip((std::numeric_limits<T>::min)());
check_round_trip(T(0.75));
check_round_trip(T(1.5));
check_round_trip(T(3));
check_round_trip((std::numeric_limits<T>::max)());
const auto floorFactors = internal::safe_scaling<T>::compute_floor_factors(T(1.5));
const auto ceilingFactors = internal::safe_scaling<T>::compute_ceiling_factors_with_normal_reciprocal(T(1.5));
VERIFY_IS_EQUAL(floorFactors.scale, T(1));
VERIFY_IS_EQUAL(ceilingFactors.scale, T(2));
const T highest = (std::numeric_limits<T>::max)();
const auto highestCeilingFactors = internal::safe_scaling<T>::compute_ceiling_factors_with_normal_reciprocal(highest);
VERIFY_IS_EQUAL(highestCeilingFactors.scale, check_factor(highest).scale);
VERIFY_IS_EQUAL(highestCeilingFactors.scale * highestCeilingFactors.invScale, T(1));
const T scaledHighest = highest * highestCeilingFactors.invScale;
VERIFY(scaledHighest >= T(1));
if (std::numeric_limits<T>::max_exponent + std::numeric_limits<T>::min_exponent <= 3) {
VERIFY(scaledHighest < T(4));
}
}
template <typename T>
void check_safe_scaling_special_values() {
using Scaling = internal::safe_scaling<T>;
using Factors = internal::safe_scaling_factors<T>;
const Factors identity;
VERIFY_IS_EQUAL(identity.scale, T(1));
VERIFY_IS_EQUAL(identity.invScale, T(1));
Matrix<T, 1, 1> zero = Matrix<T, 1, 1>::Zero();
Matrix<T, 1, 1> scaledZero;
const auto zeroFactors = Scaling::scale_to(scaledZero, zero, T(0));
VERIFY_IS_EQUAL(zeroFactors.scale, T(1));
VERIFY_IS_EQUAL(zeroFactors.invScale, T(1));
VERIFY_IS_EQUAL(scaledZero(0), T(0));
for (const T special : {std::numeric_limits<T>::infinity(), std::numeric_limits<T>::quiet_NaN()}) {
Matrix<T, 2, 1> input;
input << special, T(2);
Matrix<T, 2, 1> scaled;
const auto factors = Scaling::scale_to(scaled, input, special);
VERIFY_IS_EQUAL(factors.scale, T(1));
VERIFY_IS_EQUAL(factors.invScale, T(1));
VERIFY_IS_EQUAL(scaled(1), T(2));
if ((numext::isnan)(special)) {
VERIFY((numext::isnan)(scaled(0)));
} else {
VERIFY((numext::isinf)(scaled(0)));
}
}
}
void check_safe_scaling_special_value_frontends() {
using Scaling = internal::safe_scaling<float>;
using Factors = internal::safe_scaling_factors<float>;
for (const float special : {std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
Vector2f input(special, 2.0f);
Vector2f scaledTo;
const auto scaleToFactors = Scaling::scale_to(scaledTo, input, special);
VERIFY_IS_EQUAL(scaleToFactors.scale, 1.0f);
VERIFY_IS_EQUAL(scaleToFactors.invScale, 1.0f);
Vector2f scaledExpression;
const Factors expressionFactors =
Scaling::with_scaled(input, special, [&](const auto& expression) { scaledExpression = expression; });
VERIFY_IS_EQUAL(expressionFactors.scale, 1.0f);
VERIFY_IS_EQUAL(expressionFactors.invScale, 1.0f);
VERIFY_IS_EQUAL(scaledTo(1), 2.0f);
VERIFY_IS_EQUAL(scaledExpression(1), 2.0f);
if ((numext::isnan)(special)) {
VERIFY((numext::isnan)(scaledTo(0)));
VERIFY((numext::isnan)(scaledExpression(0)));
} else {
VERIFY((numext::isinf)(scaledTo(0)));
VERIFY((numext::isinf)(scaledExpression(0)));
}
}
}
void check_arithmetic_safe_scaling_fallback() {
using Scaling = internal::safe_scaling<double, false>;
using Factors = internal::safe_scaling_factors<double>;
Matrix<double, 2, 1> input;
input << 3.0, 6.0;
Matrix<double, 2, 1> scaled;
const auto factors = Scaling::scale_to(scaled, input, 3.0);
VERIFY_IS_EQUAL(factors.scale, 3.0);
VERIFY_IS_EQUAL(factors.invScale, 1.0);
VERIFY_IS_EQUAL(scaled(0), 1.0);
VERIFY_IS_EQUAL(scaled(1), 2.0);
Matrix<double, 2, 1> restored;
Scaling::unscale_to(restored, scaled, 3.0, factors);
VERIFY_IS_EQUAL(restored, input);
Scaling::unscale_in_place(scaled, factors);
VERIFY_IS_EQUAL(scaled, input);
const auto check_extreme_unscale = [](const double value) {
const Factors extremeFactors = Scaling::compute_floor_factors(value);
double restored;
Scaling::unscale_to(restored, 1.0, extremeFactors);
VERIFY_IS_EQUAL(restored, value);
restored = 1.0;
Scaling::unscale_in_place(restored, extremeFactors);
VERIFY_IS_EQUAL(restored, value);
};
check_extreme_unscale((std::numeric_limits<double>::max)());
volatile double denormMinInput = std::numeric_limits<double>::denorm_min();
const double denormMin = denormMinInput;
if (denormMin > 0.0) check_extreme_unscale(denormMin);
Factors normalReciprocalFactors;
const double normalMin = (std::numeric_limits<double>::min)();
VERIFY(Scaling::try_compute_ceiling_factors_with_normal_reciprocal(3.0, normalMin, normalReciprocalFactors));
VERIFY_IS_EQUAL(normalReciprocalFactors.scale, 3.0);
VERIFY_IS_EQUAL(normalReciprocalFactors.invScale, 1.0 / 3.0);
VERIFY(!Scaling::try_compute_ceiling_factors_with_normal_reciprocal(std::numeric_limits<double>::denorm_min(),
normalMin, normalReciprocalFactors));
VERIFY(!Scaling::try_compute_ceiling_factors_with_normal_reciprocal((std::numeric_limits<double>::max)(), normalMin,
normalReciprocalFactors));
}
void check_custom_scalar_scaling_exceptions() {
using Scalar = AnnoyingScalar;
using Scaling = internal::safe_scaling<Scalar>;
static_assert(!internal::supports_power_of_two_scaling<Scalar>::value, "exercise arithmetic scaling");
volatile float denormInput = std::numeric_limits<float>::denorm_min();
const float denorm = denormInput;
const float twiceDenorm = denorm + denorm;
if (!(denorm > 0.0f && twiceDenorm > 0.0f)) return; // No custom-scalar subnormal recovery is promised.
Matrix<Scalar, 2, 1> input;
input << Scalar(denorm), Scalar(twiceDenorm);
Matrix<Scalar, 2, 1> scaled;
std::fenv_t savedEnvironment;
if (std::feholdexcept(&savedEnvironment) != 0) return;
const auto factors = Scaling::scale_to(scaled, input, Scalar(twiceDenorm));
const int overflow = std::fetestexcept(FE_OVERFLOW);
std::fesetenv(&savedEnvironment);
VERIFY_IS_EQUAL(overflow, 0);
VERIFY_IS_EQUAL(scaled(0), Scalar(0.5));
VERIFY_IS_EQUAL(scaled(1), Scalar(1));
Matrix<Scalar, 2, 1> restored;
Scaling::unscale_to(restored, scaled, Scalar(twiceDenorm), factors);
VERIFY_IS_EQUAL(restored, input);
Scaling::unscale_in_place(scaled, factors);
VERIFY_IS_EQUAL(scaled, input);
}
template <typename Scalar>
void check_scale_binary_by_power_of_two() {
using Binary = internal::binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
constexpr int kMinFactorExponent = 1 - std::numeric_limits<Scalar>::min_exponent - Binary::kFractionBits;
constexpr int kMaxFactorExponent = 1 - std::numeric_limits<Scalar>::min_exponent;
const Bits magnitudes[] = {Bits(0),
Bits(1),
Bits(3),
Binary::kFractionMask,
Binary::kExponentUnit,
Binary::kExponentUnit + (Binary::kExponentUnit >> 1)};
ScopedFlushToZero flushToZero;
// Cover the entire factor range selected by tiny-input recovery. Form the reference from integer significands,
// not subnormal floating-point inputs, so it remains valid under FTZ/DAZ.
for (int exponent = kMinFactorExponent; exponent <= kMaxFactorExponent; ++exponent) {
const Scalar factor = numext::ldexp(Scalar(1), exponent);
for (Bits magnitude : magnitudes) {
const Scalar expected = numext::ldexp(Scalar(magnitude), std::numeric_limits<Scalar>::min_exponent -
std::numeric_limits<Scalar>::digits + exponent);
for (Bits sign : {Bits(0), Bits(Binary::kSignBit)}) {
const Scalar value = numext::bit_cast<Scalar>(sign | magnitude);
const Scalar actual = internal::scale_binary_by_power_of_two(value, factor);
VERIFY_IS_EQUAL(Binary::bits(actual), sign | Binary::bits(expected));
}
}
}
}
// Rounds a subnormal result exactly where hardware arithmetic may flush it: the exact product m * 2^-k denorm_min is
// normal in double, and its nearest integer, ties to even, is the expected magnitude's bit pattern (a carry into the
// exponent field encodes the smallest normal).
template <typename Scalar>
void check_scale_binary_by_power_of_two_rounding() {
using Binary = internal::binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
// Leading-bit significands in [kExponentUnit, 2 * kExponentUnit): ties and their neighbours at small shifts, and an
// all-ones tail that carries into the smallest normal.
const Bits significands[] = {Binary::kExponentUnit,
Binary::kExponentUnit | Bits(1),
Binary::kExponentUnit | Bits(2),
Binary::kExponentUnit | Bits(3),
Binary::kExponentUnit | Bits(5),
Binary::kExponentUnit + (Binary::kExponentUnit >> 1),
(Binary::kExponentUnit << 1) - Bits(1)};
// A biased input exponent e scales m by 2^(e - 1); the factor 2^-(e - 1 + k) cancels it and shifts by k.
const int exponentFields[] = {1, 2, Binary::kExponentBias - Binary::kFractionBits - 3};
ScopedFlushToZero flushToZero;
for (int field : exponentFields) {
for (int k = 0; k <= Binary::kFractionBits + 3; ++k) {
const Scalar factor = numext::ldexp(Scalar(1), -(field - 1 + k));
for (Bits m : significands) {
const Bits expected = Bits(std::nearbyint(std::ldexp(double(m), -k)));
for (Bits sign : {Bits(0), Bits(Binary::kSignBit)}) {
const Scalar value =
numext::bit_cast<Scalar>(sign | (Bits(field) << Binary::kFractionBits) | (m & Binary::kFractionMask));
VERIFY_IS_EQUAL(Binary::bits(internal::scale_binary_by_power_of_two(value, factor)), sign | expected);
}
}
}
}
// Subnormal inputs normalize before they round.
for (Bits magnitude : {Bits(1), Bits(3), Binary::kExponentUnit >> 1, Binary::kFractionMask}) {
for (int k = 1; k <= Binary::kFractionBits + 2; ++k) {
const Bits expected = Bits(std::nearbyint(std::ldexp(double(magnitude), -k)));
for (Bits sign : {Bits(0), Bits(Binary::kSignBit)}) {
const Scalar actual = internal::scale_binary_by_power_of_two(numext::bit_cast<Scalar>(sign | magnitude),
numext::ldexp(Scalar(1), -k));
VERIFY_IS_EQUAL(Binary::bits(actual), sign | expected);
}
}
}
}
// Under DAZ a comparison reads a subnormal as zero, so compare representations.
template <typename RealScalar>
bool same_bits(const RealScalar& a, const RealScalar& b) {
using Binary = internal::binary_floating_point_traits<RealScalar>;
return Binary::bits(a) == Binary::bits(b);
}
template <typename RealScalar>
bool same_bits(const std::complex<RealScalar>& a, const std::complex<RealScalar>& b) {
return same_bits(a.real(), b.real()) && same_bits(a.imag(), b.imag());
}
template <typename Scalar>
struct scaling_test_value {
using RealScalar = typename NumTraits<Scalar>::Real;
static Scalar run(RealScalar real, RealScalar) { return real; }
};
template <typename RealScalar>
struct scaling_test_value<std::complex<RealScalar>> {
static std::complex<RealScalar> run(RealScalar real, RealScalar imag) { return std::complex<RealScalar>(real, imag); }
};
template <typename Scalar>
void check_arithmetic_scaling_expression() {
using RealScalar = typename NumTraits<Scalar>::Real;
using Scaling = internal::safe_scaling<RealScalar, false>;
using Vector2 = Matrix<Scalar, 2, 1>;
using Value = scaling_test_value<Scalar>;
// Lazy scaling and its adjoint must retain the division policy even below the binary-recovery threshold.
for (const RealScalar maxCoeff :
{RealScalar(3), numext::ldexp(RealScalar(1.5), std::numeric_limits<RealScalar>::min_exponent + 7)}) {
Vector2 input;
input << Value::run(maxCoeff, RealScalar(0)), Value::run(maxCoeff / RealScalar(2), -maxCoeff / RealScalar(4));
Vector2 expected;
expected << Value::run(RealScalar(1), RealScalar(0)), Value::run(RealScalar(0.5), RealScalar(-0.25));
Vector2 materialized;
const auto factors = Scaling::scale_to(materialized, input, maxCoeff);
Vector2 lazy;
Matrix<Scalar, 1, 2> adjoint;
const auto expressionFactors = Scaling::with_scaled(input, maxCoeff, [&](const auto& expression) {
lazy = expression;
adjoint = expression.adjoint();
});
VERIFY_IS_EQUAL(materialized, expected);
VERIFY_IS_EQUAL(lazy, expected);
VERIFY_IS_EQUAL(adjoint, expected.adjoint());
VERIFY_IS_EQUAL(expressionFactors.scale, factors.scale);
VERIFY_IS_EQUAL(expressionFactors.invScale, factors.invScale);
Vector2 inPlace = input;
const auto inPlaceFactors = Scaling::scale_in_place(inPlace, maxCoeff);
VERIFY_IS_EQUAL(inPlace, expected);
VERIFY_IS_EQUAL(inPlaceFactors.scale, factors.scale);
VERIFY_IS_EQUAL(inPlaceFactors.invScale, factors.invScale);
}
}
template <typename Scalar>
void check_subnormal_preserving_scaling() {
using RealScalar = typename NumTraits<Scalar>::Real;
using Binary = internal::binary_floating_point_traits<RealScalar>;
using Bits = typename Binary::Bits;
const RealScalar maxCoeff = numext::ldexp((std::numeric_limits<RealScalar>::min)(), 8);
// Arithmetic or narrowing conversions could flush these inputs before they reach the scaling helper.
const RealScalar subnormalMaxCoeff = numext::bit_cast<RealScalar>(Bits(64));
const auto factors = internal::safe_scaling<RealScalar>::compute_floor_factors(maxCoeff);
Matrix<Scalar, 2, 1> input;
input(0) = scaling_test_value<Scalar>::run(maxCoeff, -maxCoeff);
input(1) = scaling_test_value<Scalar>::run(numext::bit_cast<RealScalar>(Binary::kExponentUnit >> 1),
numext::bit_cast<RealScalar>(Binary::kExponentUnit >> 2));
Matrix<Scalar, 1, 1> subnormalInput;
subnormalInput(0) = scaling_test_value<Scalar>::run(subnormalMaxCoeff, RealScalar(0));
ScopedFlushToZero flushToZero;
Matrix<Scalar, 2, 1> scaled;
internal::safe_scaling<RealScalar>::scale_to(scaled, input, maxCoeff, factors);
Matrix<Scalar, 2, 1> scaledExpression;
const auto expressionFactors = internal::safe_scaling<RealScalar>::with_scaled(
input, maxCoeff, [&](const auto& expression) { scaledExpression = expression; });
VERIFY_IS_EQUAL(scaled(0), scaling_test_value<Scalar>::run(RealScalar(1), RealScalar(-1)));
VERIFY_IS_EQUAL(scaled(1),
scaling_test_value<Scalar>::run(RealScalar(1) / RealScalar(512), RealScalar(1) / RealScalar(1024)));
VERIFY_IS_EQUAL(expressionFactors.scale, factors.scale);
VERIFY_IS_EQUAL(expressionFactors.invScale, factors.invScale);
VERIFY_IS_EQUAL(scaledExpression, scaled);
Matrix<Scalar, 2, 1> scaledInPlace = input;
const auto inPlaceFactors = internal::safe_scaling<RealScalar>::scale_in_place(scaledInPlace, maxCoeff);
VERIFY_IS_EQUAL(inPlaceFactors.scale, factors.scale);
VERIFY_IS_EQUAL(inPlaceFactors.invScale, factors.invScale);
VERIFY_IS_EQUAL(scaledInPlace, scaled);
Matrix<Scalar, 1, 1> scaledSubnormal;
const auto subnormalFactors =
internal::safe_scaling<RealScalar>::scale_to(scaledSubnormal, subnormalInput, subnormalMaxCoeff);
VERIFY(subnormalFactors.scale != RealScalar(1) || subnormalFactors.invScale != RealScalar(1));
VERIFY(numext::abs(scaledSubnormal(0)) > RealScalar(0));
// Unscaling restores the subnormal components exactly, still under FTZ.
Matrix<Scalar, 2, 1> restored;
internal::safe_scaling<RealScalar>::unscale_to(restored, scaled, maxCoeff, factors);
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(restored(i), input(i)));
internal::safe_scaling<RealScalar>::unscale_in_place(scaled, maxCoeff, factors);
internal::safe_scaling<RealScalar>::unscale_in_place(scaledSubnormal, subnormalMaxCoeff, subnormalFactors);
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(scaled(i), input(i)));
VERIFY(same_bits(scaledSubnormal(0), subnormalInput(0)));
// A NaN, infinite or zero maxCoeff selects identity factors, and unscaling leaves the coefficients alone.
for (const RealScalar special :
{std::numeric_limits<RealScalar>::quiet_NaN(), std::numeric_limits<RealScalar>::infinity(), RealScalar(0)}) {
Matrix<Scalar, 2, 1> unchanged;
const auto specialFactors = internal::safe_scaling<RealScalar>::scale_to(unchanged, input, special);
internal::safe_scaling<RealScalar>::unscale_in_place(unchanged, special, specialFactors);
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(unchanged(i), input(i)));
const auto inPlaceSpecialFactors = internal::safe_scaling<RealScalar>::scale_in_place(unchanged, special);
VERIFY_IS_EQUAL(inPlaceSpecialFactors.scale, RealScalar(1));
VERIFY_IS_EQUAL(inPlaceSpecialFactors.invScale, RealScalar(1));
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(unchanged(i), input(i)));
}
}
// Unscaling rounds through integer significands exactly when 0 < maxCoeff < min / eps. With floor factors, eps / 2
// unscales to min / 2 at the threshold and to min / 4 one ulp below it; FTZ flushes only the former.
template <typename Scalar>
void check_unscale_recovery_threshold() {
using Binary = internal::binary_floating_point_traits<Scalar>;
using Scaling = internal::safe_scaling<Scalar>;
const Scalar threshold = (std::numeric_limits<Scalar>::min)() / NumTraits<Scalar>::epsilon();
const Scalar belowThreshold = numext::nextafter(threshold, Scalar(0));
// Read through volatile so the products are formed at run time, under FTZ.
volatile Scalar halfEpsilon = NumTraits<Scalar>::epsilon() / Scalar(2);
Matrix<Scalar, 1, 1> atThreshold, below;
atThreshold(0) = halfEpsilon;
below(0) = halfEpsilon;
ScopedFlushToZero flushToZero;
Scaling::unscale_in_place(below, belowThreshold, Scaling::compute_floor_factors(belowThreshold));
VERIFY(same_bits(below(0), numext::bit_cast<Scalar>(Binary::kExponentUnit >> 2)));
Scaling::unscale_in_place(atThreshold, threshold, Scaling::compute_floor_factors(threshold));
if (flushToZero.isSupported()) VERIFY(same_bits(atThreshold(0), Scalar(0)));
}
template <typename Scalar>
void check_identity_scaling() {
using RealScalar = typename NumTraits<Scalar>::Real;
using Binary = internal::binary_floating_point_traits<RealScalar>;
using Scaling = internal::safe_scaling<RealScalar>;
Matrix<Scalar, 2, 1> input;
input(0) = scaling_test_value<Scalar>::run(RealScalar(1), RealScalar(-1));
input(1) = scaling_test_value<Scalar>::run(numext::bit_cast<RealScalar>(Binary::kExponentUnit >> 1),
numext::bit_cast<RealScalar>(+Binary::kSignBit));
const ScopedFlushToZero flush;
for (RealScalar maxCoeff : {RealScalar(0), RealScalar(1), RealScalar(1.5), NumTraits<RealScalar>::infinity(),
NumTraits<RealScalar>::quiet_NaN()}) {
// Keep the factor selection at run time, where a compiler can merge a copy with multiplication by one.
volatile RealScalar runtimeMaxCoeff = maxCoeff;
Matrix<Scalar, 2, 1> scaled, restored;
const auto factors = Scaling::scale_to(scaled, input, RealScalar(runtimeMaxCoeff));
VERIFY_IS_EQUAL(factors.scale, RealScalar(1));
VERIFY_IS_EQUAL(factors.invScale, RealScalar(1));
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(scaled(i), input(i)));
Scaling::unscale_to(restored, input, factors);
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(restored(i), input(i)));
Scaling::with_scaled(input, RealScalar(runtimeMaxCoeff), [&](const auto& expression) { scaled = expression; });
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(scaled(i), input(i)));
Scaling::scale_in_place(scaled, RealScalar(runtimeMaxCoeff));
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(scaled(i), input(i)));
Scaling::unscale_in_place(scaled, factors);
for (Index i = 0; i < input.size(); ++i) VERIFY(same_bits(scaled(i), input(i)));
}
}
// A reduction that flushes subnormal inputs returns zero; the rescan reads the largest real or imaginary magnitude from
// the representation instead, and leaves a nonzero maximum alone.
template <typename Scalar>
void check_recover_flushed_max_coeff() {
using RealScalar = typename NumTraits<Scalar>::Real;
using Binary = internal::binary_floating_point_traits<RealScalar>;
using Bits = typename Binary::Bits;
using Scaling = internal::safe_scaling<RealScalar>;
using Value = scaling_test_value<Scalar>;
const RealScalar small = numext::bit_cast<RealScalar>(Bits(3));
const RealScalar large = numext::bit_cast<RealScalar>(Bits(Binary::kFractionMask));
Matrix<Scalar, 2, 2> m;
m << Value::run(small, RealScalar(0)), Value::run(-small, -large), Value::run(RealScalar(0), small),
Value::run(-small, RealScalar(0));
const RealScalar expected = NumTraits<Scalar>::IsComplex ? large : small;
VERIFY(same_bits(Scaling::recover_flushed_max_coeff(m, RealScalar(0)), expected));
VERIFY(same_bits(Scaling::recover_flushed_max_coeff(m, RealScalar(1)), RealScalar(1)));
VERIFY(same_bits(Scaling::recover_flushed_max_coeff(Matrix<Scalar, 2, 2>::Zero(), RealScalar(0)), RealScalar(0)));
}
// A strided view reduces coefficient by coefficient, and under Arm FZ a scalar max(a, b) is a compare and a select
// that keeps the first subnormal operand rather than producing a zero: the rescan must not trust a subnormal maximum.
// Every flush-to-zero mode, with the largest magnitude last.
template <typename Scalar>
void check_recover_flushed_scalar_max_coeff() {
using Binary = internal::binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
using Scaling = internal::safe_scaling<Scalar>;
using VectorType = Matrix<Scalar, Dynamic, 1>;
VectorType storage = VectorType::Zero(6);
const Bits significands[3] = {Bits(5), Bits(8), Bits(23)};
for (Index i = 0; i < 3; ++i) storage(2 * i) = numext::bit_cast<Scalar>(significands[i]);
const Map<const VectorType, 0, InnerStride<2>> strided(storage.data(), 3);
forEachFlushToZeroMode([&](FlushToZeroMode) {
const Scalar maxCoeff = strided.cwiseAbs().maxCoeff();
VERIFY(same_bits(Scaling::recover_flushed_max_coeff(strided, maxCoeff), numext::bit_cast<Scalar>(Bits(23))));
VERIFY_IS_EQUAL(internal::binary_frexp_exponent(Scaling::recover_flushed_max_coeff(strided, maxCoeff)),
5 + std::numeric_limits<Scalar>::min_exponent - std::numeric_limits<Scalar>::digits);
});
}
// Each flush-to-zero mode the host selects does what its name says, read from the results of two probes through the
// representation, since a comparison under DAZ reads any subnormal as zero; the modes restore the ambient state.
template <typename Scalar>
void check_flush_to_zero_modes() {
using Binary = internal::binary_floating_point_traits<Scalar>;
const auto resultsFlushed = []() { return Binary::bits(underflowProbe<Scalar>()) == 0; };
const auto inputsFlushed = []() { return Binary::bits(subnormalInputProbe<Scalar>()) == 0; };
const bool resultsBefore = resultsFlushed(), inputsBefore = inputsFlushed();
int modes = 0;
forEachFlushToZeroMode([&](FlushToZeroMode mode) {
++modes;
VERIFY_IS_EQUAL(resultsFlushed(), flushToZeroModeIncludes(mode, FlushToZeroMode::Results));
VERIFY_IS_EQUAL(inputsFlushed(), flushToZeroModeIncludes(mode, FlushToZeroMode::Inputs));
VERIFY_IS_EQUAL(ScopedFlushToZero::hardwareFlushesSubnormalInputs(),
flushToZeroModeIncludes(mode, FlushToZeroMode::Inputs));
});
VERIFY(modes >= 1);
VERIFY_IS_EQUAL(resultsFlushed(), resultsBefore);
VERIFY_IS_EQUAL(inputsFlushed(), inputsBefore);
// The default request flushes at least results, as it always did.
{
const ScopedFlushToZero flushToZero;
if (flushToZero.isSupported()) {
VERIFY(flushToZeroModeIncludes(flushToZero.mode(), FlushToZeroMode::Results));
VERIFY(resultsFlushed());
}
}
VERIFY_IS_EQUAL(resultsFlushed(), resultsBefore);
}
// value * 2^exponent through integer significands agrees bit for bit with the correctly rounded ldexp for every input
// class and every exponent that changes the result, and keeps doing so in every flush-to-zero mode, where the
// references are taken beforehand. Beyond the exponent range it saturates to the signed zero or infinity ldexp returns.
template <typename Scalar>
void check_scale_binary_by_exponent() {
using Binary = internal::binary_floating_point_traits<Scalar>;
using Bits = typename Binary::Bits;
using Scaling = internal::safe_scaling<Scalar>;
// 2^recovery is the min / eps threshold check_unscale_recovery_threshold() exercises.
VERIFY_IS_EQUAL(numext::ldexp(Scalar(1), Scaling::subnormal_recovery_exponent()),
(std::numeric_limits<Scalar>::min)() / NumTraits<Scalar>::epsilon());
// Subnormals down to denorm_min, the normal boundary, full fractions, and the largest finite value.
const Bits magnitudes[] = {Bits(1),
Bits(3),
Binary::kExponentUnit >> 1,
Binary::kFractionMask,
Binary::kExponentUnit,
Binary::kExponentUnit | Bits(1),
Binary::kExponentUnit + (Binary::kExponentUnit >> 1),
(Binary::kExponentUnit << 1) - Bits(1),
Binary::bits(Scalar(0.75)),
Binary::bits(Scalar(1)),
Binary::bits(Scalar(1.5)),
Binary::bits((std::numeric_limits<Scalar>::max)())};
// Every exponent from full underflow to full overflow, then values that only the clamp can handle.
const int span = std::numeric_limits<Scalar>::max_exponent - std::numeric_limits<Scalar>::min_exponent +
std::numeric_limits<Scalar>::digits + 3;
std::vector<int> exponents;
for (int e = -span; e <= span; ++e) exponents.push_back(e);
for (int e : {1 << 24, 1 << 30, (std::numeric_limits<int>::max)()}) {
exponents.push_back(e);
exponents.push_back(-e);
}
exponents.push_back((std::numeric_limits<int>::min)());
struct Case {
Bits value;
int exponent;
Bits expected;
Bits expectedUnit; // ldexp(1, exponent)
int frexpExponent; // of value
Bits frexpSignificand;
};
std::vector<Case> cases;
for (Bits magnitude : magnitudes) {
for (int e : exponents) {
for (Bits sign : {Bits(0), Bits(Binary::kSignBit)}) {
const Scalar value = numext::bit_cast<Scalar>(sign | magnitude);
int frexpExponent = 0;
EIGEN_USING_STD(frexp);
const Scalar frexpSignificand = frexp(value, &frexpExponent);
cases.push_back(Case{sign | magnitude, e, Binary::bits(numext::ldexp(value, e)),
Binary::bits(numext::ldexp(Scalar(1), e)), frexpExponent, Binary::bits(frexpSignificand)});
}
}
}
for (const Scalar special : {Scalar(0), -Scalar(0), std::numeric_limits<Scalar>::infinity(),
-std::numeric_limits<Scalar>::infinity(), std::numeric_limits<Scalar>::quiet_NaN()}) {
for (int e : {-1000, -1, 0, 1, 1000}) {
cases.push_back(Case{Binary::bits(special), e, Binary::bits(special), Binary::bits(numext::ldexp(Scalar(1), e)),
0, Binary::bits(special)});
}
}
const auto check = [&]() {
for (const Case& c : cases) {
const Scalar actual = internal::scale_binary_by_exponent(numext::bit_cast<Scalar>(c.value), c.exponent);
VERIFY_IS_EQUAL(Binary::bits(actual), c.expected);
const std::complex<Scalar> complexActual = internal::scale_binary_by_exponent(
std::complex<Scalar>(numext::bit_cast<Scalar>(c.value), Scalar(1)), c.exponent);
VERIFY_IS_EQUAL(Binary::bits(complexActual.real()), c.expected);
VERIFY_IS_EQUAL(Binary::bits(complexActual.imag()), c.expectedUnit);
// The representation-based classifiers agree with the IEEE ones on every value.
const Scalar value = numext::bit_cast<Scalar>(c.value);
VERIFY_IS_EQUAL(Binary::bits(internal::ldexp_preserving_subnormals(value, c.exponent)), c.expected);
const Bits magnitude = c.value & ~Binary::kSignBit;
VERIFY_IS_EQUAL(numext::is_exactly_zero_no_flush(value), magnitude == 0);
VERIFY_IS_EQUAL(internal::is_subnormal_magnitude(value), magnitude != 0 && magnitude < Binary::kExponentUnit);
if (magnitude == c.value &&
(numext::isfinite)(value)) { // non-negative: the maximum against every larger power of two
VERIFY_IS_EQUAL(Binary::bits(internal::max_preserving_subnormals(value, numext::bit_cast<Scalar>(Bits(1)))),
magnitude == 0 ? Bits(1) : magnitude);
}
// frexp from the representation agrees with frexp on every finite value; zeros give exponent 0.
if ((numext::isfinite)(value)) {
int frexpExponent = 0;
const Scalar significand = internal::binary_frexp(value, frexpExponent);
VERIFY_IS_EQUAL(frexpExponent, c.frexpExponent);
VERIFY_IS_EQUAL(Binary::bits(significand), c.frexpSignificand);
VERIFY_IS_EQUAL(internal::binary_frexp_exponent(value), c.frexpExponent);
}
}
// A normal power-of-two factor is the exponent form of the same scaling.
for (int e = std::numeric_limits<Scalar>::min_exponent - 1; e < std::numeric_limits<Scalar>::max_exponent; ++e) {
const Scalar factor = numext::bit_cast<Scalar>(Bits(e + Binary::kExponentBias) << Binary::kFractionBits);
for (Bits magnitude : magnitudes) {
const Scalar value = numext::bit_cast<Scalar>(magnitude);
VERIFY_IS_EQUAL(Binary::bits(internal::scale_binary_by_power_of_two(value, factor)),
Binary::bits(internal::scale_binary_by_exponent(value, e)));
}
}
// The functor is the coefficient-wise scalar form, complex components included.
Matrix<std::complex<Scalar>, 3, 1> input;
input << std::complex<Scalar>(numext::bit_cast<Scalar>(Bits(3)), numext::bit_cast<Scalar>(Binary::kFractionMask)),
std::complex<Scalar>(Scalar(1.5), -Scalar(0.75)),
std::complex<Scalar>((std::numeric_limits<Scalar>::max)(), numext::bit_cast<Scalar>(Bits(1)));
for (int e : {-(std::numeric_limits<Scalar>::digits + 1), -3, 5, std::numeric_limits<Scalar>::max_exponent}) {
const Matrix<std::complex<Scalar>, 3, 1> scaled = input.unaryExpr(internal::scale_by_exponent_op<Scalar>(e));
for (Index i = 0; i < input.size(); ++i) {
VERIFY(same_bits(scaled(i), internal::scale_binary_by_exponent(input(i), e)));
}
}
};
forEachFlushToZeroMode([&](FlushToZeroMode) { check(); });
}
// The scalars without a representation path take the C library.
void check_preserving_subnormals_fallbacks() {
VERIFY_IS_EQUAL(internal::ldexp_preserving_subnormals(1.5L, 3), 12.0L);
VERIFY_IS_EQUAL(internal::frexp_exponent_preserving_subnormals(12.0L), 4);
int exponent = 0;
VERIFY_IS_EQUAL(internal::frexp_preserving_subnormals(-12.0L, exponent), -0.75L);
VERIFY_IS_EQUAL(exponent, 4);
VERIFY_IS_EQUAL(internal::frexp_exponent_preserving_subnormals(0.0L), 0);
VERIFY(numext::is_exactly_zero_no_flush(0.0L));
VERIFY(!internal::is_subnormal_magnitude(1.0L));
VERIFY(internal::is_subnormal_magnitude(std::numeric_limits<long double>::denorm_min()));
VERIFY_IS_EQUAL(internal::max_preserving_subnormals(1.5L, 2.5L), 2.5L);
}
EIGEN_DECLARE_TEST(safe_scaling) {
CALL_SUBTEST(check_flush_to_zero_modes<float>());
CALL_SUBTEST(check_flush_to_zero_modes<double>());
CALL_SUBTEST(check_recover_flushed_scalar_max_coeff<float>());
CALL_SUBTEST(check_recover_flushed_scalar_max_coeff<double>());
CALL_SUBTEST(check_scale_binary_by_exponent<float>());
CALL_SUBTEST(check_scale_binary_by_exponent<double>());
CALL_SUBTEST(check_preserving_subnormals_fallbacks());
CALL_SUBTEST(check_identity_scaling<float>());
CALL_SUBTEST(check_identity_scaling<double>());
CALL_SUBTEST(check_identity_scaling<std::complex<float>>());
CALL_SUBTEST(check_identity_scaling<std::complex<double>>());
CALL_SUBTEST(check_power_of_two_scaling_factor<half>());
CALL_SUBTEST(check_power_of_two_scaling_factor<bfloat16>());
CALL_SUBTEST(check_power_of_two_scaling_factor<float>());
CALL_SUBTEST(check_power_of_two_scaling_factor<double>());
CALL_SUBTEST(check_power_of_two_scaling_factor<long double>());
CALL_SUBTEST(check_safe_scaling_special_values<half>());
CALL_SUBTEST(check_safe_scaling_special_values<bfloat16>());
CALL_SUBTEST(check_safe_scaling_special_values<float>());
CALL_SUBTEST(check_safe_scaling_special_values<double>());
CALL_SUBTEST(check_safe_scaling_special_value_frontends());
CALL_SUBTEST(check_arithmetic_safe_scaling_fallback());
CALL_SUBTEST(check_custom_scalar_scaling_exceptions());
CALL_SUBTEST(check_arithmetic_scaling_expression<float>());
CALL_SUBTEST(check_arithmetic_scaling_expression<double>());
CALL_SUBTEST(check_arithmetic_scaling_expression<std::complex<float>>());
CALL_SUBTEST(check_arithmetic_scaling_expression<std::complex<double>>());
CALL_SUBTEST(check_scale_binary_by_power_of_two<float>());
CALL_SUBTEST(check_scale_binary_by_power_of_two<double>());
CALL_SUBTEST(check_scale_binary_by_power_of_two_rounding<float>());
CALL_SUBTEST(check_scale_binary_by_power_of_two_rounding<double>());
CALL_SUBTEST(check_subnormal_preserving_scaling<float>());
CALL_SUBTEST(check_subnormal_preserving_scaling<std::complex<float>>());
CALL_SUBTEST(check_subnormal_preserving_scaling<double>());
CALL_SUBTEST(check_subnormal_preserving_scaling<std::complex<double>>());
CALL_SUBTEST(check_unscale_recovery_threshold<float>());
CALL_SUBTEST(check_unscale_recovery_threshold<double>());
CALL_SUBTEST(check_recover_flushed_max_coeff<float>());
CALL_SUBTEST(check_recover_flushed_max_coeff<std::complex<float>>());
CALL_SUBTEST(check_recover_flushed_max_coeff<double>());
CALL_SUBTEST(check_recover_flushed_max_coeff<std::complex<double>>());
}