diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 176e8f3f9f601c..a6425666231d4a 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -38,3 +38,9 @@ Pending removal in Python 3.20 - :mod:`zlib` (Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.) + +* The ``__getformat__()`` class method of the :class:`float` is deprecated and + will be removed in Python 3.20. On CPython, ``float.__getformat__()`` always + return a string, prefixed with ``"IEEE"``: to build CPython, you need support + for IEEE 754 floating-point numbers since Python 3.11. + (Contributed by Sergey B Kirpichev in :gh:`145633`.) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 6f5d84a3b8ca80..d1a71f89311cf7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1644,6 +1644,12 @@ New deprecations * Deprecated :func:`!typing.no_type_check_decorator` has been removed. (Contributed by Nikita Sobolev in :gh:`133601`.) +* The ``__getformat__()`` class method of the :class:`float` is deprecated and + will be removed in Python 3.20. On CPython, ``float.__getformat__()`` always + return a string, prefixed with ``"IEEE"``: to build CPython, you need support + for IEEE 754 floating-point numbers since Python 3.11. + (Contributed by Sergey B Kirpichev in :gh:`145633`.) + * ``__version__`` * The ``__version__``, ``version`` and ``VERSION`` attributes have been diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 7f735d75b318e7..15e61f9e281448 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -195,11 +195,6 @@ def collect_locale(info_add): info_add('locale.getencoding', locale.getencoding()) -def collect_builtins(info_add): - info_add('builtins.float.float_format', float.__getformat__("float")) - info_add('builtins.float.double_format', float.__getformat__("double")) - - def collect_urandom(info_add): import os @@ -1050,7 +1045,6 @@ def collect_info(info): # its state. collect_urandom, - collect_builtins, collect_cc, collect_curses, collect_datetime, diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 3da662b0c4d50a..975efdf23256f3 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -518,9 +518,45 @@ def dec(*args, **kwargs): # for a discussion of this number. SOCK_MAX_SIZE = 16 * 1024 * 1024 + 1 +# This helper exists for alternative Python implementations, that use +# the CPython test suite. +def _have_ieee_doubles(): + if sys.implementation.name == 'cpython': + return True + import math + import struct + # Check parameters for encoding of floats; a quick exit + # if they aren't same as for IEC 60559 doubles. Check + # also that subnormals are present. + if (struct.calcsize('d') != 8 + or sys.float_info.radix != 2 + or sys.float_info.mant_dig != 53 + or sys.float_info.dig != 15 + or sys.float_info.min_exp != -1021 + or sys.float_info.min_10_exp != -307 + or sys.float_info.max_exp != 1024 + or sys.float_info.max_10_exp != 308 + or not math.issubnormal(math.nextafter(0, 1))): + return False + try: + import ctypes + except ImportError: + return True + # We attempt to determine if this machine is using IEC + # floating-point formats by peering at the bits of some + # carefully chosen value. Assume that integer and + # floating-point types have same endianness. + d = 9006104071832581.0 + be_d = int.from_bytes(b"\x43\x3f\xff\x01\x02\x03\x04\x05") + dp = ctypes.pointer(ctypes.c_double(d)) + lp = ctypes.cast(dp, ctypes.POINTER(ctypes.c_uint64)) + return lp[0] == be_d + +HAVE_IEEE_754 = _have_ieee_doubles() + # decorator for skipping tests on non-IEEE 754 platforms requires_IEEE_754 = unittest.skipUnless( - float.__getformat__("double").startswith("IEEE"), + HAVE_IEEE_754, "test requires IEEE 754 doubles") def requires_zlib(reason='requires zlib'): diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 8b25607b6d504f..0b469eb59a1dc4 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -7,6 +7,7 @@ from test.test_capi.test_getargs import (Float, FloatSubclass, FloatSubclass2, BadIndex2, BadFloat2, Index, BadIndex, BadFloat) +from test import support from test.support import import_helper _testcapi = import_helper.import_module('_testcapi') @@ -23,7 +24,6 @@ 8: 2.0 ** -53, # binary64 } -HAVE_IEEE_754 = float.__getformat__("double").startswith("IEEE") INF = float("inf") NAN = float("nan") @@ -170,14 +170,13 @@ def test_unpack(self): self.assertEqual(unpack(b'\x00\x00\x00\x00\x00\x00\xf8?', LITTLE_ENDIAN), 1.5) + @support.requires_IEEE_754 def test_pack_unpack_roundtrip(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack large = 2.0 ** 100 - values = [1.0, 1.5, large, 1.0/7, math.pi] - if HAVE_IEEE_754: - values.extend((INF, NAN)) + values = [1.0, 1.5, large, 1.0/7, math.pi, INF, NAN] for value in values: for size in (2, 4, 8,): if size == 2 and value == large: @@ -196,7 +195,7 @@ def test_pack_unpack_roundtrip(self): else: self.assertEqual(value2, value) - @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + @support.requires_IEEE_754 def test_pack_unpack_roundtrip_for_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack @@ -228,7 +227,7 @@ def test_pack_unpack_roundtrip_for_nans(self): self.assertTrue(math.isnan(value)) self.assertEqual(data1, data2) - @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + @support.requires_IEEE_754 @unittest.skipUnless(sys.maxsize != 2147483647, "requires 64-bit mode") def test_pack_unpack_nans_for_different_formats(self): pack = _testcapi.float_pack diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index c03b0a09f71889..784e10ea8450a2 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -8,6 +8,7 @@ import unittest from test import support +from test.support import warnings_helper from test.support.testcase import FloatsAreIdenticalMixin from test.support.numbers import ( VALID_UNDERSCORE_LITERALS, @@ -672,6 +673,7 @@ def __neg__(self): @unittest.skipUnless(hasattr(float, "__getformat__"), "requires __getformat__") class FormatFunctionsTestCase(unittest.TestCase): + @warnings_helper.ignore_warnings(category=DeprecationWarning) def test_getformat(self): self.assertIn(float.__getformat__('double'), ['unknown', 'IEEE, big-endian', 'IEEE, little-endian']) diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index fe14e7cb342c9e..a057568dabca1d 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -486,8 +486,7 @@ def test_builtin__qualname__(self): # builtin classmethod: self.assertEqual(dict.fromkeys.__qualname__, 'dict.fromkeys') - self.assertEqual(float.__getformat__.__qualname__, - 'float.__getformat__') + self.assertEqual(int.from_bytes.__qualname__, 'int.from_bytes') # builtin staticmethod: self.assertEqual(str.maketrans.__qualname__, 'str.maketrans') @@ -509,7 +508,7 @@ def test_builtin__self__(self): # builtin classmethod: self.assertIs(dict.fromkeys.__self__, dict) - self.assertIs(float.__getformat__.__self__, float) + self.assertIs(int.from_bytes.__self__, int) # builtin staticmethod: self.assertIsNone(str.maketrans.__self__) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst new file mode 100644 index 00000000000000..00109780ae21e1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-24-17-29-19.gh-issue-145633.RtktKR.rst @@ -0,0 +1,4 @@ +The ``__getformat__()`` class method of the :class:`float` is deprecated. On +CPython, ``float.__getformat__()`` always return a string, prefixed with +``"IEEE"``: to build CPython, you need support for IEEE 754 floating-point +numbers since Python 3.11. Patch by Sergey B Kirpichev. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index d91468dddded9b..5175fe4acc82fd 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1691,6 +1691,11 @@ static PyObject * float___getformat___impl(PyTypeObject *type, const char *typestr) /*[clinic end generated code: output=2bfb987228cc9628 input=0ae1ba35d192f704]*/ { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "float.__getformat__() is deprecated")) + { + return NULL; + } if (strcmp(typestr, "double") != 0 && strcmp(typestr, "float") != 0) { PyErr_SetString(PyExc_ValueError, "__getformat__() argument 1 must be "