神奇的0x5f3759df

会数学的程序员都是艺术家,第一篇文章致敬John Carmack的wtf magic number 0x5f3759df  —— 题记

  以前上网偶遇QuakeIII的完全平方根倒数的代码,就被这magic number后的注释吸引了,以致于很长一段时间都喜欢在code review时用0x5f3759df作为我wtf的文明嘴替来comment。直到有一天突然好奇这玩意到底是啥,就翻了一下源码去分析,折腾了1个多小时才搞明白,此后除了对作者John Carmack的敬佩,就只剩想被0x5f3759df comment?你不配

 

 1 float Q_rsqrt( float number )
 2 {
 3         long i;
 4         float x2, y;
 5         const float threehalfs = 1.5F;
 6         x2 = number * 0.5F;
 7         y = number;
 8         i = * ( long * ) &y;                       // evil floating point bit level hacking
 9         i = 0x5f3759df - ( i >> 1 );               // what the fuck?
10         y = * ( float * ) &i;
11         y = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
12 //      y = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
13         return y;
14 }

 

  代码见上方block,分享下到时思路。正着看下来估计谁看谁懵逼,那就反着吧,13行可以确认y就是计算结果res,再看11、12行一样的执行,等号两边都有res,再加上注释里的iteration,牛顿迭代法没跑了,手推一波

 

  完美,对上了。那就这堆莫名其妙的7-10行的意图清晰了,就是搞个初值用来估算。下面思路有了,查下float的存储方式,然后按着0x5f3759df前后代码手推一波,应该就能知道wtf是wtf了

 

  bingo,其实就是将log(1+x)在x∈(0,1)时近似成了x+0.0450465679169。当然,这有俩限制一是不能搅屎棍输入负数,二是输入的数E不能为0即不能小于2-126

  第二个限制是不是真实不行呢,懒得推了,直接编程随机测试下

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 #include <math.h>
 5 
 6 float Q_rsqrt( float number )
 7 {
 8     long i;
 9     float x2, y;
10     const float threehalfs = 1.5F;
11     x2 = number * 0.5F;
12     y = number;
13     i = * ( long * ) &y;                       // evil floating point bit level hacking
14     i = 0x5f3759df - ( i >> 1 );               // what the fuck?
15     y = * ( float * ) &i;
16     y = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
17 //  y = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
18     return y;
19 }
20 
21 int main()
22 {
23     long tmpFloatMaker = (0U << 31) | (0U << 23) | (0x1 << 0);
24     float num = *(float*)&tmpFloatMaker;
25 
26     printf("num = %.60f\n",num);
27     printf("Q_rsqrt(num) = %.20f\n",Q_rsqrt(num));
28     printf("1/sqrt(num) = %.20f\n",1/sqrt(num));
29 
30     return 0;
31 }

 

  显然,输入的数不能小于2-126,因为E全0后float对M的解析不再是+1.0而是+0.0,2的幂也变为-126而不是预期的-127。

  好了,现在正着看回来,这串代码的思路就是先用一个很神奇近似得到一个近似解,然后采用(1/*+1*/)次牛顿迭代,得到更精确点的近似解。至于那个神奇近似方法,正着真不知道怎么想的,颇有拉马努金的感觉,0x5f3759df

 

posted @ 2025-05-01 12:07  蓝bleu  阅读(274)  评论(0)    收藏  举报