Files
eigen/test/stable_norm_fastmath.cpp
2026-07-10 18:13:48 -07:00

124 lines
5.0 KiB
C++

// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
#include "fp_control.h"
template <typename RealScalar>
EIGEN_DONT_INLINE RealScalar underflow_probe() {
volatile RealScalar normal_min = (std::numeric_limits<RealScalar>::min)();
volatile RealScalar one_half = RealScalar(0.5);
return normal_min * one_half;
}
#if defined(EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC) && EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC
EIGEN_DONT_INLINE half fp16_underflow_probe(half normal_min, half one_half) { return normal_min * one_half; }
// Volatile dispatch prevents constant-propagation clones from bypassing the changed FPCR.
typedef half (*Fp16UnderflowProbe)(half, half);
static Fp16UnderflowProbe volatile fp16_underflow_probe_runtime = &fp16_underflow_probe;
void scoped_flush_to_zero_fp16() {
const half normal_min = (std::numeric_limits<half>::min)();
const half one_half(0.5f);
const half underflow_before = fp16_underflow_probe_runtime(normal_min, one_half);
bool flush_to_zero_supported = false;
{
Eigen::ScopedFlushToZero flush_to_zero;
flush_to_zero_supported = flush_to_zero.isSupported();
VERIFY(flush_to_zero_supported);
VERIFY_IS_EQUAL(static_cast<float>(fp16_underflow_probe_runtime(normal_min, one_half)), 0.0f);
}
VERIFY_IS_EQUAL(static_cast<float>(fp16_underflow_probe_runtime(normal_min, one_half)),
static_cast<float>(underflow_before));
}
#endif
template <typename RealScalar>
void stable_normalize_fastmath() {
typedef Matrix<RealScalar, 2, 1> Vector2;
typedef std::complex<RealScalar> Complex;
typedef Matrix<Complex, 1, 1> ComplexVector1;
using std::sqrt;
const RealScalar highest = (std::numeric_limits<RealScalar>::max)();
const RealScalar smallest = (std::numeric_limits<RealScalar>::min)();
const RealScalar inv_sqrt_two = RealScalar(1) / sqrt(RealScalar(2));
const Vector2 expected = Vector2::Constant(inv_sqrt_two);
Vector2 large = Vector2::Constant(highest);
VERIFY_IS_APPROX(large.stableNormalized(), expected);
large.stableNormalize();
VERIFY_IS_APPROX(large, expected);
Vector2 small = Vector2::Constant(smallest);
VERIFY_IS_APPROX(small.stableNormalized(), expected);
small.stableNormalize();
VERIFY_IS_APPROX(small, expected);
ComplexVector1 complex_large;
complex_large(0) = Complex(highest, highest);
const Complex expected_complex(inv_sqrt_two, inv_sqrt_two);
VERIFY_IS_APPROX(complex_large.stableNormalized()(0), expected_complex);
complex_large.stableNormalize();
VERIFY_IS_APPROX(complex_large(0), expected_complex);
}
template <typename RealScalar>
void stable_norm_ftz_boundaries() {
typedef Matrix<RealScalar, 2, 1> Vector2;
typedef Matrix<RealScalar, Dynamic, 1> VectorX;
using std::sqrt;
const RealScalar normal_min = (std::numeric_limits<RealScalar>::min)();
const RealScalar inv_sqrt_two = RealScalar(1) / sqrt(RealScalar(2));
const Vector2 expected_normalized = Vector2::Constant(inv_sqrt_two);
Vector2 reciprocal_boundary = Vector2::Constant(RealScalar(1) / normal_min);
const Index block_size = 4096;
const RealScalar large = RealScalar(128) * sqrt(normal_min);
const RealScalar small = RealScalar(0.5) * sqrt(normal_min);
VectorX mixed = VectorX::Constant(block_size, small);
mixed(0) = large;
const RealScalar expected_norm = large * sqrt(RealScalar(1) + RealScalar(block_size - 1) / RealScalar(65536));
const RealScalar underflow_before = underflow_probe<RealScalar>();
bool flush_to_zero_supported = false;
{
Eigen::ScopedFlushToZero flush_to_zero;
flush_to_zero_supported = flush_to_zero.isSupported();
if (flush_to_zero_supported) VERIFY_IS_EQUAL(underflow_probe<RealScalar>(), RealScalar(0));
VERIFY_IS_APPROX(reciprocal_boundary.stableNormalized(), expected_normalized);
reciprocal_boundary.stableNormalize();
VERIFY_IS_APPROX(reciprocal_boundary, expected_normalized);
VERIFY_IS_APPROX(mixed.stableNorm(), expected_norm);
}
if (flush_to_zero_supported) VERIFY_IS_EQUAL(underflow_probe<RealScalar>(), underflow_before);
}
template <typename RealScalar>
void stable_norm_multiblock_fastmath() {
typedef Matrix<RealScalar, Dynamic, 1> VectorX;
using std::sqrt;
const Index block_size = 4096;
const RealScalar scale = sqrt((std::numeric_limits<RealScalar>::max)());
VectorX input = VectorX::Zero(2 * block_size);
input(0) = scale;
input.tail(block_size).setConstant(scale / RealScalar(128));
const RealScalar expected = scale * sqrt(RealScalar(1.25));
VERIFY_IS_APPROX(input.stableNorm(), expected);
}
EIGEN_DECLARE_TEST(stable_norm_fastmath) {
CALL_SUBTEST_1(stable_normalize_fastmath<float>());
CALL_SUBTEST_2(stable_normalize_fastmath<double>());
CALL_SUBTEST_1(stable_norm_multiblock_fastmath<float>());
CALL_SUBTEST_2(stable_norm_multiblock_fastmath<double>());
CALL_SUBTEST_1(stable_norm_ftz_boundaries<float>());
CALL_SUBTEST_2(stable_norm_ftz_boundaries<double>());
#if defined(EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC) && EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC
CALL_SUBTEST_1(scoped_flush_to_zero_fp16());
#endif
}