Как бороться с избыточной точностью в вычислениях с плавающей точкой? - PullRequest
9 голосов
/ 02 февраля 2009

В моем численном моделировании у меня есть код, подобный следующему фрагменту

double x;
do {
  x = /* some computation */;
} while (x <= 0.0);
/* some algorithm that requires x to be (precisely) larger than 0 */

В некоторых компиляторах (например, gcc) на определенных платформах (например, linux, x87 math) возможно, что x вычисляется с точностью, превышающей двойную («с избыточной точностью»). ( Обновление : когда я говорю здесь о точности, я имею в виду точность / и / диапазон.) При этих обстоятельствах вполне возможно, что сравнение (x <= 0) возвращает false, даже если в следующий раз x округляется в меньшую сторону для удвоения точности он становится равным 0. (И нет никакой гарантии, что x не будет округлено в произвольный момент времени.)

Есть ли способ выполнить это сравнение, что

  • портативный,
  • работает в коде, который встроен,
  • не влияет на производительность и
  • не исключает некоторый произвольный диапазон (0, eps)?

Я пытался использовать (x < std::numeric_limits<double>::denorm_min()), но это, казалось, значительно замедляло цикл при работе с математикой SSE2. (Я знаю, что денормали могут замедлить вычисления, но я не ожидал, что они будут медленнее просто перемещаться и сравнивать.)

Обновление: Альтернативой является использование volatile для принудительного ввода x в память перед сравнением, например написав

} while (*((volatile double*)&x) <= 0.0);

Однако, в зависимости от приложения и оптимизаций, применяемых компилятором, это решение также может привести к значительным накладным расходам.

Обновление: Проблема с любым допуском заключается в том, что он довольно произвольный, т. Е. Зависит от конкретного приложения или контекста. Я предпочел бы просто выполнить сравнение без лишней точности, чтобы мне не приходилось делать какие-либо дополнительные предположения или вводить произвольные эпсилоны в документацию моих библиотечных функций.

Ответы [ 5 ]

7 голосов
/ 02 февраля 2009

Как заявил Аркадий в комментариях, явный актерский состав ((double)x) <= 0.0 должен работать - по крайней мере, в соответствии со стандартом.

C99: TC3, 5.2.4.2.2 §8:

За исключением присваивания и приведения (которые удаляют весь дополнительный диапазон и точность), значения операций с плавающими операндами и значениями, подверженными обычным арифметическим преобразованиям и плавающим константам, оцениваются в формате, диапазон и точность которого могут быть требуется по типу. [...]


Если вы используете GCC на x86, вы можете использовать флаги -mpc32, -mpc64 и -mpc80, чтобы установить точность операций с плавающей запятой равную одинарной, двойной и расширенной двойной точности.

2 голосов
/ 02 февраля 2009

В своем вопросе вы заявили, что использование volatile будет работать, но это приведет к огромному снижению производительности. Как насчет использования переменной volatile только во время сравнения, позволяющей хранить x в регистре?

double x; /* might have excess precision */
volatile double x_dbl; /* guaranteed to be double precision */
do {
  x = /* some computation */;
  x_dbl = x;
} while (x_dbl <= 0.0);

Вам также следует проверить, можете ли вы ускорить сравнение с наименьшим субнормальным значением, явно указав long double, и кешировать это значение, т.е.

const long double dbl_denorm_min = static_cast<long double>(std::numeric_limits<double>::denorm_min());

, а затем сравнить

x < dbl_denorm_min

Я бы предположил, что приличный компилятор сделает это автоматически, но никто не знает ...

1 голос
/ 02 февраля 2009

Интересно, есть ли у вас правильный критерий остановки? Похоже, что x <= 0 является условием <strong>исключение , но не условием завершения , и условие завершения легче выполнить. Возможно, в вашем цикле while должен быть оператор break, который останавливает итерацию при достижении некоторого допуска. Например, многие алгоритмы завершаются, когда две последовательные итерации достаточно близки друг к другу.

0 голосов
/ 02 февраля 2009

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

0 голосов
/ 02 февраля 2009

Ну, у GCC есть флаг -fexcess-precision, который вызывает проблему, которую вы обсуждаете. У него также есть флаг -ffloat-store, который решает проблему, которую вы обсуждаете.

"Не храните переменные с плавающей запятой в регистрах. Это предотвращает нежелательную избыточную точность на машинах, таких как 68000, где плавающие регистры (из 68881) сохраняют большую точность, чем предполагалось для двойной."

Я сомневаюсь, что решение не влияет на производительность, но, вероятно, оно не слишком дорогое. Случайный поиск в Google предполагает, что он стоит около 20%. На самом деле, я не думаю, что - это решение, которое является одновременно переносимым и не оказывает влияния на производительность, поскольку принуждение чипа не использовать избыточную точность часто предполагает некоторую несвободную операцию. Тем не менее, это, вероятно, то решение, которое вам нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...