将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.hfsqrt(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版本。