Работа с проблемами точности в числах с плавающей точкой - PullRequest
9 голосов
/ 26 февраля 2009

Мне было интересно, есть ли способ преодоления проблемы точности, которая, кажется, является результатом внутреннего представления моей машиной чисел с плавающей точкой:

Для ясности проблема сводится к следующему:

// 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 ]

15 голосов
/ 26 февраля 2009

При использовании арифметики с плавающей запятой строгое равенство практически бессмысленно. Обычно вы хотите сравнить с диапазоном допустимых значений.

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

См. Что должен знать каждый компьютерщик об арифметике с плавающей запятой и Сравнение чисел с плавающей запятой .

12 голосов
/ 26 февраля 2009

Проблем с точностью нет.

Полученный вами результат (1.9999999999999996) отличался от математического результата (2) с запасом 1E-16. Это довольно точно, учитывая ваш ввод "4.600".

Конечно, у вас есть проблема с округлением. Округление по умолчанию в C ++ - усечение; Вы хотите что-то похожее на решение Кипа. Детали зависят от вашего домена, ожидаете ли вы round(-x)== - round(x)?

11 голосов
/ 26 февраля 2009

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

5 голосов
/ 26 февраля 2009

Если точность действительно важна, то вам следует рассмотреть возможность использования чисел с плавающей запятой двойной точности, а не просто с плавающей запятой. Хотя из твоего вопроса видно, что ты уже есть. Тем не менее, у вас все еще есть проблема с проверкой конкретных значений. Вам нужен код в строке (при условии, что вы проверяете свое значение против нуля):

if (abs(value) < epsilon)
{
   // Do Stuff
}

где "эпсилон" - это небольшое, но ненулевое значение.

4 голосов
/ 26 февраля 2009

Очень простой и эффективный способ округлить число с плавающей запятой в целое число:

int rounded = (int)(f + 0.5);

Примечание: это работает, только если f всегда положительно. (спасибо случайному хакеру)

3 голосов
/ 26 февраля 2009

На компьютерах числа с плавающей запятой никогда не бывают точными. Они всегда просто близкое приближение. (1е-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; }
2 голосов
/ 26 февраля 2009

Вы можете прочитать эту бумагу , чтобы найти то, что вы ищете.

Вы можете получить абсолютное значение результата, как показано здесь :

x = 0.2;  
y = 0.3;  
equal = (Math.abs(x - y) < 0.000001)  
2 голосов
/ 26 февраля 2009

Использовать десятичные дроби: decNumber ++

...