Skip to content

Commit b55e4d6

Browse files
Hopefully completely fix printf-family %a rounding (#1287)
The a conversion specifier to printf had some issues w.r.t. rounding, in particular in edge cases w.r.t. "to nearest, ties to even" rounding (for instance, "%.1a" with 0x1.78p+4 outputted 0x1.7p+4 instead of 0x1.8p+4). This patch fixes this and adds several tests w.r.t ties to even rounding
1 parent e65fe61 commit b55e4d6

File tree

2 files changed

+45
-42
lines changed

2 files changed

+45
-42
lines changed

libc/stdio/fmt.c

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -565,12 +565,12 @@ static int __fmt_stoa(int out(const char *, void *, size_t), void *arg,
565565
static void __fmt_dfpbits(union U *u, struct FPBits *b) {
566566
int ex, i;
567567
b->fpi = kFpiDbl;
568-
// Uncomment this if needed in the future - we currently do not need it, as
569-
// the only reason we need it in __fmt_ldfpbits is because gdtoa reads
570-
// fpi.rounding to determine rounding (which dtoa does not need as it directly
571-
// reads FLT_ROUNDS)
572-
// if (FLT_ROUNDS != -1)
573-
// b->fpi.rounding = FLT_ROUNDS;
568+
569+
// dtoa doesn't need this, unlike gdtoa, but we use it for __fmt_bround
570+
i = FLT_ROUNDS;
571+
if (i != -1)
572+
b->fpi.rounding = i;
573+
574574
b->sign = u->ui[1] & 0x80000000L;
575575
b->bits[1] = u->ui[1] & 0xfffff;
576576
b->bits[0] = u->ui[0];
@@ -616,10 +616,14 @@ static void __fmt_ldfpbits(union U *u, struct FPBits *b) {
616616
#error "unsupported architecture"
617617
#endif
618618
b->fpi = kFpiLdbl;
619+
619620
// gdtoa doesn't check for FLT_ROUNDS but for fpi.rounding (which has the
620621
// same valid values as FLT_ROUNDS), so handle this here
621-
if (FLT_ROUNDS != -1)
622-
b->fpi.rounding = FLT_ROUNDS;
622+
// (we also use this in __fmt_bround now)
623+
i = FLT_ROUNDS;
624+
if (i != -1)
625+
b->fpi.rounding = i;
626+
623627
b->sign = sex & 0x8000;
624628
if ((ex = sex & 0x7fff) != 0) {
625629
if (ex != 0x7fff) {
@@ -692,7 +696,6 @@ static int __fmt_bround(struct FPBits *b, int prec, int prec1) {
692696
uint32_t *bits, t;
693697
int i, j, k, m, n;
694698
bool inc = false;
695-
int current_rounding_mode;
696699
m = prec1 - prec;
697700
bits = b->bits;
698701
k = m - 1;
@@ -701,22 +704,24 @@ static int __fmt_bround(struct FPBits *b, int prec, int prec1) {
701704
// always know in which direction we must round because of the current
702705
// rounding mode (note that if the correct value for inc is `false` then it
703706
// doesn't need to be set as we have already done so above)
704-
// The last one handles rounding to nearest
705-
current_rounding_mode = fegetround();
706-
if (current_rounding_mode == FE_TOWARDZERO ||
707-
(current_rounding_mode == FE_UPWARD && b->sign) ||
708-
(current_rounding_mode == FE_DOWNWARD && !b->sign))
707+
// They use the FLT_ROUNDS value, which are the same as gdtoa's FPI_Round_*
708+
// enum values
709+
if (b->fpi.rounding == FPI_Round_zero ||
710+
(b->fpi.rounding == FPI_Round_up && b->sign) ||
711+
(b->fpi.rounding == FPI_Round_down && !b->sign))
709712
goto have_inc;
710-
if ((current_rounding_mode == FE_UPWARD && !b->sign) ||
711-
(current_rounding_mode == FE_DOWNWARD && b->sign)) {
712-
inc = true;
713-
goto have_inc;
714-
}
713+
if ((b->fpi.rounding == FPI_Round_up && !b->sign) ||
714+
(b->fpi.rounding == FPI_Round_down && b->sign))
715+
goto inc_true;
715716

716717
if ((t = bits[k >> 3] >> (j = (k & 7) * 4)) & 8) {
717718
if (t & 7)
718719
goto inc_true;
719-
if (j && bits[k >> 3] << (32 - j))
720+
// ((1 << (j * 4)) - 1) will mask appropriately for the lower bits
721+
if ((bits[k >> 3] & ((1 << (j * 4)) - 1)) != 0)
722+
goto inc_true;
723+
// If exactly halfway and all lower bits are zero (tie), round to even
724+
if ((bits[k >> 3] >> (j + 1) * 4) & 1)
720725
goto inc_true;
721726
while (k >= 8) {
722727
k -= 8;

test/libc/stdio/snprintf_test.c

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -217,39 +217,37 @@ TEST(snprintf, testLongDoubleRounding) {
217217
ASSERT_EQ(0, fesetround(previous_rounding));
218218
}
219219

220+
void check_a_conversion_specifier_prec_1(const char *result_str, double value) {
221+
char buf[30] = {0};
222+
int i = snprintf(buf, sizeof(buf), "%.1a", value);
223+
224+
ASSERT_EQ(strlen(result_str), i);
225+
ASSERT_STREQ(result_str, buf);
226+
}
227+
220228
TEST(snprintf, testAConversionSpecifierRounding) {
221229
int previous_rounding = fegetround();
222-
ASSERT_EQ(0, fesetround(FE_DOWNWARD));
223230

224-
char buf[20];
225-
int i = snprintf(buf, sizeof(buf), "%.1a", 0x1.fffffp+4);
226-
ASSERT_EQ(8, i);
227-
ASSERT_STREQ("0x1.fp+4", buf);
231+
ASSERT_EQ(0, fesetround(FE_DOWNWARD));
232+
check_a_conversion_specifier_prec_1("0x1.fp+4", 0x1.fffffp+4);
228233

229234
ASSERT_EQ(0, fesetround(FE_UPWARD));
230-
231-
i = snprintf(buf, sizeof(buf), "%.1a", 0x1.f8p+4);
232-
ASSERT_EQ(8, i);
233-
ASSERT_STREQ("0x2.0p+4", buf);
235+
check_a_conversion_specifier_prec_1("0x2.0p+4", 0x1.f8p+4);
234236

235237
ASSERT_EQ(0, fesetround(previous_rounding));
236238
}
237239

238-
// This test currently fails because of rounding issues
239-
// If that ever gets fixed, uncomment this
240-
/*
240+
// This test specifically checks that we round to even, accordingly to IEEE
241+
// rules
241242
TEST(snprintf, testAConversionSpecifier) {
242-
char buf[20];
243-
int i = snprintf(buf, sizeof(buf), "%.1a", 0x1.7800000000001p+4);
244-
ASSERT_EQ(8, i);
245-
ASSERT_STREQ("0x1.8p+4", buf);
246-
247-
memset(buf, 0, sizeof(buf));
248-
i = snprintf(buf, sizeof(buf), "%.1a", 0x1.78p+4);
249-
ASSERT_EQ(8, i);
250-
ASSERT_STREQ("0x1.8p+4", buf);
243+
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.7800000000001p+4);
244+
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.78p+4);
245+
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.88p+4);
246+
check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.58p+4);
247+
check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.68p+4);
248+
check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.98p+4);
249+
check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.a8p+4);
251250
}
252-
*/
253251

254252
TEST(snprintf, apostropheFlag) {
255253
char buf[20];

0 commit comments

Comments
 (0)