С++ 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 Это может быть медленнее, но лучше верхний/нижний оценка.