Skip to content

Commit 3810589

Browse files
committed
Implement precision and modulo-precision format specifiers
1 parent 6ab4ace commit 3810589

File tree

1 file changed

+133
-21
lines changed

1 file changed

+133
-21
lines changed

Python/formatter_unicode.c

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

982-
/* no precision allowed on integers */
983-
if (format->precision != -1) {
984-
PyErr_SetString(PyExc_ValueError,
985-
"Precision not allowed in integer format specifier");
986-
goto done;
987-
}
988-
/* no negative zero coercion on integers */
989-
if (format->no_neg_0) {
990-
PyErr_SetString(PyExc_ValueError,
991-
"Negative zero coercion (z) not allowed in integer"
992-
" format specifier");
993-
goto done;
994-
}
995-
996982
/* special case for character formatting */
997983
if (format->type == 'c') {
998984
/* error to specify a sign */
999985
if (format->sign != '\0') {
1000986
PyErr_SetString(PyExc_ValueError,
1001987
"Sign not allowed with integer"
1002-
" format specifier 'c'");
988+
" presentation type 'c'");
1003989
goto done;
1004990
}
1005991
/* error to request alternate format */
1006992
if (format->alternate) {
1007993
PyErr_SetString(PyExc_ValueError,
1008994
"Alternate form (#) not allowed with integer"
1009-
" format specifier 'c'");
995+
" presentation type 'c'");
996+
goto done;
997+
}
998+
/* error to request precision */
999+
if (format->precision != -1) {
1000+
PyErr_SetString(PyExc_ValueError,
1001+
"Precision (.) not allowed with integer"
1002+
" presentation type 'c'");
1003+
goto done;
1004+
}
1005+
/* error to request two's complement */
1006+
if (format->no_neg_0) {
1007+
PyErr_SetString(PyExc_ValueError,
1008+
"Two's complement (z) not allowed with integer"
1009+
" presentation type 'c'");
10101010
goto done;
10111011
}
10121012

@@ -1034,35 +1034,80 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
10341034
}
10351035
else {
10361036
int base;
1037-
int leading_chars_to_skip = 0; /* Number of characters added by
1038-
PyNumber_ToBase that we want to
1039-
skip over. */
1037+
int log2_base;
1038+
int leading_chars_to_skip; /* Number of characters added by
1039+
PyNumber_ToBase that we want to
1040+
skip over. */
1041+
1042+
if (format->precision == -1) {
1043+
/* precision not requested */
1044+
1045+
if (format->no_neg_0) {
1046+
/* two's complement format only allowed with precision */
1047+
PyErr_SetString(PyExc_ValueError,
1048+
"Two's complement (z) requires precision (.)"
1049+
" format specifier");
1050+
goto done;
1051+
}
1052+
} else {
1053+
/* precision requested */
1054+
1055+
if (format->no_neg_0 && !(format->type == 'b' || format->type == 'o'
1056+
|| format->type == 'x' || format->type == 'X'))
1057+
{
1058+
/* two's complement format only allowed for bases that
1059+
are powers of two */
1060+
1061+
/* It is easier to specify which bases are allowed than
1062+
to single out 'c', 'd', 'n', and '' as forbidden,
1063+
because with '' (which is implemented the same as 'n')
1064+
the error message would have to take into account
1065+
whether the user literally specified 'n' or '' */
1066+
PyErr_SetString(PyExc_ValueError,
1067+
"Two's complement (z) only allowed"
1068+
" with integer presentation types"
1069+
" 'b', 'o', 'x', and 'X'");
1070+
goto done;
1071+
}
1072+
1073+
/* finally check the precision length is sane */
1074+
if (format->precision > INT_MAX) {
1075+
PyErr_SetString(PyExc_ValueError, "precision too big");
1076+
goto done;
1077+
}
1078+
}
10401079

10411080
/* Compute the base and how many characters will be added by
10421081
PyNumber_ToBase */
10431082
switch (format->type) {
10441083
case 'b':
10451084
base = 2;
1085+
log2_base = 1;
10461086
leading_chars_to_skip = 2; /* 0b */
10471087
break;
10481088
case 'o':
10491089
base = 8;
1090+
log2_base = 3;
10501091
leading_chars_to_skip = 2; /* 0o */
10511092
break;
10521093
case 'x':
10531094
case 'X':
10541095
base = 16;
1096+
log2_base = 4;
10551097
leading_chars_to_skip = 2; /* 0x */
10561098
break;
10571099
default: /* shouldn't be needed, but stops a compiler warning */
10581100
case 'd':
10591101
case 'n':
10601102
base = 10;
1103+
log2_base = -1; /* unused */
1104+
leading_chars_to_skip = 0;
10611105
break;
10621106
}
10631107

10641108
if (format->sign != '+' && format->sign != ' '
10651109
&& format->width == -1
1110+
&& format->precision == -1
10661111
&& format->type != 'X' && format->type != 'n'
10671112
&& !format->thousands_separators
10681113
&& PyLong_CheckExact(value))
@@ -1076,8 +1121,40 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
10761121
if (format->alternate)
10771122
n_prefix = leading_chars_to_skip;
10781123

1079-
/* Do the hard part, converting to a string in a given base */
1080-
tmp = _PyLong_Format(value, base);
1124+
if (format->no_neg_0) {
1125+
/* perform the reduction of value modulo (base ** precision) */
1126+
int64_t shift_by;
1127+
PyObject *one;
1128+
PyObject *modulus;
1129+
PyObject *reduced_value;
1130+
1131+
shift_by = log2_base * format->precision;
1132+
1133+
one = PyLong_FromLong(1);
1134+
if (one == NULL) {
1135+
goto done;
1136+
}
1137+
1138+
modulus = _PyLong_Lshift(one, shift_by);
1139+
Py_DECREF(one);
1140+
if (modulus == NULL) {
1141+
goto done;
1142+
}
1143+
1144+
reduced_value = PyNumber_Remainder(value, modulus);
1145+
Py_DECREF(modulus);
1146+
if (reduced_value == NULL) {
1147+
goto done;
1148+
}
1149+
1150+
/* Do the hard part, converting to a string in a given base */
1151+
tmp = _PyLong_Format(reduced_value, base);
1152+
Py_DECREF(reduced_value);
1153+
} else {
1154+
/* Do the hard part, converting to a string in a given base */
1155+
tmp = _PyLong_Format(value, base);
1156+
}
1157+
10811158
if (tmp == NULL)
10821159
goto done;
10831160

@@ -1097,6 +1174,41 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
10971174
/* Skip over the leading chars (0x, 0b, etc.) */
10981175
n_digits -= leading_chars_to_skip;
10991176
inumeric_chars += leading_chars_to_skip;
1177+
1178+
if (format->precision != -1 && n_digits < format->precision) {
1179+
/* prepend leading zeros (after sign and prefix if they exist) */
1180+
PyObject *tmp2;
1181+
1182+
Py_ssize_t zeros_needed = format->precision - n_digits;
1183+
Py_ssize_t tmp2_len = leading_chars_to_skip + format->precision;
1184+
1185+
tmp2 = PyUnicode_New(tmp2_len, 127);
1186+
if (tmp2 == NULL)
1187+
goto done;
1188+
1189+
if (PyUnicode_CopyCharacters(tmp2, 0, tmp, 0,
1190+
leading_chars_to_skip) < 0)
1191+
{
1192+
Py_DECREF(tmp2);
1193+
goto done;
1194+
}
1195+
if (PyUnicode_Fill(tmp2, leading_chars_to_skip,
1196+
zeros_needed, '0') < 0)
1197+
{
1198+
Py_DECREF(tmp2);
1199+
goto done;
1200+
}
1201+
if (PyUnicode_CopyCharacters(tmp2,
1202+
leading_chars_to_skip + zeros_needed,
1203+
tmp, leading_chars_to_skip, n_digits
1204+
) < 0)
1205+
{
1206+
Py_DECREF(tmp2);
1207+
goto done;
1208+
}
1209+
Py_SETREF(tmp, tmp2);
1210+
n_digits = format->precision;
1211+
}
11001212
}
11011213

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

0 commit comments

Comments
 (0)