ошибочное визуальное преобразование c float / double?


В Visual C++ я написал следующий пример в программе C++:

float f1 = 42.48f;
double d1 = 42.48;
double d2 = f1;

Я скомпилировал программу с помощью Visual Studio 2005. В отладчике я вижу следующие значения:

f1  42.480000   float
d1  42.479999999999997  double
d2  42.479999542236328  double

D1, насколько я знаю, в порядке, но d2 ошибается.

Данная проблема возникает также с /ФП=точно как с /ФП=строгими, с /ФП=быстро.

В чем тут проблема? Есть подсказка, как избежать этой проблемы? Это приводит к серьезным численным проблемам.

3 3

3 ответа:

Это не проблема с VC++ или чем - то подобным-это фундаментальная проблема с тем, как числа с плавающей запятой хранятся на компьютере. Дополнительную информацию смотрите в разделеIEEE-754 .

Проблема заключается в том, что преобразование из float в double выполняется таким образом, что обратное преобразование из double в float приводит к точно такому же значению float, с которого вы начали. Я не знаю никакого способа обойти потерю точности, за исключением использования только двойников, когда вам нужна более высокая точность. Это может быть будь то попытка round преобразовать float в два знака после запятой установит его в правильное значение, но я не уверен в этом.

Значение в f1 и значение в d2 представляют собой одно и то же число. Это число не является точно 42.480000, как и не является точно 42.479999542236328, хотя оно имеет десятичное представление, которое завершается. При отображении поплавков ваше представление отладки округляется с точностью до поплавка, а при отображении двойников-с точностью до двойника. Таким образом, вы видите примерно в два раза больше значимых фигур таинственного значения, когда вы преобразуете и отображаете, как двойник.

d1 содержит лучшее приближение к 4.48, чем значение mystery, так как d1 содержит ближайший двойник к 4.48, тогда как f1 и d2 содержат только ближайшее значение float к 4.48. Что вы ожидали, что d2 будет содержать? f1 не может "вспомнить", что это" действительно должно быть "4.48, поэтому, когда он преобразуется в double, он становится"более точным".

Способ избежать этого зависит от того, какие серьезные численные проблемы вы имеете в виду. Если проблема в том, что d1 и d2 не делают сравните равные, и вы считаете, что они должны, тогда ответ заключается в том, чтобы включить небольшой допуск в ваши сравнения, например, заменить d1 == d2 на:
fabs(d1 - d2) <= (d2 * FLT_EPSILON)
Это всего лишь пример, хотя я не проверил, имеет ли он отношение к этому делу. Вы должны выбрать допуск, который работает для вас, и вы также можете беспокоиться о множестве крайних случаев-d2 может быть нулем, любое значение может быть Бесконечностью или NaN, возможно, другие.

Если проблема заключается в том, что d2 не является достаточно точное значение для вашего алгоритма, чтобы получить точные результаты, вы должны избегать значений float и/или использовать более численно стабильный алгоритм.

В том, что здесь происходит, нет ничего плохого.

Из-за способа представления чисел с плавающей запятой в памяти, 42.479999999999997 является наиболее близким представлением 42.48, которое может иметь двойник.

Прочитайте эту статью: http://docs.sun.com/source/806-3568/ncg_goldberg.html

Это объясняет, что там происходит. К сожалению, вы ничего не можете сделать с его хранением.