Skip to content

Commit f6d143f

Browse files
committed
[APFloat] Properly implement frexp(DoubleAPFloat)
The prior implementation did not consider that the Lo component may underflow when it undergoes scaling. This means that we need to carefully handle things like binade crossings or how to handle roundTowardZero when Hi and Lo have different signs. Particularly annoying is roundTiesToAway when Hi and Lo have different signs. It basically requires us to implement roundTiesTowardZero.
1 parent e722ef4 commit f6d143f

File tree

3 files changed

+486
-85
lines changed

3 files changed

+486
-85
lines changed

llvm/include/llvm/ADT/APFloat.h

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -787,17 +787,7 @@ class IEEEFloat final {
787787
};
788788

789789
LLVM_ABI hash_code hash_value(const IEEEFloat &Arg);
790-
/// Returns the exponent of the internal representation of the APFloat.
791-
///
792-
/// Because the radix of APFloat is 2, this is equivalent to floor(log2(x)).
793-
/// For special APFloat values, this returns special error codes:
794-
///
795-
/// NaN -> \c IEK_NaN
796-
/// 0 -> \c IEK_Zero
797-
/// Inf -> \c IEK_Inf
798-
///
799790
LLVM_ABI int ilogb(const IEEEFloat &Arg);
800-
/// Returns: X * 2^Exp for integral exponents.
801791
LLVM_ABI IEEEFloat scalbn(IEEEFloat X, int Exp, roundingMode);
802792
LLVM_ABI IEEEFloat frexp(const IEEEFloat &Val, int &Exp, roundingMode RM);
803793

@@ -1529,13 +1519,7 @@ class APFloat : public APFloatBase {
15291519
}
15301520

15311521
LLVM_ABI friend hash_code hash_value(const APFloat &Arg);
1532-
friend int ilogb(const APFloat &Arg) {
1533-
if (APFloat::usesLayout<detail::IEEEFloat>(Arg.getSemantics()))
1534-
return ilogb(Arg.getIEEE());
1535-
if (APFloat::usesLayout<detail::DoubleAPFloat>(Arg.getSemantics()))
1536-
return ilogb(Arg.getIEEE());
1537-
llvm_unreachable("Unexpected semantics");
1538-
}
1522+
friend int ilogb(const APFloat &Arg);
15391523
friend APFloat scalbn(APFloat X, int Exp, roundingMode RM);
15401524
friend APFloat frexp(const APFloat &X, int &Exp, roundingMode RM);
15411525
friend IEEEFloat;
@@ -1550,6 +1534,25 @@ static_assert(sizeof(APFloat) == sizeof(detail::IEEEFloat),
15501534
/// These additional declarations are required in order to compile LLVM with IBM
15511535
/// xlC compiler.
15521536
LLVM_ABI hash_code hash_value(const APFloat &Arg);
1537+
1538+
/// Returns the exponent of the internal representation of the APFloat.
1539+
///
1540+
/// Because the radix of APFloat is 2, this is equivalent to floor(log2(x)).
1541+
/// For special APFloat values, this returns special error codes:
1542+
///
1543+
/// NaN -> \c IEK_NaN
1544+
/// 0 -> \c IEK_Zero
1545+
/// Inf -> \c IEK_Inf
1546+
///
1547+
inline int ilogb(const APFloat &Arg) {
1548+
if (APFloat::usesLayout<detail::IEEEFloat>(Arg.getSemantics()))
1549+
return ilogb(Arg.U.IEEE);
1550+
if (APFloat::usesLayout<detail::DoubleAPFloat>(Arg.getSemantics()))
1551+
return ilogb(Arg.U.Double);
1552+
llvm_unreachable("Unexpected semantics");
1553+
}
1554+
1555+
/// Returns: X * 2^Exp for integral exponents.
15531556
inline APFloat scalbn(APFloat X, int Exp, APFloat::roundingMode RM) {
15541557
if (APFloat::usesLayout<detail::IEEEFloat>(X.getSemantics()))
15551558
return APFloat(scalbn(X.U.IEEE, Exp, RM), X.getSemantics());

llvm/lib/Support/APFloat.cpp

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5749,16 +5749,15 @@ int DoubleAPFloat::getExactLog2Abs() const {
57495749
return getFirst().getExactLog2Abs();
57505750
}
57515751

5752-
int ilogb(const DoubleAPFloat& Arg) {
5753-
const APFloat& Hi = Arg.getFirst();
5754-
const APFloat& Lo = Arg.getSecond();
5752+
int ilogb(const DoubleAPFloat &Arg) {
5753+
const APFloat &Hi = Arg.getFirst();
5754+
const APFloat &Lo = Arg.getSecond();
57555755
int IlogbResult = ilogb(Hi);
57565756
// Zero and non-finite values can delegate to ilogb(Hi).
57575757
if (Arg.getCategory() != fcNormal)
57585758
return IlogbResult;
57595759
// If Lo can't change the binade, we can delegate to ilogb(Hi).
5760-
if (Lo.isZero() ||
5761-
Hi.isNegative() == Lo.isNegative())
5760+
if (Lo.isZero() || Hi.isNegative() == Lo.isNegative())
57625761
return IlogbResult;
57635762
if (Hi.getExactLog2Abs() == INT_MIN)
57645763
return IlogbResult;
@@ -5777,10 +5776,101 @@ DoubleAPFloat scalbn(const DoubleAPFloat &Arg, int Exp,
57775776
DoubleAPFloat frexp(const DoubleAPFloat &Arg, int &Exp,
57785777
APFloat::roundingMode RM) {
57795778
assert(Arg.Semantics == &semPPCDoubleDouble && "Unexpected Semantics");
5780-
APFloat First = frexp(Arg.Floats[0], Exp, RM);
5781-
APFloat Second = Arg.Floats[1];
5782-
if (Arg.getCategory() == APFloat::fcNormal)
5783-
Second = scalbn(Second, -Exp, RM);
5779+
5780+
// Get the unbiased exponent e of the number, where |Arg| = m * 2^e for m in
5781+
// [1.0, 2.0).
5782+
Exp = ilogb(Arg);
5783+
5784+
// For NaNs, quiet any signaling NaN and return the result, as per standard
5785+
// practice.
5786+
if (Exp == APFloat::IEK_NaN) {
5787+
DoubleAPFloat Quiet{Arg};
5788+
Quiet.getFirst().makeQuiet();
5789+
return Quiet;
5790+
}
5791+
5792+
// For infinity, return it unchanged. The exponent remains IEK_Inf.
5793+
if (Exp == APFloat::IEK_Inf)
5794+
return Arg;
5795+
5796+
// For zero, the fraction is zero and the standard requires the exponent be 0.
5797+
if (Exp == APFloat::IEK_Zero) {
5798+
Exp = 0;
5799+
return Arg;
5800+
}
5801+
5802+
const APFloat &Hi = Arg.getFirst();
5803+
const APFloat &Lo = Arg.getSecond();
5804+
5805+
// frexp requires the fraction's absolute value to be in [0.5, 1.0).
5806+
// ilogb provides an exponent for an absolute value in [1.0, 2.0).
5807+
// Increment the exponent to ensure the fraction is in the correct range.
5808+
++Exp;
5809+
5810+
const bool SignsDisagree = Hi.isNegative() != Lo.isNegative();
5811+
APFloat Second = Lo;
5812+
if (Arg.getCategory() == APFloat::fcNormal && Lo.isFiniteNonZero()) {
5813+
roundingMode LoRoundingMode;
5814+
// The interpretation of rmTowardZero depends on the sign of the combined
5815+
// Arg rather than the sign of the component.
5816+
if (RM == rmTowardZero)
5817+
LoRoundingMode = Arg.isNegative() ? rmTowardPositive : rmTowardNegative;
5818+
// For rmNearestTiesToAway, we face a similar problem. If signs disagree,
5819+
// Lo is a correction *toward* zero relative to Hi. Rounding Lo
5820+
// "away from zero" based on its own sign would move the value in the
5821+
// wrong direction. As a safe proxy, we use rmNearestTiesToEven, which is
5822+
// direction-agnostic. We only need to bother with this if Lo is scaled
5823+
// down.
5824+
else if (RM == rmNearestTiesToAway && SignsDisagree && Exp > 0)
5825+
LoRoundingMode = rmNearestTiesToEven;
5826+
else
5827+
LoRoundingMode = RM;
5828+
Second = scalbn(Lo, -Exp, LoRoundingMode);
5829+
// The rmNearestTiesToEven proxy is correct most of the time, but it
5830+
// differs from rmNearestTiesToAway when the scaled value of Lo is an
5831+
// exact midpoint.
5832+
// NOTE: This is morally equivalent to roundTiesTowardZero.
5833+
if (RM == rmNearestTiesToAway && LoRoundingMode == rmNearestTiesToEven) {
5834+
// Re-scale the result back to check if rounding occurred.
5835+
const APFloat RecomposedLo = scalbn(Second, Exp, rmNearestTiesToEven);
5836+
if (RecomposedLo != Lo) {
5837+
// RoundingError tells us which direction we rounded:
5838+
// - RoundingError > 0: we rounded up.
5839+
// - RoundingError < 0: we down up.
5840+
const APFloat RoundingError = RecomposedLo - Lo;
5841+
// Determine if scalbn(Lo, -Exp) landed exactly on a midpoint.
5842+
// We do this by checking if the absolute rounding error is exactly
5843+
// half a ULP of the result.
5844+
const APFloat UlpOfSecond = harrisonUlp(Second);
5845+
const APFloat ScaledUlpOfSecond =
5846+
scalbn(UlpOfSecond, Exp - 1, rmNearestTiesToEven);
5847+
const bool IsMidpoint = abs(RoundingError) == ScaledUlpOfSecond;
5848+
const bool RoundedLoAway =
5849+
Second.isNegative() == RoundingError.isNegative();
5850+
// The sign of Hi and Lo disagree and we rounded Lo away: we must
5851+
// decrease the magnitude of Second to increase the magnitude
5852+
// First+Second.
5853+
if (IsMidpoint && RoundedLoAway)
5854+
Second.next(/*nextDown=*/!Second.isNegative());
5855+
}
5856+
}
5857+
// Handle a tricky edge case where Arg is slightly less than a power of two
5858+
// (e.g., Arg = 2^k - epsilon). In this situation:
5859+
// 1. Hi is 2^k, and Lo is a small negative value -epsilon.
5860+
// 2. ilogb(Arg) correctly returns k-1.
5861+
// 3. Our initial Exp becomes (k-1) + 1 = k.
5862+
// 4. Scaling Hi (2^k) by 2^-k would yield a magnitude of 1.0 and
5863+
// scaling Lo by 2^-k would yield zero. This would make the result 1.0
5864+
// which is an invalid fraction, as the required interval is [0.5, 1.0).
5865+
// We detect this specific case by checking if Hi is a power of two and if
5866+
// the scaled Lo underflowed to zero. The fix: Increment Exp to k+1. This
5867+
// adjusts the scale factor, causing Hi to be scaled to 0.5, which is a
5868+
// valid fraction.
5869+
if (Second.isZero() && SignsDisagree && Hi.getExactLog2Abs() != INT_MIN)
5870+
++Exp;
5871+
}
5872+
5873+
APFloat First = scalbn(Hi, -Exp, RM);
57845874
return DoubleAPFloat(semPPCDoubleDouble, std::move(First), std::move(Second));
57855875
}
57865876

0 commit comments

Comments
 (0)