С плавающей запятой — Как я могу избежать этой проблемы округления числа с плавающей запятой в C ++?

С кодом ниже я получаю результат «4.31 43099».

  double f = atof("4.31");
long ff = f * 10000L;
std::cout << f << ' ' << ff << '\n';

Если я поменяю «double f» на «float f». Я получаю ожидаемый результат «4,31 43100». Я не уверен, что замена «double» на «float» является хорошим решением. Есть ли хорошее решение, чтобы убедиться, что я получу «43100»?

0

Решение

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

В этом случае вы просто хотите убедиться, что если число было округлено в меньшую сторону, оно округлено в большую сторону. Вы делаете это, добавляя наименьшую возможную сумму к значению, что делается с помощью nextafter функция если у вас есть C ++ 11:

long ff = std::nextafter(f, 1.1*f) * 10000L;

Если у вас нет nextafter Вы можете приблизить его с numeric_limits.

long ff = (f * (1.0 + std::numeric_limits<double>::epsilon())) * 10000L;

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

long ff = (f * 1.0000001) * 10000L;
2

Другие решения

Вы не сможете устранить ошибки в арифметике с плавающей запятой (хотя при правильном анализе вы можете вычислить ошибку). Для случайного использования, что вы можете сделать, чтобы получить более интуитивные результаты, это заменить встроенное преобразование с плавающей точкой в ​​целочисленное (которое выполняет усечение) обычным округлением:

double f = atof("4.31");
long ff = std::round(f * 10000L);
std::cout << f << ' ' << ff << '\n';

Это должно вывести то, что вы ожидаете: 4.31 43100


Также нет смысла использовать 10000Lпотому что, независимо от того, какой тип интегрального типа вы используете, он все равно преобразуется в fТип с плавающей точкой для умножения. просто используйте std::round(f * 10000.0);

3

Со стандартными типами C — я сомневаюсь.

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

Плавающие числа с указанием не могут хранить все значения, которые, как вы думаете, могли бы — количество битов ограничено — вы не можете поместить более 4 миллиардов различных значений в 32 бита. И это только первое ограничение.

Значения с плавающей точкой (в C) представлены как: sign - one sign bit, power - bits which defines the power of two for the number, significand - the bits that actually make the number,

Ваш фактический номер sign * significand * 2 inpowerof(power - normalization).

Double — 1 бит знака, 15 битов мощности (нормализовано, чтобы быть положительным, но это не точка) и 48 битов, чтобы представить значение;

Это много, но недостаточно для представления всех значений, особенно когда они не могут быть легко представлены в виде конечной суммы степеней двух: как двоичный код 1010.101101 (101). Например, он не может точно представлять такие значения, как 1/3 = 0,333333 (3). Это второе ограничение.

Попробуйте прочитать — приличное понимание преимуществ и недостатков арифметики с плавающей запятой может быть очень полезным:
http://en.wikipedia.org/wiki/Floating_point а также http://homepage.cs.uiowa.edu/~atkinson/m170.dir/overton.pdf

0

Здесь были некоторые запутанные ответы! Происходит следующее: 4.31 нельзя точно представить как число с одинарной или двойной точностью. Оказывается, что ближайшее представимое число с одинарной точностью чуть больше 4,31, а ближайшее представимое число с двойной точностью чуть меньше 4,31. Когда значение с плавающей запятой присваивается целочисленной переменной, оно округляется до нуля (а не до ближайшего целого числа!).

Так что если f с одинарной точностью, f * 10000L больше 43100, поэтому округляется до 43100. А если f двойная точность, f * 10000L меньше 43100, поэтому округляется до 43099.

Комментарий н.м. предполагает f * 10000L + 0.5что я считаю лучшим решением.

0
По вопросам рекламы [email protected]