From 81bc8d09633b6122052f9a5ab8ff0de40cd97442 Mon Sep 17 00:00:00 2001 From: Gabriel Ravier Date: Mon, 16 Sep 2024 01:42:51 +0200 Subject: [PATCH] Fix printing decimal-point character on printf %#a (#1296) The C standard indicates that when processing the a conversion specifier "if the precision is zero *and* the # flag is not specified, no decimal- point character appears.". This means that __fmt needs to ensure that it prints the decimal-point character not only when the precision is non-0, but also when the # flag is specified - cosmopolitan currently does not. This patch fixes this, along with adding a few tests for this behaviour. --- libc/stdio/fmt.c | 6 +++++- test/libc/stdio/snprintf_test.c | 25 +++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index 72335a580..e00593ff6 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -1497,9 +1497,13 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) { i1 = prec1 & 7; k = prec1 >> 3; __FMT_PUT(alphabet[(fpb.bits[k] >> 4 * i1) & 0xf]); - if (prec1 > 0 || prec > 0) { + + // decimal-point character appears if the precision isn't 0 + // or the # flag is specified + if (prec1 > 0 || prec > 0 || (flags & FLAGS_HASH)) { __FMT_PUT('.'); } + while (prec1 > 0) { if (--i1 < 0) { if (--k < 0) diff --git a/test/libc/stdio/snprintf_test.c b/test/libc/stdio/snprintf_test.c index 358095e30..e3f7a1aa7 100644 --- a/test/libc/stdio/snprintf_test.c +++ b/test/libc/stdio/snprintf_test.c @@ -217,12 +217,24 @@ TEST(snprintf, testLongDoubleRounding) { ASSERT_EQ(0, fesetround(previous_rounding)); } -void check_a_conversion_specifier_prec_1(const char *result_str, double value) { +void check_a_conversion_specifier_double(const char *fmt, const char *expected_str, double value) { char buf[30] = {0}; - int i = snprintf(buf, sizeof(buf), "%.1a", value); + int i = snprintf(buf, sizeof(buf), fmt, value); - ASSERT_EQ(strlen(result_str), i); - ASSERT_STREQ(result_str, buf); + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); +} + +void check_a_conversion_specifier_long_double(const char *fmt, const char *expected_str, long double value) { + char buf[30] = {0}; + int i = snprintf(buf, sizeof(buf), fmt, value); + + ASSERT_EQ(strlen(expected_str), i); + ASSERT_STREQ(expected_str, buf); +} + +void check_a_conversion_specifier_prec_1(const char *expected_str, double value) { + check_a_conversion_specifier_double("%.1a", expected_str, value); } TEST(snprintf, testAConversionSpecifierRounding) { @@ -247,6 +259,11 @@ TEST(snprintf, testAConversionSpecifier) { 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("%#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); } TEST(snprintf, apostropheFlag) {