【学习笔记】使用魔数快速求平方根

【学习笔记】使用魔数快速求平方根

简介

介绍使用魔数0x1fbd1df5快速求平方根 x {\sqrt{x}} x 的C语言实现和公式的推导。

代码

float MagicSqrt(float x)
{
    float xhalf = 0.5f * x;
    int i = *(int*)&x;
    i = 0x1fbd1df5 + (i >> 1);
    x = *(float*)&i;
    x = 0.5f * x + xhalf / x;
    
    return x;
}

代码用于快速计算平方根 x {\sqrt{x}} x

代码中核心部分是

i = 0x1fbd1df5 + (i >> 1);

该行代码就完成了计算平方根。

另外再使用一次牛顿迭代法提高下精度

x = 0.5f * x + xhalf / x;

所以整个计算过程就是

1.i = *(int*)&x;

将输入的数转换成整数

2.i = 0x1fbd1df5 + (i >> 1);

通过魔数完成平方根的计算。

3.x = *(float*)&i;

转换回浮点数。

4.x = 0.5f * x + xhalf / x;

使用一次牛顿迭代法提高下精度。

完成快速计算平方根 x {\sqrt{x}} x

如果需要提高精度,可以多进行一次牛顿迭代。

float MagicSqrt(float x)
{
    float xhalf = 0.5f * x;
    int i = *(int*)&x;
    i = 0x1fbd1df5 + (i >> 1);
    x = *(float*)&i;
    x = 0.5f * x + xhalf / x;
    x = 0.5f * x + xhalf / x;
    
    return x;
}

传进来的值x必须大于或等于0。

可以加入一个判断防止传入的值小于0.

float MagicSqrt(float x)
{
    if (x < 0)
    {
        return -1;
    }
    else
    {
        float xhalf = 0.5f * x;
        int i = *(int*)&x;
        i = 0x1fbd1df5 + (i >> 1);
        x = *(float*)&i;
        x = 0.5f * x + xhalf / x;
        //x = 0.5f * x + xhalf / x;
        
        return x;
    }
}

牛顿迭代法计算方式

一般计算平方根可以使用牛顿迭代法。

x n + 1 = x n − f ( x n ) f ′ ( x n ) x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} xn+1=xnf(xn)f(xn)

我们计算平方根的公式:

y = x = x 1 2 y = {\sqrt{x}} = x^{\frac 12} y=x =x21

所以

y 2 = x {y}^2 = x y2=x

构建以y为自变量的函数方程为

f ( y ) = y 2 − x = 0 f(y) = {y}^2 - x = 0 f(y)=y2x=0

f ′ ( y ) = 2 y = 0 f'(y) = {2}{y} = 0 f(y)=2y=0

f ( y ) f(y) f(y) f ′ ( y ) f'(y) f(y)带入

y − f ( y ) f ′ ( y ) = y − y 2 − x 2 y y - \frac{f(y)}{f'(y)} = y - \frac{{{y}^{2}} - x}{{2}{y}} yf(y)f(y)=y2yy2x

= 1 2 ( y + x y ) = \frac{1}{2}{(y + \frac{x}{y})} =21(y+yx)

所以

y n + 1 = 1 2 ( y n + x y n ) y_{n+1} = \frac{1}{2}{(y_{n} + \frac{x}{y_{n}})} yn+1=21(yn+ynx) = 0.5 y n + 0.5 x ÷ y n 0.5{y_n} + 0.5{x}÷{y_n} 0.5yn+0.5x÷yn

实际计算,设 x = 2 x = 2 x=2 y 1 = 2 y_1 = 2 y1=2

y 2 = 0.5 × 2 + 0.5 × 2 ÷ 2 = 1.5 y_2 = 0.5 × 2 + 0.5 × 2 ÷ 2 = 1.5 y2=0.5×2+0.5×2÷2=1.5

y 3 = 0.5 × 1.5 + 0.5 × 2 ÷ 1.5 = 1.416667 y_3 = 0.5 × 1.5 + 0.5 × 2 ÷ 1.5 = 1.416667 y3=0.5×1.5+0.5×2÷1.5=1.416667

y 4 = 0.5 × 1.416667 + 0.5 × 2 ÷ 1.416667 = 1.414216 y_4 = 0.5 × 1.416667 + 0.5 × 2 ÷ 1.416667 = 1.414216 y4=0.5×1.416667+0.5×2÷1.416667=1.414216

y 5 = 0.5 × 414216 + 0.5 × 2 ÷ 414216 = 1.414214 y_5 = 0.5 × 414216 + 0.5 × 2 ÷ 414216 = 1.414214 y5=0.5×414216+0.5×2÷414216=1.414214

经过多次迭代,计算出来的结果 2 ≈ 1.414214 {\sqrt{2}} \approx 1.414214 2 1.414214

使用牛顿迭代法计算平方根需要大量的浮点数运算,运算速度会远远弱于整数运算。

算法讲解

接下来解释该算法的核心i = 0x1fbd1df5 + (i >> 1);

首先需要知道浮点数的基本概念。

32位浮点数基本概念

浮点数由三部分组成:符号,指数和尾数。

32位浮点数,用二进制表示

32bitfloat

用公式表示就是

( − 1 ) s ( 1 + m ) 2 e (-1)^s(1+m)2^e (1)s(1+m)2e

这里s是符号(sign),e是指数(exponent),m是尾数(mantissa)。

因为这里计算平方根只对正数有意义,所以从假设符号位是 0 ,公式简化为

( 1 + m ) 2 e (1+m)2^e (1+m)2e

指数部分 e e e的数值范围, − 127 ≤ e ≤ 128 -127 \leq e \leq 128 127e128

尾数部分 m m m的数值范围, 0 ≤ m < 1 0 \leq m < 1 0m<1

如果将浮点数转换成整数时,整数的数值就是

M + L E M + LE M+LE

这里 E E E表示指数(exponent), M M M表示尾数(mantissa), L L L 2 23 2^{23} 223

将指数部分看做是整数,用 E E E来表示,那么范围是 0 ≤ E ≤ 255 0 \leq E \leq 255 0E255 E E E如果减去127,范围变成-127 - 128,变成一个有符号数。

所以 E E E e e e的转换关系是 e = E − B e = E - B e=EB B B B是127。

如果将尾数部分看做是整数,用 M M M来表示。数值范围是 0 ≤ M < 2 23 0 \leq M < 2^{23} 0M<223

所以 M M M m m m的转换关系是 m = M L m = \frac{M}{L} m=LM L L L 2 23 2^{23} 223

推导过程

接下来进入推导过程。

给定一个 x x x,计算平方根 y y y

y = x = x 1 2 y = {\sqrt{x}} = x^{\frac 12} y=x =x21

首先对等式两边取以 2 为底的对数

log ⁡ 2 y = 1 2 log ⁡ 2 x \log_2 y = {\frac 12}\log_2 x log2y=21log2x

将x和y用浮点数替换:

log ⁡ 2 ( ( 1 + m y ) 2 e y ) = 1 2 ( log ⁡ 2 ( ( 1 + m x ) 2 e x ) ) \log_2 ((1+m_y)2^{e_y}) = {\frac 12}(\log_2 ((1+m_x)2^{e_x})) log2((1+my)2ey)=21(log2((1+mx)2ex))

log ⁡ 2 ( 1 + m y ) + e y = 1 2 ( log ⁡ 2 ( 1 + m x ) + e x ) \log_2 (1+m_y) + e_y = {\frac 12}(\log_2 (1+m_x) + e_x) log2(1+my)+ey=21(log2(1+mx)+ex)

算式两边都有这样的项

log ⁡ 2 ( 1 + v ) \log_2(1 + v) log2(1+v)

其中v的值范围0 < v v v < 1。

当v的取值在0到1之间时,这个函数和一条直线很接近:

log2

方程式:

log ⁡ 2 ( 1 + v ) ≈ v + σ \log_2(1 + v) \approx v + \sigma log2(1+v)v+σ

其中 σ \sigma σ是一个常数,可以通过调整这个常数让两个曲线更加近似。

上面的方程

log ⁡ 2 ( 1 + m y ) + e y = 1 2 ( log ⁡ 2 ( 1 + m x ) + e x ) \log_2 (1+m_y) + e_y = {\frac 12}(\log_2 (1+m_x) + e_x) log2(1+my)+ey=21(log2(1+mx)+ex)

进行化简

m y + σ + e y ≈ 1 2 ( m x + σ + e x ) m_y + \sigma + e_y \approx {\frac 12}(m_x + \sigma + e_x) my+σ+ey21(mx+σ+ex)

接下来用整形解释下的指数和尾数来替代浮点数解释:

M y L + σ + E y − B ≈ 1 2 ( M x L + σ + E x − B ) \frac{M_y}{L} + \sigma + E_y - B \approx {\frac 12}(\frac{M_x}{L} + \sigma + E_x - B) LMy+σ+EyB21(LMx+σ+ExB)

进行化简

M y L + E y ≈ 1 2 ( M x L + σ + E x − B ) − σ + B \frac{M_y}{L} + E_y \approx {\frac 12}(\frac{M_x}{L} + \sigma + E_x - B) - \sigma + B LMy+Ey21(LMx+σ+ExB)σ+B

M y L + E y ≈ 1 2 ( M x L + E x ) − 1 2 ( σ − B ) \frac{M_y}{L} + E_y \approx {\frac 12}(\frac{M_x}{L} + E_x) - \frac{1}{2}(\sigma - B) LMy+Ey21(LMx+Ex)21(σB)

M y + L E y ≈ 1 2 L ( B − σ ) + 1 2 ( M x + L E x ) M_y + LE_y \approx {\frac 12}L(B - \sigma) + {\frac 12}(M_x + LE_x) My+LEy21L(Bσ)+21(Mx+LEx)

两边都得到了整形解释的值:

I y ≈ 1 2 L ( B − σ ) + 1 2 I x \mathbf{I_y} \approx {\frac 12}L(B - \sigma) + {\frac 12}\mathbf{I_x} Iy21L(Bσ)+21Ix

其中 1 2 L ( B − σ ) {\frac 12}L(B - \sigma) 21L(Bσ)是一个常数。

用代码表示就是:

i = K + (i >> 1);

K就是算法中的常数。通过选取合适的 σ \sigma σ的值,就能得到K值。

魔数的选定

计算魔数所用的​可以采用穷举搜索或者二分法寻找最优值。

这里选定的值为 σ \sigma σ = 0.0450465。

带入计算出K值:

K = 1 2 L ( B − σ ) = 1 2 2 23 ( 127 − 0.0450465 ) ≈ 532487669 = 0 x 1 f b d 1 d f 5 K = {\frac 12}L(B - \sigma) = {\frac 12}{2^{23}}(127 - 0.0450465) \approx 532487669 = 0x1fbd1df5 K=21L(Bσ)=21223(1270.0450465)532487669=0x1fbd1df5

这里的K值就是公式中的0x1fbd1df5。

参考资料

平方根倒数速算法
0x5f3759df


本文链接:https://blog.csdn.net/u012028275/article/details/113793827

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值