cyycoish 发表于 2016-1-23 12:09:38

指针魔“数”

还记得老C在 http://www.0xaa55.com/thread-1702-1-1.html 帖子里边最后那堆代码吗?

float RandProducer(float x)
{
    float a;
    *(int*)&a = 0x407FFFFF; //What'd Fucking number is this? I wont tell you.
    return a * x - a * x * x;
}

现在我将要“解释” *(int*)&a = 0x407FFFFF; 这句话到底在干什么。
那么在“解释”之前,我需要扯一个淡——不!扯一个小故事。
一个关于老C自己的小故事。诶!好吧,观众走了一半了.....

写http://www.0xaa55.com/thread-1544-1-1.html这个的时候,
正是我刚从帝都回来,第一次见完 @元始天尊 回来。不!并没有基情。
反而一大堆问题。这个手动开方我咋也理解不了。老C高中数学百分制卷子考过16分
也就是 0x10分或者020分。不信打电话给我高一数学老师 (1383838388)
一脸糊涂问谷歌。那是当然的。然后看到了一个惊人的算法:

float InvSqrt(float x)
{
        float xhalf = 0.5f*x;

        int i = *(int*)&x; // get bits for floating value
        
i = 0x5f3759df - (i>>1); // gives initial guess y0

        x = *(float*)&i; // convert bits back to float

        x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy
        return x;

}
这个是求1/sqrt(x)的函数。

double CustomSquare(double number)
{
        int i;
        if (number == 0.0)
                return 0.0;
        double result = number / 2.0; //迭代初始数
        for (i = 1; i <= ITERATIONTIMES; i++)
        {
                result = 0.5 * (result + number / result);
        }
        return result;
}

上面这个是老C辛辛苦苦千辛万苦习得的递归求sqrt(x)的代码。
咱们不用吐槽这个这个效率了。光看代码量,就知道能慢死人。

我来说说InvSqrt(float x)函数的故事。
它的函数原型出自于约翰卡马克(john carmack)所写的游戏:雷神之锤3。
据说呢真正的代码,在数字“0x5f3759df ”附近写有一行注释://what the fuck;
仔细观察该函数,根本没有循环!更无所谓递归。
使用了一个“不知所以然的”神秘“整数”,“0x5f3759df ”
而且直接求值,直接求值啊!
据说2呢,3D引擎底层要大量使用平方根倒数运算——这个A5比我清楚多了。问她问她:@0xAA55
我傻傻不知道的。
然后CHRIS LOMONT 这人, 是一位数学家/计算机科学家。对该数字进行了深入分析。
总结成文:论文如下:

如需下载,请支付猫粮!
好了故事讲完了。
讲故事的目的是为了混粮食吃。
下面我要扯一扯我的那段代码。
在我的代码里边,也用了0x407FFFFF 这一fucking number来提速。
这玩意等于“单精浮点数”的3.9九循环。也就是说无限接近于4.
你可能要试试了:

你看!它是4!骗子!老C是骗子。
不!它就是3.99999999999999
不信?你看!

要是4.0,4减去4等于0,0不能做被除数。
但是魔数能计算!下面的4真就计算不了了。
为什么呢?
因为大家先去看这篇帖子:http://www.0xaa55.com/forum.php?mod=viewthread&tid=462&fromuid=418
回过来看:float浮点数存储大小等于一个32位整数。也是32位的。
我们试着做一下17.00123的float表示。
首先,将十进制小数转换为二进制小数。
如果嫌麻烦,可以移步此帖:http://www.0xaa55.com/thread-1591-1-1.html
十进制17.00123约等于二进制10001.00000000010100001001101111111001
为了不丢精……度,我们将二进制小数转换为10001.0000000001010000101
事实上这个小数等于十进制17.0012302398682。
好了,我们看这个小数:将小数点移至第一位之后,1.0001000……
移动了4位,将4加上127等于131,换成二进制就是1000 0011
那么浮点数前九位已经有了。符号位0代表正数,再加上131的二进制也就是
0100 0001 1。32-9=23,还剩下23位。下面我们将这剩下的23位放在前九位后
等等!慢着!丢掉23位首位的1,还剩下00010000000001010000101
这个数字就是“不丢精……度”那一行的数字去掉小数点后再去掉首位1
成了:0100 0001 1000 1000 0000 0010 1000 0101
这不就可以表示成一个32位整数:0x41880285嘛!
所以,17.00123等于float的0x41880285
先别急着验证!我们再来看个问题。

在约翰卡马克的InvSqrt函数中用了这行代码:
        int i = *(int*)&x;
   
        i = 0x5f3759df - (i>>1);
   
        x = *(float*)&i;
我们来“硬解读”它的意思:
取得float变量x的地址。
将这个float类型的指针强转为int类型的指针。
取出这个指针指向地址内的内容给了int i,
i里边就成为float x的整数表示。
修改i的值。(第二行整行)
取得i的地址,将i整型地址/指针转为float类型的指针。
取出内容,就是i的内容,只不过现在已经变成float的表现形式了。

举个形象的例子来说明这个过程:
假如float是人类,x就是一个人,人名叫做x
如果int是猫类,i就是名为i的一只猫。
现在我们找到x这个人家里的门牌号 &x
然后把他家改造成一个猫窝!
不用那么费劲,就是将人的家门号看做猫窝号即可。(int*)&x
然后让x这个人出门。我们不看脸的。*(int*)&x
结果就当成了从猫窝里边出来一只猫。
把x这个人所有的细胞啦,血液啦……全部放在一只猫的体内。i = *(int*)&x
(好大一只猫!不要过多吐槽。No More Comment!)
第一行结束。;
第二行,改造这只猫。比如左眼换成蓝色,右眼换成黄色。i = 0x5f3759df - (i >> 1);
第三行开始,将这只猫放进猫窝,取得猫窝门牌号。&i
将猫窝号看做人类家门号。取出“猫”的所有血肉。*(float*)&i
放进人x的体内。x = *(float*)&i
改造完成。; 这时候这个人很可能就是:左眼蓝色,右眼黄色。

事已至此,我们看我们写好的17.00123f的32位整数表示:
0x41880285;
我门写一段C代码测试之:

好了!剩下的不想讲了,想必大家都已心知肚明……

0xAA55 发表于 2016-1-23 14:40:03

唔,DirectX就有要求用户这么用的,典型的,IDirect3DDevice9::SetRenderState中的好几个State都是必须*(DWORD*)&浮点数的。它的参数是DWORD类型,但是它很多State都是要输入浮点数的。
页: [1]
查看完整版本: 指针魔“数”