Работа с проблемами точности в номерах с плавающей точкой
-
09-09-2019 - |
Вопрос
Мне было интересно, есть ли способ преодолеть проблему точности, которая, по-видимому, является результатом внутреннего представления моей машины о числах с плавающей точкой:
Ради ясности проблема обобщена как:
// 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.
Любые предложения приветствуются.
Решение
Очень простой и эффективный способ окружить номер плавающего запястья на целое число:
int rounded = (int)(f + 0.5);
Примечание: это работает только если f
всегда положительный. (Спасибо J случайный хакер)
Другие советы
Когда вы используете арифметическую арифметику, строгое равенство почти бессмысленно. Обычно вы хотите сравнить с диапазоном приемлемых значений.
Обратите внимание, что некоторые значения могут нет быть представленным точно так же, как VLUE с плавающей запятой.
Видеть Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой а также Сравнение номеров с плавающей запятой.
Там нет проблемы с сопротивлением.
Результат, который вы получили (1.999999999999996), отличался от математического результата (2) по краю 1E-16. Это довольно точно, учитывая ваш вход «4.600».
У вас есть проблема с округлением, конечно. Округление по умолчанию в C ++ - усечение; Вы хотите что -то похожее на решение Кипа. Детали зависят от вашего точного домена, вы ожидаете round(-x)== - round(x)
?
Если вы не читали это, название Эта бумага действительно правильно. Пожалуйста, подумайте о том, чтобы прочитать его, узнать больше о основах арифметики с плавающей точкой на современных компьютерах, некоторых подводных камнях и объяснениях того, почему они ведут себя так, как они.
Если точность действительно важна, то вам следует рассмотреть возможность использования двойных чисел с плавающей запятой, а не просто плавающей запятой. Хотя из вашего вопроса кажется, что вы уже есть. Тем не менее, у вас все еще есть проблема с проверкой конкретных значений. Вам нужен код в соответствии с (при условии, что вы проверяете свое значение с нулем):
if (abs(value) < epsilon)
{
// Do Stuff
}
Где «Эпсилон» - это какое -то маленькое, но не нулевое значение.
На компьютерах номера с плавающими точками никогда не бывают точными. Они всегда просто близкое приближение. (1E-16 близко.)
Иногда есть скрытые кусочки, которые вы не видите. Иногда основные правила алгебры больше не применяются: a*b! = B*a. Иногда сравнение регистра с памятью показывает эти тонкие различия. Или использование математической копроцессора против библиотеки плавания во время выполнения. (Я делаю это, что это много долго.)
C99 определяет: (посмотрите в математика)
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; }
Используйте десятичные десятки: decnumber ++