ieee 754 — Как работать (быстро) на мантиссе и показательной части double или плавать на c ++?

Я использую c ++ для вычисления различных типов специальных функций (например, функции Ламберта, итерационных методов для вычисления инверсий и т. Д.). Во многих случаях существует явно лучший подход к работе с мантиссой и показателем степени напрямую.

Я нашел много ответов о том, как извлечь части мантиссы и экспоненты, однако все они были просто «академическими случаями с не очень эффективной скоростью вычислений», которые для меня немного бесполезны (моя мотивация работать с мантиссой и показателем заключается в улучшении вычислительной скорость). Иногда мне нужно вызывать какую-то конкретную функцию около миллиарда раз (очень дорогие вычисления), чтобы каждая сохраненная вычислительная работа была в порядке. И использование «frexp», которые возвращают мантиссу как double, не очень подходит.

Мои вопросы (для компилятора c ++ с плавающей точкой IEEE 754):

1) Как прочитать конкретный бит мантиссы с плавающей / двойной?

2) Как прочитать всю мантиссу в целое число / байт с плавающей запятой / двойной?

3) Те же вопросы, что и 1), 2) для показателя степени.

4) Те же вопросы, что и 1), 2), 3) для записи.

С уважением, моя мотивация — более быстрое вычисление, если я работаю с мантиссой или экспонентой напрямую. Я полагаю, что должно быть очень простое решение.

2

Решение

Во многих случаях существует явно лучший подход к работе с мантиссой и показателем степени напрямую.

Я знаю это чувство слишком хорошо из моей работы по обработке сигналов, но правда в том, что показатели и мантиссы не просто могут использоваться как отдельные числа; IEEE754 определяет несколько особых случаев, смещений и т. Д.

Я полагаю, что должно быть очень простое решение.

Инженерный опыт подсказывает мне: предложения, заканчивающиеся на «простое решение», обычно не соответствуют действительности.

«академические кейсы»

однако, это определенно не соответствует действительности (я приведу пример в конце).

На поплавках IEEE754 в реальном мире очень хорошо используется оптимизация. Тем не менее, я обнаружил, что благодаря более поздним возможностям процессоров x86 выполнять SIMD (одна инструкция, несколько данных) и общему факту, что число с плавающей запятой так же быстро, как и большинство операций с «сдвигом в битах», я обычно подозреваю, что вам не рекомендуется Попробуйте сделать это на небольшом уровне самостоятельно.

Как правило, поскольку IEEE754 является стандартом, вы найдете документацию о том, как он хранится в вашей конкретной архитектуре повсюду. Если вы смотрели, вы, по крайней мере, должны были найти статью в Википедии, объясняющую, как это сделать 1) и 2) (это не так статично, как вы думаете).

Что важнее:
не постарайтесь быть умнее вашего компилятора. Вы, вероятно, не будете, если вы явно не знаете, как векторизовать несколько идентичных операций.

Поэкспериментируйте с математическими оптимизациями вашего конкретного компилятора. Как уже упоминалось, в настоящее время они обычно мало что делают; Процессоры не медленнее выполняют вычисления с плавающей запятой, чем на целых числах, обязательно.

Я лучше посмотрю на ваши алгоритмы и поищу там потенциал для оптимизации.

Кроме того, пока я делаю это, давайте представим VOLK (Vector Optimized Library of Kernels), которая в основном является математической библиотекой для обработки сигналов. http://libvolk.org есть обзор. Заглянуть в ядра которые начинаются с 32f, например 32f_expfast. Вы заметите, что существуют разные реализации, общие и оптимизированные для ЦП, разные для каждого набора команд SIMD.

6

Другие решения

Вы можете скопировать адрес значения fp в unsigned char* и обрабатывать полученный указатель как адрес массива, который перекрывает значение fp.

1

В C или C ++, если x является двойной IEEE тогда, если L 64-битное int, выражение

L = *((long *) &x);

позволит получить доступ к битам напрямую.
Если s это байт, представляющий знак (0 = ‘+’, 1 = ‘-‘), e представляет собой целое число, представляющее беспристрастный показатель, и f является длинным int, представляющим дробные биты, то

s = (byte)(L >> 63);

e = ((int)(L >> 52) & 0x7FF) - 0x3FF;

f = (L & 0x000FFFFFFFFFFFFF);

(Если f — нормализованное число, т.е. не 0, ненормальное, inf или NaN, то последнее выражение должно иметь 0x0010000000000000 добавлен к нему для учета неявного старшего бита 1 в двойном формате IEEE.)

Перепаковка знака, экспоненты и дроби обратно в двойник похожа:

L = (с << 63) + ((e + 0x3FF) << 52) + (е & 0x000FFFFFFFFFFFFF);

х = * ((двойной *) &Л);

Приведенный выше код генерирует только несколько машинных инструкций без вызовов подпрограмм на 64-битных машинах, скомпилированных с 64-битным кодом. В 32-битном коде иногда требуется выполнить 64-битную арифметику, но хороший компилятор обычно генерирует встроенный код. В любом случае этот подход очень быстрый.

Аналогичный подход работает для C # с использованием L = bitConverter.DoubleToInt64Bits(x); а также x = BitConverter.Int64BitsToDouble(L); или точно так же, как указано выше, если разрешен небезопасный код.

0
По вопросам рекламы [email protected]