Skip to content

Commit 103fe1e

Browse files
committed
_pydecimal: add helpers for computing len(str(q)) < a
1 parent 329c31a commit 103fe1e

File tree

1 file changed

+72
-7
lines changed

1 file changed

+72
-7
lines changed

Lib/_pydecimal.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -450,19 +450,84 @@ def IEEEContext(bits, /):
450450
#
451451
# See https://github.com/python/cpython/issues/140036 for details.
452452

453-
# Claim: If 0 < z <= log2(10) and q.bit_length() < a*z, then q < 10**a.
454-
# Proof: By contradiction, q >= 10**a. By definition,
455-
# log2(q) >= a*log2(10) >= a*z > q.bit_length().
456-
# In particular, q > 2**q.bit_length(), which is impossible.
457453
_LOG_10_BASE_2_LO = float.fromhex('0x1.a934f0979a371p+1')
458454
assert pow(2, _LOG_10_BASE_2_LO) < 10
459455

460-
# Claim: If z > log2(10) and q.bit_length() >= 1 + a*z, then q > 10**a.
461-
# Proof: Since q >= 2**(q.bit_length()-1), we have
462-
# q >= 2**(q.bit_length()-1) >= 2**(a*z) > 2**(a*log2(10)) = 10**a.
463456
_LOG_10_BASE_2_HI = float.fromhex('0x1.a934f0979a372p+1')
464457
assert pow(2, _LOG_10_BASE_2_HI) > 10
465458

459+
460+
def _tento(n):
461+
"""Compute 10 ** n with 1 base-5 exponentiation and 1 bit-shift."""
462+
return (5 ** n) << n
463+
464+
465+
def _is_leq_than_pow10a_use_str(q, a):
466+
"""Try to efficiently check len(str(q)) <= a, or equivalently q < 10**a.
467+
468+
If it is not possible to efficiently compute len(str(q)),
469+
this explicitly compute str(q) instead.
470+
471+
Return (len(str(q)) <= a, None) or (len(str(q)) <= a, str(q)).
472+
"""
473+
if q.bit_length() < a * _LOG_10_BASE_2_LO:
474+
# Claim: If 0 < z <= log2(10) and q.bit_length() < a*z, then q < 10**a.
475+
# Proof: By contradiction, q >= 10**a. By definition,
476+
# log2(q) >= a*log2(10) >= a*z > q.bit_length().
477+
# In particular, q > 2**q.bit_length(), which is impossible.
478+
479+
# assert q < 10 ** context.prec
480+
return True, None
481+
elif q.bit_length() >= 1 + a * _LOG_10_BASE_2_HI:
482+
# Claim: If z > log2(10) and q.bit_length() >= 1 + a*z, then q > 10**a.
483+
# Proof: Since q >= 2**(q.bit_length()-1), we have
484+
# q >= 2**(q.bit_length()-1) >= 2**(a*z) > 2**(a*log2(10)) = 10**a.
485+
486+
# assert q > 10 ** context.prec
487+
return False, None
488+
# Handles other cases due to floating point precision loss
489+
# when computing _LOG_10_BASE_2_LO and _LOG_10_BASE_2_HI.
490+
str_q = str(q) # can raise a ValueError
491+
is_valid = len(str_q) <= a
492+
return is_valid, str_q
493+
494+
495+
def _is_leq_than_pow10a(q, a, *, exact=True, ulp_order=20):
496+
"""Check that len(str(q)) <= a without computing str(q).
497+
498+
When *exact* is false, computing len(str(q)) is replaced by f(q):
499+
500+
f(q) = floor(log10(q) + ulp(log10(q)) * ulp_order + 1.0)
501+
502+
Most of the time, f(q) = len(str(q)) but in some cases, it may
503+
happen that f(q) > len(str(q)).
504+
505+
When *exact* is true, computing len(str(q)) requires one bigint
506+
exponentiation that only depends on q.
507+
"""
508+
509+
if q < 10:
510+
return a >= 1
511+
512+
z = _math.log10(q)
513+
t = _math.ulp(z) * ulp_order
514+
515+
if exact:
516+
intlo = int(z - t)
517+
inthi = int(z + t)
518+
diff = inthi - intlo
519+
assert diff in (0, 1)
520+
if diff == 1:
521+
lo = _tento(inthi) # may be slow
522+
if q < lo:
523+
inthi -= 1
524+
assert q >= (lo // 10)
525+
ndigits = inthi + 1
526+
else:
527+
ndigits = int(z + t + 1.0)
528+
return ndigits <= a
529+
530+
466531
# Do not subclass Decimal from numbers.Real and do not register it as such
467532
# (because Decimals are not interoperable with floats). See the notes in
468533
# numbers.py for more detail.

0 commit comments

Comments
 (0)