Skip to content

Instantly share code, notes, and snippets.

@mml
Last active December 11, 2024 22:45
Show Gist options
  • Save mml/ea8c4f64cea4552f95c3653c7dec5152 to your computer and use it in GitHub Desktop.
Save mml/ea8c4f64cea4552f95c3653c7dec5152 to your computer and use it in GitHub Desktop.
Fast even digits
import sys
import timeit
def high_enough(x, lo):
return x < 10*lo
def even_digits(x):
(lo, divisor) = (10, 10)
while not high_enough(x, lo):
lo *= 100
divisor *= 10
if lo <= x:
left = x // divisor
right = x - (left * divisor)
return (left, right)
return False
if __name__ == '__main__':
def ranges():
return [range(15),
range(100, 1_015),
range(10_000, 100_015),
range(1_000_000, 10_000_015),
range(sys.maxsize-10_000_000, sys.maxsize)]
def interesting_number():
for r in ranges():
for i in r:
yield i
checked = False
numgen = False
def setup():
global checked
global numgen
checked = 0
numgen = interesting_number()
def checker():
global checked
checked += 1
return even_digits(next(numgen))
t = timeit.Timer(setup=setup,stmt=checker)
n = sum([len(r) for r in ranges()])
secs = t.timeit(n)
print(f'Checked {n} numbers in {secs}s ({1e9*secs/n:.0f}ns ave)')
# % python even-digits.py
# Checked 19090960 numbers in 15.985163406003267s (837ns ave)
import sys
import timeit
import math
import itertools
# Faster for large integers, but requires a very precise log function.
def log_even_digits(x):
if x < 1:
return False
logx = math.log(x, 100)
if logx%1 < 0.5:
return False
divisor = 10 ** math.ceil(logx)
left = x // divisor
right = x - (left * divisor)
return (left, right)
# Refined version of original, a bit less multiplication
def even_digits(x):
(hix, divisor) = (100, 10)
while x > hix:
hix *= 100
divisor *= 10
lo = hix // 10
if lo <= x:
left = x // divisor
right = x - (left * divisor)
return (left, right)
return False
# No multiplication, no logarithms, just a lookup table.
# (lo, hix, divisor)
steps = [(100**x/10, 100**x, 10**x) for x in range(1, math.ceil(math.log(sys.maxsize, 100)))]
def fast_even_digits(x):
for (lo, hix, divisor) in steps:
if x < hix:
if x < lo:
return False
left = x // divisor
right = x - (left * divisor)
return (left, right)
return False
if __name__ == '__main__':
checked = False
numgen = False
n = False
def setup(start, stop):
global checked
global numgen
global n
checked = 0
r = iter(range(start, stop))
lenr = stop-start
numgen = r
n = lenr
while n < 10_000_000:
numgen = itertools.chain(iter(range(start, stop)), numgen)
n += lenr
def checker(f):
global checked
checked += 1
return f(next(numgen))
for (start,stop) in [(0,1_000_000), (1_000_000, 10_000_000), (sys.maxsize-10_000_000, sys.maxsize)]:
print("==========")
for f in ('log_even_digits', 'even_digits', 'fast_even_digits'):
setup(start, stop)
t = timeit.Timer(setup='',stmt=f'checker({f})',globals=globals())
secs = t.timeit(n)
print(f'{f}({start}->{stop}): {n} checks in {secs:.3f}s ({1e9*secs/n:.0f}ns ave)')
# ==========
# log_even_digits(0->1000000): 10000000 checks in 4.476s (448ns ave)
# even_digits(0->1000000): 10000000 checks in 3.331s (333ns ave)
# fast_even_digits(0->1000000): 10000000 checks in 3.293s (329ns ave)
# ==========
# log_even_digits(1000000->10000000): 18000000 checks in 5.394s (300ns ave)
# even_digits(1000000->10000000): 18000000 checks in 5.307s (295ns ave)
# fast_even_digits(1000000->10000000): 18000000 checks in 4.972s (276ns ave)
# ==========
# log_even_digits(9223372036844775807->9223372036854775807): 10000000 checks in 3.484s (348ns ave)
# even_digits(9223372036844775807->9223372036854775807): 10000000 checks in 7.204s (720ns ave)
# fast_even_digits(9223372036844775807->9223372036854775807): 10000000 checks in 4.251s (425ns ave)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment