Skip to content

Commit 8b9c91e

Browse files
authored
[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)
1 parent 82f2b66 commit 8b9c91e

File tree

3 files changed

+216
-15
lines changed

3 files changed

+216
-15
lines changed

llvm/include/llvm/ADT/APFloat.h

+2
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,8 @@ class IEEEFloat final {
791791

792792
/// Sign bit of the number.
793793
unsigned int sign : 1;
794+
795+
friend class IEEEFloatUnitTestHelper;
794796
};
795797

796798
hash_code hash_value(const IEEEFloat &Arg);

llvm/lib/Support/APFloat.cpp

+35-15
Original file line numberDiff line numberDiff line change
@@ -1657,7 +1657,8 @@ APFloat::opStatus IEEEFloat::normalize(roundingMode rounding_mode,
16571657
/* Before rounding normalize the exponent of fcNormal numbers. */
16581658
omsb = significandMSB() + 1;
16591659

1660-
if (omsb) {
1660+
// Only skip this `if` if the value is exactly zero.
1661+
if (omsb || lost_fraction != lfExactlyZero) {
16611662
/* OMSB is numbered from 1. We want to place it in the integer
16621663
bit numbered PRECISION if possible, with a compensating change in
16631664
the exponent. */
@@ -1839,7 +1840,7 @@ APFloat::opStatus IEEEFloat::addOrSubtractSpecials(const IEEEFloat &rhs,
18391840
/* Add or subtract two normal numbers. */
18401841
lostFraction IEEEFloat::addOrSubtractSignificand(const IEEEFloat &rhs,
18411842
bool subtract) {
1842-
integerPart carry;
1843+
integerPart carry = 0;
18431844
lostFraction lost_fraction;
18441845
int bits;
18451846

@@ -1857,35 +1858,54 @@ lostFraction IEEEFloat::addOrSubtractSignificand(const IEEEFloat &rhs,
18571858
"This floating point format does not support signed values");
18581859

18591860
IEEEFloat temp_rhs(rhs);
1861+
bool lost_fraction_is_from_rhs = false;
18601862

18611863
if (bits == 0)
18621864
lost_fraction = lfExactlyZero;
18631865
else if (bits > 0) {
18641866
lost_fraction = temp_rhs.shiftSignificandRight(bits - 1);
1867+
lost_fraction_is_from_rhs = true;
18651868
shiftSignificandLeft(1);
18661869
} else {
18671870
lost_fraction = shiftSignificandRight(-bits - 1);
18681871
temp_rhs.shiftSignificandLeft(1);
18691872
}
18701873

18711874
// Should we reverse the subtraction.
1872-
if (compareAbsoluteValue(temp_rhs) == cmpLessThan) {
1873-
carry = temp_rhs.subtractSignificand
1874-
(*this, lost_fraction != lfExactlyZero);
1875+
cmpResult cmp_result = compareAbsoluteValue(temp_rhs);
1876+
if (cmp_result == cmpLessThan) {
1877+
bool borrow =
1878+
lost_fraction != lfExactlyZero && !lost_fraction_is_from_rhs;
1879+
if (borrow) {
1880+
// The lost fraction is being subtracted, borrow from the significand
1881+
// and invert `lost_fraction`.
1882+
if (lost_fraction == lfLessThanHalf)
1883+
lost_fraction = lfMoreThanHalf;
1884+
else if (lost_fraction == lfMoreThanHalf)
1885+
lost_fraction = lfLessThanHalf;
1886+
}
1887+
carry = temp_rhs.subtractSignificand(*this, borrow);
18751888
copySignificand(temp_rhs);
18761889
sign = !sign;
1877-
} else {
1878-
carry = subtractSignificand
1879-
(temp_rhs, lost_fraction != lfExactlyZero);
1890+
} else if (cmp_result == cmpGreaterThan) {
1891+
bool borrow = lost_fraction != lfExactlyZero && lost_fraction_is_from_rhs;
1892+
if (borrow) {
1893+
// The lost fraction is being subtracted, borrow from the significand
1894+
// and invert `lost_fraction`.
1895+
if (lost_fraction == lfLessThanHalf)
1896+
lost_fraction = lfMoreThanHalf;
1897+
else if (lost_fraction == lfMoreThanHalf)
1898+
lost_fraction = lfLessThanHalf;
1899+
}
1900+
carry = subtractSignificand(temp_rhs, borrow);
1901+
} else { // cmpEqual
1902+
zeroSignificand();
1903+
if (lost_fraction != lfExactlyZero && lost_fraction_is_from_rhs) {
1904+
// rhs is slightly larger due to the lost fraction, flip the sign.
1905+
sign = !sign;
1906+
}
18801907
}
18811908

1882-
/* Invert the lost fraction - it was on the RHS and
1883-
subtracted. */
1884-
if (lost_fraction == lfLessThanHalf)
1885-
lost_fraction = lfMoreThanHalf;
1886-
else if (lost_fraction == lfMoreThanHalf)
1887-
lost_fraction = lfLessThanHalf;
1888-
18891909
/* The code above is intended to ensure that no borrow is
18901910
necessary. */
18911911
assert(!carry);

llvm/unittests/ADT/APFloatTest.cpp

+179
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,37 @@ static std::string convertToString(double d, unsigned Prec, unsigned Pad,
4747
return std::string(Buffer.data(), Buffer.size());
4848
}
4949

50+
namespace llvm {
51+
namespace detail {
52+
class IEEEFloatUnitTestHelper {
53+
public:
54+
static void runTest(bool subtract, bool lhsSign,
55+
APFloat::ExponentType lhsExponent,
56+
APFloat::integerPart lhsSignificand, bool rhsSign,
57+
APFloat::ExponentType rhsExponent,
58+
APFloat::integerPart rhsSignificand, bool expectedSign,
59+
APFloat::ExponentType expectedExponent,
60+
APFloat::integerPart expectedSignificand,
61+
lostFraction expectedLoss) {
62+
// `addOrSubtractSignificand` only uses the sign, exponent and significand
63+
IEEEFloat lhs(1.0);
64+
lhs.sign = lhsSign;
65+
lhs.exponent = lhsExponent;
66+
lhs.significand.part = lhsSignificand;
67+
IEEEFloat rhs(1.0);
68+
rhs.sign = rhsSign;
69+
rhs.exponent = rhsExponent;
70+
rhs.significand.part = rhsSignificand;
71+
lostFraction resultLoss = lhs.addOrSubtractSignificand(rhs, subtract);
72+
EXPECT_EQ(resultLoss, expectedLoss);
73+
EXPECT_EQ(lhs.sign, expectedSign);
74+
EXPECT_EQ(lhs.exponent, expectedExponent);
75+
EXPECT_EQ(lhs.significand.part, expectedSignificand);
76+
}
77+
};
78+
} // namespace detail
79+
} // namespace llvm
80+
5081
namespace {
5182

5283
TEST(APFloatTest, isSignaling) {
@@ -560,6 +591,104 @@ TEST(APFloatTest, FMA) {
560591
EXPECT_EQ(-8.85242279E-41f, f1.convertToFloat());
561592
}
562593

594+
// The `addOrSubtractSignificand` can be considered to have 9 possible cases
595+
// when subtracting: all combinations of {cmpLessThan, cmpGreaterThan,
596+
// cmpEqual} and {no loss, loss from lhs, loss from rhs}. Test each reachable
597+
// case here.
598+
599+
// Regression test for failing the `assert(!carry)` in
600+
// `addOrSubtractSignificand` and normalizing the exponent even when the
601+
// significand is zero if there is a lost fraction.
602+
// This tests cmpEqual, loss from lhs
603+
{
604+
APFloat f1(-1.4728589E-38f);
605+
APFloat f2(3.7105144E-6f);
606+
APFloat f3(5.5E-44f);
607+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
608+
EXPECT_EQ(-0.0f, f1.convertToFloat());
609+
}
610+
611+
// Test cmpGreaterThan, no loss
612+
{
613+
APFloat f1(2.0f);
614+
APFloat f2(2.0f);
615+
APFloat f3(-3.5f);
616+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
617+
EXPECT_EQ(0.5f, f1.convertToFloat());
618+
}
619+
620+
// Test cmpLessThan, no loss
621+
{
622+
APFloat f1(2.0f);
623+
APFloat f2(2.0f);
624+
APFloat f3(-4.5f);
625+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
626+
EXPECT_EQ(-0.5f, f1.convertToFloat());
627+
}
628+
629+
// Test cmpEqual, no loss
630+
{
631+
APFloat f1(2.0f);
632+
APFloat f2(2.0f);
633+
APFloat f3(-4.0f);
634+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
635+
EXPECT_EQ(0.0f, f1.convertToFloat());
636+
}
637+
638+
// Test cmpLessThan, loss from lhs
639+
{
640+
APFloat f1(2.0000002f);
641+
APFloat f2(2.0000002f);
642+
APFloat f3(-32.0f);
643+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
644+
EXPECT_EQ(-27.999998f, f1.convertToFloat());
645+
}
646+
647+
// Test cmpGreaterThan, loss from rhs
648+
{
649+
APFloat f1(1e10f);
650+
APFloat f2(1e10f);
651+
APFloat f3(-2.0000002f);
652+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
653+
EXPECT_EQ(1e20f, f1.convertToFloat());
654+
}
655+
656+
// Test cmpGreaterThan, loss from lhs
657+
{
658+
APFloat f1(1e-36f);
659+
APFloat f2(0.0019531252f);
660+
APFloat f3(-1e-45f);
661+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
662+
EXPECT_EQ(1.953124e-39f, f1.convertToFloat());
663+
}
664+
665+
// {cmpEqual, cmpLessThan} with loss from rhs can't occur for the usage in
666+
// `fusedMultiplyAdd` as `multiplySignificand` normalises the MSB of lhs to
667+
// one bit below the top.
668+
669+
// Test cases from #104984
670+
{
671+
APFloat f1(0.24999998f);
672+
APFloat f2(2.3509885e-38f);
673+
APFloat f3(-1e-45f);
674+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
675+
EXPECT_EQ(5.87747e-39f, f1.convertToFloat());
676+
}
677+
{
678+
APFloat f1(4.4501477170144023e-308);
679+
APFloat f2(0.24999999999999997);
680+
APFloat f3(-8.475904604373977e-309);
681+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
682+
EXPECT_EQ(2.64946468816203e-309, f1.convertToDouble());
683+
}
684+
{
685+
APFloat f1(APFloat::IEEEhalf(), APInt(16, 0x8fffu));
686+
APFloat f2(APFloat::IEEEhalf(), APInt(16, 0x2bffu));
687+
APFloat f3(APFloat::IEEEhalf(), APInt(16, 0x0172u));
688+
f1.fusedMultiplyAdd(f2, f3, APFloat::rmNearestTiesToEven);
689+
EXPECT_EQ(0x808eu, f1.bitcastToAPInt().getZExtValue());
690+
}
691+
563692
// Test using only a single instance of APFloat.
564693
{
565694
APFloat F(1.5);
@@ -8168,4 +8297,54 @@ TEST(APFloatTest, Float4E2M1FNToFloat) {
81688297
EXPECT_TRUE(SmallestDenorm.isDenormal());
81698298
EXPECT_EQ(0x0.8p0, SmallestDenorm.convertToFloat());
81708299
}
8300+
8301+
TEST(APFloatTest, AddOrSubtractSignificand) {
8302+
typedef detail::IEEEFloatUnitTestHelper Helper;
8303+
// Test cases are all combinations of:
8304+
// {equal exponents, LHS larger exponent, RHS larger exponent}
8305+
// {equal significands, LHS larger significand, RHS larger significand}
8306+
// {no loss, loss}
8307+
8308+
// Equal exponents (loss cannot occur as their is no shifting)
8309+
Helper::runTest(true, false, 1, 0x10, false, 1, 0x5, false, 1, 0xb,
8310+
lfExactlyZero);
8311+
Helper::runTest(false, false, -2, 0x20, true, -2, 0x20, false, -2, 0,
8312+
lfExactlyZero);
8313+
Helper::runTest(false, true, 3, 0x20, false, 3, 0x30, false, 3, 0x10,
8314+
lfExactlyZero);
8315+
8316+
// LHS larger exponent
8317+
// LHS significand greater after shitfing
8318+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x100, false, 6, 0x1e0,
8319+
lfExactlyZero);
8320+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x101, false, 6, 0x1df,
8321+
lfMoreThanHalf);
8322+
// Significands equal after shitfing
8323+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x1000, false, 6, 0,
8324+
lfExactlyZero);
8325+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x1001, true, 6, 0,
8326+
lfLessThanHalf);
8327+
// RHS significand greater after shitfing
8328+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x10000, true, 6, 0x1e00,
8329+
lfExactlyZero);
8330+
Helper::runTest(true, false, 7, 0x100, false, 3, 0x10001, true, 6, 0x1e00,
8331+
lfLessThanHalf);
8332+
8333+
// RHS larger exponent
8334+
// RHS significand greater after shitfing
8335+
Helper::runTest(true, false, 3, 0x100, false, 7, 0x100, true, 6, 0x1e0,
8336+
lfExactlyZero);
8337+
Helper::runTest(true, false, 3, 0x101, false, 7, 0x100, true, 6, 0x1df,
8338+
lfMoreThanHalf);
8339+
// Significands equal after shitfing
8340+
Helper::runTest(true, false, 3, 0x1000, false, 7, 0x100, false, 6, 0,
8341+
lfExactlyZero);
8342+
Helper::runTest(true, false, 3, 0x1001, false, 7, 0x100, false, 6, 0,
8343+
lfLessThanHalf);
8344+
// LHS significand greater after shitfing
8345+
Helper::runTest(true, false, 3, 0x10000, false, 7, 0x100, false, 6, 0x1e00,
8346+
lfExactlyZero);
8347+
Helper::runTest(true, false, 3, 0x10001, false, 7, 0x100, false, 6, 0x1e00,
8348+
lfLessThanHalf);
8349+
}
81718350
} // namespace

0 commit comments

Comments
 (0)