2021-01-23 快速平方根求倒数算法

此算法很有名,来自《雷神之锤3》Quake引擎源码q_math.c的552行,传言是3d游戏开发大神约翰卡马克写的,代码出来之后很震惊,不过据考证最早的实现人并不是他,至于是谁第一个实现的至今成迷,源码如下:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration

    return y;
}

这篇文章内容不是原创,主要来自网上一些介绍,博主一开始也没看明白这算法,看了一些网上介绍大概了解了,这里分析一下算法原理,同时做个备忘录:

算法有两步是核心,一个就是魔数:0x5f3759df,还有一个就是
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration

下面介绍如下:
输入a, 目标是计算y=\sqrt{1/a} ,列出方程就是:
1/y^2 -a =0
f(y)= 1/y^2 - a 则目标就是求解这个方程的根,
这里有牛顿迭代法可以求解,牛顿迭代介绍比较多,这里不再缀述,直接给出公式:
y_{n+1}=y_{n}-f(y_{n})/f'(y_{n})
y=1/y^2-a代入上式并化简,得到:
y_{n+1}=y_{n}*(1.5-0.5a*y_{n}*y_{n})
这其实就是对应代码中的:

y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration

由此,可见代码中只进行了一次牛顿迭代就默认收敛了,那么为何作者那么确定能快速收敛,这主要取决于迭代初始值的选择,如果初始值与目标值越近,收敛越快

任何一个浮点数,在计算机中存储如下:
s e⋯em⋯m
s就1位,符号位置, e...e共8位 m...m共23位 取值全部为0,1
如果用2进制来看(下文中默认都是2进制)
0.m...m的值为m' , e' = e...e - B
(浮点数为了包含负指数情形,因此上式有个偏移量B=127)
则上述浮点数的值为:
(1+m')\times 2^{e'} 并且m'取值范围在[0,1]之间

如果把这个浮点数看做一个整数(也就是直接强转为整数,下同),并且令
M =m...m E=e...e L=2^{23} 则这个整数取值为:
M\times L+E (公式1)

同时M,m',E,e'之间存在如下关系:
m'=M/L L=2^{23} ,e'=e-B,B=127

现在先把这些公式放到一边,我们回到原问题,其实要求一个迭代初始值,这个初始值是这么推导来的:
要计算 y=1/sqrt(a) 两边取对数,得到:
log_2{y}=-1/2 log_2{a} 现在用上面的浮点数表示替换,y,a得到
log_2(1+m_y')+e_y' = -1/2[ log_2(1+m_a')+e_a']
可以看见两边都有形如log_2(1+X)(0<=X<=1)的形式
如果把 log_2(1+X) (0<=X<=1)这个函数近似为线性函数:X+\sigma 代入上式,得到:
m_y'+\sigma+e_y'=−1/2(m_a'+\sigma+e_a')
再用上面的公式,可以简化为:
3/2L(\sigma−B)+M_y+LE_y=−1/2(M_a+LE_a)
如果记:
I_y=M_y\times L+E_y
I_a =M_a\times L +E_a
k =3/2L(B-\sigma)
有:
I_y=(-1/2)I_a + k (2)
其中k对应的就是 魔数: 0x5f3759df ,

根据上文分析,表达式(2)意味着:
根据公式(1), 如果把输入的浮点数a,看做整数,其取值就是I_a
则根据(2)计算出的就是浮点数 y=1/sqrt(a)看成整数的值,
再把这个值强转为浮点数,就是浮点数y的值,
(当然在计算过程中我们近似利用了log_2(X+1) = X+\sigma
所以这样的算出的值是有误差的,但是误差比较小,作为牛顿迭代的初始值,只需要一次迭代就收敛了;)

而公式2对应的源码就是(已添加注释):

    y  = number; //number相当于公式中的a
    i  = * ( long * ) &y;//a强转为整数,得到I_a             
    i  = 0x5f3759df - ( i >> 1 );    //i>>1就等于I_a/2 ,
    y  = * ( float * ) &i; //最后再强转为整数

据此,就分析完了,从上面可以看出魔数主要由\sigma确定,至于\sigma怎么确定的,感兴趣的读者可以深究,但是\sigma的确定只与1个确定函数有关,即:log_2(1+X),它的取值与算法的输入输出是无关的,也就是说,不管怎么取,只会影响魔数:0x5f3759df;而不会影响代码的结构

因此,对于我们的目的已经达到了,这个算法的原理已经解释清楚了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容