求平方根
以下实例通过 Go 语言使用递归方法实现求平方根的代码:
package main
import (
"fmt"
)
func sqrtRecursive(x, guess, prevGuess, epsilon float64) float64 {
if diff := guess*guess - x; diff < epsilon && -diff < epsilon {
return guess
}
newGuess := (guess + x/guess) / 2
if newGuess == prevGuess {
return guess
}
return sqrtRecursive(x, newGuess, guess, epsilon)
}
func sqrt(x float64) float64 {
return sqrtRecursive(x, 1.0, 0.0, 1e-9)
}
func main() {
x := 25.0
result := sqrt(x)
fmt.Printf("%.2f 的平方根为 %.6f\n", x, result)
}
以上实例中,sqrtRecursive 函数使用递归方式实现平方根的计算。
sqrtRecursive 函数接受四个参数:
- x 表示待求平方根的数
- guess 表示当前猜测的平方根值
- prevGuess 表示上一次的猜测值
- epsilon 表示精度要求(即接近平方根的程度)
递归的终止条件是当前猜测的平方根与上一次猜测的平方根非常接近,差值小于给定的精度 epsilon。
在 sqrt 函数中,我们调用 sqrtRecursive 来计算平方根,并传入初始值和精度要求,然后在 main 函数中,我们调用 sqrt 函数来求解平方根,并将结果打印出来。
执行以上代码输出结果为:
25.00 的平方根为 5.000000
Grok3解释如下:
这段代码是用 Go 语言实现的牛顿法(Newton’s Method)来计算一个数的平方根。我们来逐步分析代码的逻辑和功能,并解释其输出。
代码结构与功能
1. sqrtRecursive
函数
func sqrtRecursive(x, guess, prevGuess, epsilon float64) float64 {
if diff := guess*guess - x; diff < epsilon && -diff < epsilon {
return guess
}
newGuess := (guess + x/guess) / 2
if newGuess == prevGuess {
return guess
}
return sqrtRecursive(x, newGuess, guess, epsilon)
}
-
参数说明:
x
:要计算平方根的数(目标值)。guess
:当前猜测的平方根值。prevGuess
:上一次的猜测值,用于检测是否收敛。epsilon
:误差范围,用于判断猜测值是否足够接近真实平方根。
-
逻辑说明:
- 首先计算当前猜测值
guess
的平方与目标值x
的差值diff
。 - 如果差值
diff
的绝对值小于epsilon
,说明当前猜测值已经足够接近真实平方根,返回guess
。 - 否则,使用牛顿法公式
newGuess := (guess + x/guess) / 2
计算一个新的猜测值。 - 如果新猜测值
newGuess
与上一次猜测值prevGuess
相等,说明已经收敛,返回当前猜测值guess
。 - 如果未收敛,递归调用
sqrtRecursive
,传入新的猜测值和其他参数。
- 首先计算当前猜测值
-
牛顿法原理:
牛顿法通过迭代公式x_{n+1} = (x_n + S/x_n)/2
逐步逼近平方根,其中S
是目标值(即x
)。每次迭代都会使猜测值更接近真实平方根。
2. sqrt
函数
func sqrt(x float64) float64 {
return sqrtRecursive(x, 1.0, 0.0, 1e-9)
}
- 这个函数是
sqrtRecursive
的入口函数。 - 它以初始猜测值
1.0
、初始上一次猜测值0.0
和误差范围1e-9
(即 10⁻⁹,精度非常高)调用递归函数。 x
是要计算平方根的输入值。
3. main
函数
func main() {
x := 25.0
result := sqrt(x)
fmt.Printf("%.2f 的平方根为 %.6f\n", x, result)
}
- 定义一个变量
x = 25.0
,表示要计算平方根的数。 - 调用
sqrt
函数计算x
的平方根,并将结果存储在result
中。 - 使用
fmt.Printf
格式化输出结果,保留x
两位小数,保留结果result
六位小数。
代码运行过程(以 x = 25.0 为例)
-
初始调用:
sqrtRecursive(25.0, 1.0, 0.0, 1e-9)
guess = 1.0
,guess*guess = 1.0
,与 25.0 差距很大,不满足误差条件。- 计算新猜测值:
newGuess = (1.0 + 25.0/1.0)/2 = 13.0
。 - 递归调用:
sqrtRecursive(25.0, 13.0, 1.0, 1e-9)
。
-
第二次调用:
sqrtRecursive(25.0, 13.0, 1.0, 1e-9)
guess = 13.0
,guess*guess = 169.0
,与 25.0 差距仍然较大。- 计算新猜测值:
newGuess = (13.0 + 25.0/13.0)/2 ≈ 7.4615
。 - 递归调用:
sqrtRecursive(25.0, 7.4615, 13.0, 1e-9)
。
-
第三次调用:
sqrtRecursive(25.0, 7.4615, 13.0, 1e-9)
guess ≈ 7.4615
,guess*guess ≈ 55.67
,仍不满足误差条件。- 计算新猜测值:
newGuess = (7.4615 + 25.0/7.4615)/2 ≈ 5.4060
。 - 递归调用:
sqrtRecursive(25.0, 5.4060, 7.4615, 1e-9)
。
-
第四次调用:
sqrtRecursive(25.0, 5.4060, 7.4615, 1e-9)
guess ≈ 5.4060
,guess*guess ≈ 29.22
,仍不满足误差条件。- 计算新猜测值:
newGuess = (5.4060 + 25.0/5.4060)/2 ≈ 5.0148
。 - 递归调用:
sqrtRecursive(25.0, 5.0148, 5.4060, 1e-9)
。
-
第五次调用:
sqrtRecursive(25.0, 5.0148, 5.4060, 1e-9)
guess ≈ 5.0148
,guess*guess ≈ 25.148
,差值较小,但仍不满足epsilon = 1e-9
。- 计算新猜测值:
newGuess = (5.0148 + 25.0/5.0148)/2 ≈ 5.0001
。 - 递归调用:
sqrtRecursive(25.0, 5.0001, 5.0148, 1e-9)
。
-
第六次调用:
sqrtRecursive(25.0, 5.0001, 5.0148, 1e-9)
guess ≈ 5.0001
,guess*guess ≈ 25.0005
,差值非常小。- 计算新猜测值:
newGuess ≈ 5.0000
。 - 继续递归,直到差值小于
1e-9
,最终返回guess
约为5.0000
。
输出结果
运行代码后,输出如下:
25.00 的平方根为 5.000000
x = 25.0
的平方根是5.0
,代码通过牛顿法迭代计算得到了非常接近5.0
的结果。- 由于误差范围
epsilon = 1e-9
,结果精度非常高,输出保留 6 位小数后显示为5.000000
。
总结
- 这段代码使用牛顿法通过递归迭代的方式计算平方根,逻辑清晰且高效。
- 牛顿法每次迭代都会使猜测值更接近真实平方根,通常只需几次迭代就能达到很高的精度。
- 通过设置
epsilon = 1e-9
,代码保证了结果的高精度。 - 输出结果表明对于输入
25.0
,程序正确计算出平方根为5.000000
。
如果你有其他问题或想测试不同的输入值,可以进一步探讨!
newGuess := (guess + x/guess) / 2 为什么除2
在牛顿法(Newton’s Method)中,计算平方根的迭代公式是:
newGuess = (guess + x/guess) / 2
这里的 / 2
(除以 2)是为了取 guess
和 x/guess
的平均值。让我们详细解释为什么需要除以 2,以及这个公式的数学和直观原理。
1. 牛顿法的数学原理
牛顿法是一种求解方程根的迭代方法。对于求平方根的问题,我们的目标是找到一个数 guess
,使得 guess² = x
,即求解方程:
f(guess) = guess² - x = 0
牛顿法的迭代公式为:
guess_{n+1} = guess_n - f(guess_n) / f'(guess_n)
其中:
f(guess) = guess² - x
f'(guess) = 2 * guess
(f(guess)
的导数)
代入后:
guess_{n+1} = guess_n - (guess_n² - x) / (2 * guess_n)
= (guess_n² + x) / (2 * guess_n)
= (guess_n + x / guess_n) / 2
因此,newGuess = (guess + x/guess) / 2
中的 / 2
是从导数 2 * guess
中得到的。这是数学推导的结果,确保每次迭代都更接近真实平方根。
2. 直观解释:取平均值
除了数学推导,我们也可以从直观的角度理解为什么要除以 2。
guess
是当前对平方根的猜测值。- 如果
guess
是x
的平方根,那么guess² = x
,即x/guess = guess
。 - 如果
guess
不是精确的平方根:- 当
guess
偏小时,guess² < x
,因此x/guess > guess
。 - 当
guess
偏大时,guess² > x
,因此x/guess < guess
。
- 当
也就是说,guess
和 x/guess
总是位于真实平方根的两侧(一个偏小,一个偏大)。取两者的平均值 (guess + x/guess) / 2
,可以得到一个更接近真实平方根的新猜测值。
- 除以 2 就是取平均值的操作,确保新猜测值是当前猜测值和
x/guess
的中间值,从而逐步逼近真实平方根。
3. 示例说明
以 x = 25
为例,假设初始猜测值 guess = 1.0
:
guess = 1.0
,x/guess = 25.0/1.0 = 25.0
。- 此时
guess = 1.0
远小于真实平方根5.0
,而x/guess = 25.0
远大于5.0
。 - 取平均值:
(1.0 + 25.0) / 2 = 26.0 / 2 = 13.0
,新猜测值13.0
比1.0
更接近5.0
。
第二次迭代:
guess = 13.0
,x/guess = 25.0/13.0 ≈ 1.923
。- 此时
guess = 13.0
大于5.0
,而x/guess ≈ 1.923
小于5.0
。 - 取平均值:
(13.0 + 1.923) / 2 ≈ 14.923 / 2 ≈ 7.4615
,新猜测值7.4615
比13.0
更接近5.0
。
可以看到,每次取平均值(除以 2)都能让新猜测值更接近真实平方根。如果不除以 2,直接用 guess + x/guess
,结果会是两者的和,而不是平均值,无法保证逐步收敛。
4. 不除以 2 会怎样?
如果不除以 2,迭代公式变为 newGuess = guess + x/guess
,那么:
- 对于
x = 25
,guess = 1.0
:newGuess = 1.0 + 25.0/1.0 = 26.0
,比真实值5.0
更远。- 第二次:
newGuess = 26.0 + 25.0/26.0 ≈ 26.9615
,仍然远离真实值。
- 结果不会收敛到平方根,而是不断变大或偏离目标。
因此,除以 2 是确保收敛的关键步骤,通过取平均值平衡 guess
和 x/guess
的偏差。
总结
/ 2
(除以 2)来源于牛顿法数学推导中的导数项,确保迭代公式正确。- 直观上看,除以 2 是取
guess
和x/guess
的平均值,使得新猜测值更接近真实平方根。 - 如果不除以 2,迭代结果无法收敛,无法正确计算平方根。
这就是牛顿法公式中除以 2 的原因。
Gemini-2.5-pro解释如下:
Of course. This is a great Go program that demonstrates how to calculate a square root using a classic numerical method.
Here is a detailed breakdown of the code, explaining what it does, how it works, and the algorithm behind it.
Overall Summary
This Go program calculates the square root of a number (25.0
in this case) using a recursive implementation of the Babylonian method (also known as Heron’s method, which is a specific case of Newton’s method). It finds the answer by starting with an initial guess and repeatedly refining that guess until it is acceptably close to the actual square root.
Code Breakdown
Let’s look at each function individually.
1. main
function
func main() {
x := 25.0
result := sqrt(x)
fmt.Printf("%.2f 的平方根为 %.6f\n", x, result)
}
x := 25.0
: This line declares a floating-point variablex
and initializes it with the value25.0
. This is the number we want to find the square root of.result := sqrt(x)
: This calls thesqrt
function to perform the calculation and stores the returned value in theresult
variable.fmt.Printf(...)
: This line prints the final output to the console.%.2f
: Formats the value ofx
as a float with 2 decimal places.%.6f
: Formats theresult
as a float with 6 decimal places.的平方根为
: This is Chinese for “the square root of … is”.- Expected Output:
25.00 的平方根为 5.000000
2. sqrt
function
func sqrt(x float64) float64 {
return sqrtRecursive(x, 1.0, 0.0, 1e-9)
}
This is a “wrapper” or “helper” function. Its purpose is to provide a clean and simple interface for the user. Instead of having to provide multiple arguments (like an initial guess), you can just call sqrt(x)
.
- It calls the main recursive function
sqrtRecursive
with sensible starting values:x
: The number we want the square root of.guess = 1.0
: The initial guess for the square root.1.0
is a common and safe starting point.prevGuess = 0.0
: The “previous guess,” initialized to a value that won’t be equal to the first guess.epsilon = 1e-9
: This is the desired precision (0.000000001). It’s a very small number that defines how close our guess’s square needs to be tox
before we consider the job done.
3. sqrtRecursive
function
This is the core of the algorithm where the magic happens.
func sqrtRecursive(x, guess, prevGuess, epsilon float64) float64 {
// Condition 1: Check if the guess is good enough
if diff := guess*guess - x; diff < epsilon && -diff < epsilon {
return guess
}
// Calculate a new, better guess
newGuess := (guess + x/guess) / 2
// Condition 2: Check if the guess has stopped changing
if newGuess == prevGuess {
return guess
}
// Recursive step: Call itself with the new guess
return sqrtRecursive(x, newGuess, guess, epsilon)
}
Let’s analyze its parts:
-
Termination Condition 1 (Success):
if diff := guess*guess - x; diff < epsilon && -diff < epsilon { return guess }
- This checks if our current
guess
is “good enough.” diff := guess*guess - x
: It calculates the difference between the square of our guess and the actual numberx
. If the guess is perfect,diff
will be 0.diff < epsilon && -diff < epsilon
: This is a clever way to check if the absolute value ofdiff
is less than our toleranceepsilon
. It’s equivalent tomath.Abs(diff) < epsilon
. If the difference is this small, we consider ourguess
to be the correct answer and return it.
- This checks if our current
-
The Algorithm’s Core:
newGuess := (guess + x/guess) / 2
- This is the Babylonian method formula. It takes the average of the current
guess
andx / guess
. - This formula has the excellent property of converging very quickly towards the true square root. For example, if
guess
is too small,x/guess
will be too large, and their average will be closer to the real answer.
- This is the Babylonian method formula. It takes the average of the current
-
Termination Condition 2 (Stability):
if newGuess == prevGuess { return guess }
- This is a safeguard. Due to the limits of floating-point precision, the
newGuess
might eventually stop changing and become equal to theprevGuess
. If this happens, further recursion is pointless, so we stop and return the currentguess
. This prevents an infinite loop.
- This is a safeguard. Due to the limits of floating-point precision, the
-
The Recursive Step:
return sqrtRecursive(x, newGuess, guess, epsilon)
- If neither of the termination conditions is met, the function calls itself.
- The
newGuess
becomes theguess
for the next iteration. - The current
guess
becomes theprevGuess
for the next iteration. - This process repeats, with each call getting closer to the final answer.
Alternative: Iterative Approach
While the recursive solution is elegant, this kind of problem is more commonly and often more efficiently solved in Go using a simple for
loop. This avoids the overhead of function calls and the (very small) risk of a stack overflow.
Here is the same logic implemented iteratively:
func sqrtIterative(x float64) float64 {
if x < 0 {
return 0 // Or handle error appropriately
}
guess := 1.0
epsilon := 1e-9
for {
newGuess := (guess + x/guess) / 2
// Check if the guess has stabilized or is accurate enough
if newGuess == guess || (guess*guess-x < epsilon && x-guess*guess < epsilon) {
return newGuess
}
guess = newGuess
}
}
newGuess := (guess + x/guess) / 2 为什么除2
Excellent question! That line is the core of the entire algorithm, and understanding why you divide by 2 is key to understanding how it works.
The short answer is: You are taking the average.
Let’s break down the logic in a few different ways, from simple intuition to a more formal explanation.
1. The Intuitive Explanation (Averaging)
Let’s say we want to find the square root of a number x
. We’ll call the true square root s
. So, s * s = x
. This also means s = x / s
.
Now, imagine we make a guess, which we’ll call g
.
-
Case 1: Our guess
g
is too small. (i.e.,g < s
)
Ifg
is smaller than the true square root, then when you calculatex / g
, the result will be larger than the true square root.
Example:x = 25
, true roots = 5
. Let’s guessg = 4
(too small). Thenx / g = 25 / 4 = 6.25
(too large). -
Case 2: Our guess
g
is too large. (i.e.,g > s
)
Ifg
is larger than the true square root, thenx / g
will be smaller than the true square root.
Example:x = 25
, true roots = 5
. Let’s guessg = 10
(too large). Thenx / g = 25 / 10 = 2.5
(too small).
The Key Insight:
In both cases, the true square root s
lies somewhere between our guess g
and the value x / g
.
Since the true answer is between these two numbers, a very good strategy for getting a better guess is to take their average. And how do you average two numbers? You add them together and divide by 2.
That’s exactly what the formula does:
newGuess := (guess + x/guess) / 2
// ^^^^^^^^^^^^^^^^^ ^
// Add the two numbers | Divide by 2 to get the average
This new guess (newGuess
) will be much closer to the true square root than your original guess was. By repeating this process, you rapidly close in on the correct answer.
2. The Geometric Explanation (Making a Rectangle into a Square)
This is another great way to visualize it.
- Finding the square root of
x
is the same as finding the side length of a square with an area ofx
. - Let’s start with a rectangle that has an area of
x
. We can choose any side length for our first guess, let’s call itg
. - To make the area
x
, the other side of the rectangle must have a length ofx / g
. - Our goal is to make this rectangle a square. A square is just a special rectangle where the side lengths are equal.
- To make the two sides (
g
andx/g
) more equal, we can create a new rectangle. For its side length, we’ll choose the average of the two old sides. This new side length is(g + x/g) / 2
. - This new rectangle (with sides of length
newGuess
) will be much “squarer” than the previous one. - If you repeat this process, your rectangle will get closer and closer to being a perfect square, and its side length will get closer and closer to the true square root.
3. The Mathematical Explanation (Newton’s Method)
This algorithm is a specific application of a more general technique from calculus called Newton’s method, used for finding the roots of functions (i.e., where the function equals zero).
-
We want to find
g
such thatg² = x
. -
We can rephrase this as finding the root of the function
f(g) = g² - x
. We are looking for theg
wheref(g) = 0
. -
Newton’s method provides a formula to find a better approximation (
g_next
) from a current guess (g_current
):
g_next = g_current - f(g_current) / f'(g_current)
(wheref'
is the derivative of the functionf
). -
Let’s apply it to our function:
f(g) = g² - x
- The derivative
f'(g) = 2g
-
Now, substitute these into the Newton’s method formula:
g_next = g - (g² - x) / (2g)
-
Let’s simplify this with some algebra:
g_next = (g * 2g) / 2g - (g² - x) / 2g
g_next = (2g² - (g² - x)) / 2g
g_next = (2g² - g² + x) / 2g
g_next = (g² + x) / 2g
-
And finally, splitting the fraction:
g_next = g²/2g + x/2g = g/2 + x/(2g) = (g + x/g) / 2
This shows that the simple, intuitive formula of “taking the average” is actually a mathematically proven, highly efficient algorithm for finding square roots.
评论区