1. 前置知识
- 建议首先阅读我的另外一篇文章《雷神之锤 III 竞技场》快速求平方根倒数的计算探究》。
- 建议大家自己看过《雷神之锤 III 竞技场》快速求平方根倒数的计算探究》学会快速求平方根倒数算法后,不看我这篇文章,自己推导一篇快速求平方根的算法,这也是对自己是否彻底掌握的一个测试。
- 本篇文章是《雷神之锤 III 竞技场》快速求平方根倒数的计算探究》基础上的继续推导,写出快速计算平方根的函数,要计算的公式如下:
y = x y = x 1 2 y = \sqrt{x} \\ y = x^{\frac{1}{2}} y=xy=x21
2. 计算过程
2.1第一步:对等式两边同时取以2为底的对数
log 2 ( y ) = log 2 ( x 1 2 ) log 2 ( y ) = 1 2 log 2 ( x ) \log_2(y) = \log_2(x^{\frac{1}{2}}) \\ \log_2(y) = \frac12\log_2(x) log2(y)=log2(x21)log2(y)=21log2(x)
2.2 第二步:浮点数取log的整数表达形式
《IEEE754 -浮点数的表示》 提到浮点数的计算公式为: ( − 1 ) S ∗ ( 1 + F 2 23 ) ∗ 2 ( E − 127 ) (-1)^S*(1+\frac F{2^{23}})*2^{(E-127)} (−1)S∗(1+223F)∗2(E−127)
- 不考虑符号位。对其取对数
log 2 ( ( 1 + F 2 23 ) ∗ 2 E − 127 ) \log_2\left(\left(1+\frac{\mathrm{F}}{2^{23}}\right)*2^{\mathrm{E}-127}\right) log2((1+223F)∗2E−127)
≈ 1 2 23 ( F + 2 23 ∗ E ) + u − 127 \approx \frac{1}{2^{23}} (F +2^{23}*E)+u -127 ≈2231(F+223∗E)+u−127
2.3第三步:将 第二步 带入 第一步
- 化简一下公式
l o g 2 ( y ) = 1 2 log 2 ( x ) 1 2 23 ( F y + 2 23 ∗ E y ) + μ − 127 = 1 2 ( 1 2 23 ( F x + 2 23 ∗ E x ) + μ − 127 ) − − − − − − ( F y + 2 23 ∗ E y ) = 1 2 ∗ 2 23 ( 127 − μ ) + 1 2 ( F x + 2 23 ∗ E x ) log_2(y) = \frac12\log_2(x) \\ \frac{1}{2^{23}}(\mathrm{F}_{y}+2^{23}*\mathrm{E}_{y})+\mu-127=\frac{1}{2}\left(\frac{1}{2^{23}}(\mathrm{F}_{x}+2^{23}*\mathrm{E}_{x})+\mu-127\right) \\ ------\\ (\mathrm{F}_y+2^{23}*\mathrm{E}_y)=\frac12*2^{23}(127-\mu)+\frac12(\mathrm{F}_x+2^{23}*\mathrm{E}_x) log2(y)=21log2(x)2231(Fy+223∗Ey)+μ−127=21(2231(Fx+223∗Ex)+μ−127)−−−−−−(Fy+223∗Ey)=21∗223(127−μ)+21(Fx+223∗Ex) - 结果已经呼之欲出了,如下图所示,红框代表的是
y
y
y的二进制值, 蓝框代表的是
x
x
x的二进制值。
y 二进制 = 1 2 ∗ 2 23 ( 127 − μ ) + 1 2 ( x 二进制 ) y_{二进制} = \frac12*2^{23}(127-\mu)+\frac12(x_{二进制}) y二进制=21∗223(127−μ)+21(x二进制)
- 令
μ
=
0.0450465679168701171875
μ =0.0450465679168701171875
μ=0.0450465679168701171875(这个数字是《雷神之锤 III 竞技场》快速求平方根倒数的计算探究》中所给出的魔数反推出来的),计算出 新的魔数 。
y 二进制 = 0 x 1 f b d 1 d f 5 + 1 2 ( x 二进制 ) y_{二进制} = 0x1fbd1df5+\frac12(x_{二进制}) y二进制=0x1fbd1df5+21(x二进制)
3. 最后在使用牛顿迭代法再次逼近一次结果,提高精度
3.1 将 y = x y = \sqrt{x} y=x变换为 f ( x ) = 0 f(x) = 0 f(x)=0的形式
- 消除根号 y = x y 2 = x y = \sqrt{x} \\y^2 = x y=xy2=x
- 整理一下,得到 f ( x ) = 0 f(x) = 0 f(x)=0 的形式: y 2 − x = 0 y^2 - x = 0 y2−x=0
- 带入牛顿迭代法的通用公式 f ( y ) = y 2 − x f ′ ( y ) = 2 y − − − − − − − − − − − y n + 1 = y n − f ( y n ) f ′ ( y n ) − − − − − − − − − − − y n + 1 = y n − y n 2 − x 2 y n − − − − − − − − − − − − − − − − − − − − − − − y n + 1 = 1 2 ( y n + x y n ) f(y) = y^2 - x\\f'(y) = 2y\\ -----------\\ y_{n+1}=y_n-\frac{f(y_n)}{f'(y_n)}\\ -----------\\ y_{n+1}=y_n-\frac{{y_n^2}-x}{2y_n} \\ -----------------------\\ y_{n+1}=\frac12\left(y_n+\frac x{y_n}\right) f(y)=y2−xf′(y)=2y−−−−−−−−−−−yn+1=yn−f′(yn)f(yn)−−−−−−−−−−−yn+1=yn−2ynyn2−x−−−−−−−−−−−−−−−−−−−−−−−yn+1=21(yn+ynx)
4. 代码实现
float Q_sqrt(float number) {
int i;
float x, y;
const float half = 0.5f;
x = number;
// 1. 位级黑魔法:生成初始近似值
i = *(long*)&x; // 将浮点位模式转为整数
i = 0x1fbd1df5 + (i >> 1); // 魔法常数调整
y = *(float*)&i; // 转回浮点数
// 2. 牛顿迭代法优化精度
y = half * (y + x / y); // 第一次迭代
// y = half * (y + x / y); // 可选:第二次迭代
return y;
}