From e260d90096a2c185cdcf8c4762cfbd9485e7276c Mon Sep 17 00:00:00 2001 From: Gabriel Ravier Date: Mon, 16 Sep 2024 03:02:47 +0200 Subject: [PATCH] Fix 0 before decimal-point in hex float printf fns (#1297) The C standard specifies that, upon handling the a conversion specifier, the argument is converted to a string in which "there is one hexadecimal digit (which is nonzero [...]) before the decimal-point character", this being a requirement which cosmopolitan does not currently always handle, sometimes printing numbers like "0x0.1p+5", where a correct output would have been e.g. "0x1.0p+1" (despite both representing the same value, the first one illegally has a '0' digit before the decimal-point character). --- libc/stdio/fmt.c | 8 +++++++- test/libc/stdio/snprintf_test.c | 23 +++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index e00593ff6..d309cf629 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -714,6 +714,7 @@ static int __fmt_bround(struct FPBits *b, int prec, int prec1) { (b->fpi.rounding == FPI_Round_down && b->sign)) goto inc_true; + // Rounding to nearest, ties to even if ((t = bits[k >> 3] >> (j = (k & 7) * 4)) & 8) { if (t & 7) goto inc_true; @@ -757,7 +758,12 @@ have_inc: donothing; if (j > k) { onebit: - bits[0] = 1; + // We use 0x10 instead of 1 here to ensure that the digit before the + // decimal-point is non-0 (the C standard mandates this, i.e. considers + // that printing 0x0.1p+5 is illegal where 0x1.0p+1 is even though both + // evaluate to the same value because the first has 0 as the digit before + // the decimal-point character) + bits[0] = 0x10; b->ex += 4 * prec; return 1; } diff --git a/test/libc/stdio/snprintf_test.c b/test/libc/stdio/snprintf_test.c index e3f7a1aa7..33c472d98 100644 --- a/test/libc/stdio/snprintf_test.c +++ b/test/libc/stdio/snprintf_test.c @@ -233,7 +233,7 @@ void check_a_conversion_specifier_long_double(const char *fmt, const char *expec ASSERT_STREQ(expected_str, buf); } -void check_a_conversion_specifier_prec_1(const char *expected_str, double value) { +void check_a_conversion_specifier_double_prec_1(const char *expected_str, double value) { check_a_conversion_specifier_double("%.1a", expected_str, value); } @@ -241,10 +241,10 @@ TEST(snprintf, testAConversionSpecifierRounding) { int previous_rounding = fegetround(); ASSERT_EQ(0, fesetround(FE_DOWNWARD)); - check_a_conversion_specifier_prec_1("0x1.fp+4", 0x1.fffffp+4); + check_a_conversion_specifier_double_prec_1("0x1.fp+4", 0x1.fffffp+4); ASSERT_EQ(0, fesetround(FE_UPWARD)); - check_a_conversion_specifier_prec_1("0x2.0p+4", 0x1.f8p+4); + check_a_conversion_specifier_double_prec_1("0x2.0p+4", 0x1.f8p+4); ASSERT_EQ(0, fesetround(previous_rounding)); } @@ -252,18 +252,21 @@ TEST(snprintf, testAConversionSpecifierRounding) { // This test specifically checks that we round to even, accordingly to IEEE // rules TEST(snprintf, testAConversionSpecifier) { - check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.7800000000001p+4); - check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.78p+4); - check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.88p+4); - check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.58p+4); - check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.68p+4); - check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.98p+4); - check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.a8p+4); + check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.7800000000001p+4); + check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.78p+4); + check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.88p+4); + check_a_conversion_specifier_double_prec_1("0x1.6p+4", 0x1.58p+4); + check_a_conversion_specifier_double_prec_1("0x1.6p+4", 0x1.68p+4); + check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.98p+4); + check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.a8p+4); check_a_conversion_specifier_double("%#a", "0x0.p+0", 0x0.0p0); check_a_conversion_specifier_double("%#A", "0X0.P+0", 0x0.0p0); check_a_conversion_specifier_long_double("%#La", "0x0.p+0", 0x0.0p0L); check_a_conversion_specifier_long_double("%#LA", "0X0.P+0", 0x0.0p0L); + + check_a_conversion_specifier_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030); + check_a_conversion_specifier_long_double("%.1La", "0x1.0p+1", 1.999L); } TEST(snprintf, apostropheFlag) {