Skip to content

Commit b50f6f2

Browse files
[libc] Properly fix printf long double subnormals (#172103)
In a previous PR I fixed one case where subnormal long doubles would cause an infinite loop in printf. It was an improper fix though. The problem was that a shift on the fixed point representation would sometimes go negative, since the effective exponent of a subnormal is lower than the minimum allowed exponent value. This patch extends the fixed point representation to have space for subnormals, and adds an assert to check that lshifts are always positive. The previous fix of sometimes shifting right instead of left caused a loss of precision which also sometimes caused infinite loops in the %e code.
1 parent ad2fca7 commit b50f6f2

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

libc/src/__support/float_to_string.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -618,16 +618,18 @@ template <> class FloatToString<long double> {
618618
fputil::FPBits<long double> float_bits;
619619
bool is_negative = 0;
620620
int exponent = 0;
621-
FPBits::StorageType mantissa = 0;
621+
fputil::FPBits<long double>::StorageType mantissa = 0;
622622

623623
static constexpr int FRACTION_LEN = fputil::FPBits<long double>::FRACTION_LEN;
624624
static constexpr int EXP_BIAS = fputil::FPBits<long double>::EXP_BIAS;
625625
static constexpr size_t UINT_WORD_SIZE = 64;
626626

627627
static constexpr size_t FLOAT_AS_INT_WIDTH =
628-
internal::div_ceil(fputil::FPBits<long double>::MAX_BIASED_EXPONENT -
629-
FPBits::EXP_BIAS,
630-
UINT_WORD_SIZE) *
628+
internal::div_ceil(
629+
fputil::FPBits<long double>::MAX_BIASED_EXPONENT -
630+
fputil::FPBits<long double>::EXP_BIAS +
631+
FRACTION_LEN, // Add fraction len to provide space for subnormals.
632+
UINT_WORD_SIZE) *
631633
UINT_WORD_SIZE;
632634
static constexpr size_t EXTRA_INT_WIDTH =
633635
internal::div_ceil(sizeof(long double) * CHAR_BIT, UINT_WORD_SIZE) *
@@ -699,12 +701,11 @@ template <> class FloatToString<long double> {
699701
float_as_fixed = mantissa;
700702

701703
const int SHIFT_AMOUNT = FLOAT_AS_INT_WIDTH + exponent;
704+
// if the shift amount would be negative, then the shift would cause a
705+
// loss of precision.
706+
LIBC_ASSERT(SHIFT_AMOUNT >= 0);
702707
static_assert(EXTRA_INT_WIDTH >= sizeof(long double) * 8);
703-
if (SHIFT_AMOUNT > 0) {
704-
float_as_fixed <<= SHIFT_AMOUNT;
705-
} else {
706-
float_as_fixed >>= -SHIFT_AMOUNT;
707-
}
708+
float_as_fixed <<= SHIFT_AMOUNT;
708709

709710
// If there are still digits above the decimal point, handle those.
710711
if (cpp::countl_zero(float_as_fixed) <

libc/test/src/stdio/sprintf_test.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2484,6 +2484,18 @@ TEST(LlvmLibcSPrintfTest, FloatExponentLongDoubleConv) {
24842484

24852485
written = LIBC_NAMESPACE::sprintf(buff, "%Le", 1.2345678e4900L);
24862486
ASSERT_STREQ_LEN(written, buff, "1.234568e+4900");
2487+
2488+
#ifndef LIBC_COPT_FLOAT_TO_STR_REDUCED_PRECISION
2489+
// Minimum normal + epsilon
2490+
written =
2491+
LIBC_NAMESPACE::sprintf(buff, "%.20Le", 3.36210314311209350663E-4932L);
2492+
ASSERT_STREQ_LEN(written, buff, "3.36210314311209350663e-4932");
2493+
2494+
// Minimum subnormal
2495+
written =
2496+
LIBC_NAMESPACE::sprintf(buff, "%.20Le", 3.64519953188247460253E-4951L);
2497+
ASSERT_STREQ_LEN(written, buff, "3.64519953188247460253e-4951");
2498+
#endif // LIBC_COPT_FLOAT_TO_STR_REDUCED_PRECISION
24872499
#endif
24882500
}
24892501

0 commit comments

Comments
 (0)