diff options
author | beetrees <b@beetr.ee> | 2025-03-10 02:44:27 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-10 09:44:27 +0700 |
commit | 8b9c91ec7da0ca3daab8ff6711126443e500bb66 (patch) | |
tree | 2fd76fd6a9e9ea442b149058960ce4685d1d35c1 /llvm/unittests/ADT/APFloatTest.cpp | |
parent | 82f2b661101d1133b72872141009035f01a42595 (diff) | |
download | llvm-8b9c91ec7da0ca3daab8ff6711126443e500bb66.zip llvm-8b9c91ec7da0ca3daab8ff6711126443e500bb66.tar.gz llvm-8b9c91ec7da0ca3daab8ff6711126443e500bb66.tar.bz2 |
[APFloat] Fix `IEEEFloat::addOrSubtractSignificand` and `IEEEFloat::normalize` (#98721)
Fixes #63895
Fixes #104984
Before this PR, `addOrSubtractSignificand` presumed that the loss came
from the side being subtracted, and didn't handle the case where lhs ==
rhs and there was loss. This can occur during FMA. This PR fixes the
situation by correctly determining where the loss came from and handling
it appropriately.
Additionally, `normalize` failed to adjust the exponent when the
significand is zero but `lost_fraction != lfExactlyZero`. This meant
that the test case from #63895 was rounded incorrectly as the loss
wasn't adjusted to account for the exponent being below the minimum
exponent. This PR fixes this by only skipping the exponent adjustment if
the significand is zero and there was no lost fraction.
(Note to reviewer: I don't have commit access)
Diffstat (limited to 'llvm/unittests/ADT/APFloatTest.cpp')
-rw-r--r-- | llvm/unittests/ADT/APFloatTest.cpp | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp index c2d99f3..0935619 100644 --- a/llvm/unittests/ADT/APFloatTest.cpp +++ b/llvm/unittests/ADT/APFloatTest.cpp @@ -47,6 +47,37 @@ static std::string convertToString(double d, unsigned Prec, unsigned Pad, return std::string(Buffer.data(), Buffer.size()); } +namespace llvm { +namespace detail { +class IEEEFloatUnitTestHelper { +public: + static void runTest(bool subtract, bool lhsSign, + APFloat::ExponentType lhsExponent, + APFloat::integerPart lhsSignificand, bool rhsSign, + APFloat::ExponentType rhsExponent, + APFloat::integerPart rhsSignificand, bool expectedSign, + APFloat::ExponentType expectedExponent, + APFloat::integerPart expectedSignificand, + lostFraction expectedLoss) { + // `addOrSubtractSignificand` only uses the sign, exponent and significand + IEEEFloat lhs(1.0); + lhs.sign = lhsSign; + lhs.exponent = lhsExponent; + lhs.significand.part = lhsSignificand; + IEEEFloat rhs(1.0); + rhs.sign = rhsSign; + rhs.exponent = rhsExponent; + rhs.significand.part = rhsSignificand; + lostFraction resultLoss = lhs.addOrSubtractSignificand(rhs, subtract); + EXPECT_EQ(resultLoss, expectedLoss); + EXPECT_EQ(lhs.sign, expectedSign); + EXPECT_EQ(lhs.exponent, expectedExponent); + EXPECT_EQ(lhs.significand.part, expectedSignificand); + } +}; +} // namespace detail +} // namespace llvm + namespace { TEST(APFloatTest, isSignaling) { @@ -560,6 +591,104 @@ TEST(APFloatTest, FMA) { EXPECT_EQ(-8.85242279E-41f, f1.convertToFloat()); } + // The `addOrSubtractSignificand` can be considered to have 9 possible cases + // when subtracting: all combinations of {cmpLessThan, cmpGreaterThan, + // cmpEqual} and {no loss, loss from lhs, loss from rhs}. Test each reachable + // case here. + + // Regression test for failing the `assert(!carry)` in + // `addOrSubtractSignificand` and normalizing the exponent even when the + // significand is zero if there is a lost fraction. + // This tests cmpEqual, loss from lhs + { + APFloat f1(-1.4728589E-38f); + APFloat f2(3.7105144E-6f); + APFloat f3(5.5E-44f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(-0.0f, f1.convertToFloat()); + } + + // Test cmpGreaterThan, no loss + { + APFloat f1(2.0f); + APFloat f2(2.0f); + APFloat f3(-3.5f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(0.5f, f1.convertToFloat()); + } + + // Test cmpLessThan, no loss + { + APFloat f1(2.0f); + APFloat f2(2.0f); + APFloat f3(-4.5f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(-0.5f, f1.convertToFloat()); + } + + // Test cmpEqual, no loss + { + APFloat f1(2.0f); + APFloat f2(2.0f); + APFloat f3(-4.0f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(0.0f, f1.convertToFloat()); + } + + // Test cmpLessThan, loss from lhs + { + APFloat f1(2.0000002f); + APFloat f2(2.0000002f); + APFloat f3(-32.0f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(-27.999998f, f1.convertToFloat()); + } + + // Test cmpGreaterThan, loss from rhs + { + APFloat f1(1e10f); + APFloat f2(1e10f); + APFloat f3(-2.0000002f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(1e20f, f1.convertToFloat()); + } + + // Test cmpGreaterThan, loss from lhs + { + APFloat f1(1e-36f); + APFloat f2(0.0019531252f); + APFloat f3(-1e-45f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(1.953124e-39f, f1.convertToFloat()); + } + + // {cmpEqual, cmpLessThan} with loss from rhs can't occur for the usage in + // `fusedMultiplyAdd` as `multiplySignificand` normalises the MSB of lhs to + // one bit below the top. + + // Test cases from #104984 + { + APFloat f1(0.24999998f); + APFloat f2(2.3509885e-38f); + APFloat f3(-1e-45f); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(5.87747e-39f, f1.convertToFloat()); + } + { + APFloat f1(4.4501477170144023e-308); + APFloat f2(0.24999999999999997); + APFloat f3(-8.475904604373977e-309); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(2.64946468816203e-309, f1.convertToDouble()); + } + { + APFloat f1(APFloat::IEEEhalf(), APInt(16, 0x8fffu)); + APFloat f2(APFloat::IEEEhalf(), APInt(16, 0x2bffu)); + APFloat f3(APFloat::IEEEhalf(), APInt(16, 0x0172u)); + f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven); + EXPECT_EQ(0x808eu, f1.bitcastToAPInt().getZExtValue()); + } + // Test using only a single instance of APFloat. { APFloat F(1.5); @@ -8168,4 +8297,54 @@ TEST(APFloatTest, Float4E2M1FNToFloat) { EXPECT_TRUE(SmallestDenorm.isDenormal()); EXPECT_EQ(0x0.8p0, SmallestDenorm.convertToFloat()); } + +TEST(APFloatTest, AddOrSubtractSignificand) { + typedef detail::IEEEFloatUnitTestHelper Helper; + // Test cases are all combinations of: + // {equal exponents, LHS larger exponent, RHS larger exponent} + // {equal significands, LHS larger significand, RHS larger significand} + // {no loss, loss} + + // Equal exponents (loss cannot occur as their is no shifting) + Helper::runTest(true, false, 1, 0x10, false, 1, 0x5, false, 1, 0xb, + lfExactlyZero); + Helper::runTest(false, false, -2, 0x20, true, -2, 0x20, false, -2, 0, + lfExactlyZero); + Helper::runTest(false, true, 3, 0x20, false, 3, 0x30, false, 3, 0x10, + lfExactlyZero); + + // LHS larger exponent + // LHS significand greater after shitfing + Helper::runTest(true, false, 7, 0x100, false, 3, 0x100, false, 6, 0x1e0, + lfExactlyZero); + Helper::runTest(true, false, 7, 0x100, false, 3, 0x101, false, 6, 0x1df, + lfMoreThanHalf); + // Significands equal after shitfing + Helper::runTest(true, false, 7, 0x100, false, 3, 0x1000, false, 6, 0, + lfExactlyZero); + Helper::runTest(true, false, 7, 0x100, false, 3, 0x1001, true, 6, 0, + lfLessThanHalf); + // RHS significand greater after shitfing + Helper::runTest(true, false, 7, 0x100, false, 3, 0x10000, true, 6, 0x1e00, + lfExactlyZero); + Helper::runTest(true, false, 7, 0x100, false, 3, 0x10001, true, 6, 0x1e00, + lfLessThanHalf); + + // RHS larger exponent + // RHS significand greater after shitfing + Helper::runTest(true, false, 3, 0x100, false, 7, 0x100, true, 6, 0x1e0, + lfExactlyZero); + Helper::runTest(true, false, 3, 0x101, false, 7, 0x100, true, 6, 0x1df, + lfMoreThanHalf); + // Significands equal after shitfing + Helper::runTest(true, false, 3, 0x1000, false, 7, 0x100, false, 6, 0, + lfExactlyZero); + Helper::runTest(true, false, 3, 0x1001, false, 7, 0x100, false, 6, 0, + lfLessThanHalf); + // LHS significand greater after shitfing + Helper::runTest(true, false, 3, 0x10000, false, 7, 0x100, false, 6, 0x1e00, + lfExactlyZero); + Helper::runTest(true, false, 3, 0x10001, false, 7, 0x100, false, 6, 0x1e00, + lfLessThanHalf); +} } // namespace |