Читаючи вихідний код Lua , я помітив, що Lua використовує a macro
для округлення a 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
визначається як ендіанство , 0
для маленького ендіана, 1
для великого ендіана. Луа дбайливо поводиться з витримкою. 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
межах. Вони особливо часто зустрічаються при виконанні цілочислових згортків, використовуючи ПНП з плаваючою комою.