如何用代码计算平方根

269 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

平方根定义

有一个有理数n>=0有理数n>=0, 如果有m2=nm^2=n, 那么 m 就是 n 的平方根。我们知道,m一般有两个,正负各一个。这里规定一下,本文讲的是m>=0m>=0

一个粗劣的方法

比如说,我们要计算 x2=88x^2 = 88 这个xx的值。可以这样

  • 先比较 8828888^2 和 88 , 显然 882>8888^2 > 88
  • 再取一半44 , 比较 4428844^2 和 88, 显然 442>8844^2 > 88
  • ...
  • ...
  • ...
  • 取 11, 比较 1128811^2 和 88, 显然 112>8811^2 > 88
  • 取 5.5, 比较 5.5288{5.5}^2 和 88, 显然 5.52<885.5^2 < 88
  • 好,那么往上取,11和5.5取一个中间数,8.25
  • 比较 8.25288{8.25}^2 和 88, 8.252=68.0625<88{8.25}^2 = 68.0625 < 88
  • 再往上取,8.2511取一个中间数,9.625{8.25 和 11取一个中间数, 9.625}
  • 比较 9.625288{9.625}^2 和 88, 9.6252=92.640625>88{9.625}^2 = 92.640625 > 88
  • 此时,往下取 8.259.625取一个中间数,8.9375{8.25 和 9.625 取一个中间数, 8.9375}
  • 比较 8.9375288{8.9375}^2 和 88, 8.93752=79.87890625<88{8.9375}^2 = 79.87890625 < 88
  • 此时,往上取 8.93759.625取一个中间数,9.28125{8.9375 和 9.625 取一个中间数, 9.28125}
  • 比较 9.28125288{9.28125}^2 和 88, 9.281252=86.1416015625<88{9.28125}^2 = 86.1416015625 < 88, 此时很逼近了
  • 继续此步骤
  • ...
  • ...
  • ...
  • ...
  • ...

总不能无限循环下去吧,所以我们搞一个终止条件,x288相差不超过0.001x^2 与 88 相差不超过0.001, 我们就认为找到了。

代码实现(Golang)

func sqrt(input float64) float64 {
	var x float64 = input
	up, down := input, float64(1)
	var compare = func(a, b float64) int {
		if a > b {
			if a-b < 0.001 {
				return 0
			}
			return 1
		}
		if b-a < 0.001 {
			return 0
		}
		return -1
	}
	for loop := 0; ; loop++ {
		if res := compare(x*x, input); res > 0 {
			up = x
			x = (up + down) / 2
		} else if res < 0 {
			down = x
			x = (up + down) / 2
		} else {
			fmt.Println("loop", loop, x)
			return x
		}
	}
}

这里值得关注的有下面几点:

    1. 这个函数只能计算 input>1input > 1,因为对于 input<1,input>inputinput < 1, \sqrt{input} > input, 此时迭代方向应该翻转。
    1. 如果想计算input<1input < 1, 只需要根据一个公式:input=11input \sqrt{input} = \frac{1}{\sqrt{\frac{1}{input}}}, 也就是说,先算 1input\frac{1}{input}的平方根,然后倒过来就行了。这是因为1input>1\frac{1}{input}>1
    1. 注意其中的compare函数,我们这里精确到正负0.001正负0.001

牛顿迭代法

我们这里给出一个递推公式:

xk+1=12(xk+inputxk)x_{k+1} = \frac{1}{2}(x_k + \frac{input}{x_k})

比如说要计算88,input就是88\sqrt {88}, input 就是 88

这个递推过程,如下:

  • x0=1x_0 = 1 代入上式计算 x1=12(x0+88x0)=44.5x_1 = \frac{1}{2}(x_0 + \frac{88}{x_0}) = 44.5
  • x1=44.5x_1 = 44.5 代入上式计算 x2=12(x1+88x1)=23.23876404494382x_2 = \frac{1}{2}(x_1 + \frac{88}{x_1}) = 23.23876404494382
  • 依次往下一直计算
  • 计算次数越多,xkx_k就越逼近 88\sqrt{88}。那么也是要搞一个终止条件的,这个根据你需求的精确度来定。

牛顿迭代法代码(Golang)

这里把上面的compare函数弄到外面去

func compare(a, b float64) int {
	if a > b {
		if a-b < 0.001 {
			return 0
		}
		return 1
	}
	if b-a < 0.001 {
		return 0
	}
	return -1
}

func sqrt_newton(input float64) float64 {
	var x float64 = 1
	for loop := 0; ; loop++ {
		x = 0.5 * (x + input/x)
		fmt.Println(loop, x)
		if res := compare(x*x, input); res == 0 {
			return x
		}
	}
}

这个注意点:

  • compare的精确度也是0.001
  • 我们用input=88input=88作为例子,经过运行,我们发现,逼近同样的精确度时,牛顿法只用了5次。
  • 上面的粗劣迭代法,用了20次。
  • 这里可见牛顿法快多了。