Skip to content

Commit 3cb40da

Browse files
committed
Use Ryū for PyOS_double_to_string format code 'r'
Add double_repr_buffered(), a standalone formatter that calls _Py_ryu_dtoa() for the digits and applies Python's repr layout (exponential for decpt outside (-4, 16], otherwise positional with optional trailing '.0') into a caller-provided 32-byte buffer. It honours all Py_DTSF_* flags so it can serve any 'r'-mode caller. PyOS_double_to_string('r', ...) now allocates a 32-byte buffer and calls double_repr_buffered() instead of going through format_float_short(), which is left to handle only the fixed-precision 'e'/'f'/'g' modes via _Py_dg_dtoa(). While here, replace the per-call sprintf("%+.02d", exp) in format_float_short()'s exponent emission with direct character output: for IEEE 754 doubles |exp| < 1000 so this is at most a sign and three digits. This benefits all format codes that use exponential notation.
1 parent 7dfdcb6 commit 3cb40da

1 file changed

Lines changed: 171 additions & 23 deletions

File tree

Python/pystrtod.c

Lines changed: 171 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <Python.h>
44
#include "pycore_dtoa.h" // _Py_dg_strtod()
55
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
6+
#include "pycore_ryu.h" // _Py_ryu_dtoa()
67

78
#include <locale.h> // localeconv()
89

@@ -942,19 +943,19 @@ static const char * const uc_float_strings[] = {
942943
943944
Arguments:
944945
d is the double to be converted
945-
format_code is one of 'e', 'f', 'g', 'r'. 'e', 'f' and 'g'
946-
correspond to '%e', '%f' and '%g'; 'r' corresponds to repr.
947-
mode is one of '0', '2' or '3', and is completely determined by
948-
format_code: 'e' and 'g' use mode 2; 'f' mode 3, 'r' mode 0.
946+
format_code is one of 'e', 'f', 'g'. 'e', 'f' and 'g'
947+
correspond to '%e', '%f' and '%g'. ('r' is handled separately
948+
by double_repr_buffered() below.)
949+
mode is one of '2' or '3', and is completely determined by
950+
format_code: 'e' and 'g' use mode 2; 'f' mode 3.
949951
precision is the desired precision
950952
always_add_sign is nonzero if a '+' sign should be included for positive
951953
numbers
952954
add_dot_0_if_integer is nonzero if integers in non-exponential form
953-
should have ".0" added. Only applies to format codes 'r' and 'g'.
955+
should have ".0" added. Only applies to format code 'g'.
954956
use_alt_formatting is nonzero if alternative formatting should be
955-
used. Only applies to format codes 'e', 'f' and 'g'. For code 'g',
956-
at most one of use_alt_formatting and add_dot_0_if_integer should
957-
be nonzero.
957+
used. For code 'g', at most one of use_alt_formatting and
958+
add_dot_0_if_integer should be nonzero.
958959
type, if non-NULL, will be set to one of these constants to identify
959960
the type of the 'd' argument:
960961
Py_DTST_FINITE
@@ -976,7 +977,7 @@ format_float_short(double d, char format_code,
976977
char *p = NULL;
977978
Py_ssize_t bufsize = 0;
978979
char *digits, *digits_end;
979-
int decpt_as_int, sign, exp_len, exp = 0, use_exp = 0;
980+
int decpt_as_int, sign, exp = 0, use_exp = 0;
980981
Py_ssize_t decpt, digits_len, vdigits_start, vdigits_end;
981982
_Py_SET_53BIT_PRECISION_HEADER;
982983

@@ -1090,15 +1091,6 @@ format_float_short(double d, char format_code,
10901091
if (use_alt_formatting)
10911092
vdigits_end = precision;
10921093
break;
1093-
case 'r':
1094-
/* convert to exponential format at 1e16. We used to convert
1095-
at 1e17, but that gives odd-looking results for some values
1096-
when a 16-digit 'shortest' repr is padded with bogus zeros.
1097-
For example, repr(2e16+8) would give 20000000000000010.0;
1098-
the true value is 20000000000000008.0. */
1099-
if (decpt <= -4 || decpt > 16)
1100-
use_exp = 1;
1101-
break;
11021094
default:
11031095
PyErr_BadInternalCall();
11041096
goto exit;
@@ -1201,8 +1193,24 @@ format_float_short(double d, char format_code,
12011193
/* Now that we've done zero padding, add an exponent if needed. */
12021194
if (use_exp) {
12031195
*p++ = float_strings[OFS_E][0];
1204-
exp_len = sprintf(p, "%+.02d", exp);
1205-
p += exp_len;
1196+
/* Emit a sign followed by at least two exponent digits. For
1197+
IEEE 754 doubles |exp| < 1000, so at most three digits. */
1198+
unsigned int uexp;
1199+
if (exp < 0) {
1200+
*p++ = '-';
1201+
uexp = (unsigned int)(-exp);
1202+
}
1203+
else {
1204+
*p++ = '+';
1205+
uexp = (unsigned int)exp;
1206+
}
1207+
assert(uexp < 1000); /* matches the bufsize computation above */
1208+
if (uexp >= 100) {
1209+
*p++ = (char)('0' + uexp / 100);
1210+
uexp %= 100;
1211+
}
1212+
*p++ = (char)('0' + uexp / 10);
1213+
*p++ = (char)('0' + uexp % 10);
12061214
}
12071215
exit:
12081216
if (buf) {
@@ -1218,6 +1226,140 @@ format_float_short(double d, char format_code,
12181226
}
12191227

12201228

1229+
/* Write the Python repr of a finite or non-finite double into the
1230+
caller-provided buffer. This is equivalent to PyOS_double_to_string
1231+
with format code 'r', but never allocates.
1232+
1233+
The 'r' formatting rules are simple enough to implement directly here
1234+
rather than going through format_float_short. */
1235+
static Py_ssize_t
1236+
double_repr_buffered(double val, char *buf, Py_ssize_t bufsize,
1237+
int flags, int *type)
1238+
{
1239+
assert(bufsize >= _Py_DOUBLE_REPR_BUFSIZE);
1240+
(void)bufsize;
1241+
1242+
char digits[_Py_RYU_DTOA_BUFSIZE];
1243+
int decpt, sign;
1244+
int digits_len = _Py_ryu_dtoa(val, digits, &decpt, &sign);
1245+
1246+
char *p = buf;
1247+
1248+
if (!Py_ISDIGIT(digits[0])) {
1249+
int is_nan = (digits[0] == 'N');
1250+
if (type) {
1251+
*type = is_nan ? Py_DTST_NAN : Py_DTST_INFINITE;
1252+
}
1253+
if (is_nan) {
1254+
sign = 0;
1255+
}
1256+
if (sign) {
1257+
*p++ = '-';
1258+
}
1259+
else if (flags & Py_DTSF_SIGN) {
1260+
*p++ = '+';
1261+
}
1262+
memcpy(p, is_nan ? "nan" : "inf", 3);
1263+
p += 3;
1264+
*p = '\0';
1265+
return p - buf;
1266+
}
1267+
1268+
if (type) {
1269+
*type = Py_DTST_FINITE;
1270+
}
1271+
if ((flags & Py_DTSF_NO_NEG_0) && sign
1272+
&& digits_len == 1 && digits[0] == '0')
1273+
{
1274+
sign = 0;
1275+
}
1276+
if (sign) {
1277+
*p++ = '-';
1278+
}
1279+
else if (flags & Py_DTSF_SIGN) {
1280+
*p++ = '+';
1281+
}
1282+
1283+
/* Convert to exponential format at 1e16. We used to convert
1284+
at 1e17, but that gives odd-looking results for some values
1285+
when a 16-digit 'shortest' repr is padded with bogus zeros.
1286+
For example, repr(2e16+8) would give 20000000000000010.0;
1287+
the true value is 20000000000000008.0. */
1288+
if (decpt <= -4 || decpt > 16) {
1289+
/* Exponential form: D.DDDe+NN */
1290+
*p++ = digits[0];
1291+
if (digits_len > 1) {
1292+
*p++ = '.';
1293+
memcpy(p, digits + 1, (size_t)(digits_len - 1));
1294+
p += digits_len - 1;
1295+
}
1296+
else if (flags & Py_DTSF_ALT) {
1297+
*p++ = '.';
1298+
}
1299+
*p++ = 'e';
1300+
int exp = decpt - 1;
1301+
unsigned int uexp;
1302+
if (exp < 0) {
1303+
*p++ = '-';
1304+
uexp = (unsigned int)(-exp);
1305+
}
1306+
else {
1307+
*p++ = '+';
1308+
uexp = (unsigned int)exp;
1309+
}
1310+
assert(uexp < 1000);
1311+
if (uexp >= 100) {
1312+
*p++ = (char)('0' + uexp / 100);
1313+
uexp %= 100;
1314+
}
1315+
*p++ = (char)('0' + uexp / 10);
1316+
*p++ = (char)('0' + uexp % 10);
1317+
}
1318+
else if (decpt <= 0) {
1319+
/* 0.[000]DDD */
1320+
*p++ = '0';
1321+
*p++ = '.';
1322+
memset(p, '0', (size_t)(-decpt));
1323+
p += -decpt;
1324+
memcpy(p, digits, (size_t)digits_len);
1325+
p += digits_len;
1326+
}
1327+
else if (decpt < digits_len) {
1328+
/* DD.DDD */
1329+
memcpy(p, digits, (size_t)decpt);
1330+
p += decpt;
1331+
*p++ = '.';
1332+
memcpy(p, digits + decpt, (size_t)(digits_len - decpt));
1333+
p += digits_len - decpt;
1334+
}
1335+
else {
1336+
/* DDD[000][.0] */
1337+
memcpy(p, digits, (size_t)digits_len);
1338+
p += digits_len;
1339+
memset(p, '0', (size_t)(decpt - digits_len));
1340+
p += decpt - digits_len;
1341+
if (flags & Py_DTSF_ADD_DOT_0) {
1342+
*p++ = '.';
1343+
*p++ = '0';
1344+
}
1345+
else if (flags & Py_DTSF_ALT) {
1346+
*p++ = '.';
1347+
}
1348+
}
1349+
1350+
*p = '\0';
1351+
assert(p - buf < _Py_DOUBLE_REPR_BUFSIZE);
1352+
return p - buf;
1353+
}
1354+
1355+
1356+
Py_ssize_t
1357+
_Py_double_repr_buffered(double val, char *buf, Py_ssize_t bufsize, int flags)
1358+
{
1359+
return double_repr_buffered(val, buf, bufsize, flags, NULL);
1360+
}
1361+
1362+
12211363
char * PyOS_double_to_string(double val,
12221364
char format_code,
12231365
int precision,
@@ -1262,14 +1404,20 @@ char * PyOS_double_to_string(double val,
12621404
break;
12631405

12641406
/* repr format */
1265-
case 'r':
1266-
mode = 0;
1407+
case 'r': {
12671408
/* Supplied precision is unused, must be 0. */
12681409
if (precision != 0) {
12691410
PyErr_BadInternalCall();
12701411
return NULL;
12711412
}
1272-
break;
1413+
char *buf = (char *)PyMem_Malloc(_Py_DOUBLE_REPR_BUFSIZE);
1414+
if (buf == NULL) {
1415+
PyErr_NoMemory();
1416+
return NULL;
1417+
}
1418+
double_repr_buffered(val, buf, _Py_DOUBLE_REPR_BUFSIZE, flags, type);
1419+
return buf;
1420+
}
12731421

12741422
default:
12751423
PyErr_BadInternalCall();

0 commit comments

Comments
 (0)