Решение проблем точности в числах с плавающей запятой

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

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

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

Решение

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

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

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

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

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

x = 0.2;  
y = 0.3;  
equal = (Math.abs(x - y) < 0.000001)  

Используйте десятичные дроби: 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; }