Как написать переносимую арифметику с плавающей точкой в ​​C ++? - PullRequest
8 голосов
/ 11 июня 2009

Допустим, вы пишете приложение на C ++, выполняющее много арифметических операций с плавающей запятой. Скажем, это приложение должно быть переносимым на разумном диапазоне аппаратных средств и платформ ОС (скажем, 32- и 64-разрядное оборудование, Windows и Linux как в 32-, так и в 64-разрядном вариантах ...)

Как бы вы убедились, что ваша арифметика с плавающей запятой одинакова на всех платформах? Например, как быть уверенным, что 32-битное значение с плавающей запятой действительно будет 32-битным на всех платформах?

Для целых чисел у нас есть stdint.h , но, похоже, не существует эквивалента с плавающей запятой.


[EDIT] * * +1010

Я получил очень интересные ответы, но я бы хотел добавить немного точности в вопрос.

Для целых чисел я могу написать:

#include <stdint>
[...]
int32_t myInt;

и убедитесь, что на какой бы платформе (C99-совместимой) я ни находился, myInt представляет собой 32-битное целое число.

Если я напишу:

double myDouble;
float myFloat;

Уверен ли я, что это скомпилирует, соответственно, 64-битные и 32-битные числа с плавающей запятой на всех платформах?

Ответы [ 6 ]

9 голосов
/ 11 июня 2009

Не IEEE 754

Как правило, вы не можете. Всегда есть компромисс между последовательностью и производительностью, и C ++ передает это вам.

Для платформ, в которых нет операций с плавающей запятой (например, встроенных процессоров и процессоров обработки сигналов), вы не можете использовать «родные» операции C ++ с плавающей запятой, по крайней мере, не переносимые. Хотя программный уровень был бы возможен, это, конечно, неосуществимо для устройств такого типа.

Для них вы можете использовать 16-битную или 32-битную арифметику с фиксированной точкой (но вы можете даже обнаружить, что long поддерживается только в зачаточном состоянии - и часто div очень дорогой). Однако это будет намного медленнее, чем встроенная арифметика с фиксированной точкой, и станет болезненным после четырех основных операций.

Я не встречал устройств с поддержкой плавающей запятой в другом формате, чем IEEE 754 . Исходя из моего опыта, вам лучше всего надеяться на стандарт, потому что в противном случае вы обычно заканчиваете тем, что строите алгоритмы и кодируете возможности устройства. Когда sin(x) внезапно стоит в 1000 раз дороже, вам лучше выбрать алгоритм, который ему не нужен.

IEEE 754 - Согласованность

Единственная непереносимость, которую я обнаружил здесь, - это когда вы ожидаете идентичные результаты на разных платформах. Наибольшее влияние оказывает оптимизатор. Опять же, вы можете обменять точность и скорость на последовательность. У большинства компиляторов есть возможность для этого - например, «согласованность с плавающей точкой» в Visual C ++. Но учтите, что это всегда точность за пределами гарантий стандарта.

Почему результаты становятся противоречивыми? Во-первых, регистры FPU часто имеют более высокое разрешение, чем двойные (например, 80 бит), поэтому до тех пор, пока генератор кода не сохранит значение обратно, промежуточные значения сохраняются с более высокой точностью.

Во-вторых, эквивалентности типа a*(b+c) = a*b + a*c не являются точными из-за ограниченной точности. Тем не менее, оптимизатор, если разрешено, может использовать их.

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

поплавок

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

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

4 голосов
/ 11 июня 2009

Использовать фиксированную точку.

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

И даже тогда вы не гарантируете, что сможете генерировать идентичное поведение с плавающей запятой; это также зависит от того, на каком CPU / FPU вы работаете.

2 голосов
/ 11 июня 2009

Переносимость - это одно, а получение согласованных результатов на разных платформах - это другое. В зависимости от того, что вы пытаетесь сделать, написание переносимого кода не должно быть слишком сложным, но получить согласованные результаты на ЛЮБОЙ платформе практически невозможно.

2 голосов
/ 11 июня 2009

Это не должно быть проблемой, IEEE 754 уже определяет все детали размещения поплавков.

Максимальное и минимальное хранимые значения должны быть определены в пределах. H

0 голосов
/ 12 июня 2009

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

0 голосов
/ 11 июня 2009

Я полагаю, что "limit.h" будет включать константы библиотеки C INT_MAX и ее братьев. Тем не менее, предпочтительно использовать «пределы» и классы, которые оно определяет:

std::numeric_limits<float>, std::numeric_limits<double>, std::numberic_limits<int>, etc...
...