Быстрый метод для округления двойного до 32-битного int

При чтении исходного кода Lua я заметил, что Lua использует macro для округления до 32-битного int . Я извлек macro , и он выглядит так:

 union i_cast {double d; int i[2]}; #define double2int(i, d, t) \ {volatile union i_cast u; ud = (d) + 6755399441055744.0; \ (i) = (t)ui[ENDIANLOC];} 

Здесь ENDIANLOC определяется как endianness , 0 для little endian, 1 для большого endian. Lua тщательно обрабатывает сущность. 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(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 .

ОБНОВЛЕНИЕ :

  1. Как отмечает @Mysticial, этот метод не ограничивается 32-битным int , его также можно развернуть до 64-битного int пока число находится в диапазоне 2 ^ 52. ( macro нуждается в некоторой модификации.)
  2. Некоторые материалы говорят, что этот метод не может использоваться в Direct3D .
  3. При работе с ассемблером Microsoft для x86 в assembly выполняется еще более быстрый macro (это также извлекается из источника Lua):

     #define double2int(i,n) __asm {__asm fld n __asm fistp i} 
  4. Существует аналогичное магическое число для единственного числа точности: 1.5 * 2 ^23

double выглядит следующим образом:

двойное представление

и это можно рассматривать как два 32-битных целых числа; теперь int взятый во всех версиях вашего кода (предположим, что это 32-битный int ) – тот, что справа на рисунке, поэтому то, что вы делаете в конце, просто берет самые низкие 32 бита мантиссы.


Теперь, к волшебному числу; как вы правильно заявили, 6755399441055744 составляет 2 ^ 51 + 2 ^ 52; добавление такого числа заставляет double перейти в «сладкий диапазон» между 2 ^ 52 и 2 ^ 53, что, как объяснено в Википедии, имеет интересное свойство:

Между 2 52 = 4 503 599 627 370 496 и 2 53 = 9 007 199 994 740 992 представляемые числа – это целые числа

Это следует из того, что мантисса шириной 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.

  • Преобразование float в double без потери точности
  • Странная проблема сравнения поплавков в объективе-C
  • Double vs float на iPhone
  • Сравнение с плавающей точкой
  • Почему арифметика с плавающей запятой в C # неточна?
  • Библиотека высокой точности с плавающей запятой Java
  • Почему я вижу двойную переменную, инициализированную некоторым значением, например 21.4, как 21.399999618530273?
  • Операции с плавающей запятой в C-ассоциативном?
  • Эмуляция «double» с использованием 2 "float" s
  • извлечение мантиссы и экспонента из двойного в c #
  • Как преобразовать строку в float?
  • Interesting Posts

    Файлы Mex: как вернуть уже выделенный массив Matlab

    Любой способ совместного использования состояния сеанса между различными приложениями в tomcat?

    Каково текущее состояние разработки кодека FLAC?

    Как решить уравнения с помощью java?

    переносимый способ записи csv-файла в python 2 или python 3

    NSBundle pathForResource имеет значение NULL

    Удалить символы из NSString?

    Не удается удалить папку, и я администратор. "Вам нужно разрешение для выполнения этого действия. Вам требуется разрешение … "

    Как включить приглашение классического входа в систему вместо экрана приветствия в Windows 7

    Как изменить размер основного раздела Windows XP?

    Как установить .NET framework как необходимое условие с помощью InnoSetup?

    Как очистить cookies и кеширование веб-обозревателя на Android, если вы не находитесь в веб-просмотре?

    IE предлагает открыть или сохранить результат json с сервера

    Java 8 U40 TextFormatter (JavaFX) для ограничения ввода пользователя только для десятичного числа

    Настройка переменных среды через launchd.conf больше не работает в OS X Yosemite / El Capitan / macOS Sierra?

    Давайте будем гением компьютера.