将Sqrt(x)计算为x * InvSqrt(x)是否在Doom 3 BFG代码中有意义?
我浏览了最近发布的Doom 3 BFG源代码 ,当时我遇到了一些似乎没有任何意义的东西。 Doom 3将math函数包装在idMath类中。 一些函数只是从math.h
获得相应的函数,但有些函数是重新实现(例如idMath :: exp16() ),我认为它们的性能要高于math.h
(可能会牺牲精度)。
然而,让我感到困惑的是他们实现float idMath::Sqrt(float x)
函数的方式:
ID_INLINE float idMath::InvSqrt( float x ) { return ( x > FLT_SMALLEST_NON_DENORMAL ) ? sqrtf( 1.0f / x ) : INFINITY; } ID_INLINE float idMath::Sqrt( float x ) { return ( x >= 0.0f ) ? x * InvSqrt( x ) : 0.0f; }
这似乎执行两个不必要的浮点操作:首先是除法,然后是乘法。
有趣的是, 原始的Doom 3源代码也是这样实现了平方根函数,但是反平方根使用了快速平方根algorithm 。
ID_INLINE float idMath::InvSqrt( float x ) { dword a = ((union _flint*)(&x))->i; union _flint seed; assert( initialized ); double y = x * 0.5f; seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<<EXP_POS) | iSqrt[(a >> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; double r = seed.f; r = r * ( 1.5f - r * r * y ); r = r * ( 1.5f - r * r * y ); return (float) r; } ID_INLINE float idMath::Sqrt( float x ) { return x * InvSqrt( x ); }
如果InvSqrt(x)
内部调用math.h
的fsqrt(1.f/x)
你认为在计算Sqrt(x)
什么优势? 我可能在这里丢失了一些有关非规范化浮点数的重要内容,或者这只是id软件部分的唾弃吗?
我可以看到这样做的两个原因:首先,“快速invSqrt”方法(真正的牛顿Raphson)现在是在很多硬件中使用的方法,所以这种方法留下了利用这种硬件的可能性(和一次做四次或更多次这样的操作)。 本文将对此进行一点讨论:
计算平方根有多慢(多less个周期)?
第二个原因是兼容性。 如果更改计算平方根的代码path,则可能会得到不同的结果(特别是对于零,NaN等),并且会失去与依赖旧系统的代码的兼容性。
据我所知, InvSqrt
是用来计算颜色的,因为颜色取决于光线从表面reflection的angular度,这可以使用平方根的倒数给出一些函数。
在他们的情况下,他们在计算这些数字时不需要很高的精度,所以Doom 3的代码(最初来自Quake III)背后的工程师提出了一个非常快速的计算InvSqrt
逼近的方法,只用几个Newton-Raphson迭代。
这就是为什么他们在所有代码中使用InvSqrt
,而不是使用内置(较慢)的函数。 我想使用x * InvSqrt(x)
是为了避免乘以2(通过有两个非常有效的函数,一个用于InvSqrt
而另一个用于Sqrt
)。
你应该阅读这篇文章,可能会对这个问题有所了解。
当代码被多人修改时,很难回答为什么它有当前的forms,特别是没有修订历史。
但是,如果有三分之一的编程经验,这个代码适合其他人提到的模式:一次, InvSqrt
是快速的,用它来计算InvSqrt
是有意义的。 然后InvSqrt
改变了,没有人更新Sqrt
。
也有可能他们遇到了一个比较天真的sqrtf
版本。