Skip to content

Commit e260d90

Browse files
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).
1 parent 81bc8d0 commit e260d90

File tree

2 files changed

+20
-11
lines changed

2 files changed

+20
-11
lines changed

libc/stdio/fmt.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ static int __fmt_bround(struct FPBits *b, int prec, int prec1) {
714714
(b->fpi.rounding == FPI_Round_down && b->sign))
715715
goto inc_true;
716716

717+
// Rounding to nearest, ties to even
717718
if ((t = bits[k >> 3] >> (j = (k & 7) * 4)) & 8) {
718719
if (t & 7)
719720
goto inc_true;
@@ -757,7 +758,12 @@ static int __fmt_bround(struct FPBits *b, int prec, int prec1) {
757758
donothing;
758759
if (j > k) {
759760
onebit:
760-
bits[0] = 1;
761+
// We use 0x10 instead of 1 here to ensure that the digit before the
762+
// decimal-point is non-0 (the C standard mandates this, i.e. considers
763+
// that printing 0x0.1p+5 is illegal where 0x1.0p+1 is even though both
764+
// evaluate to the same value because the first has 0 as the digit before
765+
// the decimal-point character)
766+
bits[0] = 0x10;
761767
b->ex += 4 * prec;
762768
return 1;
763769
}

test/libc/stdio/snprintf_test.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -233,37 +233,40 @@ void check_a_conversion_specifier_long_double(const char *fmt, const char *expec
233233
ASSERT_STREQ(expected_str, buf);
234234
}
235235

236-
void check_a_conversion_specifier_prec_1(const char *expected_str, double value) {
236+
void check_a_conversion_specifier_double_prec_1(const char *expected_str, double value) {
237237
check_a_conversion_specifier_double("%.1a", expected_str, value);
238238
}
239239

240240
TEST(snprintf, testAConversionSpecifierRounding) {
241241
int previous_rounding = fegetround();
242242

243243
ASSERT_EQ(0, fesetround(FE_DOWNWARD));
244-
check_a_conversion_specifier_prec_1("0x1.fp+4", 0x1.fffffp+4);
244+
check_a_conversion_specifier_double_prec_1("0x1.fp+4", 0x1.fffffp+4);
245245

246246
ASSERT_EQ(0, fesetround(FE_UPWARD));
247-
check_a_conversion_specifier_prec_1("0x2.0p+4", 0x1.f8p+4);
247+
check_a_conversion_specifier_double_prec_1("0x2.0p+4", 0x1.f8p+4);
248248

249249
ASSERT_EQ(0, fesetround(previous_rounding));
250250
}
251251

252252
// This test specifically checks that we round to even, accordingly to IEEE
253253
// rules
254254
TEST(snprintf, testAConversionSpecifier) {
255-
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.7800000000001p+4);
256-
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.78p+4);
257-
check_a_conversion_specifier_prec_1("0x1.8p+4", 0x1.88p+4);
258-
check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.58p+4);
259-
check_a_conversion_specifier_prec_1("0x1.6p+4", 0x1.68p+4);
260-
check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.98p+4);
261-
check_a_conversion_specifier_prec_1("0x1.ap+4", 0x1.a8p+4);
255+
check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.7800000000001p+4);
256+
check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.78p+4);
257+
check_a_conversion_specifier_double_prec_1("0x1.8p+4", 0x1.88p+4);
258+
check_a_conversion_specifier_double_prec_1("0x1.6p+4", 0x1.58p+4);
259+
check_a_conversion_specifier_double_prec_1("0x1.6p+4", 0x1.68p+4);
260+
check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.98p+4);
261+
check_a_conversion_specifier_double_prec_1("0x1.ap+4", 0x1.a8p+4);
262262

263263
check_a_conversion_specifier_double("%#a", "0x0.p+0", 0x0.0p0);
264264
check_a_conversion_specifier_double("%#A", "0X0.P+0", 0x0.0p0);
265265
check_a_conversion_specifier_long_double("%#La", "0x0.p+0", 0x0.0p0L);
266266
check_a_conversion_specifier_long_double("%#LA", "0X0.P+0", 0x0.0p0L);
267+
268+
check_a_conversion_specifier_double("%.2a", "0x1.00p-1026", 0xf.fffp-1030);
269+
check_a_conversion_specifier_long_double("%.1La", "0x1.0p+1", 1.999L);
267270
}
268271

269272
TEST(snprintf, apostropheFlag) {

0 commit comments

Comments
 (0)