Merge pull request #6901 from ShaharNaveh/update-decimal

Update `decimal` from 3.14.2
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-01-30 23:41:34 +09:00
commit bb0a201502
3 files changed
+203 -96

No files matched your search

+75 -13
View File
@@ -38,10 +38,10 @@ __all__ = [
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP',
# Functions for manipulating contexts
'setcontext', 'getcontext', 'localcontext',
'setcontext', 'getcontext', 'localcontext', 'IEEEContext',
# Limits for the C version for compatibility
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', 'IEEE_CONTEXT_MAX_BITS',
# C version: compile time choice that enables the thread local context (deprecated, now always true)
'HAVE_THREADS',
@@ -83,10 +83,12 @@ if sys.maxsize == 2**63-1:
MAX_PREC = 999999999999999999
MAX_EMAX = 999999999999999999
MIN_EMIN = -999999999999999999
IEEE_CONTEXT_MAX_BITS = 512
else:
MAX_PREC = 425000000
MAX_EMAX = 425000000
MIN_EMIN = -425000000
IEEE_CONTEXT_MAX_BITS = 256
MIN_ETINY = MIN_EMIN - (MAX_PREC-1)
@@ -417,6 +419,27 @@ def localcontext(ctx=None, **kwargs):
return ctx_manager
def IEEEContext(bits, /):
"""
Return a context object initialized to the proper values for one of the
IEEE interchange formats. The argument must be a multiple of 32 and less
than IEEE_CONTEXT_MAX_BITS.
"""
if bits <= 0 or bits > IEEE_CONTEXT_MAX_BITS or bits % 32:
raise ValueError("argument must be a multiple of 32, "
f"with a maximum of {IEEE_CONTEXT_MAX_BITS}")
ctx = Context()
ctx.prec = 9 * (bits//32) - 2
ctx.Emax = 3 * (1 << (bits//16 + 3))
ctx.Emin = 1 - ctx.Emax
ctx.rounding = ROUND_HALF_EVEN
ctx.clamp = 1
ctx.traps = dict.fromkeys(_signals, False)
return ctx
##### Decimal class #######################################################
# Do not subclass Decimal from numbers.Real and do not register it as such
@@ -582,6 +605,21 @@ class Decimal(object):
raise TypeError("Cannot convert %r to Decimal" % value)
@classmethod
def from_number(cls, number):
"""Converts a real number to a decimal number, exactly.
>>> Decimal.from_number(314) # int
Decimal('314')
>>> Decimal.from_number(0.1) # float
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal.from_number(Decimal('3.14')) # another decimal instance
Decimal('3.14')
"""
if isinstance(number, (int, Decimal, float)):
return cls(number)
raise TypeError("Cannot convert %r to Decimal" % number)
@classmethod
def from_float(cls, f):
"""Converts a float to a decimal number, exactly.
@@ -2425,12 +2463,12 @@ class Decimal(object):
return ans
def __rpow__(self, other, context=None):
def __rpow__(self, other, modulo=None, context=None):
"""Swaps self/other and returns __pow__."""
other = _convert_other(other)
if other is NotImplemented:
return other
return other.__pow__(self, context=context)
return other.__pow__(self, modulo, context=context)
def normalize(self, context=None):
"""Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""
@@ -3302,7 +3340,10 @@ class Decimal(object):
return opa, opb
def logical_and(self, other, context=None):
"""Applies an 'and' operation between self and other's digits."""
"""Applies an 'and' operation between self and other's digits.
Both self and other must be logical numbers.
"""
if context is None:
context = getcontext()
@@ -3319,14 +3360,20 @@ class Decimal(object):
return _dec_from_triple(0, result.lstrip('0') or '0', 0)
def logical_invert(self, context=None):
"""Invert all its digits."""
"""Invert all its digits.
The self must be logical number.
"""
if context is None:
context = getcontext()
return self.logical_xor(_dec_from_triple(0,'1'*context.prec,0),
context)
def logical_or(self, other, context=None):
"""Applies an 'or' operation between self and other's digits."""
"""Applies an 'or' operation between self and other's digits.
Both self and other must be logical numbers.
"""
if context is None:
context = getcontext()
@@ -3343,7 +3390,10 @@ class Decimal(object):
return _dec_from_triple(0, result.lstrip('0') or '0', 0)
def logical_xor(self, other, context=None):
"""Applies an 'xor' operation between self and other's digits."""
"""Applies an 'xor' operation between self and other's digits.
Both self and other must be logical numbers.
"""
if context is None:
context = getcontext()
@@ -6058,7 +6108,7 @@ _parser = re.compile(r""" # A numeric string consists of:
(?P<diag>\d*) # with (possibly empty) diagnostic info.
)
# \s*
\Z
\z
""", re.VERBOSE | re.IGNORECASE).match
_all_zeros = re.compile('0*$').match
@@ -6082,11 +6132,15 @@ _parse_format_specifier_regex = re.compile(r"""\A
(?P<no_neg_0>z)?
(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?
(?P<thousands_sep>,)?
(?:\.(?P<precision>0|(?!0)\d+))?
(?P<minimumwidth>\d+)?
(?P<thousands_sep>[,_])?
(?:\.
(?=[\d,_]) # lookahead for digit or separator
(?P<precision>\d+)?
(?P<frac_separators>[,_])?
)?
(?P<type>[eEfFgGn%])?
\Z
\z
""", re.VERBOSE|re.DOTALL)
del re
@@ -6177,6 +6231,9 @@ def _parse_format_specifier(format_spec, _localeconv=None):
format_dict['grouping'] = [3, 0]
format_dict['decimal_point'] = '.'
if format_dict['frac_separators'] is None:
format_dict['frac_separators'] = ''
return format_dict
def _format_align(sign, body, spec):
@@ -6296,6 +6353,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
sign = _format_sign(is_negative, spec)
frac_sep = spec['frac_separators']
if fracpart and frac_sep:
fracpart = frac_sep.join(fracpart[pos:pos + 3]
for pos in range(0, len(fracpart), 3))
if fracpart or spec['alt']:
fracpart = spec['decimal_point'] + fracpart
+2 -2
View File
@@ -100,8 +100,8 @@ NaN
try:
from _decimal import *
from _decimal import __version__
from _decimal import __libmpdec_version__
from _decimal import __version__ # noqa: F401
from _decimal import __libmpdec_version__ # noqa: F401
except ImportError:
import _pydecimal
import sys
+126 -81
View File
@@ -45,6 +45,7 @@ from test.support import warnings_helper
import random
import inspect
import threading
import contextvars
if sys.platform == 'darwin':
@@ -753,7 +754,7 @@ class ExplicitConstructionTest:
for v in [-2**63-1, -2**63, -2**31-1, -2**31, 0,
2**31-1, 2**31, 2**63-1, 2**63]:
d = nc.create_decimal(v)
self.assertTrue(isinstance(d, Decimal))
self.assertIsInstance(d, Decimal)
self.assertEqual(int(d), v)
nc.prec = 3
@@ -813,6 +814,29 @@ class ExplicitConstructionTest:
x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
self.assertEqual(x, float(nc.create_decimal(x))) # roundtrip
def test_from_number(self, cls=None):
Decimal = self.decimal.Decimal
if cls is None:
cls = Decimal
def check(arg, expected):
d = cls.from_number(arg)
self.assertIs(type(d), cls)
self.assertEqual(d, expected)
check(314, Decimal(314))
check(3.14, Decimal.from_float(3.14))
check(Decimal('3.14'), Decimal('3.14'))
self.assertRaises(TypeError, cls.from_number, 3+4j)
self.assertRaises(TypeError, cls.from_number, '314')
self.assertRaises(TypeError, cls.from_number, (0, (3, 1, 4), 0))
self.assertRaises(TypeError, cls.from_number, object())
def test_from_number_subclass(self, cls=None):
class DecimalSubclass(self.decimal.Decimal):
pass
self.test_from_number(DecimalSubclass)
def test_unicode_digits(self):
Decimal = self.decimal.Decimal
@@ -830,9 +854,8 @@ class CExplicitConstructionTest(ExplicitConstructionTest, unittest.TestCase):
class PyExplicitConstructionTest(ExplicitConstructionTest, unittest.TestCase):
decimal = P
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_unicode_digits(self): # TODO(RUSTPYTHON): Remove this test when it pass
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_unicode_digits(self):
return super().test_unicode_digits()
class ImplicitConstructionTest:
@@ -963,6 +986,7 @@ class FormatTest:
('.0f', '0e-2', '0'),
('.0f', '3.14159265', '3'),
('.1f', '3.14159265', '3.1'),
('.01f', '3.14159265', '3.1'), # leading zero in precision
('.4f', '3.14159265', '3.1416'),
('.6f', '3.14159265', '3.141593'),
('.7f', '3.14159265', '3.1415926'), # round-half-even!
@@ -1048,6 +1072,7 @@ class FormatTest:
('8,', '123456', ' 123,456'),
('08,', '123456', '0,123,456'), # special case: extra 0 needed
('+08,', '123456', '+123,456'), # but not if there's a sign
('008,', '123456', '0,123,456'), # leading zero in width
(' 08,', '123456', ' 123,456'),
('08,', '-123456', '-123,456'),
('+09,', '123456', '+0,123,456'),
@@ -1064,6 +1089,20 @@ class FormatTest:
(',%', '123.456789', '12,345.6789%'),
(',e', '123456', '1.23456e+5'),
(',E', '123456', '1.23456E+5'),
# ... with '_' instead
('_', '1234567', '1_234_567'),
('07_', '1234.56', '1_234.56'),
('_', '1.23456789', '1.23456789'),
('_%', '123.456789', '12_345.6789%'),
# and now for something completely different...
('.,', '1.23456789', '1.234,567,89'),
('._', '1.23456789', '1.234_567_89'),
('.6_f', '12345.23456789', '12345.234_568'),
(',._%', '123.456789', '12,345.678_9%'),
(',._e', '123456', '1.234_56e+5'),
(',.4_e', '123456', '1.234_6e+5'),
(',.3_e', '123456', '1.235e+5'),
(',._E', '123456', '1.234_56E+5'),
# negative zero: default behavior
('.1f', '-0', '-0.0'),
@@ -1137,6 +1176,10 @@ class FormatTest:
# bytes format argument
self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
# precision or fractional part separator should follow after dot
self.assertRaises(ValueError, format, Decimal(1), '.f')
self.assertRaises(ValueError, format, Decimal(1), '._6f')
def test_negative_zero_format_directed_rounding(self):
with self.decimal.localcontext() as ctx:
ctx.rounding = ROUND_CEILING
@@ -1708,8 +1751,13 @@ class ThreadingTest:
self.finish1 = threading.Event()
self.finish2 = threading.Event()
th1 = threading.Thread(target=thfunc1, args=(self,))
th2 = threading.Thread(target=thfunc2, args=(self,))
# This test wants to start threads with an empty context, no matter
# the setting of sys.flags.thread_inherit_context. We pass the
# 'context' argument explicitly with an empty context instance.
th1 = threading.Thread(target=thfunc1, args=(self,),
context=contextvars.Context())
th2 = threading.Thread(target=thfunc2, args=(self,),
context=contextvars.Context())
th1.start()
th2.start()
@@ -2573,8 +2621,8 @@ class PythonAPItests:
def test_abc(self):
Decimal = self.decimal.Decimal
self.assertTrue(issubclass(Decimal, numbers.Number))
self.assertFalse(issubclass(Decimal, numbers.Real))
self.assertIsSubclass(Decimal, numbers.Number)
self.assertNotIsSubclass(Decimal, numbers.Real)
self.assertIsInstance(Decimal(0), numbers.Number)
self.assertNotIsInstance(Decimal(0), numbers.Real)
@@ -2673,7 +2721,7 @@ class PythonAPItests:
def __init__(self, _):
self.x = 'y'
self.assertTrue(issubclass(MyDecimal, Decimal))
self.assertIsSubclass(MyDecimal, Decimal)
r = MyDecimal.from_float(0.1)
self.assertEqual(type(r), MyDecimal)
@@ -2891,31 +2939,31 @@ class PythonAPItests:
Rounded = decimal.Rounded
Clamped = decimal.Clamped
self.assertTrue(issubclass(DecimalException, ArithmeticError))
self.assertIsSubclass(DecimalException, ArithmeticError)
self.assertTrue(issubclass(InvalidOperation, DecimalException))
self.assertTrue(issubclass(FloatOperation, DecimalException))
self.assertTrue(issubclass(FloatOperation, TypeError))
self.assertTrue(issubclass(DivisionByZero, DecimalException))
self.assertTrue(issubclass(DivisionByZero, ZeroDivisionError))
self.assertTrue(issubclass(Overflow, Rounded))
self.assertTrue(issubclass(Overflow, Inexact))
self.assertTrue(issubclass(Overflow, DecimalException))
self.assertTrue(issubclass(Underflow, Inexact))
self.assertTrue(issubclass(Underflow, Rounded))
self.assertTrue(issubclass(Underflow, Subnormal))
self.assertTrue(issubclass(Underflow, DecimalException))
self.assertIsSubclass(InvalidOperation, DecimalException)
self.assertIsSubclass(FloatOperation, DecimalException)
self.assertIsSubclass(FloatOperation, TypeError)
self.assertIsSubclass(DivisionByZero, DecimalException)
self.assertIsSubclass(DivisionByZero, ZeroDivisionError)
self.assertIsSubclass(Overflow, Rounded)
self.assertIsSubclass(Overflow, Inexact)
self.assertIsSubclass(Overflow, DecimalException)
self.assertIsSubclass(Underflow, Inexact)
self.assertIsSubclass(Underflow, Rounded)
self.assertIsSubclass(Underflow, Subnormal)
self.assertIsSubclass(Underflow, DecimalException)
self.assertTrue(issubclass(Subnormal, DecimalException))
self.assertTrue(issubclass(Inexact, DecimalException))
self.assertTrue(issubclass(Rounded, DecimalException))
self.assertTrue(issubclass(Clamped, DecimalException))
self.assertIsSubclass(Subnormal, DecimalException)
self.assertIsSubclass(Inexact, DecimalException)
self.assertIsSubclass(Rounded, DecimalException)
self.assertIsSubclass(Clamped, DecimalException)
self.assertTrue(issubclass(decimal.ConversionSyntax, InvalidOperation))
self.assertTrue(issubclass(decimal.DivisionImpossible, InvalidOperation))
self.assertTrue(issubclass(decimal.DivisionUndefined, InvalidOperation))
self.assertTrue(issubclass(decimal.DivisionUndefined, ZeroDivisionError))
self.assertTrue(issubclass(decimal.InvalidContext, InvalidOperation))
self.assertIsSubclass(decimal.ConversionSyntax, InvalidOperation)
self.assertIsSubclass(decimal.DivisionImpossible, InvalidOperation)
self.assertIsSubclass(decimal.DivisionUndefined, InvalidOperation)
self.assertIsSubclass(decimal.DivisionUndefined, ZeroDivisionError)
self.assertIsSubclass(decimal.InvalidContext, InvalidOperation)
@requires_cdecimal
class CPythonAPItests(PythonAPItests, unittest.TestCase):
@@ -2923,7 +2971,6 @@ class CPythonAPItests(PythonAPItests, unittest.TestCase):
class PyPythonAPItests(PythonAPItests, unittest.TestCase):
decimal = P
class ContextAPItests:
def test_none_args(self):
@@ -4371,6 +4418,51 @@ class CContextSubclassing(ContextSubclassing, unittest.TestCase):
class PyContextSubclassing(ContextSubclassing, unittest.TestCase):
decimal = P
class IEEEContexts:
def test_ieee_context(self):
# issue 8786: Add support for IEEE 754 contexts to decimal module.
IEEEContext = self.decimal.IEEEContext
def assert_rest(self, context):
self.assertEqual(context.clamp, 1)
assert_signals(self, context, 'traps', [])
assert_signals(self, context, 'flags', [])
c = IEEEContext(32)
self.assertEqual(c.prec, 7)
self.assertEqual(c.Emax, 96)
self.assertEqual(c.Emin, -95)
assert_rest(self, c)
c = IEEEContext(64)
self.assertEqual(c.prec, 16)
self.assertEqual(c.Emax, 384)
self.assertEqual(c.Emin, -383)
assert_rest(self, c)
c = IEEEContext(128)
self.assertEqual(c.prec, 34)
self.assertEqual(c.Emax, 6144)
self.assertEqual(c.Emin, -6143)
assert_rest(self, c)
# Invalid values
self.assertRaises(ValueError, IEEEContext, -1)
self.assertRaises(ValueError, IEEEContext, 123)
self.assertRaises(ValueError, IEEEContext, 1024)
def test_constants(self):
# IEEEContext
IEEE_CONTEXT_MAX_BITS = self.decimal.IEEE_CONTEXT_MAX_BITS
self.assertIn(IEEE_CONTEXT_MAX_BITS, {256, 512})
@requires_cdecimal
class CIEEEContexts(IEEEContexts, unittest.TestCase):
decimal = C
class PyIEEEContexts(IEEEContexts, unittest.TestCase):
decimal = P
@skip_if_extra_functionality
@requires_cdecimal
class CheckAttributes(unittest.TestCase):
@@ -4382,6 +4474,7 @@ class CheckAttributes(unittest.TestCase):
self.assertEqual(C.MAX_EMAX, P.MAX_EMAX)
self.assertEqual(C.MIN_EMIN, P.MIN_EMIN)
self.assertEqual(C.MIN_ETINY, P.MIN_ETINY)
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, P.IEEE_CONTEXT_MAX_BITS)
self.assertTrue(C.HAVE_THREADS is True or C.HAVE_THREADS is False)
self.assertTrue(P.HAVE_THREADS is True or P.HAVE_THREADS is False)
@@ -4465,12 +4558,10 @@ class Coverage:
self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True)
# three arg power
self.assertEqual(pow(Decimal(10), 2, 7), 2)
self.assertEqual(pow(10, Decimal(2), 7), 2)
if self.decimal == C:
self.assertEqual(pow(10, Decimal(2), 7), 2)
self.assertEqual(pow(10, 2, Decimal(7)), 2)
else:
# XXX: Three-arg power doesn't use __rpow__.
self.assertRaises(TypeError, pow, 10, Decimal(2), 7)
# XXX: There is no special method to dispatch on the
# third arg of three-arg power.
self.assertRaises(TypeError, pow, 10, 2, Decimal(7))
@@ -4684,10 +4775,6 @@ class PyCoverage(Coverage, unittest.TestCase):
sys.set_int_max_str_digits(self._previous_int_limit)
super().tearDown()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_implicit_context(self): # TODO(RUSTPYTHON): Remove this test when it pass
return super().test_implicit_context()
class PyFunctionality(unittest.TestCase):
"""Extra functionality in decimal.py"""
@@ -4872,42 +4959,6 @@ class PyWhitebox(unittest.TestCase):
class CFunctionality(unittest.TestCase):
"""Extra functionality in _decimal"""
@requires_extra_functionality
def test_c_ieee_context(self):
# issue 8786: Add support for IEEE 754 contexts to decimal module.
IEEEContext = C.IEEEContext
DECIMAL32 = C.DECIMAL32
DECIMAL64 = C.DECIMAL64
DECIMAL128 = C.DECIMAL128
def assert_rest(self, context):
self.assertEqual(context.clamp, 1)
assert_signals(self, context, 'traps', [])
assert_signals(self, context, 'flags', [])
c = IEEEContext(DECIMAL32)
self.assertEqual(c.prec, 7)
self.assertEqual(c.Emax, 96)
self.assertEqual(c.Emin, -95)
assert_rest(self, c)
c = IEEEContext(DECIMAL64)
self.assertEqual(c.prec, 16)
self.assertEqual(c.Emax, 384)
self.assertEqual(c.Emin, -383)
assert_rest(self, c)
c = IEEEContext(DECIMAL128)
self.assertEqual(c.prec, 34)
self.assertEqual(c.Emax, 6144)
self.assertEqual(c.Emin, -6143)
assert_rest(self, c)
# Invalid values
self.assertRaises(OverflowError, IEEEContext, 2**63)
self.assertRaises(ValueError, IEEEContext, -1)
self.assertRaises(ValueError, IEEEContext, 1024)
@requires_extra_functionality
def test_c_context(self):
Context = C.Context
@@ -4928,12 +4979,6 @@ class CFunctionality(unittest.TestCase):
C.DecSubnormal, C.DecUnderflow
)
# IEEEContext
self.assertEqual(C.DECIMAL32, 32)
self.assertEqual(C.DECIMAL64, 64)
self.assertEqual(C.DECIMAL128, 128)
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512)
# Conditions
for i, v in enumerate(cond):
self.assertEqual(v, 1<<i)