Skip to content

Commit 3561899

Browse files
committed
+1
1 parent ae9e54c commit 3561899

File tree

8 files changed

+79
-25
lines changed

8 files changed

+79
-25
lines changed

Doc/c-api/conversion.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ The following functions provide locale-independent string to number conversions.
8888
*format_code*, *precision*, and *flags*.
8989
9090
*format_code* must be one of ``'e'``, ``'E'``, ``'f'``, ``'F'``,
91-
``'g'``, ``'G'`` or ``'r'``. For ``'r'``, the supplied *precision*
91+
``'g'``, ``'G'``, ``'x'``, ``'X'`` or ``'r'``. For ``'r'``, the supplied *precision*
9292
must be 0 and is ignored. The ``'r'`` format code specifies the
9393
standard :func:`repr` format.
9494
@@ -115,6 +115,9 @@ The following functions provide locale-independent string to number conversions.
115115
116116
.. versionadded:: 3.1
117117
118+
.. versionchanged:: 3.13
119+
Support ``'x'`` and ``'X'`` format types for :class:`float`.
120+
118121
119122
.. c:function:: int PyOS_stricmp(const char *s1, const char *s2)
120123

Doc/library/stdtypes.rst

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,9 +2430,9 @@ The conversion types are:
24302430
+------------+-----------------------------------------------------+-------+
24312431
| ``'u'`` | Obsolete type -- it is identical to ``'d'``. | \(6) |
24322432
+------------+-----------------------------------------------------+-------+
2433-
| ``'x'`` | Signed hexadecimal (lowercase). | \(2) |
2433+
| ``'x'`` | Signed hexadecimal integer or float (lowercase). | \(2) |
24342434
+------------+-----------------------------------------------------+-------+
2435-
| ``'X'`` | Signed hexadecimal (uppercase). | \(2) |
2435+
| ``'X'`` | Signed hexadecimal integer or float (uppercase). | \(2) |
24362436
+------------+-----------------------------------------------------+-------+
24372437
| ``'e'`` | Floating point exponential format (lowercase). | \(3) |
24382438
+------------+-----------------------------------------------------+-------+
@@ -2473,8 +2473,19 @@ Notes:
24732473
inserted before the first digit.
24742474

24752475
(2)
2476-
The alternate form causes a leading ``'0x'`` or ``'0X'`` (depending on whether
2477-
the ``'x'`` or ``'X'`` format was used) to be inserted before the first digit.
2476+
The alternate form for an integer causes a leading ``'0x'`` or ``'0X'``
2477+
(depending on whether the ``'x'`` or ``'X'`` format was used) to be
2478+
inserted before the first digit.
2479+
2480+
For floats, represent the number by a hexadecimal string in the style
2481+
``[±]0xh.[hhhp±d``, where there is one hexadecimal digit before the
2482+
decimal-point character and the number of hexadecimal digits after it is
2483+
equal to the precision; if the precision is missing, then the precision is
2484+
sufficient for an exact representation of the value. If the precision is
2485+
zero and the alternate form is not specified, no decimal-point character
2486+
appears. The exponent ``d`` is written in decimal, it always contains at
2487+
least one digit, and it gives the power of 2 by which to multiply the
2488+
coefficient.
24782489

24792490
(3)
24802491
The alternate form causes the result to always contain a decimal point, even if
@@ -2505,6 +2516,9 @@ that ``'\0'`` is the end of the string.
25052516
``%f`` conversions for numbers whose absolute value is over 1e50 are no
25062517
longer replaced by ``%g`` conversions.
25072518

2519+
.. versionchanged:: 3.13
2520+
Support ``'x'`` and ``'X'`` format types for :class:`float`.
2521+
25082522

25092523
.. index::
25102524
single: buffer protocol; binary sequence types

Lib/test/test_format.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,7 @@ def test_common_format(self):
279279
test_exc_common('%d', b'1', TypeError,
280280
"%d format: a real number is required, not bytes")
281281
test_exc_common('%x', '1', TypeError,
282-
"%x format: an integer is required, not str")
283-
test_exc_common('%x', 3.14, TypeError,
284-
"%x format: an integer is required, not float")
282+
"%x format: an integer or float is required, not str")
285283

286284
def test_str_format(self):
287285
testformat("%r", "\u0378", "'\\u0378'") # non printable

Lib/test/test_peepholer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,9 +586,7 @@ def test_format_errors(self):
586586
eval("'%s%z' % (x, 5)", {'x': 1234})
587587
with self.assertRaisesRegex(TypeError, 'a real number is required, not str'):
588588
eval("'%d' % (x,)", {'x': '1234'})
589-
with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
590-
eval("'%x' % (x,)", {'x': 1234.56})
591-
with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
589+
with self.assertRaisesRegex(TypeError, 'an integer or float is required, not str'):
592590
eval("'%x' % (x,)", {'x': '1234'})
593591
with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
594592
eval("'%f' % (x,)", {'x': '1234'})

Lib/test/test_str.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,12 +1564,10 @@ def __int__(self):
15641564
self.assertEqual('%X' % letter_m, '6D')
15651565
self.assertEqual('%o' % letter_m, '155')
15661566
self.assertEqual('%c' % letter_m, 'm')
1567-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not float', operator.mod, '%x', 3.14)
1568-
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not float', operator.mod, '%X', 2.11)
15691567
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not float', operator.mod, '%o', 1.79)
1570-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not PseudoFloat', operator.mod, '%x', pi)
1571-
self.assertRaisesRegex(TypeError, '%x format: an integer is required, not complex', operator.mod, '%x', 3j)
1572-
self.assertRaisesRegex(TypeError, '%X format: an integer is required, not complex', operator.mod, '%X', 2j)
1568+
self.assertRaisesRegex(TypeError, '%x format: an integer or float is required, not PseudoFloat', operator.mod, '%x', pi)
1569+
self.assertRaisesRegex(TypeError, '%x format: an integer or float is required, not complex', operator.mod, '%x', 3j)
1570+
self.assertRaisesRegex(TypeError, '%X format: an integer or float is required, not complex', operator.mod, '%X', 2j)
15731571
self.assertRaisesRegex(TypeError, '%o format: an integer is required, not complex', operator.mod, '%o', 1j)
15741572
self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j)
15751573
self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j)

Objects/bytesobject.c

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,13 +463,36 @@ formatlong(PyObject *v, int flags, int prec, int type)
463463
Py_DECREF(iobj);
464464
return result;
465465
}
466-
if (!PyErr_ExceptionMatches(PyExc_TypeError))
466+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
467+
if (type == 'x' || type == 'X') {
468+
PyErr_Clear();
469+
470+
PyObject *fobj;
471+
if (!PyFloat_Check(v)) {
472+
fobj = PyNumber_Float(v);
473+
if (!fobj) {
474+
goto wrongtype;
475+
}
476+
}
477+
else {
478+
fobj = Py_NewRef(v);
479+
}
480+
formatfloat(fobj, flags, prec, type, &result, NULL, NULL);
481+
if (!result) {
482+
return NULL;
483+
}
484+
return PyUnicode_FromEncodedObject(result, NULL, NULL);
485+
}
486+
}
487+
else {
467488
return NULL;
489+
}
468490
}
491+
wrongtype:
469492
PyErr_Format(PyExc_TypeError,
470493
"%%%c format: %s is required, not %.200s", type,
471-
(type == 'o' || type == 'x' || type == 'X') ? "an integer"
472-
: "a real number",
494+
(type == 'x' || type == 'X') ?
495+
"an integer or float" : type == 'o' ? "an integer": "a real number",
473496
Py_TYPE(v)->tp_name);
474497
return NULL;
475498
}

Objects/floatobject.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1225,13 +1225,21 @@ static PyObject *
12251225
float_hex_impl(PyObject *self)
12261226
/*[clinic end generated code: output=0ebc9836e4d302d4 input=bec1271a33d47e67]*/
12271227
{
1228+
PyObject *result = NULL;
12281229
double x;
12291230

12301231
CONVERT_TO_DOUBLE(self, x);
12311232

12321233
char *buf = PyOS_double_to_string(x, 'x', -1, 0, NULL);
1234+
if (buf) {
1235+
result = PyUnicode_FromString(buf);
1236+
PyMem_Free(buf);
1237+
}
1238+
else {
1239+
PyErr_NoMemory();
1240+
}
12331241

1234-
return PyUnicode_FromString(buf);
1242+
return result;
12351243
}
12361244

12371245
/* Convert a hexadecimal string to a float. */

Objects/unicodeobject.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13902,11 +13902,18 @@ mainformatlong(PyObject *v,
1390213902
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
1390313903
if (type == 'x' || type == 'X') {
1390413904
PyErr_Clear();
13905-
if (PyFloat_Check(v)) {
13906-
if (formatfloat(v, arg, NULL, writer) == -1)
13907-
return -1;
13908-
return 1;
13905+
13906+
PyObject *fobj;
13907+
if (!PyFloat_Check(v)) {
13908+
fobj = PyNumber_Float(v);
13909+
if (!fobj) {
13910+
goto wrongtype;
13911+
}
1390913912
}
13913+
else {
13914+
fobj = Py_NewRef(v);
13915+
}
13916+
return formatfloat(fobj, arg, p_output, NULL);
1391013917
}
1391113918
goto wrongtype;
1391213919
}
@@ -13964,10 +13971,15 @@ mainformatlong(PyObject *v,
1396413971
switch(type)
1396513972
{
1396613973
case 'o':
13974+
PyErr_Format(PyExc_TypeError,
13975+
"%%%c format: an integer is required, "
13976+
"not %.200s",
13977+
type, Py_TYPE(v)->tp_name);
13978+
break;
1396713979
case 'x':
1396813980
case 'X':
1396913981
PyErr_Format(PyExc_TypeError,
13970-
"%%%c format: an integer is required, "
13982+
"%%%c format: an integer or float is required, "
1397113983
"not %.200s",
1397213984
type, Py_TYPE(v)->tp_name);
1397313985
break;

0 commit comments

Comments
 (0)