Читая исходный код Lua , я заметил, что Lua использует macroокругление doubleдо 32-разрядного числа int. Я извлек macro, и это выглядит так:
union i_cast {double d; int i[2]};
#define double2int(i, d, t) \
{volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
(i) = (t)u.i[ENDIANLOC];}
Здесь ENDIANLOCопределяется как endianness , 0для little-endian, 1для big-endian. Луа осторожно обращается с порядком байтов. tобозначает целочисленный тип, например intили unsigned int.
Я провел небольшое исследование, и есть более простой формат, macroкоторый использует ту же мысль:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Или в стиле C ++:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Этот трюк может работать на любой машине, использующей IEEE 754 (что означает практически любую машину сегодня). Он работает как для положительных, так и для отрицательных чисел, а округление следует правилу банкира . (Это не удивительно, поскольку следует IEEE 754.)
Я написал небольшую программу для проверки:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
И это выдает -12345679, как и ожидалось.
Я хотел бы вникнуть в детали, как этот хитрый macroработает. Магическое число 6755399441055744.0на самом деле 2^51 + 2^52или 1.5 * 2^52, и 1.5в двоичном виде может быть представлено как 1.1. Когда к этому магическому числу добавляется любое 32-разрядное целое число, я теряюсь отсюда. Как работает этот трюк?
PS: Это в исходном коде Lua, Llimits.h .
ОБНОВЛЕНИЕ :
- Как указывает @Mysticial, этот метод не ограничивается 32-разрядным
int, он также может быть расширен до 64-разрядного,intесли число находится в диапазоне 2 ^ 52. (macroНеобходимы некоторые изменения.) - В некоторых материалах говорится, что этот метод нельзя использовать в Direct3D .
При работе с ассемблером Microsoft для x86
macroнаписано еще быстрееassembly(это также извлечено из исходного кода Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}Для числа одинарной точности существует похожее магическое число:
1.5 * 2 ^23
ftoi. Но если вы говорите SSE, почему бы просто не использовать одну инструкцию CVTTSD2SI?
double -> int64в самом деле в пределах 2^52диапазона. Они особенно распространены при выполнении целочисленных сверток с использованием БПФ с плавающей точкой.
