In response to the possible inaccuracy of math.sqrt
I have benchmarked two different methods for performing an isqrt(n)
call. isqrt_2(n)
is coming from this article and this C code
The most common method seen:
def isqrt_1(n):
x = n
while True:
y = (n // x + x) // 2
if x <= y:
return x
x = y
cProfile.run('isqrt_2(10**308)')
Benchmark results:
isqrt_1 at 10000 iterations: 12.25
Can perform 816 calls per second.
10006 function calls in 12.904 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 12.904 12.904 :1()
1 0.690 0.690 12.904 12.904 math.py:10(func)
10000 12.213 0.001 12.213 0.001 math.py:24(isqrt_1)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {range}
2 0.000 0.000 0.000 0.000 {time.time}
This method is incredibly slow. So we try the next method:
def isqrt_2(n):
if n < 0:
raise ValueError('Square root is not defined for negative numbers.')
x = int(n)
if x == 0:
return 0
a, b = divmod(x.bit_length(), 2)
n = 2 ** (a + b)
while True:
y = (n + x // n) >> 1
if y >= n:
return n
n = y
cProfile.run('isqrt_2(10**308)')
Benchmark results:
isqrt_2 at 10000 iterations: 0.391000032425
Can perform 25575 calls per second.
30006 function calls in 1.059 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.059 1.059 :1()
1 0.687 0.687 1.059 1.059 math.py:10(func)
10000 0.348 0.000 0.372 0.000 math.py:34(isqrt_2)
10000 0.013 0.000 0.013 0.000 {divmod}
10000 0.011 0.000 0.011 0.000 {method 'bit_length' of 'long' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {range}
2 0.000 0.000 0.000 0.000 {time.time}
As you can see, the difference in isqrt_1(n)
and isqrt_2(n)
is an amazing 11.858999967575 seconds
faster.
You can see this in action on Ideone.com or get the code
note: Ideone.com resulted in execution timeout for isqrt_1(n)
so the benchmark was reduced to 10**200