我该如何求浮点数的平方根?牛顿-拉夫逊法是一个好方法吗?我也没有硬件平方根。虽然我已经实现了浮点数除法,但如果可能的话,我想尽可能减少除法的数量,因为它们非常昂贵。
另外,为了减少总迭代次数,初始猜测应该是多少?
非常感谢!
我该如何求浮点数的平方根?牛顿-拉夫逊法是一个好方法吗?我也没有硬件平方根。虽然我已经实现了浮点数除法,但如果可能的话,我想尽可能减少除法的数量,因为它们非常昂贵。
另外,为了减少总迭代次数,初始猜测应该是多少?
非常感谢!
当你使用牛顿-拉弗森方法计算平方根时,实际上是希望使用迭代来寻找倒数平方根(之后你可以通过乘以输入的值(需要注意舍入)来得到平方根)。
更准确地说:我们使用函数 f(x) = x^-2 - n
。显然,如果f(x) = 0
,那么x = 1/sqrt(n)
。这引出了牛顿迭代公式:
x_(i+1) = x_i - f(x_i)/f'(x_i)
= x_i - (x_i^-2 - n)/(-2x_i^-3)
= x_i + (x_i - nx_i^3)/2
= x_i*(3/2 - 1/2 nx_i^2)
请注意(与求平方根的迭代不同),这个求倒数平方根的迭代不涉及除法,因此通常更加高效。sqrt(612)
。 612
可以写作 1.1953125 x 2^9
(或者如果你喜欢二进制,可以写成 b1.0011001 x 2^9
)。将指数的偶数部分(9)提取出来,将输入写成 f * 2^(2m)
的形式,其中 m
是整数,f
在 [1,4) 范围内。然后我们会有:sqrt(n) = sqrt(f * 2^2m) = sqrt(f)*2^m
将这个约简应用到我们的示例中得到 f = 1.1953125 * 2 = 2.390625
(b10.011001
) 和 m = 4
。现在进行牛顿-拉夫逊迭代,以找到 x = 1/sqrt(f)
,使用起始猜测值为0.5(正如我在评论中指出的那样,这个猜测对于所有的f
都收敛,但你可以使用线性近似作为初始猜测,效果会更好):
x_0 = 0.5
x_1 = x_0*(3/2 - 1/2 * 2.390625 * x_0^2)
= 0.6005859...
x_2 = x_1*(3/2 - 1/2 * 2.390625 * x_1^2)
= 0.6419342...
x_3 = 0.6467077...
x_4 = 0.6467616...
因此,即使有(相对较差的)初始猜测,我们也能快速收敛到真实值1/sqrt(f) = 0.6467616600226026
。
现在我们只需组装最终结果:
sqrt(f) = x_n * f = 1.5461646...
sqrt(n) = sqrt(f) * 2^m = 24.738633...
并且检查:sqrt(612) = 24.738633...
显然,如果您想进行正确的舍入,需要仔细分析以确保在计算的每个阶段都具有足够的精度。这需要仔细的记账,但并不是什么高深的学问。您只需保持仔细的误差边界并将其通过算法传播。
如果您想在不明确检查残留值的情况下进行正确的舍入,则需要将sqrt(f)计算到2p + 2位的精度(其中p是源类型和目标类型的精度)。然而,您还可以采取计算比p位多一点的sqrt(f)的策略,对该值进行平方,如果必要的话,将尾数位调整为1(这通常更便宜)。
sqrt很好,因为它是一个一元函数,这使得在商品硬件上进行单精度的详尽测试成为可能。
您可以在opensource.apple.com上找到OS X软浮点sqrtf函数,该函数使用上述算法(恰好是我写的)。它受APSL许可证的管辖,这可能适合或不适合您的需求。
x^-2 - n
可避免在迭代中进行除法运算,在保留二次收敛的同时,使其在典型硬件上更加高效。 - Stephen Canon612 = 2.390625 x 2^8
,因此 sqrt(612) = sqrt(2.390625) x sqrt(2^8) = sqrt(2.390625) x 2^4
。提取指数的偶数部分,你会得到一个值在 [1,4) 范围内。 - Stephen Canon1.1 - 1/6 * f
的线性近似。(1/6是最优的,你可以解出最优的第一项;我想不起来确切的值,但它几乎等于1.1
) - Stephen Canondef sqrt(x, TOL=0.000001):
y=1.0
while( abs(x/y -y) > TOL ):
y= (y+x/y)/2.0
return y
Initialize a such that a has only one bit equal to one and a^2<= n < (2*a)^2.
Let q=s(a).
b=a
sqra = a*a
For i = q-1 to -10 (or whatever significance you want):
b=b/2
sqrab = sqra + 2ab + b^2
if sqrab > n:
continue
sqra = sqrab
a=a+b
n=612
a=10000 (16)
sqra = 256
Iteration 1:
b=01000 (8)
sqrab = (a+b)^2 = 24^2 = 576
sqrab < n => a=a+b = 24
Iteration 2:
b = 4
sqrab = (a+b)^2 = 28^2 = 784
sqrab > n => a=a
Iteration 3:
b = 2
sqrab = (a+b)^2 = 26^2 = 676
sqrab > n => a=a
Iteration 4:
b = 1
sqrab = (a+b)^2 = 25^2 = 625
sqrab > n => a=a
Iteration 5:
b = 0.5
sqrab = (a+b)^2 = 24.5^2 = 600.25
sqrab < n => a=a+b = 24.5
Iteration 6:
b = 0.25
sqrab = (a+b)^2 = 24.75^2 = 612.5625
sqrab < n => a=a
Iteration 7:
b = 0.125
sqrab = (a+b)^2 = 24.625^2 = 606.390625
sqrab < n => a=a+b = 24.625
and so on.
在区间[1,4)
上,一个很好的平方根近似值是
def sqrt(x):
y = x*-0.000267
y = x*(0.004686+y)
y = x*(-0.034810+y)
y = x*(0.144780+y)
y = x*(-0.387893+y)
y = x*(0.958108+y)
return y+0.315413
将浮点数规范化,使尾数在[1,4)
范围内,然后使用上述算法对其进行处理,最后将指数除以2。不要进行任何浮点除法。
在相同的CPU时间预算下,您可能可以做得更好,但这似乎是一个很好的起点。