44

我浏览了最近发布的Doom 3 BFG 源代码,当我发现一些似乎没有任何意义的东西时。Doom 3 在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 源代码也以这种方式实现了平方根函数,但反平方根使用的是快速反平方根算法

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 );
}

您认为在计算Sqrt(x)中是否有任何优势,就x * InvSqrt(x)好像在InvSqrt(x)内部只调用math.h's fsqrt(1.f/x)?我是否可能在这里遗漏了一些关于非规范化浮点数的重要信息,或者这只是 id 软件方面的草率?

4

4 回答 4

8

我可以看到这样做的两个原因:首先,“快速 invSqrt”方法(实际上是 Newton Raphson)现在是许多硬件中使用的方法,因此这种方法为利用此类硬件(和一次可能进行四个或更多这样的操作)。这篇文章稍微讨论一下:

计算平方根有多慢(多少个周期)?

第二个原因是兼容性。如果更改计算平方根的代码路径,可能会得到不同的结果(尤其是零、NaN 等),并失去与依赖于旧系统的代码的兼容性。

于 2012-11-28T22:42:16.360 回答
5

据我所知,InvSqrt它用于计算颜色,因为颜色取决于光线从表面反射的角度,这为您提供了一些使用平方根的倒数的函数。

在他们的例子中,他们在计算这些数字时不需要很高的精度,因此 Doom 3 代码背后的工程师(最初来自 Quake III)想出了一种非常非常快速的方法来计算InvSqrt仅使用几次 Newton-Raphson 迭代的近似值.

这就是为什么他们InvSqrt在所有代码中使用,而不是使用内置(较慢)函数。我想使用x * InvSqrt(x)is 是为了避免将工作乘以二(通过有两个非常有效的函数,一个 forInvSqrt和另一个 for Sqrt)。

你应该阅读这篇文章,它可能会对这个问题有所了解。

于 2012-12-04T20:45:03.723 回答
3

当代码被多人修改时,很难回答为什么它具有当前形式的问题,尤其是在没有修订历史的情况下。

然而,鉴于 1/3 世纪的编程经验,这段代码符合其他人提到的模式:InvSqrt曾经很快,用它来计算平方根是有意义的。然后InvSqrt改了,也没人更新Sqrt

于 2012-12-04T21:09:04.843 回答
2

他们也有可能遇到了一个相对幼稚的版本,sqrtf对于更大的数字来说,它的速度明显较慢。

于 2012-12-04T20:55:36.370 回答