在 python 中仅使用整数数学计算平方根
Calculating square root using only integer math in python
我正在研究不支持浮点数学的微控制器。仅限整数数学。因此,没有 sqrt() 函数,我无法导入任何数学模块。 MCU 运行 python 的子集,支持八种 Python 数据类型:None、整数、布尔值、字符串、函数、元组、字节列表和迭代器。另外,MCU不能做楼层划分(//)。
我的问题是我需要计算 3 个有符号整数的大小。
mag = sqrt(x**2+y**2+z**2)
FWIW,这些值只能在 +/-1024 的范围内,我只需要一个近似值。有没有人有解决这个问题的模式?
有一个算法可以计算它,但是它使用了楼层划分,没有这个就是我想到的
def isqrt_linel(n):
x = 0
while (x+1)**2 <= n:
x+=1
return x
顺便说一句,我知道的算法使用牛顿法:
def isqrt(n):
#https://en.wikipedia.org/wiki/Integer_square_root
#https://gist.github.com/bnlucas/5879594
if n>=0:
if n == 0:
return 0
a, b = divmod(n.bit_length(), 2)
x = 2 ** (a + b)
while True:
y = (x + n // x) >> 1
if y >= x:
return x
x = y
else:
raise ValueError("negative number")
请注意,最大可能的总和为 3*1024**2,因此最大可能的平方根为 1773(下限 - 或 1774 四舍五入)。
所以你可以简单地以0作为起始猜测,并重复加1直到平方超过总和。这不会超过 1770 次迭代。
当然这可能太慢了。一个简单的二进制搜索可以将其减少到 11 次迭代,并且不需要除法(我假设 MCU 可以右移 1 位,这与 floor-division by 2 相同)。
编辑
这是一些代码,用于二分搜索返回真平方根的底数:
def isqrt(n):
if n <= 1:
return n
lo = 0
hi = n >> 1
while lo <= hi:
mid = (lo + hi) >> 1
sq = mid * mid
if sq == n:
return mid
elif sq < n:
lo = mid + 1
result = mid
else:
hi = mid - 1
return result
检查,运行:
from math import sqrt
assert all(isqrt(i) == int(sqrt(i)) for i in range(3*1024**2 + 1))
这会根据您所说的内容检查所有可能的输入 - 由于二进制搜索在所有情况下都很难正确处理,因此最好检查每种情况!在 "real" 机器上不需要很长时间 ;-)
可能很重要
为了防止可能的溢出并显着加快它的速度,将 lo
和 hi
的初始化更改为:
hi = 1
while hi * hi <= n:
hi <<= 1
lo = hi >> 1
然后 运行时间与结果中的位数成正比,大大加快了较小结果的速度。事实上,对于 "close" 的足够 定义,你可以就此打住。
为了子孙后代 ;-)
看起来 OP 实际上根本不需要平方根。但是对于可能负担不起除法的人来说,这里有一个简化版本的代码,也从初始化中删除了乘法。注意:我没有使用 .bit_length()
,因为许多已部署的 Python 版本不支持它。
def isqrt(n):
if n <= 1:
return n
hi, hisq = 2, 4
while hisq <= n:
hi <<= 1
hisq <<= 2
lo = hi >> 1
while hi - lo > 1:
mid = (lo + hi) >> 1
if mid * mid <= n:
lo = mid
else:
hi = mid
assert lo + 1 == hi
assert lo**2 <= n < hi**2
return lo
from math import sqrt
assert all(isqrt(i) == int(sqrt(i)) for i in range(3*1024**2 + 1))
我正在研究不支持浮点数学的微控制器。仅限整数数学。因此,没有 sqrt() 函数,我无法导入任何数学模块。 MCU 运行 python 的子集,支持八种 Python 数据类型:None、整数、布尔值、字符串、函数、元组、字节列表和迭代器。另外,MCU不能做楼层划分(//)。
我的问题是我需要计算 3 个有符号整数的大小。
mag = sqrt(x**2+y**2+z**2)
FWIW,这些值只能在 +/-1024 的范围内,我只需要一个近似值。有没有人有解决这个问题的模式?
有一个算法可以计算它,但是它使用了楼层划分,没有这个就是我想到的
def isqrt_linel(n):
x = 0
while (x+1)**2 <= n:
x+=1
return x
顺便说一句,我知道的算法使用牛顿法:
def isqrt(n):
#https://en.wikipedia.org/wiki/Integer_square_root
#https://gist.github.com/bnlucas/5879594
if n>=0:
if n == 0:
return 0
a, b = divmod(n.bit_length(), 2)
x = 2 ** (a + b)
while True:
y = (x + n // x) >> 1
if y >= x:
return x
x = y
else:
raise ValueError("negative number")
请注意,最大可能的总和为 3*1024**2,因此最大可能的平方根为 1773(下限 - 或 1774 四舍五入)。
所以你可以简单地以0作为起始猜测,并重复加1直到平方超过总和。这不会超过 1770 次迭代。
当然这可能太慢了。一个简单的二进制搜索可以将其减少到 11 次迭代,并且不需要除法(我假设 MCU 可以右移 1 位,这与 floor-division by 2 相同)。
编辑
这是一些代码,用于二分搜索返回真平方根的底数:
def isqrt(n):
if n <= 1:
return n
lo = 0
hi = n >> 1
while lo <= hi:
mid = (lo + hi) >> 1
sq = mid * mid
if sq == n:
return mid
elif sq < n:
lo = mid + 1
result = mid
else:
hi = mid - 1
return result
检查,运行:
from math import sqrt
assert all(isqrt(i) == int(sqrt(i)) for i in range(3*1024**2 + 1))
这会根据您所说的内容检查所有可能的输入 - 由于二进制搜索在所有情况下都很难正确处理,因此最好检查每种情况!在 "real" 机器上不需要很长时间 ;-)
可能很重要
为了防止可能的溢出并显着加快它的速度,将 lo
和 hi
的初始化更改为:
hi = 1
while hi * hi <= n:
hi <<= 1
lo = hi >> 1
然后 运行时间与结果中的位数成正比,大大加快了较小结果的速度。事实上,对于 "close" 的足够 定义,你可以就此打住。
为了子孙后代 ;-)
看起来 OP 实际上根本不需要平方根。但是对于可能负担不起除法的人来说,这里有一个简化版本的代码,也从初始化中删除了乘法。注意:我没有使用 .bit_length()
,因为许多已部署的 Python 版本不支持它。
def isqrt(n):
if n <= 1:
return n
hi, hisq = 2, 4
while hisq <= n:
hi <<= 1
hisq <<= 2
lo = hi >> 1
while hi - lo > 1:
mid = (lo + hi) >> 1
if mid * mid <= n:
lo = mid
else:
hi = mid
assert lo + 1 == hi
assert lo**2 <= n < hi**2
return lo
from math import sqrt
assert all(isqrt(i) == int(sqrt(i)) for i in range(3*1024**2 + 1))