Skip to content

Commit b02e73a

Browse files
[libc] Fix printf %f bugs
Fuzzing revealed several bugs in the %f float conversion. This patch fixes them. Most of these bugs are related to rounding, such as 1.999...999 being rounded to 2.999...999 instead of 2.000...000 due to rounding up not properly changing the nines to zeros. Additionally, much of the rounding infrastructure has been refactored out so it can be shared with the other conversions. Reviewed By: sivachandra Differential Revision: https://reviews.llvm.org/D157535
1 parent 89cdaa8 commit b02e73a

File tree

2 files changed

+115
-45
lines changed

2 files changed

+115
-45
lines changed

libc/src/stdio/printf_core/float_dec_converter.h

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,47 @@ constexpr char DECIMAL_POINT = '.';
5353
// This is used to represent which direction the number should be rounded.
5454
enum class RoundDirection { Up, Down, Even };
5555

56+
LIBC_INLINE RoundDirection get_round_direction(int last_digit, bool truncated,
57+
bool is_negative) {
58+
switch (fputil::quick_get_round()) {
59+
case FE_TONEAREST:
60+
// Round to nearest, if it's exactly halfway then round to even.
61+
if (last_digit != 5) {
62+
return last_digit > 5 ? RoundDirection::Up : RoundDirection::Down;
63+
} else {
64+
return !truncated ? RoundDirection::Even : RoundDirection::Up;
65+
}
66+
case FE_DOWNWARD:
67+
if (is_negative && (truncated || last_digit > 0)) {
68+
return RoundDirection::Up;
69+
} else {
70+
return RoundDirection::Down;
71+
}
72+
case FE_UPWARD:
73+
if (!is_negative && (truncated || last_digit > 0)) {
74+
return RoundDirection::Up;
75+
} else {
76+
return RoundDirection::Down;
77+
}
78+
return is_negative ? RoundDirection::Down : RoundDirection::Up;
79+
case FE_TOWARDZERO:
80+
return RoundDirection::Down;
81+
default:
82+
return RoundDirection::Down;
83+
}
84+
}
85+
86+
template <typename T>
87+
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_integral_v<T>, bool>
88+
zero_after_digits(int32_t base_2_exp, int32_t digits_after_point, T mantissa) {
89+
const int32_t required_twos = -base_2_exp - digits_after_point - 1;
90+
const bool has_trailing_zeros =
91+
required_twos <= 0 ||
92+
(required_twos < 60 &&
93+
multiple_of_power_of_2(mantissa, static_cast<uint32_t>(required_twos)));
94+
return has_trailing_zeros;
95+
}
96+
5697
class PaddingWriter {
5798
bool left_justified = false;
5899
bool leading_zeroes = false;
@@ -127,7 +168,9 @@ class FloatWriter {
127168
Writer *writer; // Writes to the final output.
128169
PaddingWriter padding_writer; // Handles prefixes/padding, uses total_digits.
129170

130-
int flush_buffer() {
171+
int flush_buffer(bool round_up_max_blocks = false) {
172+
const char MAX_BLOCK_DIGIT = (round_up_max_blocks ? '0' : '9');
173+
131174
// Write the most recent buffered block, and mark has_written
132175
if (!has_written) {
133176
has_written = true;
@@ -174,12 +217,12 @@ class FloatWriter {
174217
has_decimal_point) {
175218
size_t digits_to_write = digits_before_decimal - total_digits_written;
176219
if (digits_to_write > 0) {
177-
RET_IF_RESULT_NEGATIVE(writer->write('9', digits_to_write));
220+
RET_IF_RESULT_NEGATIVE(writer->write(MAX_BLOCK_DIGIT, digits_to_write));
178221
}
179222
RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT));
180223
if ((BLOCK_SIZE * max_block_count) - digits_to_write > 0) {
181224
RET_IF_RESULT_NEGATIVE(writer->write(
182-
'9', (BLOCK_SIZE * max_block_count) - digits_to_write));
225+
MAX_BLOCK_DIGIT, (BLOCK_SIZE * max_block_count) - digits_to_write));
183226
}
184227
// add 1 for the decimal point
185228
total_digits_written += BLOCK_SIZE * max_block_count + 1;
@@ -189,7 +232,8 @@ class FloatWriter {
189232

190233
// Clear the buffer of max blocks
191234
if (max_block_count > 0) {
192-
RET_IF_RESULT_NEGATIVE(writer->write('9', max_block_count * BLOCK_SIZE));
235+
RET_IF_RESULT_NEGATIVE(
236+
writer->write(MAX_BLOCK_DIGIT, max_block_count * BLOCK_SIZE));
193237
total_digits_written += max_block_count * BLOCK_SIZE;
194238
max_block_count = 0;
195239
}
@@ -268,19 +312,23 @@ class FloatWriter {
268312
end_buff[count] = int_to_str[count + 1 + (BLOCK_SIZE - block_digits)];
269313
}
270314

271-
char low_digit;
315+
char low_digit = '0';
272316
if (block_digits > 0) {
273317
low_digit = end_buff[block_digits - 1];
274318
} else if (max_block_count > 0) {
275319
low_digit = '9';
276-
} else {
320+
} else if (buffered_digits > 0) {
277321
low_digit = block_buffer[buffered_digits - 1];
278322
}
279323

324+
bool round_up_max_blocks = false;
325+
280326
// Round up
281327
if (round == RoundDirection::Up ||
282328
(round == RoundDirection::Even && low_digit % 2 != 0)) {
283329
bool has_carry = true;
330+
round_up_max_blocks = true; // if we're rounding up, we might need to
331+
// round up the max blocks that are buffered.
284332
// handle the low block that we're adding
285333
for (int count = static_cast<int>(block_digits) - 1;
286334
count >= 0 && has_carry; --count) {
@@ -289,6 +337,8 @@ class FloatWriter {
289337
} else {
290338
end_buff[count] += 1;
291339
has_carry = false;
340+
round_up_max_blocks = false; // If the low block isn't all nines, then
341+
// the max blocks aren't rounded up.
292342
}
293343
}
294344
// handle the high block that's buffered
@@ -333,7 +383,7 @@ class FloatWriter {
333383
// Either we intend to round down, or the rounding up is complete. Flush the
334384
// buffers.
335385

336-
RET_IF_RESULT_NEGATIVE(flush_buffer());
386+
RET_IF_RESULT_NEGATIVE(flush_buffer(round_up_max_blocks));
337387

338388
// And then write the final block.
339389
RET_IF_RESULT_NEGATIVE(writer->write({end_buff, block_digits}));
@@ -571,42 +621,11 @@ LIBC_INLINE int convert_float_decimal_typed(Writer *writer,
571621
digits /= 10;
572622
}
573623
RoundDirection round;
574-
// Is m * 10^(additionalDigits + 1) / 2^(-exponent) integer?
575-
const int32_t requiredTwos =
576-
-(exponent - MANT_WIDTH) - static_cast<int32_t>(precision) - 1;
577-
const bool trailingZeros =
578-
requiredTwos <= 0 ||
579-
(requiredTwos < 60 &&
580-
multiple_of_power_of_2(float_bits.get_explicit_mantissa(),
581-
static_cast<uint32_t>(requiredTwos)));
582-
switch (fputil::quick_get_round()) {
583-
case FE_TONEAREST:
584-
// Round to nearest, if it's exactly halfway then round to even.
585-
if (last_digit != 5) {
586-
round = last_digit > 5 ? RoundDirection::Up : RoundDirection::Down;
587-
} else {
588-
round = trailingZeros ? RoundDirection::Even : RoundDirection::Up;
589-
}
590-
break;
591-
case FE_DOWNWARD:
592-
if (is_negative && (!trailingZeros || last_digit > 0)) {
593-
round = RoundDirection::Up;
594-
} else {
595-
round = RoundDirection::Down;
596-
}
597-
break;
598-
case FE_UPWARD:
599-
if (!is_negative && (!trailingZeros || last_digit > 0)) {
600-
round = RoundDirection::Up;
601-
} else {
602-
round = RoundDirection::Down;
603-
}
604-
round = is_negative ? RoundDirection::Down : RoundDirection::Up;
605-
break;
606-
case FE_TOWARDZERO:
607-
round = RoundDirection::Down;
608-
break;
609-
}
624+
const bool truncated =
625+
!zero_after_digits(exponent - MANT_WIDTH, precision,
626+
float_bits.get_explicit_mantissa());
627+
round = get_round_direction(last_digit, truncated, is_negative);
628+
610629
RET_IF_RESULT_NEGATIVE(
611630
float_writer.write_last_block_dec(digits, maximum, round));
612631
break;

libc/test/src/stdio/sprintf_test.cpp

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "test/UnitTest/RoundingModeUtils.h"
1414
#include "test/UnitTest/Test.h"
1515

16+
// TODO: Add a comment here explaining the printf format string.
17+
1618
// #include <stdio.h>
1719
// namespace __llvm_libc {
1820
// using ::sprintf;
@@ -886,6 +888,8 @@ TEST_F(LlvmLibcSPrintfTest, FloatDecimalConv) {
886888
double inf = __llvm_libc::fputil::FPBits<double>::inf().get_val();
887889
double nan = __llvm_libc::fputil::FPBits<double>::build_nan(1);
888890

891+
char big_buff[10000];
892+
889893
written = __llvm_libc::sprintf(buff, "%f", 1.0);
890894
ASSERT_STREQ_LEN(written, buff, "1.000000");
891895

@@ -958,8 +962,6 @@ TEST_F(LlvmLibcSPrintfTest, FloatDecimalConv) {
958962
"99999999999999999996693535322073426194986990198284960792713"
959963
"91541752018669482644324418977840117055488.000000");
960964

961-
char big_buff[10000];
962-
963965
written = __llvm_libc::sprintf(big_buff, "%Lf", 1e1000L);
964966
ASSERT_STREQ_LEN(
965967
written, big_buff,
@@ -1281,6 +1283,55 @@ TEST_F(LlvmLibcSPrintfTest, FloatDecimalConv) {
12811283
written = __llvm_libc::sprintf(buff, "%.0f", 0x1.1000000000006p+3);
12821284
ASSERT_STREQ_LEN(written, buff, "9");
12831285

1286+
// Most of these tests are checking rounding behavior when the precision is
1287+
// set. As an example, %.9f has a precision of 9, meaning it should be rounded
1288+
// to 9 digits after the decimal point. In this case, that means that it
1289+
// should be rounded up. Many of these tests have precisions divisible by 9
1290+
// since when printing the floating point numbers are broken up into "blocks"
1291+
// of 9 digits. They often also have a 5 after the end of what's printed,
1292+
// since in round to nearest mode, that requires checking additional digits.
1293+
written = __llvm_libc::sprintf(buff, "%.9f", 1.9999999999999514);
1294+
ASSERT_STREQ_LEN(written, buff, "2.000000000");
1295+
1296+
// The number continues after the literal because floating point numbers can't
1297+
// represent every value. The printed value is the closest value a double can
1298+
// represent, rounded to the requested precision.
1299+
written = __llvm_libc::sprintf(buff, "%.238f", 1.131959884853339E-72);
1300+
ASSERT_STREQ_LEN(
1301+
written, buff,
1302+
"0."
1303+
"000000000000000000000000000000000000000000000000000000000000000000000001"
1304+
"131959884853339045938639911360973972585316399767392273697826861241937664"
1305+
"824105639342441431495119762431744054912109728706985341609159156917030486"
1306+
"5110665559768676757812");
1307+
1308+
written = __llvm_libc::sprintf(buff, "%.36f", 9.9e-77);
1309+
ASSERT_STREQ_LEN(written, buff, "0.000000000000000000000000000000000000");
1310+
1311+
written = __llvm_libc::sprintf(big_buff, "%.1071f", 2.0226568751604562E-314);
1312+
ASSERT_STREQ_LEN(
1313+
written, big_buff,
1314+
"0."
1315+
"000000000000000000000000000000000000000000000000000000000000000000000000"
1316+
"000000000000000000000000000000000000000000000000000000000000000000000000"
1317+
"000000000000000000000000000000000000000000000000000000000000000000000000"
1318+
"000000000000000000000000000000000000000000000000000000000000000000000000"
1319+
"000000000000000000000000020226568751604561683387695750739190248658016786"
1320+
"876938365740768295004457513021760887468117675879956193821375945376632621"
1321+
"367998639317487303530427946024002091961988296562516210434394107910027236"
1322+
"308233439098296717697919471698168200340836487924061502604112643734560622"
1323+
"258525943451473162532620033398739382796482175564084902819878893430369431"
1324+
"907237673154867595954110791891883281880339550955455702452422857027182100"
1325+
"606009588295886640782228837851739241290179512817803196347460636150182981"
1326+
"085084829941917048152725177119574542042352896161225179181967347829576272"
1327+
"242480201291872969114441104973910102402751449901108484914924879541248714"
1328+
"939096548775588293353689592872854495101242645279589976452453829724479805"
1329+
"750016448075109469332839157162950982637994457036256790161132812");
1330+
1331+
// If no precision is specified it defaults to 6 for %f.
1332+
written = __llvm_libc::sprintf(buff, "%f", 2325885.4901960781);
1333+
ASSERT_STREQ_LEN(written, buff, "2325885.490196");
1334+
12841335
// Subnormal Precision Tests
12851336

12861337
written = __llvm_libc::sprintf(buff, "%.310f", 0x1.0p-1022);

0 commit comments

Comments
 (0)