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