19
19
except ImportError :
20
20
_decimal = None
21
21
22
+ # A number of functions have this form, where `w` is a desired number of
23
+ # digits in base `base`:
24
+ #
25
+ # def inner(...w...):
26
+ # if w <= LIMIT:
27
+ # return something
28
+ # lo = w >> 1
29
+ # hi = w - lo
30
+ # something involving base**lo, inner(...lo...), j, and inner(...hi...)
31
+ # figure out largest w needed
32
+ # result = inner(w)
33
+ #
34
+ # They all had some on-the-fly scheme to cache `base**lo` results for reuse.
35
+ # Power is costly.
36
+ #
37
+ # This routine aims to compute all amd only the needed powers in advance, as
38
+ # efficiently as reasonably possible. This isn't trivial, and all the
39
+ # on-the-fly methods did needless work in many cases. The driving code above
40
+ # changes to:
41
+ #
42
+ # figure out largest w needed
43
+ # mycache = compute_powers(w, base, LIMIT)
44
+ # result = inner(w)
45
+ #
46
+ # and `mycache[lo]` replaces `base**lo` in the inner function.
47
+ #
48
+ # While this does give minor speedups (a few percent at best), the primary
49
+ # intent is to simplify the functions using this, by eliminating the need for
50
+ # them to craft their own ad-hoc caching schemes.
51
+ def compute_powers (w , base , more_than , show = False ):
52
+ seen = set ()
53
+ need = set ()
54
+ ws = {w }
55
+ while ws :
56
+ w = ws .pop () # any element is fine to use next
57
+ if w in seen or w <= more_than :
58
+ continue
59
+ seen .add (w )
60
+ lo = w >> 1
61
+ # only _need_ lo here; some other path may, or may not, need hi
62
+ need .add (lo )
63
+ ws .add (lo )
64
+ if w & 1 :
65
+ ws .add (lo + 1 )
66
+
67
+ d = {}
68
+ if not need :
69
+ return d
70
+ it = iter (sorted (need ))
71
+ first = next (it )
72
+ if show :
73
+ print ("pow at" , first )
74
+ d [first ] = base ** first
75
+ for this in it :
76
+ if this - 1 in d :
77
+ if show :
78
+ print ("* base at" , this )
79
+ d [this ] = d [this - 1 ] * base # cheap
80
+ else :
81
+ lo = this >> 1
82
+ hi = this - lo
83
+ assert lo in d
84
+ if show :
85
+ print ("square at" , this )
86
+ # Multiplying a bigint by itself (same object!) is about twice
87
+ # as fast in CPython.
88
+ sq = d [lo ] * d [lo ]
89
+ if hi != lo :
90
+ assert hi == lo + 1
91
+ if show :
92
+ print (" and * base" )
93
+ sq *= base
94
+ d [this ] = sq
95
+ return d
96
+
97
+ _unbounded_dec_context = decimal .getcontext ().copy ()
98
+ _unbounded_dec_context .prec = decimal .MAX_PREC
99
+ _unbounded_dec_context .Emax = decimal .MAX_EMAX
100
+ _unbounded_dec_context .Emin = decimal .MIN_EMIN
101
+ _unbounded_dec_context .traps [decimal .Inexact ] = 1 # sanity check
22
102
23
103
def int_to_decimal (n ):
24
104
"""Asymptotically fast conversion of an 'int' to Decimal."""
@@ -33,57 +113,32 @@ def int_to_decimal(n):
33
113
# "clever" recursive way. If we want a string representation, we
34
114
# apply str to _that_.
35
115
36
- D = decimal .Decimal
37
- D2 = D (2 )
38
-
39
- BITLIM = 128
40
-
41
- mem = {}
42
-
43
- def w2pow (w ):
44
- """Return D(2)**w and store the result. Also possibly save some
45
- intermediate results. In context, these are likely to be reused
46
- across various levels of the conversion to Decimal."""
47
- if (result := mem .get (w )) is None :
48
- if w <= BITLIM :
49
- result = D2 ** w
50
- elif w - 1 in mem :
51
- result = (t := mem [w - 1 ]) + t
52
- else :
53
- w2 = w >> 1
54
- # If w happens to be odd, w-w2 is one larger then w2
55
- # now. Recurse on the smaller first (w2), so that it's
56
- # in the cache and the larger (w-w2) can be handled by
57
- # the cheaper `w-1 in mem` branch instead.
58
- result = w2pow (w2 ) * w2pow (w - w2 )
59
- mem [w ] = result
60
- return result
116
+ from decimal import Decimal as D
117
+ BITLIM = 200
61
118
119
+ # Don't bother caching the "lo" mask in this; the time to compute it is
120
+ # tiny compared to the multiply.
62
121
def inner (n , w ):
63
122
if w <= BITLIM :
64
123
return D (n )
65
124
w2 = w >> 1
66
125
hi = n >> w2
67
- lo = n - (hi << w2 )
68
- return inner (lo , w2 ) + inner (hi , w - w2 ) * w2pow (w2 )
69
-
70
- with decimal .localcontext () as ctx :
71
- ctx .prec = decimal .MAX_PREC
72
- ctx .Emax = decimal .MAX_EMAX
73
- ctx .Emin = decimal .MIN_EMIN
74
- ctx .traps [decimal .Inexact ] = 1
126
+ lo = n & ((1 << w2 ) - 1 )
127
+ return inner (lo , w2 ) + inner (hi , w - w2 ) * w2pow [w2 ]
75
128
129
+ with decimal .localcontext (_unbounded_dec_context ):
130
+ nbits = n .bit_length ()
131
+ w2pow = compute_powers (nbits , D (2 ), BITLIM )
76
132
if n < 0 :
77
133
negate = True
78
134
n = - n
79
135
else :
80
136
negate = False
81
- result = inner (n , n . bit_length () )
137
+ result = inner (n , nbits )
82
138
if negate :
83
139
result = - result
84
140
return result
85
141
86
-
87
142
def int_to_decimal_string (n ):
88
143
"""Asymptotically fast conversion of an 'int' to a decimal string."""
89
144
w = n .bit_length ()
@@ -97,14 +152,13 @@ def int_to_decimal_string(n):
97
152
# available. This algorithm is asymptotically worse than the algorithm
98
153
# using the decimal module, but better than the quadratic time
99
154
# implementation in longobject.c.
155
+
156
+ DIGLIM = 1000
100
157
def inner (n , w ):
101
- if w <= 1000 :
158
+ if w <= DIGLIM :
102
159
return str (n )
103
160
w2 = w >> 1
104
- d = pow10_cache .get (w2 )
105
- if d is None :
106
- d = pow10_cache [w2 ] = 5 ** w2 << w2 # 10**i = (5*2)**i = 5**i * 2**i
107
- hi , lo = divmod (n , d )
161
+ hi , lo = divmod (n , pow10 [w2 ])
108
162
return inner (hi , w - w2 ) + inner (lo , w2 ).zfill (w2 )
109
163
110
164
# The estimation of the number of decimal digits.
@@ -115,7 +169,9 @@ def inner(n, w):
115
169
# only if the number has way more than 10**15 digits, that exceeds
116
170
# the 52-bit physical address limit in both Intel64 and AMD64.
117
171
w = int (w * 0.3010299956639812 + 1 ) # log10(2)
118
- pow10_cache = {}
172
+ pow10 = compute_powers (w , 5 , DIGLIM )
173
+ for k , v in pow10 .items ():
174
+ pow10 [k ] = v << k # 5**k << k == 5**k * 2**k == 10**k
119
175
if n < 0 :
120
176
n = - n
121
177
sign = '-'
@@ -128,7 +184,6 @@ def inner(n, w):
128
184
s = s .lstrip ('0' )
129
185
return sign + s
130
186
131
-
132
187
def _str_to_int_inner (s ):
133
188
"""Asymptotically fast conversion of a 'str' to an 'int'."""
134
189
@@ -144,35 +199,15 @@ def _str_to_int_inner(s):
144
199
145
200
DIGLIM = 2048
146
201
147
- mem = {}
148
-
149
- def w5pow (w ):
150
- """Return 5**w and store the result.
151
- Also possibly save some intermediate results. In context, these
152
- are likely to be reused across various levels of the conversion
153
- to 'int'.
154
- """
155
- if (result := mem .get (w )) is None :
156
- if w <= DIGLIM :
157
- result = 5 ** w
158
- elif w - 1 in mem :
159
- result = mem [w - 1 ] * 5
160
- else :
161
- w2 = w >> 1
162
- # If w happens to be odd, w-w2 is one larger then w2
163
- # now. Recurse on the smaller first (w2), so that it's
164
- # in the cache and the larger (w-w2) can be handled by
165
- # the cheaper `w-1 in mem` branch instead.
166
- result = w5pow (w2 ) * w5pow (w - w2 )
167
- mem [w ] = result
168
- return result
169
-
170
202
def inner (a , b ):
171
203
if b - a <= DIGLIM :
172
204
return int (s [a :b ])
173
205
mid = (a + b + 1 ) >> 1
174
- return inner (mid , b ) + ((inner (a , mid ) * w5pow (b - mid )) << (b - mid ))
206
+ return (inner (mid , b )
207
+ + ((inner (a , mid ) * w5pow [b - mid ])
208
+ << (b - mid )))
175
209
210
+ w5pow = compute_powers (len (s ), 5 , DIGLIM )
176
211
return inner (0 , len (s ))
177
212
178
213
@@ -186,7 +221,6 @@ def int_from_string(s):
186
221
s = s .rstrip ().replace ('_' , '' )
187
222
return _str_to_int_inner (s )
188
223
189
-
190
224
def str_to_int (s ):
191
225
"""Asymptotically fast version of decimal string to 'int' conversion."""
192
226
# FIXME: this doesn't support the full syntax that int() supports.
0 commit comments