Skip to content

Commit 4754f20

Browse files
Fix printf-family long double prec/rounding issues (#1283)
Currently, in cosmopolitan, there is no handling of the current rounding mode for long double conversions, such that round-to-nearest gets always used, regardless of the current rounding mode. %Le also improperly calls gdtoa with a too small precision (which led to relatively similar bugs). This patch fixes these issues, in particular by modifying the FPI object passed to gdtoa such that it is modifiable (so that __fmt can adjust its rounding field to correspond to FLT_ROUNDS (note that this is not needed for dtoa, which checks FLT_ROUNDS directly)) and ors STRTOG_Neg into the kind field in both of the __fmt_dfpbits and __fmt_ldfpbits functions, as the gdtoa function also depends on it to be able to accurately round any negative arguments. The change to kind also requires a few other changes to make sure kind's upper bits (which include STRTOG_Neg) are masked off when attempting to only examine the lower bits' value. Furthermore, this patch also makes exactly one change in gdtoa, which appears to be needed to fix rounding issues with FE_TOWARDZERO (this seems like a gdtoa bug). The patch also adds a few tests for these issues, along with also taking the opportunity to clean up some of the previous tests to do the asserts in the right order (i.e. with the first argument as the expected result, and the second one being used as the value that it is compared against).
1 parent f882887 commit 4754f20

File tree

3 files changed

+110
-60
lines changed

3 files changed

+110
-60
lines changed

libc/stdio/fmt.c

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include "libc/math.h"
5454
#include "libc/mem/mem.h"
5555
#include "libc/mem/reverse.internal.h"
56+
#include "libc/runtime/fenv.h"
5657
#include "libc/runtime/internal.h"
5758
#include "libc/serialize.h"
5859
#include "libc/str/str.h"
@@ -90,7 +91,7 @@
9091

9192
struct FPBits {
9293
uint32_t bits[4];
93-
const FPI *fpi;
94+
FPI fpi;
9495
int sign;
9596
int ex; // exponent
9697
int kind;
@@ -563,7 +564,13 @@ static int __fmt_stoa(int out(const char *, void *, size_t), void *arg,
563564

564565
static void __fmt_dfpbits(union U *u, struct FPBits *b) {
565566
int ex, i;
566-
b->fpi = &kFpiDbl;
567+
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;
567574
b->sign = u->ui[1] & 0x80000000L;
568575
b->bits[1] = u->ui[1] & 0xfffff;
569576
b->bits[0] = u->ui[0];
@@ -582,6 +589,7 @@ static void __fmt_dfpbits(union U *u, struct FPBits *b) {
582589
} else {
583590
i = STRTOG_Zero;
584591
}
592+
i |= signbit(u->d) ? STRTOG_Neg : 0;
585593
b->kind = i;
586594
b->ex = ex - (0x3ff + 52);
587595
}
@@ -607,7 +615,11 @@ static void __fmt_ldfpbits(union U *u, struct FPBits *b) {
607615
#else
608616
#error "unsupported architecture"
609617
#endif
610-
b->fpi = &kFpiLdbl;
618+
b->fpi = kFpiLdbl;
619+
// gdtoa doesn't check for FLT_ROUNDS but for fpi.rounding (which has the
620+
// same valid values as FLT_ROUNDS), so handle this here
621+
if (FLT_ROUNDS != -1)
622+
b->fpi.rounding = FLT_ROUNDS;
611623
b->sign = sex & 0x8000;
612624
if ((ex = sex & 0x7fff) != 0) {
613625
if (ex != 0x7fff) {
@@ -626,6 +638,7 @@ static void __fmt_ldfpbits(union U *u, struct FPBits *b) {
626638
} else {
627639
i = STRTOG_Zero;
628640
}
641+
i |= signbit(u->ld) ? STRTOG_Neg : 0;
629642
b->kind = i;
630643
b->ex = ex - (0x3fff + (LDBL_MANT_DIG - 1));
631644
#endif
@@ -636,9 +649,9 @@ static int __fmt_fpiprec(struct FPBits *b) {
636649
const FPI *fpi;
637650
int i, j, k, m;
638651
uint32_t *bits;
639-
if (b->kind == STRTOG_Zero)
652+
if ((b->kind & STRTOG_Retmask) == STRTOG_Zero)
640653
return (b->ex = 0);
641-
fpi = b->fpi;
654+
fpi = &b->fpi;
642655
bits = b->bits;
643656
for (k = (fpi->nbits - 1) >> 2; k > 0; --k) {
644657
if ((bits[k >> 3] >> 4 * (k & 7)) & 0xf) {
@@ -1163,8 +1176,8 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
11631176
} else {
11641177
un.ld = va_arg(va, long double);
11651178
__fmt_ldfpbits(&un, &fpb);
1166-
s = s0 =
1167-
gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, 3, prec, &decpt, &se);
1179+
s = s0 = gdtoa(&fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, 3, prec, &decpt,
1180+
&se);
11681181
}
11691182
if (s0 == NULL)
11701183
return -1;
@@ -1181,7 +1194,10 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
11811194
} else if (flags & FLAGS_SPACE) {
11821195
*q++ = ' ';
11831196
}
1184-
memcpy(q, kSpecialFloats[fpb.kind == STRTOG_NaN][d >= 'a'], 4);
1197+
memcpy(q,
1198+
kSpecialFloats[(fpb.kind & STRTOG_Retmask) == STRTOG_NaN]
1199+
[d >= 'a'],
1200+
4);
11851201
flags &= ~(FLAGS_PRECISION | FLAGS_PLUS | FLAGS_HASH | FLAGS_SPACE);
11861202
prec = 0;
11871203
rc = __fmt_stoa(out, arg, s, flags, prec, width, signbit, qchar);
@@ -1278,7 +1294,7 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
12781294
} else {
12791295
un.ld = va_arg(va, long double);
12801296
__fmt_ldfpbits(&un, &fpb);
1281-
s = s0 = gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
1297+
s = s0 = gdtoa(&fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
12821298
prec, &decpt, &se);
12831299
}
12841300
if (s0 == NULL)
@@ -1326,8 +1342,8 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
13261342
} else {
13271343
un.ld = va_arg(va, long double);
13281344
__fmt_ldfpbits(&un, &fpb);
1329-
s = s0 = gdtoa(fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
1330-
prec, &decpt, &se);
1345+
s = s0 = gdtoa(&fpb.fpi, fpb.ex, fpb.bits, &fpb.kind, prec ? 2 : 0,
1346+
prec + 1, &decpt, &se);
13311347
}
13321348
if (s0 == NULL)
13331349
return -1;
@@ -1411,7 +1427,8 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
14111427
un.d = va_arg(va, double);
14121428
__fmt_dfpbits(&un, &fpb);
14131429
}
1414-
if (fpb.kind == STRTOG_Infinite || fpb.kind == STRTOG_NaN) {
1430+
if ((fpb.kind & STRTOG_Retmask) == STRTOG_Infinite ||
1431+
(fpb.kind & STRTOG_Retmask) == STRTOG_NaN) {
14151432
s0 = 0;
14161433
goto FormatDecpt9999Or32768;
14171434
}
@@ -1429,7 +1446,7 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) {
14291446
i1 /= 10;
14301447
}
14311448
}
1432-
if (fpb.sign /* && (sign || fpb.kind != STRTOG_Zero) */) {
1449+
if (fpb.sign /* && (sign || (fpb.kind & STRTOG_Retmask) != STRTOG_Zero) */) {
14331450
sign = '-';
14341451
}
14351452
if ((width -= bw + 5) > 0) {

test/libc/stdio/snprintf_test.c

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
19+
#include "libc/runtime/fenv.h"
1920
#include "libc/stdio/stdio.h"
2021
#include "libc/str/str.h"
2122
#include "libc/testlib/testlib.h"
@@ -24,64 +25,64 @@ TEST(snprintf, testVeryLargePrecision) {
2425
char buf[512] = {};
2526
int i = snprintf(buf, sizeof(buf), "%.9999u", 10);
2627

27-
ASSERT_EQ(i, 9999);
28-
ASSERT_EQ(strlen(buf), 511);
28+
ASSERT_EQ(9999, i);
29+
ASSERT_EQ(511, strlen(buf));
2930
}
3031

3132
TEST(snprintf, testPlusFlagOnChar) {
3233
char buf[10] = {};
3334
int i = snprintf(buf, sizeof(buf), "%+c", '=');
3435

35-
ASSERT_EQ(i, 1);
36-
ASSERT_STREQ(buf, "=");
36+
ASSERT_EQ(1, i);
37+
ASSERT_STREQ("=", buf);
3738
}
3839

3940
TEST(snprintf, testInf) {
4041
char buf[10] = {};
4142
int i = snprintf(buf, sizeof(buf), "%f", 1.0 / 0.0);
4243

43-
ASSERT_EQ(i, 3);
44-
ASSERT_STREQ(buf, "inf");
44+
ASSERT_EQ(3, i);
45+
ASSERT_STREQ("inf", buf);
4546

4647
memset(buf, '\0', 4);
4748
i = snprintf(buf, sizeof(buf), "%Lf", 1.0L / 0.0L);
48-
ASSERT_EQ(i, 3);
49-
ASSERT_STREQ(buf, "inf");
49+
ASSERT_EQ(3, i);
50+
ASSERT_STREQ("inf", buf);
5051

5152
memset(buf, '\0', 4);
5253
i = snprintf(buf, sizeof(buf), "%e", 1.0 / 0.0);
53-
ASSERT_EQ(i, 3);
54-
ASSERT_STREQ(buf, "inf");
54+
ASSERT_EQ(3, i);
55+
ASSERT_STREQ("inf", buf);
5556

5657
memset(buf, '\0', 4);
5758
i = snprintf(buf, sizeof(buf), "%Le", 1.0L / 0.0L);
58-
ASSERT_EQ(i, 3);
59-
ASSERT_STREQ(buf, "inf");
59+
ASSERT_EQ(3, i);
60+
ASSERT_STREQ("inf", buf);
6061

6162
memset(buf, '\0', 4);
6263
i = snprintf(buf, sizeof(buf), "%g", 1.0 / 0.0);
63-
ASSERT_EQ(i, 3);
64-
ASSERT_STREQ(buf, "inf");
64+
ASSERT_EQ(3, i);
65+
ASSERT_STREQ("inf", buf);
6566

6667
memset(buf, '\0', 4);
6768
i = snprintf(buf, sizeof(buf), "%Lg", 1.0L / 0.0L);
68-
ASSERT_EQ(i, 3);
69-
ASSERT_STREQ(buf, "inf");
69+
ASSERT_EQ(3, i);
70+
ASSERT_STREQ("inf", buf);
7071

7172
for (i = 4; i < 10; ++i)
72-
ASSERT_EQ(buf[i], '\0');
73+
ASSERT_EQ('\0', buf[i]);
7374
}
7475

7576
TEST(snprintf, testUppercaseCConversionSpecifier) {
7677
char buf[10] = {};
7778
int i = snprintf(buf, sizeof(buf), "%C", L'a');
7879

79-
ASSERT_EQ(i, 1);
80-
ASSERT_STREQ(buf, "a");
80+
ASSERT_EQ(1, i);
81+
ASSERT_STREQ("a", buf);
8182

8283
i = snprintf(buf, sizeof(buf), "%C", L'☺');
83-
ASSERT_EQ(i, 3);
84-
ASSERT_STREQ(buf, "☺");
84+
ASSERT_EQ(3, i);
85+
ASSERT_STREQ("☺", buf);
8586
}
8687

8788
// Make sure we don't va_arg the wrong argument size on wide character
@@ -92,96 +93,126 @@ TEST(snprintf,
9293
int i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%lc%d", 0, 0, 0, 0, 0, 0,
9394
0, 0, L'x', 1);
9495

95-
ASSERT_EQ(i, 10);
96-
ASSERT_STREQ(buf, "00000000x1");
96+
ASSERT_EQ(10, i);
97+
ASSERT_STREQ("00000000x1", buf);
9798

9899
memset(buf, 0, sizeof(buf));
99100
i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%C%d", 0, 0, 0, 0, 0, 0, 0, 0,
100101
L'x', 1);
101-
ASSERT_EQ(i, 10);
102-
ASSERT_STREQ(buf, "00000000x1");
102+
ASSERT_EQ(10, i);
103+
ASSERT_STREQ("00000000x1", buf);
103104
}
104105

105106
static void check_n_buffer_contents(char buf[350]) {
106107
for (int i = 0; i < 284; ++i)
107-
ASSERT_EQ(buf[i], ' ');
108-
ASSERT_STREQ(&buf[284], "428463");
108+
ASSERT_EQ(' ', buf[i]);
109+
ASSERT_STREQ("428463", &buf[284]);
109110
for (int i = 290; i < 350; ++i)
110-
ASSERT_EQ(buf[i], '\0');
111+
ASSERT_EQ('\0', buf[i]);
111112
}
112113

113114
TEST(snprintf, testNConversionSpecifier) {
114115
char buf[350] = {};
115116

116117
int n_res_int = -1;
117118
int i = snprintf(buf, sizeof(buf), "%286d%d%n%d", 42, 84, &n_res_int, 63);
118-
ASSERT_EQ(i, 290);
119+
ASSERT_EQ(290, i);
119120
check_n_buffer_contents(buf);
120-
ASSERT_EQ(n_res_int, 288);
121+
ASSERT_EQ(288, n_res_int);
121122

122123
memset(&buf, '\0', sizeof(buf));
123124
long n_res_long = -1;
124125
i = snprintf(buf, sizeof(buf), "%286ld%ld%ln%ld", 42L, 84L, &n_res_long, 63L);
125-
ASSERT_EQ(i, 290);
126+
ASSERT_EQ(290, i);
126127
check_n_buffer_contents(buf);
127-
ASSERT_EQ(n_res_long, 288);
128+
ASSERT_EQ(288, n_res_long);
128129

129130
memset(&buf, '\0', sizeof(buf));
130131
long long n_res_long_long = -1;
131132
i = snprintf(buf, sizeof(buf), "%286lld%lld%lln%lld", 42LL, 84LL,
132133
&n_res_long_long, 63LL);
133-
ASSERT_EQ(i, 290);
134+
ASSERT_EQ(290, i);
134135
check_n_buffer_contents(buf);
135-
ASSERT_EQ(n_res_long_long, 288);
136+
ASSERT_EQ(288, n_res_long_long);
136137

137138
ASSERT_EQ(sizeof(short), 2);
138139
ASSERT_EQ(sizeof(int), 4);
139140
memset(&buf, '\0', sizeof(buf));
140141
short n_res_short = -1;
141142
i = snprintf(buf, sizeof(buf), "%286hd%hd%hn%hd", (42 | 0xFFFF0000),
142143
(84 | 0xFFFF0000), &n_res_short, (63 | 0xFFFF0000));
143-
ASSERT_EQ(i, 290);
144+
ASSERT_EQ(290, i);
144145
check_n_buffer_contents(buf);
145-
ASSERT_EQ(n_res_short, 288);
146+
ASSERT_EQ(288, n_res_short);
146147

147148
ASSERT_EQ(sizeof(unsigned char), 1);
148149
memset(&buf, '\0', sizeof(buf));
149150
signed char n_res_char = -1;
150151
i = snprintf(buf, sizeof(buf), "%286hhd%hhd%hhn%hhd", (42 | 0xFFFFFF00),
151152
(84 | 0xFFFFFF00), &n_res_char, (63 | 0xFFFFFF00));
152-
ASSERT_EQ(i, 290);
153+
ASSERT_EQ(290, i);
153154
check_n_buffer_contents(buf);
154-
ASSERT_EQ(n_res_char, (signed char)288);
155+
ASSERT_EQ((signed char)288, n_res_char);
155156

156157
memset(&buf, '\0', sizeof(buf));
157158
ssize_t n_res_size_t = -1;
158159
i = snprintf(buf, sizeof(buf), "%286zd%zd%zn%zd", (ssize_t)42, (ssize_t)84,
159160
&n_res_size_t, (ssize_t)63);
160-
ASSERT_EQ(i, 290);
161+
ASSERT_EQ(290, i);
161162
check_n_buffer_contents(buf);
162-
ASSERT_EQ(n_res_size_t, 288);
163+
ASSERT_EQ(288, n_res_size_t);
163164

164165
memset(&buf, '\0', sizeof(buf));
165166
intmax_t n_res_intmax_t = -1;
166167
i = snprintf(buf, sizeof(buf), "%286jd%jd%jn%jd", (intmax_t)42, (intmax_t)84,
167168
&n_res_intmax_t, (intmax_t)63);
168-
ASSERT_EQ(i, 290);
169+
ASSERT_EQ(290, i);
169170
check_n_buffer_contents(buf);
170-
ASSERT_EQ(n_res_intmax_t, 288);
171+
ASSERT_EQ(288, n_res_intmax_t);
171172

172173
memset(&buf, '\0', sizeof(buf));
173174
int128_t n_res_int128_t = -1;
174175
i = snprintf(buf, sizeof(buf), "%286jjd%jjd%jjn%jjd", (int128_t)42,
175176
(int128_t)84, &n_res_int128_t, (int128_t)63);
176-
ASSERT_EQ(i, 290);
177+
ASSERT_EQ(290, i);
177178
check_n_buffer_contents(buf);
178-
ASSERT_EQ(n_res_int128_t, 288);
179+
ASSERT_EQ(288, n_res_int128_t);
179180

180181
memset(&buf, '\0', sizeof(buf));
181182
ptrdiff_t n_res_ptrdiff_t = -1;
182183
i = snprintf(buf, sizeof(buf), "%286td%td%tn%td", (ptrdiff_t)42,
183184
(ptrdiff_t)84, &n_res_ptrdiff_t, (ptrdiff_t)63);
184-
ASSERT_EQ(i, 290);
185+
ASSERT_EQ(290, i);
185186
check_n_buffer_contents(buf);
186-
ASSERT_EQ(n_res_ptrdiff_t, 288);
187+
ASSERT_EQ(288, n_res_ptrdiff_t);
188+
}
189+
190+
TEST(snprintf, testLongDoubleEConversionSpecifier) {
191+
char buf[20] = {};
192+
int i = snprintf(buf, sizeof(buf), "%Le", 1234567.8L);
193+
194+
ASSERT_EQ(12, i);
195+
ASSERT_STREQ("1.234568e+06", buf);
196+
}
197+
198+
TEST(snprintf, testLongDoubleRounding) {
199+
int previous_rounding = fegetround();
200+
ASSERT_EQ(0, fesetround(FE_DOWNWARD));
201+
202+
char buf[20];
203+
int i = snprintf(buf, sizeof(buf), "%.3Lf", 4.4375L);
204+
ASSERT_EQ(5, i);
205+
ASSERT_STREQ("4.437", buf);
206+
207+
i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L);
208+
ASSERT_EQ(6, i);
209+
ASSERT_STREQ("-4.438", buf);
210+
211+
ASSERT_EQ(0, fesetround(FE_TOWARDZERO));
212+
213+
i = snprintf(buf, sizeof(buf), "%.3Lf", -4.4375L);
214+
ASSERT_EQ(6, i);
215+
ASSERT_STREQ("-4.437", buf);
216+
217+
ASSERT_EQ(0, fesetround(previous_rounding));
187218
}

third_party/gdtoa/gdtoa.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,9 @@ gdtoa(const FPI *fpi, int be, ULong *bits, int *kindp, int mode, int ndigits, in
293293
else if ( (rdir = fpi->rounding - 1) !=0) {
294294
if (rdir < 0)
295295
rdir = 2;
296-
if (kind & STRTOG_Neg)
296+
// note that we check for fpi->rounding == 0 as in that case we
297+
// must *always* round towards 0, i.e. downwards, with rdir = 2
298+
if (kind & STRTOG_Neg && fpi->rounding != 0)
297299
rdir = 3 - rdir;
298300
}
299301
/* Now rdir = 0 ==> round near, 1 ==> round up, 2 ==> round down. */

0 commit comments

Comments
 (0)