Решение проблем точности в числах с плавающей запятой
Мне было интересно, есть ли способ преодолеть проблему с точностью, которая, похоже, является результатом внутреннего представления моей машиной чисел с плавающей запятой:
Для ясности проблема резюмируется следующим образом:
// str is "4.600"; atof( str ) is 4.5999999999999996
double mw = atof( str )
// The variables used in the columns calculation below are:
//
// mw = 4.5999999999999996
// p = 0.2
// g = 0.2
// h = 1 (integer)
int columns = (int) ( ( mw - ( h * 11 * p ) ) / ( ( h * 11 * p ) + g ) ) + 1;
Перед приведением к целочисленному типу результат вычисления столбцов равен 1.9999999999999996; так близко, но так далеко от желаемого результата 2.0.
Любые предложения приветствуются.
Ответов (8)8
Используйте десятичные дроби: decNumber ++
Когда вы используете арифметику с плавающей запятой, строгое равенство практически бессмысленно. Обычно вы хотите сравнить с диапазоном допустимых значений.
Обратите внимание, что некоторые значения не могут быть представлены точно как vlues с плавающей запятой.
Узнайте, что должен знать каждый компьютерный ученый об арифметике с плавающей запятой и сравнении чисел с плавающей запятой .
Если вы его не читали, название статьи действительно правильное. Пожалуйста, прочтите его, чтобы узнать больше об основах арифметики с плавающей запятой на современных компьютерах, некоторых подводных камнях и объяснениях, почему они ведут себя именно так.
Нет проблем с точностью.
Полученный результат (1.9999999999999996) отличался от математического результата (2) на величину 1E-16. Это довольно точно, учитывая ваш ввод «4,600».
Конечно, у вас есть проблема с округлением. По умолчанию в C++ округление - это усечение; вам нужно что-то похожее на решение Кипа. Вы ожидаете, что детали зависят от вашего точного домена round(-x)== - round(x)
?
Если точность действительно важна, вам следует рассмотреть возможность использования чисел с плавающей запятой двойной точности, а не просто чисел с плавающей запятой. Хотя из вашего вопроса кажется, что вы уже есть. Однако у вас все еще есть проблема с проверкой конкретных значений. Вам нужен код в строках (при условии, что вы проверяете свое значение на ноль):
if (abs(value) < epsilon)
{
// Do Stuff
}
где «эпсилон» - небольшое, но ненулевое значение.
На компьютерах числа с плавающей запятой никогда не бывают точными. Они всегда лишь близкое приближение. (1e-16 близко.)
Иногда есть скрытые фрагменты, которых вы не видите. Иногда основные правила алгебры перестают действовать: a * b! = B * a. Иногда сравнение регистра с памятью обнаруживает эти тонкие различия. Или использовать математический сопроцессор против библиотеки времени выполнения с плавающей запятой. (Я слишком долго этим занимаюсь.)
C99 определяет: (Посмотрите в math.h )
double round(double x);
float roundf(float x);
long double roundl(long double x);
.
Или вы можете скатить свою:
template<class TYPE> inline int ROUND(const TYPE & x)
{ return int( (x > 0) ? (x + 0.5) : (x - 0.5) ); }
Для эквивалентности с плавающей запятой попробуйте:
template<class TYPE> inline TYPE ABS(const TYPE & t)
{ return t>=0 ? t : - t; }
template<class TYPE> inline bool FLOAT_EQUIVALENT(
const TYPE & x, const TYPE & y, const TYPE & epsilon )
{ return ABS(x-y) < epsilon; }