Рассмотрим следующий фрагмент кода:
float f = 0.01 ;
printf("%f\n",f - 0.01);
if (f - 0.01 == 0)
{
printf("%f\n",f - 0.01);
}
Когда я запускаю этот код, для второй строки я получаю вывод -0.000000
, а условие if не выполняется.
В чем причина -0.000000
?
Я помню из класса цифровой логики, который я взял в колледже, что это возникает из-за внутреннего представления с использованием одного дополнения. Пожалуйста, исправьте меня, если я ошибаюсь, и, пожалуйста, предложите исправления и как избежать этого в будущем.
Я использую clang для компиляции моего кода, если это имеет значение.
0.01
является double
не float
(у вас, вероятно, есть предупреждения об этом при компиляции кода.)
Таким образом, вы в основном конвертируете "0.01" назад и вперед между поплавками и удвоениями, что и является причиной ваших расхождений.
Поэтому решите, хотите ли вы использовать поплавки (например, 0.01f
) или парные, и придерживайтесь одной версии на всем протяжении.
Однако, как указывали другие ответы, вы никогда не получите "точного" значения при выполнении арифметики с плавающей запятой - это просто не работает.
Для справки, обе эти версии дадут ответ, который вы ожидаете
float f = 0.01f ;
printf("%f\n",f - 0.01f);
if (f - 0.01f == 0)
{
printf("%f\n",f - 0.01f);
}
или
double f = 0.01 ;
printf("%f\n",f - 0.01);
if (f - 0.01 == 0)
{
printf("%f\n",f - 0.01);
}
оба печатают
0.000000
0.000000
float
. Хотя значение 0.01F
не равно 0,01, оно должно быть везде одинаковым.
У вас есть две проблемы:
0.01
не может быть точно представлена как двоичное значение с плавающей запятойf
имеет тип float
а 0.01
имеет тип double
Для вашего расчета требуется преобразование из double
в float
и обратно, которое (по-видимому) не дает точно того же значения, с которого оно начиналось.
Возможно, вы сможете исправить этот конкретный пример, придерживаясь одного типа (float
или double
) для всех значений; но у вас все еще будут проблемы, если вы хотите сравнить результаты более сложных вычислений для точного равенства.
У вас два двойных 0,01 - один конвертирован в поплавок. Из-за потери точности double (float (0.01))! = Double (0.01)
Даже без очевидной потери точности вы можете столкнуться с проблемами, используя только double (s). Компилятор может хранить один в виде расширенного двойника в регистре и извлекать другой из памяти (сохраняемый как двойной)
1.0 / 10.0
, с другой стороны, все ставки отключены, и такие вещи, как double d1 = 1.0, d2 = 1.0; d1 /= 10.0; d2 /= 10.0; if ( d1 != d2 )
могут привести к неожиданным результатам. В зависимости от архитектуры и параметров компилятора.
-std=c99
программы, на blog.frama-c.com/index.php?post/2013/07/24/… , особенно для r4
оценкой 0.
Причина в том, что 0.01
не может быть правильно отображено в двоичном плавающем числе. Это можно понять на примере: 10/3
дает результат 3.333333333333333.....
, Т.е. он не может быть правильно отображен в десятичном значении. Аналогичный случай с 0.01
. Каждое десятичное число с плавающей запятой не может быть правильно представлено в бинарном эквиваленте с плавающей запятой.
0.01
не может быть точно представлен в двоичной форме с плавающей точкой.float
не сохраняются как дополнение к 1. Читайте здесь: floatingpoint-gui.de обо всем, что вам нужно знать о значениях с плавающей запятой.