Как написать портативную арифметику с плавающей запятой в c++?
скажем, вы пишете приложение C++, выполняющее много арифметики с плавающей запятой. Скажем, это приложение должно быть портативным в разумном диапазоне аппаратных и ОС-платформ (скажем, 32 и 64 бит оборудования, Windows и Linux как в 32 и 64 битах вкусов...).
Как бы вы убедились, что ваша арифметика с плавающей запятой одинакова на всех платформах ? Например, как убедиться, что 32-битное значение с плавающей запятой действительно будет 32 бит на всех платформах ?
для целых чисел у нас есть stdint.h но, похоже, не существует эквивалента с плавающей запятой.
[EDIT]
Я получил очень интересные ответы, но я хотел бы добавить некоторую ясность в вопрос.
для целых чисел, я могу написать:
#include <stdint>
[...]
int32_t myInt;
и убедитесь, что независимо от платформы (совместимой с C99), на которой я нахожусь, myInt является 32-битным целым числом.
Если я напишу:
double myDouble;
float myFloat;
Я уверен, что это будет компилироваться, соответственно, 64 бит и 32 бит с плавающей запятой на всех платформах ?
6 ответов
не-IEEE 754
как правило, вы не можете. Всегда есть компромисс между согласованностью и производительностью, и C++ передает Это вам.
для платформ, которые не имеют операций с плавающей запятой (например, встроенные и процессоры обработки сигналов), вы не можете использовать "родные" операции с плавающей запятой C++, по крайней мере, не переносимо. Хотя программный уровень был бы возможен, это, безусловно, невозможно для устройств такого типа.
для них можно использовать 16-битная или 32-битная арифметика с фиксированной точкой (но вы можете даже обнаружить, что long поддерживается только рудиментарно - и часто div очень дорого). Однако это будет намного медленнее, чем встроенная арифметика с фиксированной точкой, и станет болезненным после основных четырех операций.
Я не сталкивался с устройствами, которые поддерживают плавающую точку в другом формате, чем IEEE 754. По моему опыту, лучше всего надеяться на стандарт, потому что в противном случае вы обычно в конечном итоге построение алгоритмов и кода вокруг возможностей устройства. Когда sin(x)
внезапно стоит в 1000 раз больше, вам лучше выбрать алгоритм, который в этом не нуждается.
IEEE 754 - согласованность
единственная непереносимость, которую я нашел здесь, - это когда вы ожидаете бит-идентичных результатов на разных платформах. Наибольшее влияние оказывает оптимизатор. Опять же, вы можете обменять точность и скорость на согласованность. У большинства компиляторов есть опция для этого-например, " плавающая точка согласованность " в Visual C++. Но обратите внимание, что это всегда точность дальше гарантии стандартные.
почему результаты становятся несовместимы? Во-первых, регистры FPU часто имеют более высокое разрешение, чем double (например, 80 бит), поэтому, пока генератор кода не сохраняет значение обратно, промежуточные значения хранятся с более высокой точностью.
во-вторых, эквивалентности, такие как a*(b+c) = a*b + a*c
не являются точными из-за ограниченной точности. Однако оптимизатор, если это разрешено, может использовать их.
также-то, что я узнал на собственном опыте - функции печати и синтаксического анализа не обязательно согласованы между платформами, вероятно, из-за числовых неточностей.
float
Это распространенное заблуждение, что операции float по своей сути быстрее, чем double. работа с большими массивами float быстрее, как правило,за счет меньшего количества пропусков кэша.
будьте осторожны с точностью поплавок. это может быть "достаточно хорошо" в течение длительного времени, но я часто видел, как это происходит быстрее, чем ожидалось. Float-based FFT может быть намного быстрее из-за поддержки SIMD, но генерировать заметные артефакты довольно рано для обработки звука.
использовать фиксированную точку.
однако, если вы хотите приблизиться к области возможного создания переносимых операций с плавающей запятой, вам, по крайней мере, нужно использовать controlfp
для обеспечения согласованного поведения FPU, а также обеспечения соответствия компилятора ANSI в отношении операций с плавающей запятой. Почему ANSI? Потому что это стандарт.
и даже тогда вы не гарантируете, что можете генерировать одинаковое поведение с плавающей запятой; это также зависит от CPU / FPU ты бежишь дальше.
Это не должно быть проблемой, IEEE 754 уже определяет все детали компоновки поплавков.
максимальное и минимальное значения storable должны быть определены в пределах.h
Portable-это одно, а генерирование согласованных результатов на разных платформах-другое. В зависимости от того, что вы пытаетесь сделать, писать портативный код не должно быть слишком сложно, но получить согласованные результаты на любой платформе практически невозможно.
Я считаю, что "ограничения.h " будет включать константы библиотеки C INT_MAX и ее собратьев. Однако предпочтительнее использовать "ограничения" и классы, которые он определяет:
std::numeric_limits<float>, std::numeric_limits<double>, std::numberic_limits<int>, etc...
Если вы предполагаете, что получите те же результаты в другой системе, прочитайте что может вызвать детерминированный процесс для генерации ошибок с плавающей запятой первый. Вы можете быть удивлены, узнав, что ваша арифметика с плавающей запятой даже не одинакова в разных запусках на одной и той же машине!