求平方根算法 Heron’s algorithm

求平方根问题

  • 概述:本文介绍一个古老但是高效的求平方根的算法及其python实现,分析它为什么可以快速求解,并说明它为何就是牛顿迭代法的特例。
  • 问题:求一个正实数的平方根。

    给定正实数 \(m\),如何求其平方根\(\sqrt{m}\)?
    你可能记住了一些完全平方数的平方根,比如\(4, 9, 16, 144, 256\)等。那其它数字(比如\(105.6\))的平方根怎么求呢?

    实际上,正实数的平方根有很多算法可以求。这里介绍一个最早可能是巴比伦人发现的算法,叫做Heron’s algorithm。

算法描述

假设要求的是\(x = \sqrt{m}\),Heron’s 算法是一个迭代方法。
在迭代的第\(k=0\)步,算法随机找一个正数作为\(x\)的初始值,记为\(x_0\),例如\(x_0 = 1\)。
在第\(k \in [1, 2, ...]\)步,计算 \(x_{k} = \frac{x_{k-1}}{2} + \frac{m}{2 x_{k-1}}\)。
每迭代一步,算法计算\(x_k\)与\(x_{k-1}\)的变化,\(\Delta_k = |x_k - x_{k-1}|\),若 \(\Delta_k < \epsilon\),则算法结束,输出\(x_k\)。
这里\(\epsilon\)是计算精度要求,可以取一个小正数,如\(1e-14\)。算法描述如下:

  1. 初始化 \(x = 1\);
  2. 计算 \(\Delta_k = |x_k - x_{k-1}|\);
  3. 若\(\Delta_k < \epsilon\),返回\(x\),算法结束;
  4. \(x \leftarrow \frac{x}{2} + \frac{m}{2 x}\);
  5. 返回第2步;

在 \(m\)比较大时,可能会出现迭代次数增多,数值不稳定的情况。
我们可以通过将\(m\)缩放到一个较小的区间,求缩放后的平方根,然后再反缩放得到\(m\)的平方根。
考虑任意一个正实数 \(m\),我们总是可以将\(m\)看作两个正实数的乘积 \(m = n * 4^u\),其中\(n \in [0.5, 2]\)。
此时有 \(\sqrt{m} = \sqrt{n} * \sqrt{2^{2u}} = 2^u * \sqrt{n}\)。
因此只需要计算 \(\sqrt{n}\),计算结果乘以\(2^u\),就可以得到 \(\sqrt{m} = 2^u *\sqrt{n}\)。
由于\(n \in [0.5, 2]\),求根过程更容易,误差也可以得到较好的控制。
这里的\(u\)是将\(m\)连续\(u\)次除以4,或者将\(m\)连续\(u\)次乘以4来缩放到指定的\([0.5, 2]\)区间:

  1. 初始化 \(u=0\);
  2. 若 \(0.5 \leq m \leq 2\),返回 \(u\),算法结束;
  3. 若 \(m > 2\): \(m \leftarrow m / 4\), \(u \leftarrow u+1\);
  4. 若 \(m < 1\): \(m \leftarrow m * 4\), \(u \leftarrow u-1\);
  5. 返回第2步;

代码实现

"""
Heron's 求平方根算法
@data_algorithms
"""

def heron(m, epsilon=1e-15):
    """
    Heron's 求根算法
    @m: 带求根的正实数
    @epsilon: 迭代结束条件
    @return: m的平方根
    """
    if m <= 0:
        raise ValueError("Non-negative real numbers only.")
    m, u = scale(m) #m缩放到[0.5, 2]区间
    x = 1
    delta = abs(x)
    while delta >= epsilon:
        newx = 0.5 * (x + m / x)
        delta = abs(newx - x)
        x = newx
    return x * (2 ** u)

def scale(m, base=4):
    """
    @m: 正实数m
    @base: m = base ^ u + m~
    @return: m~, u
    """
    u = 0
    while m > 2:
        m = m / 4
        u += 1
    while m < 0.5:
        m = m * 4
        u -= 1
    return m, u

def heron_test():
    from math import sqrt
    from random import random
    epsilon = 1e-15
    for _ in range(100000):
        m = random()* 1e10
        error = abs(heron(m) - sqrt(m))
        assert error < 1e-10

if __name__ == "__main__":
    from math import sqrt
    for x in [105.6, 0.1, 0.5, 1.5, 2,
              4, 10, 123, 256, 1234]:
        print(x, heron(x), sqrt(x))
    heron_test()

算法分析

为什么Heron’s 算法能够快速找到平方根呢?

我们可以通过观察每一步迭代的误差来进行分析。

假设要求解的真值为 \(x\),即\(x=\sqrt{m} \Rightarrow m = x^2 = m\)。
在第\(k\)步,算法的误差是\(e_k = (x_k - x)\)。
在第\(k+1\)步,\(x_{k+1} = \frac{x_{k}}{2} + \frac{m}{2 x_{k}}\), 其误差是 $e_{k+1} = (x_{k+1} - x) =(\frac{x_{k}}{2} + \frac{m}{2 x_{k}} - x) = (\frac{x_k - x}{2} + \frac{m - x_k x}{2x_k}) =(\frac{e_k}{2} - \frac{x(x_k - x)}{2x_k}) = (\frac{e_k}{2} - \frac{xe_k}{2x_k}) = \frac{e_k^2}{2x_k} $。
所以 \(e_{k+1} = \frac{e_k^2}{2x_k}\),即后一次迭代的误差与前一次的平方成正比。
只要某一步的误差在\((0, 1)\)区间内,则误差会快速的缩小,所以算法可以快速地找到平方根。

与牛顿法的关系

这个算法其实就是在用牛顿法求一个函数的根,所以是牛顿法的一个特列。

为什么呢?牛顿法求根的迭代公式是:\(x_{k} = x_{k-1} - f(x_{k-1}) / f'(x_{k-1})\)。
这里令 \(f(x) = m - x^2\),则有 \(f'(x) = -2x\),所以 \(f(x) / f'(x) = -(m - x^2)/(2x)\),牛顿法迭代公式就变为 \(x_{k} = x_{k-1} + (m - x_{k-1}^2)/(2x_{k-1}) = x_{k-1} + m / (2x_{k-1}) - x_{k-1} / 2 = x_{k-1} / 2 + m / (2x_{k-1})\),这也就是Heron‘s的迭代公式了。

原文地址:https://www.cnblogs.com/data_algorithms/p/heron_algorithm_square_root_python.html

时间: 2024-08-25 17:56:22

求平方根算法 Heron’s algorithm的相关文章

经典算法:牛顿迭代法求平方根

//牛顿迭代法求平方根 1 double mysqrt(double num) 2 { 3 double x = num/2; 4 double y = 0; 5 do{ 6 x = x/2+num/(2*x); 7 y = x*x-num; 8 if(y<0) y = -y; 9 } while(y>0.0001); 10 return x; 11 } 12 int main(int argc, char* argv[]) 13 { 14 printf("%.3f",my

算法练习之牛顿法求平方根

牛顿法求平方根公式:Xn+1 = 1/2 * (Xn+ a/Xn); 若求a的平方根,将公式进行迭代计算迭代越多,越接近结果最后Xn为a的平方根 代码实现: 参数:要求平方根的数,迭代次数 var sqrt = function (a,accur){ var pre = 1; for(var i = 0;i<accur;i++){ var cur = 1/2 * (pre + a/pre); pre = cur; } return cur; } console.log(sqrt(2,10000)

求平方根的倒数速算法--向卡马克等人致敬

昨日,风雨交加,气温骤降,所有人都蜷缩在不暖和的厚衣服里,无神的盯着显示器.我也不例外,颤抖的手点击着鼠标,一边埋怨这天气,一边埋怨这电脑.突然,一段代码映入眼帘,定睛一看,没看懂,代码是这样的: float Q_rsqrt( float number ) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = * ( long * ) &y; // 这TM是啥 i = 0

C语言之基本算法11—牛顿迭代法求平方根

//迭代法 /* ================================================================== 题目:牛顿迭代法求a的平方根!迭代公式:Xn+1=(Xn+a/Xn)/2. ================================================================== */ #include<stdio.h> #include<math.h> main() { float a,x0,x1;

利用牛顿迭代法求平方根

求n的平方根,先如果一推測值X0 = 1,然后依据下面公式求出X1,再将X1代入公式右边,继续求出X2…通过有效次迭代后就可以求出n的平方根,Xk+1 先让我们来验证下这个巧妙的方法准确性,来算下2的平方根 (Computed by Mathomatic) 1-> x_new = ( x_old + y/x_old )/2 y (x_old + -----) x_old #1: x_new = --------------- 2 1-> calculate x_old 1 Enter y: 2

[转]快速平方根算法

在3D图形编程中,经常要求平方根或平方根的倒数,例如:求向量的长度或将向量归一化.C数学函数库中的sqrt具有理想的精度,但对于3D游戏程式来说速度太慢.我们希望能够在保证足够的精度的同时,进一步提高速度. Carmack在QUAKE3中使用了下面的算法,它第一次在公众场合出现的时候,几乎震住了所有的人.据说该算法其实并不是Carmack发明的,它真正的作者是Nvidia的Gary Tarolli(未经证实). // // 计算参数x的平方根的倒数 // float InvSqrt (float

速求平方根倒数

在游戏3D建模方面很多时候要用到求平方根的倒数,而本文章打算介绍的算法会比正常算法快上4倍左右.这对于产品性能将是一个大幅度的提高. 那我们要从哪里开始呢?首先不得不提一提 idsoftware.这是一个创建之初只有13个人的小公司,但它推出的毁灭战士(DOOM)系列游戏可以说改变了游戏世界,极大地推动了游戏产业的发展,因为在当时贫瘠的电脑性能的支撑下,一个开发者能够在游戏中加入一段流畅的动画都会让人惊叹不已,而我们所说的idsoftware,在那个年代就已经做出了画面远超同代其余作品的游戏,像

牛顿法求平方根

求平方根的方法有很多种,这里介绍的是牛顿法求平方根. 方法是这样的:如果对x的平方根的值有了一个猜测y,那么就可以通过执行一个简单操作去得到一个更好的猜测:只需求出y和x/y的平均值(他更接近实际的平方根值) 代码实现: float sqrt(float x) { float guess = x; while (guess * guess - x > 0.0001) { guess = (guess + x / guess) / 2; } return guess; } 注:这一平方根算法实际上

Leetcode 69. Sqrt(x) 解题报告【C库函数sqrt(x)模拟-求平方根】

69. Sqrt(x) Total Accepted: 93296 Total Submissions: 368340 Difficulty: Medium 提交网址: https://leetcode.com/problems/sqrtx/ Implement int sqrt(int x). Compute and return the square root of x. 分析: 解法1:牛顿迭代法(牛顿切线法) Newton's Method(牛顿切线法)是由艾萨克·牛顿在<流数法>(M