【问题标题】:Calculating square root using only integer math in python在python中仅使用整数数学计算平方根
【发布时间】:2016-07-04 04:33:44
【问题描述】:

我正在开发一个不支持浮点数学的微控制器。仅限整数数学。因此,没有 sqrt() 函数,我无法导入任何数学模块。 MCU 正在运行支持八种 Python 数据类型的 python 子集:无、整数、布尔值、字符串、函数、元组、字节列表和迭代器。另外,MCU不能做楼层划分(//)。

我的问题是我需要计算 3 个有符号整数的大小。

mag = sqrt(x**2+y**2+z**2)

FWIW,值只能在 +/-1024 范围内,我只需要一个近似值。有没有人有解决这个问题的模式?

【问题讨论】:

  • 所以你需要最接近sqrt(x**2+y**2+z**2)的int?
  • “关闭”是什么意思?速度有多重要?您可以使用预先计算的常量表吗?如果可以,您可以负担多少字节的预先计算的数据? MCU 支持什么 - 整数加法?右移?整数乘法?以及什么大小的整数?
  • 真的需要幅度,还是直接使用幅度平方有效?
  • 啊,好问题@o11c ...
  • 它是否支持除法?地板除法运算符是最近添加的,用于区分实际除法,如果仅支持整数,则没有意义的区别。但是首先,除法通常很昂贵。使用移位、加法和乘法的逐次逼近不太可能更便宜,这反过来可能会受益于 ctz 或 clrsb 等操作以找到最重要的 1 位。我很好奇您使用的是什么类似 Python 的系统。也许是 PyMite?

标签: python math square-root


【解决方案1】:

请注意,最大可能的和是 3*1024**2,因此最大可能的平方根是 1773(下限 - 或 1774 舍入)。

因此,您可以简单地将 0 作为开始猜测,然后反复加 1,直到平方超过总和。这不会超过大约 1770 次迭代。

当然,这可能太慢了。简单的二分搜索可以将其减少到 11 次迭代,并且不需要除法(我假设 MCU 可以右移 1 位,这与地板除以 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))

根据您所说的,这会检查所有可能的输入 - 由于二进制搜索在所有情况下都很难正确,因此最好检查每个情况!在“真正的”机器上不需要很长时间;-)

可能很重要

为防止可能的溢出并显着加快速度,请将 lohi 的初始化更改为:

    hi = 1
    while hi * hi <= n:
        hi <<= 1
    lo = hi >> 1

然后运行时间与结果中的位数成正比,大大加快了较小的结果。事实上,对于“关闭”的草率足够定义,你可以停在那里。

为了后代;-)

看起来 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))

【讨论】:

  • 这和我的一样……除了它总是向上取整而不是向下取整……(即 10.01 -> 11)但是 +1 因为这很快
  • 在没有定义“close”的情况下... ;-) 但是可以对二进制搜索进行编码以计算真实 sqrt 的下限或上限,或选择@987654328 @ 使得 i**2 最接近输入 n。例如,如果它“自然”生成地板,那么要获得天花板,只需在末尾添加 if i*i != n: i += 1
  • @TimPeters,我错了范围。 adxl345 将加速度数据存储在每个轴的 2 个八位寄存器中。我必须转移 msbyte
  • 感谢您抽出宝贵时间帮助我。我会将答案标记为正确。
  • @cce1911,如果您错过了对已删除答案的评论:检查 sqrt(n) &gt; t 是否与检查 n &gt; t*t 是否相同。所以你根本不需要平方根。如果您的阈值是t,只需将x**2 + y**2 + z**2t**2 进行比较。
【解决方案2】:

有一种算法可以计算它,但它使用地板除法,没有这个是我想到的

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")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多