Update {_py,}decimal.py from 3.13.5 (#6034)

This commit is contained in:
Shahar Naveh
2025-07-25 12:04:49 +02:00
committed by GitHub
parent fb9147736d
commit ae03bacb39
14 changed files with 314 additions and 224 deletions

259
Lib/_pydecimal.py vendored
View File

@@ -13,104 +13,7 @@
# bug) and will be backported. At this point the spec is stabilizing
# and the updates are becoming fewer, smaller, and less significant.
"""
This is an implementation of decimal floating point arithmetic based on
the General Decimal Arithmetic Specification:
http://speleotrove.com/decimal/decarith.html
and IEEE standard 854-1987:
http://en.wikipedia.org/wiki/IEEE_854-1987
Decimal floating point has finite precision with arbitrarily large bounds.
The purpose of this module is to support arithmetic using familiar
"schoolhouse" rules and to avoid some of the tricky representation
issues associated with binary floating point. The package is especially
useful for financial applications or for contexts where users have
expectations that are at odds with binary floating point (for instance,
in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 instead
of 0.0; Decimal('1.00') % Decimal('0.1') returns the expected
Decimal('0.00')).
Here are some examples of using the decimal module:
>>> from decimal import *
>>> setcontext(ExtendedContext)
>>> Decimal(0)
Decimal('0')
>>> Decimal('1')
Decimal('1')
>>> Decimal('-.0123')
Decimal('-0.0123')
>>> Decimal(123456)
Decimal('123456')
>>> Decimal('123.45e12345678')
Decimal('1.2345E+12345680')
>>> Decimal('1.33') + Decimal('1.27')
Decimal('2.60')
>>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41')
Decimal('-2.20')
>>> dig = Decimal(1)
>>> print(dig / Decimal(3))
0.333333333
>>> getcontext().prec = 18
>>> print(dig / Decimal(3))
0.333333333333333333
>>> print(dig.sqrt())
1
>>> print(Decimal(3).sqrt())
1.73205080756887729
>>> print(Decimal(3) ** 123)
4.85192780976896427E+58
>>> inf = Decimal(1) / Decimal(0)
>>> print(inf)
Infinity
>>> neginf = Decimal(-1) / Decimal(0)
>>> print(neginf)
-Infinity
>>> print(neginf + inf)
NaN
>>> print(neginf * inf)
-Infinity
>>> print(dig / 0)
Infinity
>>> getcontext().traps[DivisionByZero] = 1
>>> print(dig / 0)
Traceback (most recent call last):
...
...
...
decimal.DivisionByZero: x / 0
>>> c = Context()
>>> c.traps[InvalidOperation] = 0
>>> print(c.flags[InvalidOperation])
0
>>> c.divide(Decimal(0), Decimal(0))
Decimal('NaN')
>>> c.traps[InvalidOperation] = 1
>>> print(c.flags[InvalidOperation])
1
>>> c.flags[InvalidOperation] = 0
>>> print(c.flags[InvalidOperation])
0
>>> print(c.divide(Decimal(0), Decimal(0)))
Traceback (most recent call last):
...
...
...
decimal.InvalidOperation: 0 / 0
>>> print(c.flags[InvalidOperation])
1
>>> c.flags[InvalidOperation] = 0
>>> c.traps[InvalidOperation] = 0
>>> print(c.divide(Decimal(0), Decimal(0)))
NaN
>>> print(c.flags[InvalidOperation])
1
>>>
"""
"""Python decimal arithmetic module"""
__all__ = [
# Two major classes
@@ -140,8 +43,11 @@ __all__ = [
# Limits for the C version for compatibility
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
# C version: compile time choice that enables the thread local context
'HAVE_THREADS'
# C version: compile time choice that enables the thread local context (deprecated, now always true)
'HAVE_THREADS',
# C version: compile time choice that enables the coroutine local context
'HAVE_CONTEXTVAR'
]
__xname__ = __name__ # sys.modules lookup (--without-threads)
@@ -156,7 +62,7 @@ import sys
try:
from collections import namedtuple as _namedtuple
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent')
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent', module='decimal')
except ImportError:
DecimalTuple = lambda *args: args
@@ -172,6 +78,7 @@ ROUND_05UP = 'ROUND_05UP'
# Compatibility with the C version
HAVE_THREADS = True
HAVE_CONTEXTVAR = True
if sys.maxsize == 2**63-1:
MAX_PREC = 999999999999999999
MAX_EMAX = 999999999999999999
@@ -190,7 +97,7 @@ class DecimalException(ArithmeticError):
Used exceptions derive from this.
If an exception derives from another exception besides this (such as
Underflow (Inexact, Rounded, Subnormal) that indicates that it is only
Underflow (Inexact, Rounded, Subnormal)) that indicates that it is only
called if the others are present. This isn't actually used for
anything, though.
@@ -238,7 +145,7 @@ class InvalidOperation(DecimalException):
x ** (+-)INF
An operand is invalid
The result of the operation after these is a quiet positive NaN,
The result of the operation after this is a quiet positive NaN,
except when the cause is a signaling NaN, in which case the result is
also a quiet NaN, but with the original sign, and an optional
diagnostic information.
@@ -431,82 +338,40 @@ _rounding_modes = (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING,
##### Context Functions ##################################################
# The getcontext() and setcontext() function manage access to a thread-local
# current context. Py2.4 offers direct support for thread locals. If that
# is not available, use threading.current_thread() which is slower but will
# work for older Pythons. If threads are not part of the build, create a
# mock threading object with threading.local() returning the module namespace.
# current context.
try:
import threading
except ImportError:
# Python was compiled without threads; create a mock object instead
class MockThreading(object):
def local(self, sys=sys):
return sys.modules[__xname__]
threading = MockThreading()
del MockThreading
import contextvars
try:
threading.local
_current_context_var = contextvars.ContextVar('decimal_context')
except AttributeError:
_context_attributes = frozenset(
['prec', 'Emin', 'Emax', 'capitals', 'clamp', 'rounding', 'flags', 'traps']
)
# To fix reloading, force it to create a new context
# Old contexts have different exceptions in their dicts, making problems.
if hasattr(threading.current_thread(), '__decimal_context__'):
del threading.current_thread().__decimal_context__
def getcontext():
"""Returns this thread's context.
def setcontext(context):
"""Set this thread's context to context."""
if context in (DefaultContext, BasicContext, ExtendedContext):
context = context.copy()
context.clear_flags()
threading.current_thread().__decimal_context__ = context
If this thread does not yet have a context, returns
a new context and sets this thread's context.
New contexts are copies of DefaultContext.
"""
try:
return _current_context_var.get()
except LookupError:
context = Context()
_current_context_var.set(context)
return context
def getcontext():
"""Returns this thread's context.
def setcontext(context):
"""Set this thread's context to context."""
if context in (DefaultContext, BasicContext, ExtendedContext):
context = context.copy()
context.clear_flags()
_current_context_var.set(context)
If this thread does not yet have a context, returns
a new context and sets this thread's context.
New contexts are copies of DefaultContext.
"""
try:
return threading.current_thread().__decimal_context__
except AttributeError:
context = Context()
threading.current_thread().__decimal_context__ = context
return context
del contextvars # Don't contaminate the namespace
else:
local = threading.local()
if hasattr(local, '__decimal_context__'):
del local.__decimal_context__
def getcontext(_local=local):
"""Returns this thread's context.
If this thread does not yet have a context, returns
a new context and sets this thread's context.
New contexts are copies of DefaultContext.
"""
try:
return _local.__decimal_context__
except AttributeError:
context = Context()
_local.__decimal_context__ = context
return context
def setcontext(context, _local=local):
"""Set this thread's context to context."""
if context in (DefaultContext, BasicContext, ExtendedContext):
context = context.copy()
context.clear_flags()
_local.__decimal_context__ = context
del threading, local # Don't contaminate the namespace
def localcontext(ctx=None):
def localcontext(ctx=None, **kwargs):
"""Return a context manager for a copy of the supplied context
Uses a copy of the current context if no context is specified
@@ -542,8 +407,14 @@ def localcontext(ctx=None):
>>> print(getcontext().prec)
28
"""
if ctx is None: ctx = getcontext()
return _ContextManager(ctx)
if ctx is None:
ctx = getcontext()
ctx_manager = _ContextManager(ctx)
for key, value in kwargs.items():
if key not in _context_attributes:
raise TypeError(f"'{key}' is an invalid keyword argument for this function")
setattr(ctx_manager.new_context, key, value)
return ctx_manager
##### Decimal class #######################################################
@@ -553,7 +424,7 @@ def localcontext(ctx=None):
# numbers.py for more detail.
class Decimal(object):
"""Floating point class for decimal arithmetic."""
"""Floating-point class for decimal arithmetic."""
__slots__ = ('_exp','_int','_sign', '_is_special')
# Generally, the value of the Decimal instance is given by
@@ -993,7 +864,7 @@ class Decimal(object):
if self.is_snan():
raise TypeError('Cannot hash a signaling NaN value.')
elif self.is_nan():
return _PyHASH_NAN
return object.__hash__(self)
else:
if self._sign:
return -_PyHASH_INF
@@ -1674,13 +1545,13 @@ class Decimal(object):
__trunc__ = __int__
@property
def real(self):
return self
real = property(real)
@property
def imag(self):
return Decimal(0)
imag = property(imag)
def conjugate(self):
return self
@@ -2260,10 +2131,16 @@ class Decimal(object):
else:
return None
if xc >= 10**p:
# An exact power of 10 is representable, but can convert to a
# string of any length. But an exact power of 10 shouldn't be
# possible at this point.
assert xc > 1, self
assert xc % 10 != 0, self
strxc = str(xc)
if len(strxc) > p:
return None
xe = -e-xe
return _dec_from_triple(0, str(xc), xe)
return _dec_from_triple(0, strxc, xe)
# now y is positive; find m and n such that y = m/n
if ye >= 0:
@@ -2272,7 +2149,7 @@ class Decimal(object):
if xe != 0 and len(str(abs(yc*xe))) <= -ye:
return None
xc_bits = _nbits(xc)
if xc != 1 and len(str(abs(yc)*xc_bits)) <= -ye:
if len(str(abs(yc)*xc_bits)) <= -ye:
return None
m, n = yc, 10**(-ye)
while m % 2 == n % 2 == 0:
@@ -2285,7 +2162,7 @@ class Decimal(object):
# compute nth root of xc*10**xe
if n > 1:
# if 1 < xc < 2**n then xc isn't an nth power
if xc != 1 and xc_bits <= n:
if xc_bits <= n:
return None
xe, rem = divmod(xe, n)
@@ -2313,13 +2190,18 @@ class Decimal(object):
return None
xc = xc**m
xe *= m
if xc > 10**p:
# An exact power of 10 is representable, but can convert to a string
# of any length. But an exact power of 10 shouldn't be possible at
# this point.
assert xc > 1, self
assert xc % 10 != 0, self
str_xc = str(xc)
if len(str_xc) > p:
return None
# by this point the result *is* exactly representable
# adjust the exponent to get as close as possible to the ideal
# exponent, if necessary
str_xc = str(xc)
if other._isinteger() and other._sign == 0:
ideal_exponent = self._exp*int(other)
zeros = min(xe-ideal_exponent, p-len(str_xc))
@@ -3837,6 +3719,10 @@ class Decimal(object):
# represented in fixed point; rescale them to 0e0.
if not self and self._exp > 0 and spec['type'] in 'fF%':
self = self._rescale(0, rounding)
if not self and spec['no_neg_0'] and self._sign:
adjusted_sign = 0
else:
adjusted_sign = self._sign
# figure out placement of the decimal point
leftdigits = self._exp + len(self._int)
@@ -3867,7 +3753,7 @@ class Decimal(object):
# done with the decimal-specific stuff; hand over the rest
# of the formatting to the _format_number function
return _format_number(self._sign, intpart, fracpart, exp, spec)
return _format_number(adjusted_sign, intpart, fracpart, exp, spec)
def _dec_from_triple(sign, coefficient, exponent, special=False):
"""Create a decimal instance directly, without any validation,
@@ -5677,8 +5563,6 @@ class _WorkRep(object):
def __repr__(self):
return "(%r, %r, %r)" % (self.sign, self.int, self.exp)
__str__ = __repr__
def _normalize(op1, op2, prec = 0):
@@ -6187,7 +6071,7 @@ _exact_half = re.compile('50*$').match
#
# A format specifier for Decimal looks like:
#
# [[fill]align][sign][#][0][minimumwidth][,][.precision][type]
# [[fill]align][sign][z][#][0][minimumwidth][,][.precision][type]
_parse_format_specifier_regex = re.compile(r"""\A
(?:
@@ -6195,6 +6079,7 @@ _parse_format_specifier_regex = re.compile(r"""\A
(?P<align>[<>=^])
)?
(?P<sign>[-+ ])?
(?P<no_neg_0>z)?
(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?

108
Lib/decimal.py vendored
View File

@@ -1,11 +1,109 @@
"""Decimal fixed-point and floating-point arithmetic.
This is an implementation of decimal floating-point arithmetic based on
the General Decimal Arithmetic Specification:
http://speleotrove.com/decimal/decarith.html
and IEEE standard 854-1987:
http://en.wikipedia.org/wiki/IEEE_854-1987
Decimal floating point has finite precision with arbitrarily large bounds.
The purpose of this module is to support arithmetic using familiar
"schoolhouse" rules and to avoid some of the tricky representation
issues associated with binary floating point. The package is especially
useful for financial applications or for contexts where users have
expectations that are at odds with binary floating point (for instance,
in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 instead
of 0.0; Decimal('1.00') % Decimal('0.1') returns the expected
Decimal('0.00')).
Here are some examples of using the decimal module:
>>> from decimal import *
>>> setcontext(ExtendedContext)
>>> Decimal(0)
Decimal('0')
>>> Decimal('1')
Decimal('1')
>>> Decimal('-.0123')
Decimal('-0.0123')
>>> Decimal(123456)
Decimal('123456')
>>> Decimal('123.45e12345678')
Decimal('1.2345E+12345680')
>>> Decimal('1.33') + Decimal('1.27')
Decimal('2.60')
>>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41')
Decimal('-2.20')
>>> dig = Decimal(1)
>>> print(dig / Decimal(3))
0.333333333
>>> getcontext().prec = 18
>>> print(dig / Decimal(3))
0.333333333333333333
>>> print(dig.sqrt())
1
>>> print(Decimal(3).sqrt())
1.73205080756887729
>>> print(Decimal(3) ** 123)
4.85192780976896427E+58
>>> inf = Decimal(1) / Decimal(0)
>>> print(inf)
Infinity
>>> neginf = Decimal(-1) / Decimal(0)
>>> print(neginf)
-Infinity
>>> print(neginf + inf)
NaN
>>> print(neginf * inf)
-Infinity
>>> print(dig / 0)
Infinity
>>> getcontext().traps[DivisionByZero] = 1
>>> print(dig / 0)
Traceback (most recent call last):
...
...
...
decimal.DivisionByZero: x / 0
>>> c = Context()
>>> c.traps[InvalidOperation] = 0
>>> print(c.flags[InvalidOperation])
0
>>> c.divide(Decimal(0), Decimal(0))
Decimal('NaN')
>>> c.traps[InvalidOperation] = 1
>>> print(c.flags[InvalidOperation])
1
>>> c.flags[InvalidOperation] = 0
>>> print(c.flags[InvalidOperation])
0
>>> print(c.divide(Decimal(0), Decimal(0)))
Traceback (most recent call last):
...
...
...
decimal.InvalidOperation: 0 / 0
>>> print(c.flags[InvalidOperation])
1
>>> c.flags[InvalidOperation] = 0
>>> c.traps[InvalidOperation] = 0
>>> print(c.divide(Decimal(0), Decimal(0)))
NaN
>>> print(c.flags[InvalidOperation])
1
>>>
"""
try:
from _decimal import *
from _decimal import __doc__
from _decimal import __version__
from _decimal import __libmpdec_version__
except ImportError:
from _pydecimal import *
from _pydecimal import __doc__
from _pydecimal import __version__
from _pydecimal import __libmpdec_version__
import _pydecimal
import sys
_pydecimal.__doc__ = __doc__
sys.modules[__name__] = _pydecimal

View File

@@ -20,7 +20,7 @@
version: 2.59
-- This set of tests primarily tests the existence of the operator.
-- Additon, subtraction, rounding, and more overflows are tested
-- Addition, subtraction, rounding, and more overflows are tested
-- elsewhere.
precision: 9

View File

@@ -1663,7 +1663,7 @@ ddfma375087 fma 1 12345678 1E-33 -> 12345678.00000001 Inexac
ddfma375088 fma 1 12345678 1E-34 -> 12345678.00000001 Inexact Rounded
ddfma375089 fma 1 12345678 1E-35 -> 12345678.00000001 Inexact Rounded
-- desctructive subtraction (from remainder tests)
-- destructive subtraction (from remainder tests)
-- +++ some of these will be off-by-one remainder vs remainderNear

View File

@@ -462,7 +462,7 @@ ddqua520 quantize 1.234 1e359 -> 0E+359 Inexact Rounded
ddqua521 quantize 123.456 1e359 -> 0E+359 Inexact Rounded
ddqua522 quantize 1.234 1e359 -> 0E+359 Inexact Rounded
ddqua523 quantize 123.456 1e359 -> 0E+359 Inexact Rounded
-- next four are "won't fit" overfl
-- next four are "won't fit" overflow
ddqua526 quantize 1.234 1e-299 -> NaN Invalid_operation
ddqua527 quantize 123.456 1e-299 -> NaN Invalid_operation
ddqua528 quantize 1.234 1e-299 -> NaN Invalid_operation

View File

@@ -422,7 +422,7 @@ ddrem757 remainder 1 sNaN -> NaN Invalid_operation
ddrem758 remainder 1000 sNaN -> NaN Invalid_operation
ddrem759 remainder Inf -sNaN -> -NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
ddrem760 remainder NaN1 NaN7 -> NaN1
ddrem761 remainder sNaN2 NaN8 -> NaN2 Invalid_operation
ddrem762 remainder NaN3 sNaN9 -> NaN9 Invalid_operation

View File

@@ -450,7 +450,7 @@ ddrmn757 remaindernear 1 sNaN -> NaN Invalid_operation
ddrmn758 remaindernear 1000 sNaN -> NaN Invalid_operation
ddrmn759 remaindernear Inf -sNaN -> -NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
ddrmn760 remaindernear NaN1 NaN7 -> NaN1
ddrmn761 remaindernear sNaN2 NaN8 -> NaN2 Invalid_operation
ddrmn762 remaindernear NaN3 sNaN9 -> NaN9 Invalid_operation

View File

@@ -418,7 +418,7 @@ dqrem757 remainder 1 sNaN -> NaN Invalid_operation
dqrem758 remainder 1000 sNaN -> NaN Invalid_operation
dqrem759 remainder Inf -sNaN -> -NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
dqrem760 remainder NaN1 NaN7 -> NaN1
dqrem761 remainder sNaN2 NaN8 -> NaN2 Invalid_operation
dqrem762 remainder NaN3 sNaN9 -> NaN9 Invalid_operation

View File

@@ -450,7 +450,7 @@ dqrmn757 remaindernear 1 sNaN -> NaN Invalid_operation
dqrmn758 remaindernear 1000 sNaN -> NaN Invalid_operation
dqrmn759 remaindernear Inf -sNaN -> -NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
dqrmn760 remaindernear NaN1 NaN7 -> NaN1
dqrmn761 remaindernear sNaN2 NaN8 -> NaN2 Invalid_operation
dqrmn762 remaindernear NaN3 sNaN9 -> NaN9 Invalid_operation

View File

@@ -28,7 +28,7 @@ rounding: half_even
maxExponent: 384
minexponent: -383
-- basics (examples in specificiation, etc.)
-- basics (examples in specification, etc.)
expx001 exp -Infinity -> 0
expx002 exp -10 -> 0.0000453999298 Inexact Rounded
expx003 exp -1 -> 0.367879441 Inexact Rounded

View File

@@ -156,7 +156,7 @@ extr1302 fma -Inf 0E-456 sNaN148 -> NaN Invalid_operation
-- max/min/max_mag/min_mag bug in 2.5.2/2.6/3.0: max(NaN, finite) gave
-- incorrect answers when the finite number required rounding; similarly
-- for the other thre functions
-- for the other three functions
maxexponent: 999
minexponent: -999
precision: 6

View File

@@ -435,7 +435,7 @@ remx757 remainder 1 sNaN -> NaN Invalid_operation
remx758 remainder 1000 sNaN -> NaN Invalid_operation
remx759 remainder Inf -sNaN -> -NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
remx760 remainder NaN1 NaN7 -> NaN1
remx761 remainder sNaN2 NaN8 -> NaN2 Invalid_operation
remx762 remainder NaN3 sNaN9 -> NaN9 Invalid_operation

View File

@@ -498,7 +498,7 @@ rmnx758 remaindernear 1000 sNaN -> NaN Invalid_operation
rmnx759 remaindernear Inf sNaN -> NaN Invalid_operation
rmnx760 remaindernear NaN sNaN -> NaN Invalid_operation
-- propaging NaNs
-- propagating NaNs
rmnx761 remaindernear NaN1 NaN7 -> NaN1
rmnx762 remaindernear sNaN2 NaN8 -> NaN2 Invalid_operation
rmnx763 remaindernear NaN3 -sNaN9 -> -NaN9 Invalid_operation

View File

@@ -24,6 +24,7 @@ you're working through IDLE, you can import this test module and call test()
with the corresponding argument.
"""
import logging
import math
import os, sys
import operator
@@ -812,8 +813,6 @@ class ExplicitConstructionTest:
x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
self.assertEqual(x, float(nc.create_decimal(x))) # roundtrip
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_unicode_digits(self):
Decimal = self.decimal.Decimal
@@ -831,6 +830,11 @@ 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
return super().test_unicode_digits()
class ImplicitConstructionTest:
'''Unit tests for Implicit Construction cases of Decimal.'''
@@ -916,8 +920,6 @@ class PyImplicitConstructionTest(ImplicitConstructionTest, unittest.TestCase):
class FormatTest:
'''Unit tests for the format function.'''
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_formatting(self):
Decimal = self.decimal.Decimal
@@ -1113,6 +1115,13 @@ class FormatTest:
('z>z6.1f', '-0.', 'zzz0.0'),
('x>z6.1f', '-0.', 'xxx0.0'),
('🖤>z6.1f', '-0.', '🖤🖤🖤0.0'), # multi-byte fill char
('\x00>z6.1f', '-0.', '\x00\x00\x000.0'), # null fill char
# issue 114563 ('z' format on F type in cdecimal)
('z3,.10F', '-6.24E-323', '0.0000000000'),
# issue 91060 ('#' format in cdecimal)
('#', '0', '0.'),
# issue 6850
('a=-7.0', '0.12345', 'aaaa0.1'),
@@ -1128,8 +1137,6 @@ class FormatTest:
# bytes format argument
self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_negative_zero_format_directed_rounding(self):
with self.decimal.localcontext() as ctx:
ctx.rounding = ROUND_CEILING
@@ -1228,7 +1235,31 @@ class FormatTest:
self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'),
'-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5')
@run_with_locale('LC_ALL', 'ps_AF')
def test_deprecated_N_format(self):
Decimal = self.decimal.Decimal
h = Decimal('6.62607015e-34')
if self.decimal == C:
with self.assertWarns(DeprecationWarning) as cm:
r = format(h, 'N')
self.assertEqual(cm.filename, __file__)
self.assertEqual(r, format(h, 'n').upper())
with self.assertWarns(DeprecationWarning) as cm:
r = format(h, '010.3N')
self.assertEqual(cm.filename, __file__)
self.assertEqual(r, format(h, '010.3n').upper())
else:
self.assertRaises(ValueError, format, h, 'N')
self.assertRaises(ValueError, format, h, '010.3N')
with warnings_helper.check_no_warnings(self):
self.assertEqual(format(h, 'N>10.3'), 'NN6.63E-34')
self.assertEqual(format(h, 'N>10.3n'), 'NN6.63e-34')
self.assertEqual(format(h, 'N>10.3e'), 'N6.626e-34')
self.assertEqual(format(h, 'N>10.3f'), 'NNNNN0.000')
self.assertRaises(ValueError, format, h, '>Nf')
self.assertRaises(ValueError, format, h, '10Nf')
self.assertRaises(ValueError, format, h, 'Nx')
@run_with_locale('LC_ALL', 'ps_AF', '')
def test_wide_char_separator_decimal_point(self):
# locale with wide char separator and decimal point
Decimal = self.decimal.Decimal
@@ -1911,8 +1942,6 @@ class UsabilityTest:
x = 1100 ** 1248
self.assertEqual(hashit(Decimal(x)), hashit(x))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_hash_method_nan(self):
Decimal = self.decimal.Decimal
self.assertRaises(TypeError, hash, Decimal('sNaN'))
@@ -2048,7 +2077,9 @@ class UsabilityTest:
#to quantize, which is already extensively tested
test_triples = [
('123.456', -4, '0E+4'),
('-123.456', -4, '-0E+4'),
('123.456', -3, '0E+3'),
('-123.456', -3, '-0E+3'),
('123.456', -2, '1E+2'),
('123.456', -1, '1.2E+2'),
('123.456', 0, '123'),
@@ -2718,8 +2749,6 @@ class PythonAPItests:
x = d.quantize(context=c, exp=Decimal("1e797"), rounding=ROUND_DOWN)
self.assertEqual(x, Decimal('8.71E+799'))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_complex(self):
Decimal = self.decimal.Decimal
@@ -2894,6 +2923,11 @@ class CPythonAPItests(PythonAPItests, unittest.TestCase):
class PyPythonAPItests(PythonAPItests, unittest.TestCase):
decimal = P
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_complex(self): # TODO(RUSTPYTHON): Remove this test when it pass
return super().test_complex()
class ContextAPItests:
def test_none_args(self):
@@ -3663,8 +3697,6 @@ class ContextWithStatement:
self.assertIsNot(new_ctx, set_ctx, 'did not copy the context')
self.assertIs(set_ctx, enter_ctx, '__enter__ returned wrong context')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_localcontext_kwargs(self):
with self.decimal.localcontext(
prec=10, rounding=ROUND_HALF_DOWN,
@@ -3693,8 +3725,6 @@ class ContextWithStatement:
self.assertRaises(TypeError, self.decimal.localcontext, Emin="")
self.assertRaises(TypeError, self.decimal.localcontext, Emax="")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_local_context_kwargs_does_not_overwrite_existing_argument(self):
ctx = self.decimal.getcontext()
orig_prec = ctx.prec
@@ -4362,7 +4392,8 @@ class CheckAttributes(unittest.TestCase):
self.assertEqual(C.__version__, P.__version__)
self.assertEqual(dir(C), dir(P))
self.assertLessEqual(set(dir(C)), set(dir(P)))
self.assertEqual([n for n in dir(C) if n[:2] != '__'], sorted(P.__all__))
def test_context_attributes(self):
@@ -4438,6 +4469,15 @@ class Coverage:
self.assertIs(Decimal("NaN").fma(7, 1).is_nan(), True)
# three arg power
self.assertEqual(pow(Decimal(10), 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))
# exp
self.assertEqual(Decimal("1.01").exp(), 3)
# is_normal
@@ -4648,6 +4688,11 @@ 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"""
@@ -4699,9 +4744,33 @@ class PyWhitebox(unittest.TestCase):
c.prec = 1
x = Decimal("152587890625") ** Decimal('-0.5')
self.assertEqual(x, Decimal('3e-6'))
c.prec = 2
x = Decimal("152587890625") ** Decimal('-0.5')
self.assertEqual(x, Decimal('2.6e-6'))
c.prec = 3
x = Decimal("152587890625") ** Decimal('-0.5')
self.assertEqual(x, Decimal('2.56e-6'))
c.prec = 28
x = Decimal("152587890625") ** Decimal('-0.5')
self.assertEqual(x, Decimal('2.56e-6'))
c.prec = 201
x = Decimal(2**578) ** Decimal("-0.5")
# See https://github.com/python/cpython/issues/118027
# Testing for an exact power could appear to hang, in the Python
# version, as it attempted to compute 10**(MAX_EMAX + 1).
# Fixed via https://github.com/python/cpython/pull/118503.
c.prec = P.MAX_PREC
c.Emax = P.MAX_EMAX
c.Emin = P.MIN_EMIN
c.traps[P.Inexact] = 1
D2 = Decimal(2)
# If the bug is still present, the next statement won't complete.
res = D2 ** 117
self.assertEqual(res, 1 << 117)
def test_py_immutability_operations(self):
# Do operations and check that it didn't change internal objects.
Decimal = P.Decimal
@@ -5625,6 +5694,25 @@ class CWhitebox(unittest.TestCase):
self.assertEqual(Decimal.from_float(cls(101.1)),
Decimal.from_float(101.1))
def test_c_immutable_types(self):
SignalDict = type(C.Context().flags)
SignalDictMixin = SignalDict.__bases__[0]
ContextManager = type(C.localcontext())
types = (
SignalDictMixin,
ContextManager,
C.Decimal,
C.Context,
)
for tp in types:
with self.subTest(tp=tp):
with self.assertRaisesRegex(TypeError, "immutable"):
tp.foo = 1
def test_c_disallow_instantiation(self):
ContextManager = type(C.localcontext())
check_disallow_instantiation(self, ContextManager)
def test_c_signaldict_segfault(self):
# See gh-106263 for details.
SignalDict = type(C.Context().flags)
@@ -5655,6 +5743,20 @@ class CWhitebox(unittest.TestCase):
with self.assertRaisesRegex(ValueError, err_msg):
sd.copy()
def test_format_fallback_capitals(self):
# Fallback to _pydecimal formatting (triggered by `#` format which
# is unsupported by mpdecimal) should honor the current context.
x = C.Decimal('6.09e+23')
self.assertEqual(format(x, '#'), '6.09E+23')
with C.localcontext(capitals=0):
self.assertEqual(format(x, '#'), '6.09e+23')
def test_format_fallback_rounding(self):
y = C.Decimal('6.09')
self.assertEqual(format(y, '#.1f'), '6.1')
with C.localcontext(rounding=C.ROUND_DOWN):
self.assertEqual(format(y, '#.1f'), '6.0')
@requires_docstrings
@requires_cdecimal
class SignatureTest(unittest.TestCase):
@@ -5818,13 +5920,17 @@ def load_tests(loader, tests, pattern):
if TODO_TESTS is None:
from doctest import DocTestSuite, IGNORE_EXCEPTION_DETAIL
orig_context = orig_sys_decimal.getcontext().copy()
for mod in C, P:
if not mod:
continue
def setUp(slf, mod=mod):
sys.modules['decimal'] = mod
def tearDown(slf):
init(mod)
def tearDown(slf, mod=mod):
sys.modules['decimal'] = orig_sys_decimal
mod.setcontext(ORIGINAL_CONTEXT[mod].copy())
orig_sys_decimal.setcontext(orig_context.copy())
optionflags = IGNORE_EXCEPTION_DETAIL if mod is C else 0
sys.modules['decimal'] = mod
tests.addTest(DocTestSuite(mod, setUp=setUp, tearDown=tearDown,
@@ -5839,11 +5945,12 @@ def setUpModule():
TEST_ALL = ARITH if ARITH is not None else is_resource_enabled('decimal')
def tearDownModule():
if C: C.setcontext(ORIGINAL_CONTEXT[C])
P.setcontext(ORIGINAL_CONTEXT[P])
if C: C.setcontext(ORIGINAL_CONTEXT[C].copy())
P.setcontext(ORIGINAL_CONTEXT[P].copy())
if not C:
warnings.warn('C tests skipped: no module named _decimal.',
UserWarning)
logging.getLogger(__name__).warning(
'C tests skipped: no module named _decimal.'
)
if not orig_sys_decimal is sys.modules['decimal']:
raise TestFailed("Internal error: unbalanced number of changes to "
"sys.modules['decimal'].")