最近,我在智虎上看到了一个话题:世界上有哪些经典算法或项目案例代码很少?答案之一是《雷神之锤3》中的快速平方根倒数算法。我以为这是电影《雷神3》中出现的代码。我特别想进去看看。结果实际上对应于代码注释中的一句话“搞什么鬼?”。
你越不好奇,就越知道这是游戏代码的一部分。它于1999年发布,并于2005年开放。从现在起已经20年了。据说这部分代码在公开场合出现时几乎震惊了所有人,即下面几行代码:
浮点数
{
长I;
浮动x2,y;
常量浮点三等分= 1.5f
x2 =数字* 0.5f
y =数字。
i = *(长*)& y;//邪恶的浮点位级黑客攻击
I = 0x5f 3759 df-(I > > 1);//搞什么鬼?
y = *(浮点*)& I;
y = y *(三等分-(x2 * y * y));//第一次迭代
// y = y *(三等分-(x2 * y * y));//第二次迭代,这可以删除
返回y;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
许多程序员可能知道牛B的代码,但对许多人来说它应该是空白的。为了找到一个数的平方根,我们通常使用库中内置的方法,即sqrt。例如,在C语言中,要找到一个数的平方根的倒数,使用float(1.0/sqrt(x))。
也许当时没有sqrt方法,作者被迫想到了这个方法,但最好的是这个方法比sqrt方法快得多。据说在一些中央处理器上它可以快4倍,快20%。然而,我电脑上的编译速度快了近30%,其中一个重要原因是代码中有一串神秘的数字0x5f3759df。下面结合一些相关的知识和推导来介绍作者的几个风骚运算,最终目的就是得到这个神秘的数字。
首先,我们需要澄清一个基本知识,即单精度浮点数是如何以32位存储的。32位可分为三部分:S、E和M,其中S只有一位,0代表正数,1代表负数;e代表十进制,总共8位数;m代表尾数,共23位,如下图所示:
以数字4.25为例,整数4的二进制表示是0100;正如二进制表示整数一样,小数也表示整数,如202 02
0
右边是212 {-1} 2
1
,那么4.25到二进制形式的转换是0100.01。二进制形式可以表示为1.0001 × 22 = (1+0.0001) × 221.0001 \乘以2 ^ 2 =(1+0.0001)\乘以2 ^ 21.0001×2
2
=(1+0.0001)×2
2
。
在这里,它首先由S、E和M表示,因为S、E和M有其他含义
由于4.25是一个正数,所以S位存储为0,然后索引处的2+127可以以二进制形式存储在E中。添加127的原因是将索引范围从(-127,128)移动到(0,255),这样就可以在不考虑符号问题的情况下使用索引位置。最后,0001之后的小数点被存储在m中,因为它后面的数字足以确定小数点之前的1,所以没有必要存储1。总而言之,它是存储在32位内存中的单精度浮点数,如下图所示:
因此,对于要打开的浮点数x,有以下表达式,暂时称为存储表达式(此表达式需要稍后使用):
x =(1)s+(1+m)×2ex =(-1)^s+(1+m)\times 2^e
x =(1)
s
+(1+m)×2
e
虽然E和M的实际含义是浮点数,但毕竟两个二进制数都可以转换成十进制整数。那么,从整数的角度来看,整数E和浮点数E、整数M和浮点数M之间有什么关系?答案是肯定的,但我不知道原因。=
E=127+eE = 127+e
E=127+e
m = 1019mm毫米= 10^{19}m
M=10
19
m
上面已经介绍了添加127的原因,即调整索引的符号范围;101910^{19}10
19
是m中1之后要填充的零的数量,因为从浮点数来看,1左边的0起着占据作用,而从整数来看,1右边的0有实际意义,所以用1之后的零的数量的幂乘以原始基数m可以转换成整数m。
让我们回到原来的问题,对于一个浮点数x,求它的平方根倒数:
y = 1x√= x12y = \ frac { 1 } \ quad }= x^ {-\ frac { 1 } { 2 } }
y=
x
1
=x
−
2
1
如果等式两边都取对数:
log2y = 12 log2xlog _ 2y =-\ frac { 1 } { 2 } log _ 2x
原木
2
y =-
2
1
原木
2
x
如果将上述存储表达式结合起来,您可以得到一个新的等式:
对于上述公式中的log2(1+mx)log_2(1+m_x)log,
2
(1+m
x
)可以画一条平滑的曲线,我们可以用直线y = mxy = m _ xy = m。
x
通过比较(如下图所示),我们可以看到(0,1)之间的两条线非常相似。如果这一行向上平移一点,我们可以假设平移距离为B,那么这两行具有近似相等的关系:log2 (1+MX) ≈ MX+blog _ 2 (1+m _ x) \近似m _ x+blog
2
(1+m
x
)≈m
x
+b .
应该注意,这种关系的前提是0 ≤ MX ≤ 10 \ leq m _ x \ leq 10 ≤ m。
x
1或更小,则通过替换获得以下公式:
在log2ylog_2 ylog的情况下
2
y做同样的处理,则有以下公式:
my+ey+b≈12(MX+ex+b)m _ y+e _ y+b \大约{1}{2}(m_x+e_x+b)
m
y
+e
y
+b∈
2
1
(m
x
+e
x
+b)
上面,我们没有使用整数类型M和E来分别表示M和E,所以我们自然可以在上面的公式中使用整数类型来应用浮点类型。对于不同的数字,整型和浮点型之间的关系也是不同的,所以这里我们统一使用常量B和L,如下所示:
E=B+eE = B+e
E=B+e
M=LmM = Lm
M=Lm
用整数类型替换浮点类型可以得到一个新类型:
可以看出,在完成后,公式的左右两边有一个相同的部分M+LEM+LEM+LE。我们已经知道这三个字母的含义,但是它们一起表达了一个新的概念。合并两个整数是一个非常简单的问题,例如合并33和55,33× 100+55 = 335533 \乘以100+55 = 33533。那么LE+MLE+MLE+M就是真值,只是前者是十进制的,后者是二进制的,所以LE+MLE+MLE+M的作用是将M和e存储的数字绑定在一起,以表示存储的数字。有些人认为他们前面有一个s。s的作用是表示存储的数字是正数还是负数,并且根下的数字必须是正数,所以s的这个位必须是0,这没有实际意义。你真的觉得秒!
由于这个组合用来表示一个数,它可以用另一个变量I来表示:
iy≈32L(B)12 ixi _ y约{ 3 } { 2 } L(B-B)-(1 } { 2 } I _ x
i
y
* 7
2
3
l(B)B
2
1
i
x
事实上,代码中的以下语句就是应用的公式。
代码中使用右移操作来表示公式中的12 { 1 } { 2 }
2
1
和32l (bb) \ frac {3} {2} l (b-b)
2
3
L (bb)是在代码中找到这个神秘数字的基础,只有作者知道如何找到它。后来,另一个大哥哥推导出了这一系列的数字,并通过精确计算得到了一个新的数字系列0x5f375a86,比原来的常数略好。老大哥只是计算了一下,我们只是崇拜它。
因为上面包含I的公式是从整数的角度计算的,所以有必要强制类型转换将整数转换回浮点类型,然后最后一行代码使用牛顿迭代法来提高结果的准确性,所以没有什么奇怪的。让我们回顾一下作者在上述过程中的几个动作:
用整数类型表示浮点类型
log2(1+MX)≈MX+blog _ 2(1+m _ x)\大约m_x+blog
2
(1+m
x
)≈m
x
+b等价关系的替代
LE+极大似然+极大似然+M丛二进制
也许作者的想法是已经存在了很长时间的理论,但是如果我们能结合这些理论并灵活地应用它们来创建这个新的和有效的算法,我们真的不得不感叹牛b,但是应该注意的是这个算法依赖于浮点数的存储和字节顺序,所以它不能在Mac上工作。
上面的代码可以简化,但原理是一样的,只是一些变量被简化了:
浮动InSqrt(浮动x)
{
浮点xhalf = 0.5f * x;
int I = *(int *)& x;
I = 0x5f 3759 df-(I > > I);
x = *(浮点*)& I;
x = x *(1.5f-xhalf * x * x);
返回x;
}
1
2
3
4
5
6
7
8
9
也许你看到了最后一句“他妈的”。毕竟,太多的公式没有相应的理论基础。仅仅依靠作者开阔思路的想法,是不是“我不想让你思考,我只想让我思考?”,关键是在这个过程中要知道一些非常棒的想法,这可以看作是平时编程的一种方法,也可以看作是知识的扩展。
哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通
IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载
IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建
新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP
关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建
联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务