Можно ли написать Quake fast InvSqrt() в С#?
Это просто для удовлетворения моего любопытства.
Есть ли реализация этого:
float InvSqrt (float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x;
i = 0x5f3759df - (i>>1);
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x);
return x;
}
в С#? Если он существует, отправьте код.
Думаю, я должен был упомянуть, что искал "безопасную" реализацию... В любом случае, код BitConverter решает проблему. Идея союза интересна. Я проверю и опубликую свои результаты.
Изменить:
Как и ожидалось, небезопасный метод является самым быстрым, за которым следует объединение (внутри функции), за которым следует битконвертер. Функции выполнялись 10000000 раз, а я использовал класс System.Diagnostics.Stopwatch для синхронизации. Результаты расчетов показаны в скобках.
Input: 79.67
BitConverter Method: 00:00:01.2809018 (0.1120187)
Union Method: 00:00:00.6838758 (0.1120187)
Unsafe Method: 00:00:00.3376401 (0.1120187)
Для полноты я протестировал встроенный метод Math.Pow и "наивный" метод (1/Sqrt (x)).
Math.Pow(x, -0.5): 00:00:01.7133228 (0.112034710535584)
1 / Math.Sqrt(x): 00:00:00.3757084 (0.1120347)
Разница между 1/Math.Sqrt() настолько мала, что я не думаю, что нужно использовать метод Unsafe Fast InvSqrt() в С# (или любой другой небезопасный метод). Если действительно не нужно выжимать последний бит сока из CPU... 1/Math.Sqrt() также намного точнее.
Ответы
Ответ 1
Вы должны иметь возможность использовать атрибуты StructLayout и FieldOffset для подделки объединения для простых старых данных, таких как float и ints.
[StructLayout(LayoutKind.Explicit, Size=4)]
private struct IntFloat {
[FieldOffset(0)]
public float floatValue;
[FieldOffset(0)]
public int intValue;
// redundant assignment to avoid any complaints about uninitialized members
IntFloat(int x) {
floatValue = 0;
intValue = x;
}
IntFloat(float x) {
intValue = 0;
floatValue = x;
}
public static explicit operator float (IntFloat x) {
return x.floatValue;
}
public static explicit operator int (IntFloat x) {
return x.intValue;
}
public static explicit operator IntFloat (int i) {
return new IntFloat(i);
}
public static explicit operator IntFloat (float f) {
return new IntFloat(f);
}
}
Тогда перевод InvSqrt легко.
Ответ 2
Используйте BitConverter, если вы хотите избежать небезопасного кода.
float InvSqrt(float x)
{
float xhalf = 0.5f * x;
int i = BitConverter.ToInt32(BitConverter.GetBytes(x), 0);
i = 0x5f3759df - (i >> 1);
x = BitConverter.ToSingle(BitConverter.GetBytes(i), 0);
x = x * (1.5f - xhalf * x * x);
return x;
}
В противном случае код С# будет точно таким же, как и код C, который вы указали, за исключением того, что метод должен быть помечен как небезопасный:
unsafe float InvSqrt(float x) { ... }
Ответ 3
Определенно возможно в небезопасном режиме. Обратите внимание, что, хотя в исходном коде Quake 3 использовалась константа 0x5f3759df
, численное исследование показало, что константа 0x5f375a86
дает лучшие результаты для аппроксимаций Ньютона.
Ответ 4
Я не понимаю, почему это невозможно, используя опцию небезопасного компилятора.