ошибочное преобразование с плавающей запятой/двойное преобразование Visual C?
-
25-09-2019 - |
Вопрос
В 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=precision, и с /fp=strict, и с /fp=fast.
В чем здесь проблема?Любая подсказка, как избежать этой проблемы?Это приводит к серьезным численным проблемам.
Решение
Это не проблема с VC ++ или что-то в этом роде - это фундаментальная проблема с тем, как на компьютере хранятся цифровые номера. Для получения дополнительной информации см. IEEE-754..
Вопрос состоит в том, что преобразование из Float в двойной сделан так, что преобразование обратно из Double, чтобы поплавок приводит к тому же значение поплавка, с которой вы начали. Я не знаю ни о потере точности, за исключением использования только удваивает, когда вам нужна более длительная точность. Может быть, что пытаться round
Преобразованный поплавок до двух десятичных знаков настроит его на правильное значение, но я не уверен в этом.
Другие советы
Ценность в 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
значения и / или используют более численно стабильный алгоритм.
В том, что здесь происходит, нет ничего плохого.
Из-за того, как числа с плавающей запятой представлены в памяти, 42,479999999999997 является ближайшим представлением 42,48, которое может иметь тип double.
Прочтите эту статью:http://docs.sun.com/source/806-3568/ncg_goldberg.html
Это объясняет, что там происходит.К сожалению, с его хранением ничего не поделаешь.