Возможный дубликат:
странный вывод в сравнении float с плавающим литералом
float f = 1.1;
double d = 1.1;
if(f == d) // returns false!
Почему это так?
Возможный дубликат:
странный вывод в сравнении float с плавающим литералом
float f = 1.1;
double d = 1.1;
if(f == d) // returns false!
Почему это так?
Важные факторы, рассматриваемые с номерами float
или double
:
Точность и Округление
Точность:
Точность числа с плавающей запятой - это количество цифр, которое оно может представлять, не теряя при этом никакой информации.
Рассмотрим долю 1/3
. Десятичное представление этого числа 0.33333333333333…
с выходом 3 на бесконечность. Для бесконечной длины требуется бесконечная память для точной точности, но типы данных float
или double
обычно имеют только 4
или 8
байты. Таким образом, плавающие точки и двойные числа могут хранить только определенное количество цифр, а остальные будут потеряны. Таким образом, нет определенного точного способа представления чисел с плавающей точкой или двойными номерами с числами, которые требуют большей точности, чем могут удерживать переменные.
Округление:
Между значениями binary
и decimal (base 10)
существуют неочевидные различия.
Рассмотрим долю 1/10
. В decimal
это легко представить в виде 0.1
, а 0.1
можно рассматривать как легко представимое число. Однако в двоичном выражении 0.1
представляется бесконечной последовательностью: 0.00011001100110011…
Пример:
#include <iomanip>
int main()
{
using namespace std;
cout << setprecision(17);
double dValue = 0.1;
cout << dValue << endl;
}
Этот вывод:
0.10000000000000001
И не
0.1.
Это связано с тем, что двойнику пришлось усекать аппроксимацию из-за ограниченной памяти, что приводит к числу, которое не точно 0.1
. Такой сценарий называется Ошибка округления.
Всякий раз, когда вы сравниваете два близких поплавка и двойные числа, такие ошибки округления начинаются, и в итоге сравнение дает неверные результаты, и поэтому вы никогда не должны сравнивать числа с плавающей запятой или использовать double ==
.
Лучшее, что вы можете сделать, это принять их разницу и проверить, меньше ли он эпсилон.
abs(x - y) < epsilon
Попробуйте запустить этот код, результаты станут очевидными.
#include <iomanip>
#include <iostream>
int main()
{
std::cout << std::setprecision(100) << (double)1.1 << std::endl;
std::cout << std::setprecision(100) << (float)1.1 << std::endl;
std::cout << std::setprecision(100) << (double)((float)1.1) << std::endl;
}
Выход:
1.100000000000000088817841970012523233890533447265625
1.10000002384185791015625
1.10000002384185791015625
Ни float
, ни double
не может точно представлять 1.1. Когда вы пытаетесь выполнить сравнение, число float неявно преобразуется в double. Двойной тип данных может точно представлять содержимое float, поэтому сравнение дает false.
Как правило, вам не следует сравнивать поплавки с поплавками, удваивать до удваивания или плавать до удвоения с помощью ==
.
Лучшая практика состоит в том, чтобы вычесть их и проверить, является ли абсолютное значение разницы менее маленьким эпсилон.
if(std::fabs(f - d) < std::numeric_limits<float>::epsilon())
{
// ...
}
Одна из причин заключается в том, что числа с плавающей запятой являются (более или менее) двоичными дробями и могут приближать только число десятичных чисел. Многие десятичные числа обязательно должны быть преобразованы в повторяющиеся двоичные "десятичные знаки" или иррациональные числа. Это приведет к ошибке округления.
Например, 1/5 нельзя представить точно как число с плавающей запятой, используя двоичную базу, но может быть представлено точно с использованием десятичной базы.
В вашем конкретном случае float и double будут иметь различное округление для иррациональной/повторяющейся фракции, которая должна использоваться для представления 1.1
в двоичном формате. Вам будет трудно заставить их быть "равными" после того, как их соответствующие преобразования внесут разные уровни ошибки округления.
Код, приведенный выше, решает это, просто проверяя, находятся ли значения в пределах очень короткой дельта. Ваше сравнение меняется от "равны ли эти значения?" для "являются ли эти значения в пределах небольшой погрешности друг от друга?"
Также см. этот вопрос: Каков наиболее эффективный способ для float и double сравнения?
Существует также множество других странностей чисел с плавающей запятой, которые нарушают простое сравнение равенства. Ознакомьтесь с этой статьей для описания некоторых из них:
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
32-разрядный float
IEEE 754 может хранить: 1.1000000238...
64-разрядный double
IEEE 754 может хранить: 1.1000000000000000888...
Посмотрите, почему они не "равны"?
В IEEE 754 фракции хранятся со степенями 2:
2^(-1), 2^(-2), 2^(-3), ...
1/2, 1/4, 1/8, ...
Теперь нам нужен способ представления 0.1
. Это (упрощенная версия) 32-битного представления IEEE 754 (float):
2^(-4) + 2^(-5) + 2^(-8) + 2^(-9) + 2^(-12) + 2^(-13) + ... + 2^(-24) + 2^(-25) + 2^(-27)
00011001100110011001101
1.10000002384185791015625
С 64-битным double
он еще более точным. Он не останавливается на 2^(-25)
, он продолжает расти примерно в два раза больше. (2^(-48) + 2^(-49) + 2^(-51)
, может быть?)
Ресурсы
Конвертер IEEE 754 (32-разрядный)
Поплавки и двойники хранятся в двоичном формате, который не может точно представлять каждое число (невозможно представить бесконечное число возможных разных чисел в конечном пространстве).
В результате они округляются. Поплавок должен округлить более чем вдвое, потому что он меньше, поэтому округление 1.1 до ближайшего действительного Float отличается от 1,1 округленным до ближайшего valud Double.
Чтобы узнать, какие числа действительны, поплавки и двойники см. Плавающая точка