Я просмотрел недавно выпущенный исходный код 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
fsqrt(1.f/x)
? Возможно, мне не хватает чего-то важного для денормализованных чисел с плавающей запятой или это просто небрежность в части программного обеспечения id?