ошибочный Visual C с плавающей точкой / двойное преобразование? - PullRequest
3 голосов
/ 07 апреля 2010

В 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 не так.

Проблема возникает также с / fp = точной и с / fp = строгой, как с /fp=fast.

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

Ответы [ 3 ]

4 голосов
/ 07 апреля 2010

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

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

3 голосов
/ 07 апреля 2010

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

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

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

fabs(d1 - d2) <= (d2 * FLT_EPSILON)

Это всего лишь пример, хотя я не проверял, имеет ли он дело с этим делом. Вы должны выбрать допустимое отклонение, которое вам подходит, и вам, возможно, придется беспокоиться о множестве крайних случаев - d2 может быть нулем, либо значение может быть бесконечностью, либо NaN, возможно, другие.

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

2 голосов
/ 07 апреля 2010

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

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

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

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

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