С++ sqrt гарантированная точность, верхняя/нижняя граница

Я должен проверить неравенство, содержащее квадратные корни. Чтобы избежать неправильных результатов из-за неточности и округления с плавающей запятой, я использую std::nextafter() для получения верхней/нижней границы:

#include <cfloat> // DBL_MAX
#include <cmath> // std::nextafter, std::sqrt

double x = 42.0; //just an example number
double y = std::nextafter(std::sqrt(x), DBL_MAX);

a) Гарантируется ли y*y >= x с использованием компилятора GCC?

b) Будет ли это работать для других операций, таких как + - * / или даже std::cos() и std::acos()?

c) Есть ли лучшие способы получить верхнюю/нижнюю границы?

Обновление: I читать, это не гарантируется стандартом С++, но должно работать в соответствии с IEEE-754. Будет ли это работать с компилятором GCC?

Ответы

Ответ 1

В общем случае операции с плавающей запятой приводят к ошибке ULP. IEEE 754 требует, чтобы результаты для большинства операций были корректными с точностью до 0,5 ULP, но могут накапливаться ошибки, что означает, что результат может быть не в пределах одного ULP от точного результата. Существуют также пределы точности, поэтому в зависимости от количества цифр в результирующих значениях вы также можете не работать со значениями одинаковых величин. Трансцендентальные функции также несколько пресловутые для введения ошибки в вычисления.

Однако, если вы используете GNU glibc, sqrt будет корректным в пределах 0,5 ULP (округленный), поэтому вы являетесь конкретным примером (пренебрегая NaN, +/-0, +/-Inf). Хотя, вероятно, лучше определить некоторый эпсилон в качестве допустимости ошибок и использовать его в качестве вашей привязки. Для exmaple

bool gt(double a, double b, double eps) {

  return (a > b - eps);
}

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

Итак, чтобы ответить на ваши вопросы...

a) Является ли y * y >= x гарантированным с использованием компилятора GCC?

Предполагая, что вы используете GNU glibc или встроенные функции SSE2, да.

b) Будет ли это работать для других операций, таких как + - */или даже std:: cos() и std:: acos()?

Предполагая, что вы используете GNU glibc и одну операцию, да. Хотя некоторые трансценденталы не гарантированы правильно округлены.

c) Есть ли лучшие способы получить верхнюю/нижнюю границы?

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

Ответ 2

Для GCC эта страница предполагает, что она будет работать, если вы используете встроенную sqrt-функцию GCC __builtin_sqrt.

Кроме того, это поведение будет зависеть от того, как вы компилируете свой код и машину, на которой он запущен на

  • Если процессор поддерживает SSE2, вы должны скомпилировать свой код с флагами -mfpmath=sse -msse2, чтобы гарантировать, что все операции с плавающей запятой выполняются с использованием регистров SSE.

  • Если процессор не поддерживает SSE2, вы должны использовать тип long double для значений с плавающей запятой и скомпилировать с флагом -ffloat-store, чтобы заставить GCC не использовать регистры для хранения значений с плавающей запятой (вы у меня будет штраф за выполнение для этого)

Ответ 3

Относительно

c) Есть ли лучшие способы получить верхнюю/нижнюю границы?

Другим способом является использование другого режима округления, т.е. FE_UPWARD или FE_DOWNWARD вместо стандартного FE_TONEAREST. См. /info/179729/change-floating-point-rounding-mode/1006368#1006368 Это может быть медленнее, но лучше верхний/нижний оценка.