From 64521d33f1a8502de11ea5c9bb6a0897ab61e64c Mon Sep 17 00:00:00 2001 From: Gabriel Ravier Date: Tue, 10 Sep 2024 16:02:41 +0200 Subject: [PATCH] Partially fix printf hex float numbers/%a rounding Hexadecimal printing of floating-point numbers on cosmopolitan (that is, using the a conversion specifier) currently does not correctly round the result in any rounding mode except for the default one (i.e. FE_NEAREST) This commit fixes this, and adds tests for the change (note that there's still some rounding issues with the a conversion specifier in general in relatively rare cases (that is, without non-default rounding modes), but this author gave up on fixing them after they tried to read the floating point formatting code, and wounded up almost more confused than before). --- libc/stdio/fmt.c | 35 +++++++++++++++++++++++++-------- test/libc/stdio/snprintf_test.c | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index 579cd96f086..6e1492252d6 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -690,26 +690,45 @@ static int __fmt_fpiprec(struct FPBits *b) { // prec1 = incoming precision (after ".") static int __fmt_bround(struct FPBits *b, int prec, int prec1) { uint32_t *bits, t; - int i, inc, j, k, m, n; + int i, j, k, m, n; + bool inc = false; + int current_rounding_mode; m = prec1 - prec; bits = b->bits; - inc = 0; k = m - 1; + + // The first two ifs here handle cases where rounding is simple, i.e. where we + // always know in which direction we must round because of the current + // rounding mode (note that if the correct value for inc is `false` then it + // doesn't need to be set as we have already done so above) + // The last one handles rounding to nearest + current_rounding_mode = fegetround(); + if (current_rounding_mode == FE_TOWARDZERO || + (current_rounding_mode == FE_UPWARD && b->sign) || + (current_rounding_mode == FE_DOWNWARD && !b->sign)) + goto have_inc; + if ((current_rounding_mode == FE_UPWARD && !b->sign) || + (current_rounding_mode == FE_DOWNWARD && b->sign)) { + inc = true; + goto have_inc; + } + if ((t = bits[k >> 3] >> (j = (k & 7) * 4)) & 8) { if (t & 7) - goto inc1; + goto inc_true; if (j && bits[k >> 3] << (32 - j)) - goto inc1; + goto inc_true; while (k >= 8) { k -= 8; if (bits[k >> 3]) { - inc1: - inc = 1; - goto haveinc; + inc_true: + inc = true; + goto have_inc; } } } -haveinc: + +have_inc: b->ex += m * 4; i = m >> 3; k = prec1 >> 3; diff --git a/test/libc/stdio/snprintf_test.c b/test/libc/stdio/snprintf_test.c index cd255860d42..e3cea6fcb43 100644 --- a/test/libc/stdio/snprintf_test.c +++ b/test/libc/stdio/snprintf_test.c @@ -216,3 +216,37 @@ TEST(snprintf, testLongDoubleRounding) { ASSERT_EQ(0, fesetround(previous_rounding)); } + +TEST(snprintf, testAConversionSpecifierRounding) { + int previous_rounding = fegetround(); + ASSERT_EQ(0, fesetround(FE_DOWNWARD)); + + char buf[20]; + int i = snprintf(buf, sizeof(buf), "%.1a", 0x1.fffffp+4); + ASSERT_EQ(8, i); + ASSERT_STREQ("0x1.fp+4", buf); + + ASSERT_EQ(0, fesetround(FE_UPWARD)); + + i = snprintf(buf, sizeof(buf), "%.1a", 0x1.f8p+4); + ASSERT_EQ(8, i); + ASSERT_STREQ("0x2.0p+4", buf); + + ASSERT_EQ(0, fesetround(previous_rounding)); +} + +// This test currently fails because of rounding issues +// If that ever gets fixed, uncomment this +/* +TEST(snprintf, testAConversionSpecifier) { + char buf[20]; + int i = snprintf(buf, sizeof(buf), "%.1a", 0x1.7800000000001p+4); + ASSERT_EQ(8, i); + ASSERT_STREQ("0x1.8p+4", buf); + + memset(buf, 0, sizeof(buf)); + i = snprintf(buf, sizeof(buf), "%.1a", 0x1.78p+4); + ASSERT_EQ(8, i); + ASSERT_STREQ("0x1.8p+4", buf); +} +*/