Kaydet (Commit) 7ce0fa87 authored tarafından Mark Dickinson's avatar Mark Dickinson

Issue #12080: Fix a performance issue in Decimal._power_exact that causes some…

Issue #12080: Fix a performance issue in Decimal._power_exact that causes some corner-case Decimal.__pow__ calls to take an unreasonably long time.
üst 4dfcb1a0
...@@ -2001,9 +2001,9 @@ class Decimal(object): ...@@ -2001,9 +2001,9 @@ class Decimal(object):
nonzero. For efficiency, other._exp should not be too large, nonzero. For efficiency, other._exp should not be too large,
so that 10**abs(other._exp) is a feasible calculation.""" so that 10**abs(other._exp) is a feasible calculation."""
# In the comments below, we write x for the value of self and # In the comments below, we write x for the value of self and y for the
# y for the value of other. Write x = xc*10**xe and y = # value of other. Write x = xc*10**xe and abs(y) = yc*10**ye, with xc
# yc*10**ye. # and yc positive integers not divisible by 10.
# The main purpose of this method is to identify the *failure* # The main purpose of this method is to identify the *failure*
# of x**y to be exactly representable with as little effort as # of x**y to be exactly representable with as little effort as
...@@ -2011,13 +2011,12 @@ class Decimal(object): ...@@ -2011,13 +2011,12 @@ class Decimal(object):
# eliminate the possibility of x**y being exact. Only if all # eliminate the possibility of x**y being exact. Only if all
# these tests are passed do we go on to actually compute x**y. # these tests are passed do we go on to actually compute x**y.
# Here's the main idea. First normalize both x and y. We # Here's the main idea. Express y as a rational number m/n, with m and
# express y as a rational m/n, with m and n relatively prime # n relatively prime and n>0. Then for x**y to be exactly
# and n>0. Then for x**y to be exactly representable (at # representable (at *any* precision), xc must be the nth power of a
# *any* precision), xc must be the nth power of a positive # positive integer and xe must be divisible by n. If y is negative
# integer and xe must be divisible by n. If m is negative # then additionally xc must be a power of either 2 or 5, hence a power
# then additionally xc must be a power of either 2 or 5, hence # of 2**n or 5**n.
# a power of 2**n or 5**n.
# #
# There's a limit to how small |y| can be: if y=m/n as above # There's a limit to how small |y| can be: if y=m/n as above
# then: # then:
...@@ -2089,21 +2088,43 @@ class Decimal(object): ...@@ -2089,21 +2088,43 @@ class Decimal(object):
return None return None
# now xc is a power of 2; e is its exponent # now xc is a power of 2; e is its exponent
e = _nbits(xc)-1 e = _nbits(xc)-1
# find e*y and xe*y; both must be integers
if ye >= 0: # We now have:
y_as_int = yc*10**ye #
e = e*y_as_int # x = 2**e * 10**xe, e > 0, and y < 0.
xe = xe*y_as_int #
else: # The exact result is:
ten_pow = 10**-ye #
e, remainder = divmod(e*yc, ten_pow) # x**y = 5**(-e*y) * 10**(e*y + xe*y)
if remainder: #
return None # provided that both e*y and xe*y are integers. Note that if
xe, remainder = divmod(xe*yc, ten_pow) # 5**(-e*y) >= 10**p, then the result can't be expressed
if remainder: # exactly with p digits of precision.
return None #
# Using the above, we can guard against large values of ye.
if e*65 >= p*93: # 93/65 > log(10)/log(5) # 93/65 is an upper bound for log(10)/log(5), so if
#
# ye >= len(str(93*p//65))
#
# then
#
# -e*y >= -y >= 10**ye > 93*p/65 > p*log(10)/log(5),
#
# so 5**(-e*y) >= 10**p, and the coefficient of the result
# can't be expressed in p digits.
# emax >= largest e such that 5**e < 10**p.
emax = p*93//65
if ye >= len(str(emax)):
return None
# Find -e*y and -xe*y; both must be integers
e = _decimal_lshift_exact(e * yc, ye)
xe = _decimal_lshift_exact(xe * yc, ye)
if e is None or xe is None:
return None
if e > emax:
return None return None
xc = 5**e xc = 5**e
...@@ -2117,19 +2138,20 @@ class Decimal(object): ...@@ -2117,19 +2138,20 @@ class Decimal(object):
while xc % 5 == 0: while xc % 5 == 0:
xc //= 5 xc //= 5
e -= 1 e -= 1
if ye >= 0:
y_as_integer = yc*10**ye # Guard against large values of ye, using the same logic as in
e = e*y_as_integer # the 'xc is a power of 2' branch. 10/3 is an upper bound for
xe = xe*y_as_integer # log(10)/log(2).
else: emax = p*10//3
ten_pow = 10**-ye if ye >= len(str(emax)):
e, remainder = divmod(e*yc, ten_pow) return None
if remainder:
return None e = _decimal_lshift_exact(e * yc, ye)
xe, remainder = divmod(xe*yc, ten_pow) xe = _decimal_lshift_exact(xe * yc, ye)
if remainder: if e is None or xe is None:
return None return None
if e*3 >= p*10: # 10/3 > log(10)/log(2)
if e > emax:
return None return None
xc = 2**e xc = 2**e
else: else:
...@@ -5529,6 +5551,27 @@ def _normalize(op1, op2, prec = 0): ...@@ -5529,6 +5551,27 @@ def _normalize(op1, op2, prec = 0):
_nbits = int.bit_length _nbits = int.bit_length
def _decimal_lshift_exact(n, e):
""" Given integers n and e, return n * 10**e if it's an integer, else None.
The computation is designed to avoid computing large powers of 10
unnecessarily.
>>> _decimal_lshift_exact(3, 4)
30000
>>> _decimal_lshift_exact(300, -999999999) # returns None
"""
if n == 0:
return 0
elif e >= 0:
return n * 10**e
else:
# val_n = largest power of 10 dividing n.
str_n = str(abs(n))
val_n = len(str_n) - len(str_n.rstrip('0'))
return None if val_n < -e else n // 10**-e
def _sqrt_nearest(n, a): def _sqrt_nearest(n, a):
"""Closest integer to the square root of the positive integer n. a is """Closest integer to the square root of the positive integer n. a is
an initial approximation to the square root. Any positive integer an initial approximation to the square root. Any positive integer
......
...@@ -222,12 +222,25 @@ extr1700 power 10 1e-999999999 -> 1.000000000000000 Inexact Rounded ...@@ -222,12 +222,25 @@ extr1700 power 10 1e-999999999 -> 1.000000000000000 Inexact Rounded
extr1701 power 100.0 -557.71e-742888888 -> 1.000000000000000 Inexact Rounded extr1701 power 100.0 -557.71e-742888888 -> 1.000000000000000 Inexact Rounded
extr1702 power 10 1e-100 -> 1.000000000000000 Inexact Rounded extr1702 power 10 1e-100 -> 1.000000000000000 Inexact Rounded
-- Another one (see issue #12080). Thanks again to Stefan Krah.
extr1703 power 4 -1.2e-999999999 -> 1.000000000000000 Inexact Rounded
-- A couple of interesting exact cases for power. Note that the specification -- A couple of interesting exact cases for power. Note that the specification
-- requires these to be reported as Inexact. -- requires these to be reported as Inexact.
extr1710 power 1e375 56e-3 -> 1.000000000000000E+21 Inexact Rounded extr1710 power 1e375 56e-3 -> 1.000000000000000E+21 Inexact Rounded
extr1711 power 10000 0.75 -> 1000.000000000000 Inexact Rounded extr1711 power 10000 0.75 -> 1000.000000000000 Inexact Rounded
extr1712 power 1e-24 0.875 -> 1.000000000000000E-21 Inexact Rounded extr1712 power 1e-24 0.875 -> 1.000000000000000E-21 Inexact Rounded
-- Some more exact cases, exercising power with negative second argument.
extr1720 power 400 -0.5 -> 0.05000000000000000 Inexact Rounded
extr1721 power 4096 -0.75 -> 0.001953125000000000 Inexact Rounded
extr1722 power 625e4 -0.25 -> 0.02000000000000000 Inexact Rounded
-- Nonexact cases, to exercise some of the early exit conditions from
-- _power_exact.
extr1730 power 2048 -0.75 -> 0.003284751622084822 Inexact Rounded
-- Tests for the is_* boolean operations -- Tests for the is_* boolean operations
precision: 9 precision: 9
maxExponent: 999 maxExponent: 999
......
...@@ -184,6 +184,9 @@ Core and Builtins ...@@ -184,6 +184,9 @@ Core and Builtins
Library Library
------- -------
- Issue #12080: Fix a Decimal.power() case that took an unreasonably long time
to compute.
- Issue #12221: Remove __version__ attributes from pyexpat, pickle, tarfile, - Issue #12221: Remove __version__ attributes from pyexpat, pickle, tarfile,
pydoc, tkinter, and xml.parsers.expat. This were useless version constants pydoc, tkinter, and xml.parsers.expat. This were useless version constants
left over from the Mercurial transition left over from the Mercurial transition
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment