Skip to content

Commit

Permalink
pythongh-113804: Add 'x' format to float.format()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jun 2, 2024
1 parent 53b1981 commit 9f63b2a
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 3 deletions.
5 changes: 5 additions & 0 deletions Doc/library/string.rst
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,11 @@ The available presentation types for :class:`float` and
| | the current locale setting to insert the appropriate |
| | number separator characters. |
+---------+----------------------------------------------------------+
| ``'x'`` | For :class:`float`, hex format. Outputs the number in |
| | base 16, using lower-case letters for the digits above 9.|
| | The alternate form adds ``'0x'`` prefix. .|
| | Precision and width are ignored. |
+---------+----------------------------------------------------------+
| ``'%'`` | Percentage. Multiplies the number by 100 and displays |
| | in fixed (``'f'``) format, followed by a percent sign. |
+---------+----------------------------------------------------------+
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ New Features
Other Language Changes
======================

* Add ``x`` format to :meth:`float.format`: similar formatting than
:meth:`float.hex` without the ``'0x'`` prefix. The alternate format ``#x``
uses the ``'0x'`` prefix. Precision and width are ignored.
(Contributed by Victor Stinner in :gh:`113804`.)


New Modules
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct _Py_global_strings {
STRUCT_FOR_STR(dot_locals, ".<locals>")
STRUCT_FOR_STR(empty, "")
STRUCT_FOR_STR(generic_base, ".generic_base")
STRUCT_FOR_STR(hex, "hex")
STRUCT_FOR_STR(json_decoder, "json.decoder")
STRUCT_FOR_STR(kwdefaults, ".kwdefaults")
STRUCT_FOR_STR(list_err, "list index out of range")
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,12 +700,24 @@ def test_format(self):
# % formatting
self.assertEqual(format(-1.0, '%'), '-100.000000%')

# x formatting
for value in (-1.5, -0.0, 0.0, 1.0, 3.14):
with self.subTest(value=value):
self.assertEqual(format(value, 'x'), value.hex()[2:])
self.assertEqual(format(value, '#x'), value.hex())

class MyFloat(float):
def hex(self):
return 123

self.assertEqual(format(MyFloat(1.0), 'x'), (1.0).hex()[2:])

# conversion to string should fail
self.assertRaises(ValueError, format, 3.0, "s")

# confirm format options expected to fail on floats, such as integer
# presentation types
for format_spec in 'sbcdoxX':
for format_spec in 'sbcdoX':
self.assertRaises(ValueError, format, 0.0, format_spec)
self.assertRaises(ValueError, format, 1.0, format_spec)
self.assertRaises(ValueError, format, -1.0, format_spec)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add ``x`` format to :meth:`float.format`: similar formatting than
:meth:`float.hex` without the ``'0x'`` prefix. The alternate format ``#x``
uses the ``'0x'`` prefix. Precision and width are ignored. Patch by Victor
Stinner.
29 changes: 27 additions & 2 deletions Python/formatter_unicode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1085,14 +1085,38 @@ format_float_internal(PyObject *value,
default_precision = 0;
}

if (type == 'n')
if (type == 'n') {
/* 'n' is the same as 'g', except for the locale used to
format the result. We take care of that later. */
type = 'g';
}

if (type == 'x') {
_Py_DECLARE_STR(hex, "hex");
PyObject *hex_str = PyObject_CallMethodObjArgs(
(PyObject*)&PyFloat_Type,
&_Py_STR(hex), value, NULL);
if (hex_str == NULL) {
return -1;
}
assert(PyUnicode_Check(hex_str));

if (format->alternate) {
result = _PyUnicodeWriter_WriteStr(writer, hex_str);
}
else {
result = _PyUnicodeWriter_WriteSubstring(
writer, hex_str,
2, PyUnicode_GET_LENGTH(hex_str));
}
Py_DECREF(hex_str);
return result;
}

val = PyFloat_AsDouble(value);
if (val == -1.0 && PyErr_Occurred())
if (val == -1.0 && PyErr_Occurred()) {
goto done;
}

if (type == '%') {
type = 'f';
Expand Down Expand Up @@ -1573,6 +1597,7 @@ _PyFloat_FormatAdvancedWriter(_PyUnicodeWriter *writer,
case 'g':
case 'G':
case 'n':
case 'x':
case '%':
/* no conversion, already a float. do the formatting */
return format_float_internal(obj, &format, writer);
Expand Down

0 comments on commit 9f63b2a

Please sign in to comment.