-
Notifications
You must be signed in to change notification settings - Fork 12.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[APFloat] Fix IEEEFloat::addOrSubtractSignificand
and IEEEFloat::normalize
#98721
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-llvm-support @llvm/pr-subscribers-llvm-adt Author: None (beetrees) ChangesFixes #63895 Before this PR, Additionally, Full diff: https://github.com/llvm/llvm-project/pull/98721.diff 2 Files Affected:
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 3664de71d06df..46fe1af79a084 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -1607,7 +1607,8 @@ IEEEFloat::opStatus IEEEFloat::normalize(roundingMode rounding_mode,
/* Before rounding normalize the exponent of fcNormal numbers. */
omsb = significandMSB() + 1;
- if (omsb) {
+ // Only skip this `if` if the value is exactly zero.
+ if (omsb || lost_fraction != lfExactlyZero) {
/* OMSB is numbered from 1. We want to place it in the integer
bit numbered PRECISION if possible, with a compensating change in
the exponent. */
@@ -1782,7 +1783,7 @@ IEEEFloat::opStatus IEEEFloat::addOrSubtractSpecials(const IEEEFloat &rhs,
/* Add or subtract two normal numbers. */
lostFraction IEEEFloat::addOrSubtractSignificand(const IEEEFloat &rhs,
bool subtract) {
- integerPart carry;
+ integerPart carry = 0;
lostFraction lost_fraction;
int bits;
@@ -1796,11 +1797,13 @@ lostFraction IEEEFloat::addOrSubtractSignificand(const IEEEFloat &rhs,
/* Subtraction is more subtle than one might naively expect. */
if (subtract) {
IEEEFloat temp_rhs(rhs);
+ auto lost_fraction_is_from_rhs = false;
if (bits == 0)
lost_fraction = lfExactlyZero;
else if (bits > 0) {
lost_fraction = temp_rhs.shiftSignificandRight(bits - 1);
+ lost_fraction_is_from_rhs = true;
shiftSignificandLeft(1);
} else {
lost_fraction = shiftSignificandRight(-bits - 1);
@@ -1808,23 +1811,41 @@ lostFraction IEEEFloat::addOrSubtractSignificand(const IEEEFloat &rhs,
}
// Should we reverse the subtraction.
- if (compareAbsoluteValue(temp_rhs) == cmpLessThan) {
- carry = temp_rhs.subtractSignificand
- (*this, lost_fraction != lfExactlyZero);
+ auto cmp_result = compareAbsoluteValue(temp_rhs);
+ if (cmp_result == cmpLessThan) {
+ auto borrow = false;
+ if (lost_fraction != lfExactlyZero && !lost_fraction_is_from_rhs) {
+ // The lost fraction is being subtracted, borrow from the significand
+ // and invert `lost_fraction`.
+ borrow = true;
+ if (lost_fraction == lfLessThanHalf)
+ lost_fraction = lfMoreThanHalf;
+ else if (lost_fraction == lfMoreThanHalf)
+ lost_fraction = lfLessThanHalf;
+ }
+ carry = temp_rhs.subtractSignificand(*this, borrow);
copySignificand(temp_rhs);
sign = !sign;
- } else {
- carry = subtractSignificand
- (temp_rhs, lost_fraction != lfExactlyZero);
+ } else if (cmp_result == cmpGreaterThan) {
+ auto borrow = false;
+ if (lost_fraction != lfExactlyZero && lost_fraction_is_from_rhs) {
+ // The lost fraction is being subtracted, borrow from the significand
+ // and invert `lost_fraction`.
+ borrow = true;
+ if (lost_fraction == lfLessThanHalf)
+ lost_fraction = lfMoreThanHalf;
+ else if (lost_fraction == lfMoreThanHalf)
+ lost_fraction = lfLessThanHalf;
+ }
+ carry = subtractSignificand(temp_rhs, borrow);
+ } else { // cmpEqual
+ zeroSignificand();
+ if (lost_fraction != lfExactlyZero && lost_fraction_is_from_rhs) {
+ // rhs is slightly larger due to the lost fraction, flip the sign.
+ sign = !sign;
+ }
}
- /* Invert the lost fraction - it was on the RHS and
- subtracted. */
- if (lost_fraction == lfLessThanHalf)
- lost_fraction = lfMoreThanHalf;
- else if (lost_fraction == lfMoreThanHalf)
- lost_fraction = lfLessThanHalf;
-
/* The code above is intended to ensure that no borrow is
necessary. */
assert(!carry);
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index 86a25f4394e19..d6c488e75d335 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -560,6 +560,16 @@ TEST(APFloatTest, FMA) {
EXPECT_EQ(-8.85242279E-41f, f1.convertToFloat());
}
+ // Regression test for failing the `assert(!carry)` in
+ // `addOrSubtractSignificand`.
+ {
+ 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 using only a single instance of APFloat.
{
APFloat F(1.5);
|
cf8c805
to
ecd06e2
Compare
The title claims to fix |
ecd06e2
to
2d0f63f
Compare
The test will fail if both fixes are not applied. I've updated the test comment to mention that. |
2d0f63f
to
3f36539
Compare
|
3f36539
to
9b17751
Compare
Done. I also had to make the fields of |
llvm/include/llvm/ADT/APFloat.h
Outdated
@@ -306,7 +306,8 @@ struct APFloatBase { | |||
|
|||
namespace detail { | |||
|
|||
class IEEEFloat final : public APFloatBase { | |||
// Not final for testing purposes only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to leave it as final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addOrSubtractSignificand
went from private to protected. It is the only non-fishy way to get access for testing.
llvm/unittests/ADT/APFloatTest.cpp
Outdated
// `addOrSubtractSignificand` only uses the sign, exponent and significand | ||
this->sign = sign; | ||
this->exponent = exponent; | ||
this->significand.part = significand; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this justifies adding a subclass. You can just construct the appropriate value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fields and addOrSubtractSignificand
are protected so the only way to access them is via a subclass. Unfortunately just constructing a value via the public constructors is not sufficient as non-normalized significands are required to hit all the edge cases in addOrSubtractSignificand
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the problem with adding public accessors for these? Or adding a test class as a friend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm but I think there are better options than making this protected. Can the test function just be a friend, adding public accessors, or adding another base class for the tester subclass
llvm/unittests/ADT/APFloatTest.cpp
Outdated
// `addOrSubtractSignificand` only uses the sign, exponent and significand | ||
this->sign = sign; | ||
this->exponent = exponent; | ||
this->significand.part = significand; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the problem with adding public accessors for these? Or adding a test class as a friend?
Either public accessors specifically for testing or a friend test class would also work. Is there any preference to which solution is best to use? |
9b17751
to
eabd142
Compare
eabd142
to
0dec30d
Compare
I've rebased to fix the merge conflict and added the test cases suggested in #104984. I've also changed the |
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 butlost_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)