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