Читая исходный код 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
диапазона. Они особенно распространены при выполнении целочисленных сверток с использованием БПФ с плавающей точкой.