Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,10 @@ are always available. They are listed here in alphabetical order.
parts (the sign of the imaginary part is mandatory in this case).
The string can optionally be surrounded by whitespaces and the round
parentheses ``'('`` and ``')'``, which are ignored.
The string must not contain whitespace between ``'+'``, ``'-'``, the
``'j'`` or ``'J'`` suffix, and the decimal number.
For example, ``complex('1+2j')`` is fine, but ``complex('1 + 2j')`` raises
:exc:`ValueError`.
The string must not contain whitespace between ``'+'`` (ASCII plus sign),
``'-'`` (ASCII hyphen minus), ``''`` (Unicode minus sign, U+2212), the
``'j'`` or ``'J'`` suffix, and the decimal number. For example,
``complex('1+2j')`` is fine, but ``complex('1 + 2j')`` raises :exc:`ValueError`.
More precisely, the input must conform to the :token:`~float:complexvalue`
production rule in the following grammar, after parentheses and leading and
trailing whitespace characters are removed:
Expand Down Expand Up @@ -466,6 +466,11 @@ are always available. They are listed here in alphabetical order.
Passing a complex number as the *real* or *imag* argument is now
deprecated; it should only be passed as a single positional argument.

.. versionchanged:: next
``'−'`` (Unicode minus sign, U+2212) can be now used as an
alternative to ``'-'`` (ASCII hyphen minus) for denoting
negative sign.


.. function:: delattr(object, name, /)

Expand Down Expand Up @@ -781,15 +786,16 @@ are always available. They are listed here in alphabetical order.

If the argument is a string, it should contain a decimal number, optionally
preceded by a sign, and optionally embedded in whitespace. The optional
sign may be ``'+'`` or ``'-'``; a ``'+'`` sign has no effect on the value
sign may be ``'+'`` (ASCII plus sign), ``'-'`` (ASCII hyphen minus) or ``−``
(Unicode minus sign, U+2212); a ``'+'`` sign has no effect on the value
produced. The argument may also be a string representing a NaN
(not-a-number), or positive or negative infinity.
More precisely, the input must conform to the :token:`~float:floatvalue`
production rule in the following grammar, after leading and trailing
whitespace characters are removed:

.. productionlist:: float
sign: "+" | "-"
sign: "+" | "-" | "−" <Unicode minus sign (U+2212)>
infinity: "Infinity" | "inf"
nan: "nan"
digit: <a Unicode decimal digit, i.e. characters in Unicode general category Nd>
Expand Down Expand Up @@ -827,6 +833,11 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.8
Falls back to :meth:`~object.__index__` if :meth:`~object.__float__` is not defined.

.. versionchanged:: next
Added support for ``−`` (Unicode minus sign, U+2212) as an
alternative to ``-`` (ASCII hyphen minus) for denoting negative
floats.


.. index::
single: __format__
Expand Down Expand Up @@ -1036,8 +1047,9 @@ are always available. They are listed here in alphabetical order.

If the argument is not a number or if *base* is given, then it must be a string,
:class:`bytes`, or :class:`bytearray` instance representing an integer
in radix *base*. Optionally, the string can be preceded by ``+`` or ``-``
(with no space in between), have leading zeros, be surrounded by whitespace,
in radix *base*. Optionally, the string can be directly preceded (with no whitespaces
in between) by ``+`` (ASCII plus sign), ``-`` (ASCII hyphen minus) or ``−`` (Unicode
minus sign, U+2212), have leading zeros, be surrounded by whitespace,
and have single underscores interspersed between digits.

A base-n integer string contains digits, each representing a value from 0 to
Expand Down Expand Up @@ -1080,6 +1092,11 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.14
:func:`int` no longer delegates to the :meth:`~object.__trunc__` method.

.. versionchanged:: next
:func:`int` now supports ``−`` (Unicode minus sign, U+2212)
as an alternative to ``-`` (ASCII hyphen minus) for denoting
negative integers.

.. function:: isinstance(object, classinfo, /)

Return ``True`` if the *object* argument is an instance of the *classinfo*
Expand Down
5 changes: 3 additions & 2 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,9 @@ Notes:
alternative conversions.

(4)
float also accepts the strings "nan" and "inf" with an optional prefix "+"
or "-" for Not a Number (NaN) and positive or negative infinity.
float also accepts the strings "nan" and "inf" with an optional prefix ASCII plus
sign "+" or ASCII hyphen minus "-" or Unicode minus sign "−" (U+2212) for Not a Number
(NaN) and positive or negative infinity.

(5)
Python defines ``pow(0, 0)`` and ``0 ** 0`` to be ``1``, as is common for
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ def test_long_fromunicodeobject(self):
self.assertEqual(fromunicodeobject('0xcafe', 0), 0xcafe)
self.assertRaises(ValueError, fromunicodeobject, 'cafe', 0)
self.assertEqual(fromunicodeobject('-123', 10), -123)
self.assertEqual(fromunicodeobject('\N{MINUS SIGN}123', 10), -123)
self.assertEqual(fromunicodeobject(' -123 ', 10), -123)
self.assertEqual(fromunicodeobject(' \N{MINUS SIGN}123 ', 10), -123)
self.assertEqual(fromunicodeobject('1_23', 10), 123)
self.assertRaises(ValueError, fromunicodeobject, '- 123', 10)
self.assertRaises(ValueError, fromunicodeobject, '', 10)
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,18 +669,22 @@ def check(z, x, y):
check(complex("1"), 1.0, 0.0)
check(complex("1j"), 0.0, 1.0)
check(complex("-1"), -1.0, 0.0)
check(complex("\N{MINUS SIGN}1"), -1.0, 0.0)
check(complex("+1"), 1.0, 0.0)
check(complex("1+2j"), 1.0, 2.0)
check(complex("(1+2j)"), 1.0, 2.0)
check(complex("(1.5+4.25j)"), 1.5, 4.25)
check(complex("4.25+1J"), 4.25, 1.0)
check(complex(" ( +4.25-6J )"), 4.25, -6.0)
check(complex(" ( +4.25\N{MINUS SIGN}6J )"), 4.25, -6.0)
check(complex(" ( +4.25-J )"), 4.25, -1.0)
check(complex(" ( +4.25\N{MINUS SIGN}J )"), 4.25, -1.0)
check(complex(" ( +4.25+j )"), 4.25, 1.0)
check(complex("J"), 0.0, 1.0)
check(complex("( j )"), 0.0, 1.0)
check(complex("+J"), 0.0, 1.0)
check(complex("( -j)"), 0.0, -1.0)
check(complex("( \N{MINUS SIGN}j)"), 0.0, -1.0)
check(complex('1-1j'), 1.0, -1.0)
check(complex('1J'), 0.0, 1.0)

Expand All @@ -690,6 +694,7 @@ def check(z, x, y):
check(complex('-1e-500+1e-500j'), -0.0, 0.0)
check(complex('1e-500-1e-500j'), 0.0, -0.0)
check(complex('-1e-500-1e-500j'), -0.0, -0.0)
check(complex('\N{MINUS SIGN}1e\N{MINUS SIGN}500\N{MINUS SIGN}1e\N{MINUS SIGN}500j'), -0.0, -0.0)

# SF bug 543840: complex(string) accepts strings with \0
# Fixed in 2.3.
Expand All @@ -700,6 +705,8 @@ def check(z, x, y):
self.assertRaises(ValueError, complex, "1+")
self.assertRaises(ValueError, complex, "1+1j+1j")
self.assertRaises(ValueError, complex, "--")
self.assertRaises(ValueError, complex, "-\N{MINUS SIGN}")
self.assertRaises(ValueError, complex, "\N{MINUS SIGN}\N{MINUS SIGN}")
self.assertRaises(ValueError, complex, "(1+2j")
self.assertRaises(ValueError, complex, "1+2j)")
self.assertRaises(ValueError, complex, "1+(2j)")
Expand All @@ -726,7 +733,9 @@ def test_constructor_negative_nans_from_string(self):
self.assertEqual(copysign(1., complex("-nan").real), -1.)
self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.)
self.assertEqual(copysign(1., complex("-nan\N{MINUS SIGN}nanj").real), -1.)
self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.)
self.assertEqual(copysign(1., complex("\N{MINUS SIGN}nan\N{MINUS SIGN}nanj").imag), -1.)

def test_underscores(self):
# check underscores
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ def test_float(self):
self.assertRaises(ValueError, float, " +0x3.p-1 ")
self.assertRaises(ValueError, float, "++3.14")
self.assertRaises(ValueError, float, "+-3.14")
self.assertRaises(ValueError, float, "+\N{MINUS SIGN}3.14")
self.assertRaises(ValueError, float, "-+3.14")
self.assertRaises(ValueError, float, "--3.14")
self.assertRaises(ValueError, float, "-\N{MINUS SIGN}3.14")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}\N{MINUS SIGN}3.14")
self.assertRaises(ValueError, float, ".nan")
self.assertRaises(ValueError, float, "+.inf")
self.assertRaises(ValueError, float, ".")
self.assertRaises(ValueError, float, "-.")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}.")
self.assertRaises(TypeError, float, {})
self.assertRaisesRegex(TypeError, "not 'dict'", float, {})
# Lone surrogate
Expand Down Expand Up @@ -1110,30 +1114,40 @@ def test_nan_from_str(self):
self.assertTrue(isnan(float("nan")))
self.assertTrue(isnan(float("+nan")))
self.assertTrue(isnan(float("-nan")))
self.assertTrue(isnan(float("\N{MINUS SIGN}nan")))

self.assertEqual(repr(float("nan")), "nan")
self.assertEqual(repr(float("+nan")), "nan")
self.assertEqual(repr(float("-nan")), "nan")
self.assertEqual(repr(float("\N{MINUS SIGN}nan")), "nan")

self.assertEqual(repr(float("NAN")), "nan")
self.assertEqual(repr(float("+NAn")), "nan")
self.assertEqual(repr(float("-NaN")), "nan")
self.assertEqual(repr(float("\N{MINUS SIGN}NaN")), "nan")

self.assertEqual(str(float("nan")), "nan")
self.assertEqual(str(float("+nan")), "nan")
self.assertEqual(str(float("-nan")), "nan")
self.assertEqual(str(float("\N{MINUS SIGN}nan")), "nan")

self.assertRaises(ValueError, float, "nana")
self.assertRaises(ValueError, float, "+nana")
self.assertRaises(ValueError, float, "-nana")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}nana")
self.assertRaises(ValueError, float, "na")
self.assertRaises(ValueError, float, "+na")
self.assertRaises(ValueError, float, "-na")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}na")

self.assertRaises(ValueError, float, "++nan")
self.assertRaises(ValueError, float, "-+NAN")
self.assertRaises(ValueError, float, "+-NaN")
self.assertRaises(ValueError, float, "--nAn")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}+NAN")
self.assertRaises(ValueError, float, "-\N{MINUS SIGN}nAn")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}-nAn")
self.assertRaises(ValueError, float, "\N{MINUS SIGN}\N{MINUS SIGN}nAn")

def test_nan_as_str(self):
self.assertEqual(repr(1e300 * 1e300 * 0), "nan")
Expand All @@ -1145,11 +1159,13 @@ def test_nan_as_str(self):
def test_inf_signs(self):
self.assertEqual(copysign(1.0, float('inf')), 1.0)
self.assertEqual(copysign(1.0, float('-inf')), -1.0)
self.assertEqual(copysign(1.0, float('\N{MINUS SIGN}inf')), -1.0)

def test_nan_signs(self):
# The sign of float('nan') should be predictable.
self.assertEqual(copysign(1.0, float('nan')), 1.0)
self.assertEqual(copysign(1.0, float('-nan')), -1.0)
self.assertEqual(copysign(1.0, float('\N{MINUS SIGN}nan')), -1.0)


fromHex = float.fromhex
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ def test_long(self):
('1' + '0'*100, 10**100)
]
for s, v in LL:
for sign in "", "+", "-":
for sign in "", "+", "-", "\N{MINUS SIGN}":
for prefix in "", " ", "\t", " \t\t ":
ss = prefix + sign + s
vv = v
if sign == "-" and v is not ValueError:
if sign in ("-", "\N{MINUS SIGN}") and v is not ValueError:
vv = -v
try:
self.assertEqual(int(ss), vv)
Expand Down Expand Up @@ -360,9 +360,11 @@ def test_long(self):
self.assertEqual(int('0', 0), 0)
self.assertEqual(int('+0', 0), 0)
self.assertEqual(int('-0', 0), 0)
self.assertEqual(int('\N{MINUS SIGN}0', 0), 0)
self.assertEqual(int('00', 0), 0)
self.assertRaises(ValueError, int, '08', 0)
self.assertRaises(ValueError, int, '-012395', 0)
self.assertRaises(ValueError, int, '\N{MINUS SIGN}012395', 0)

# invalid bases
invalid_bases = [-909,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`int`, :func:`float` and :func:`complex` now support strings
with ``−`` (Unicode minus sign, U+2212) as an alternative to ``-``
(ASCII hyphen minus, U+002D). Contributed by Bartosz Sławecki.
3 changes: 3 additions & 0 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -9475,6 +9475,9 @@ _PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode)
else if (Py_UNICODE_ISSPACE(ch)) {
out[i] = ' ';
}
else if (ch == 0x2212) { /* MINUS SIGN */
out[i] = '-';
}
else {
int decimal = Py_UNICODE_TODECIMAL(ch);
if (decimal < 0) {
Expand Down
Loading