Skip to content

Commit b636dd9

Browse files
committed
Implement precision and modulo-precision format specifiers
1 parent e44993a commit b636dd9

File tree

1 file changed

+133
-21
lines changed

1 file changed

+133
-21
lines changed

Objects/unicode_formatter.c

Lines changed: 133 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,34 +1214,34 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
12141214
from a hard-code pseudo-locale */
12151215
LocaleInfo locale = LocaleInfo_STATIC_INIT;
12161216

1217-
/* no precision allowed on integers */
1218-
if (format->precision != -1) {
1219-
PyErr_SetString(PyExc_ValueError,
1220-
"Precision not allowed in integer format specifier");
1221-
goto done;
1222-
}
1223-
/* no negative zero coercion on integers */
1224-
if (format->no_neg_0) {
1225-
PyErr_SetString(PyExc_ValueError,
1226-
"Negative zero coercion (z) not allowed in integer"
1227-
" format specifier");
1228-
goto done;
1229-
}
1230-
12311217
/* special case for character formatting */
12321218
if (format->type == 'c') {
12331219
/* error to specify a sign */
12341220
if (format->sign != '\0') {
12351221
PyErr_SetString(PyExc_ValueError,
12361222
"Sign not allowed with integer"
1237-
" format specifier 'c'");
1223+
" presentation type 'c'");
12381224
goto done;
12391225
}
12401226
/* error to request alternate format */
12411227
if (format->alternate) {
12421228
PyErr_SetString(PyExc_ValueError,
12431229
"Alternate form (#) not allowed with integer"
1244-
" format specifier 'c'");
1230+
" presentation type 'c'");
1231+
goto done;
1232+
}
1233+
/* error to request precision */
1234+
if (format->precision != -1) {
1235+
PyErr_SetString(PyExc_ValueError,
1236+
"Precision (.) not allowed with integer"
1237+
" presentation type 'c'");
1238+
goto done;
1239+
}
1240+
/* error to request two's complement */
1241+
if (format->no_neg_0) {
1242+
PyErr_SetString(PyExc_ValueError,
1243+
"Two's complement (z) not allowed with integer"
1244+
" presentation type 'c'");
12451245
goto done;
12461246
}
12471247

@@ -1269,35 +1269,80 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
12691269
}
12701270
else {
12711271
int base;
1272-
int leading_chars_to_skip = 0; /* Number of characters added by
1273-
PyNumber_ToBase that we want to
1274-
skip over. */
1272+
int log2_base;
1273+
int leading_chars_to_skip; /* Number of characters added by
1274+
PyNumber_ToBase that we want to
1275+
skip over. */
1276+
1277+
if (format->precision == -1) {
1278+
/* precision not requested */
1279+
1280+
if (format->no_neg_0) {
1281+
/* two's complement format only allowed with precision */
1282+
PyErr_SetString(PyExc_ValueError,
1283+
"Two's complement (z) requires precision (.)"
1284+
" format specifier");
1285+
goto done;
1286+
}
1287+
} else {
1288+
/* precision requested */
1289+
1290+
if (format->no_neg_0 && !(format->type == 'b' || format->type == 'o'
1291+
|| format->type == 'x' || format->type == 'X'))
1292+
{
1293+
/* two's complement format only allowed for bases that
1294+
are powers of two */
1295+
1296+
/* It is easier to specify which bases are allowed than
1297+
to single out 'c', 'd', 'n', and '' as forbidden,
1298+
because with '' (which is implemented the same as 'n')
1299+
the error message would have to take into account
1300+
whether the user literally specified 'n' or '' */
1301+
PyErr_SetString(PyExc_ValueError,
1302+
"Two's complement (z) only allowed"
1303+
" with integer presentation types"
1304+
" 'b', 'o', 'x', and 'X'");
1305+
goto done;
1306+
}
1307+
1308+
/* finally check the precision length is sane */
1309+
if (format->precision > INT_MAX) {
1310+
PyErr_SetString(PyExc_ValueError, "precision too big");
1311+
goto done;
1312+
}
1313+
}
12751314

12761315
/* Compute the base and how many characters will be added by
12771316
PyNumber_ToBase */
12781317
switch (format->type) {
12791318
case 'b':
12801319
base = 2;
1320+
log2_base = 1;
12811321
leading_chars_to_skip = 2; /* 0b */
12821322
break;
12831323
case 'o':
12841324
base = 8;
1325+
log2_base = 3;
12851326
leading_chars_to_skip = 2; /* 0o */
12861327
break;
12871328
case 'x':
12881329
case 'X':
12891330
base = 16;
1331+
log2_base = 4;
12901332
leading_chars_to_skip = 2; /* 0x */
12911333
break;
12921334
default: /* shouldn't be needed, but stops a compiler warning */
12931335
case 'd':
12941336
case 'n':
12951337
base = 10;
1338+
log2_base = -1; /* unused */
1339+
leading_chars_to_skip = 0;
12961340
break;
12971341
}
12981342

12991343
if (format->sign != '+' && format->sign != ' '
13001344
&& format->width == -1
1345+
&& format->precision == -1
13011346
&& format->type != 'X' && format->type != 'n'
13021347
&& !format->thousands_separators
13031348
&& PyLong_CheckExact(value))
@@ -1311,8 +1356,40 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
13111356
if (format->alternate)
13121357
n_prefix = leading_chars_to_skip;
13131358

1314-
/* Do the hard part, converting to a string in a given base */
1315-
tmp = _PyLong_Format(value, base);
1359+
if (format->no_neg_0) {
1360+
/* perform the reduction of value modulo (base ** precision) */
1361+
int64_t shift_by;
1362+
PyObject *one;
1363+
PyObject *modulus;
1364+
PyObject *reduced_value;
1365+
1366+
shift_by = log2_base * format->precision;
1367+
1368+
one = PyLong_FromLong(1);
1369+
if (one == NULL) {
1370+
goto done;
1371+
}
1372+
1373+
modulus = _PyLong_Lshift(one, shift_by);
1374+
Py_DECREF(one);
1375+
if (modulus == NULL) {
1376+
goto done;
1377+
}
1378+
1379+
reduced_value = PyNumber_Remainder(value, modulus);
1380+
Py_DECREF(modulus);
1381+
if (reduced_value == NULL) {
1382+
goto done;
1383+
}
1384+
1385+
/* Do the hard part, converting to a string in a given base */
1386+
tmp = _PyLong_Format(reduced_value, base);
1387+
Py_DECREF(reduced_value);
1388+
} else {
1389+
/* Do the hard part, converting to a string in a given base */
1390+
tmp = _PyLong_Format(value, base);
1391+
}
1392+
13161393
if (tmp == NULL)
13171394
goto done;
13181395

@@ -1332,6 +1409,41 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
13321409
/* Skip over the leading chars (0x, 0b, etc.) */
13331410
n_digits -= leading_chars_to_skip;
13341411
inumeric_chars += leading_chars_to_skip;
1412+
1413+
if (format->precision != -1 && n_digits < format->precision) {
1414+
/* prepend leading zeros (after sign and prefix if they exist) */
1415+
PyObject *tmp2;
1416+
1417+
Py_ssize_t zeros_needed = format->precision - n_digits;
1418+
Py_ssize_t tmp2_len = leading_chars_to_skip + format->precision;
1419+
1420+
tmp2 = PyUnicode_New(tmp2_len, 127);
1421+
if (tmp2 == NULL)
1422+
goto done;
1423+
1424+
if (PyUnicode_CopyCharacters(tmp2, 0, tmp, 0,
1425+
leading_chars_to_skip) < 0)
1426+
{
1427+
Py_DECREF(tmp2);
1428+
goto done;
1429+
}
1430+
if (PyUnicode_Fill(tmp2, leading_chars_to_skip,
1431+
zeros_needed, '0') < 0)
1432+
{
1433+
Py_DECREF(tmp2);
1434+
goto done;
1435+
}
1436+
if (PyUnicode_CopyCharacters(tmp2,
1437+
leading_chars_to_skip + zeros_needed,
1438+
tmp, leading_chars_to_skip, n_digits
1439+
) < 0)
1440+
{
1441+
Py_DECREF(tmp2);
1442+
goto done;
1443+
}
1444+
Py_SETREF(tmp, tmp2);
1445+
n_digits = format->precision;
1446+
}
13351447
}
13361448

13371449
/* Determine the grouping, separator, and decimal point, if any. */

0 commit comments

Comments
 (0)