Читая исходный код 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
диапазона. Они особенно распространены при выполнении целочисленных сверток с использованием БПФ с плавающей точкой.Ответы:
А
double
представляется так:и это можно рассматривать как два 32-битных целых числа; Теперь,
int
принято во всех версиях коды (предположу , что это 32-битноеint
) является один справа на рисунке, так что вы делаете в конце концов, просто принимая самый низкий 32 бит мантиссы.Теперь к магическому номеру; как вы правильно сказали, 6755399441055744 - 2 ^ 51 + 2 ^ 52; добавление такого числа вынуждает «
double
войти» в «сладкий диапазон» между 2 ^ 52 и 2 ^ 53, который, как объясняется здесь в Википедии , обладает интересным свойством:Это следует из того, что мантисса имеет ширину 52 бита.
Другой интересный факт о добавлении 2 51 +2 52 заключается в том, что он влияет на мантиссу только в двух старших битах, которые в любом случае отбрасываются, поскольку мы берем только его младшие 32 бита.
Последнее, но не менее важное: знак.
IEEE 754 с плавающей запятой использует представление величины и знака, в то время как целые числа на "обычных" машинах используют арифметику дополнения 2; как это обрабатывается здесь?
Мы говорили только о натуральных числах; теперь предположим, что мы имеем дело с отрицательным числом в диапазоне, представленном 32-битным
int
, то есть меньше (в абсолютном значении), чем (-2 ^ 31 + 1); назвать его-a
. Такое число, очевидно, становится положительным, добавляя магическое число, и полученное значение равно 2 52 +2 51 + (- a).Теперь, что мы получим, если интерпретируем мантиссу в представлении дополнения 2? Он должен быть результатом суммы дополнения 2 (2 52 +2 51 ) и (-a). Опять же, первый член влияет только на верхние два бита, а то, что остается в битах 0 ~ 50, является представлением дополнения 2 (-a) (опять же, минус два старших бита).
Поскольку уменьшение числа дополнения до 2 до меньшей ширины выполняется простым вырезанием лишних битов слева, взятие младших 32 битов дает нам правильное значение (-a) в 32-битной арифметике дополнения 2.
источник
int64_t
вас, это можно сделать, сместив мантиссу влево и вправо на 13 бит. Это очистит показатель степени и два бита от «магического» числа, но сохранит и распространит знак на все 64-разрядное целое число со знаком.union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;