Подтвердить что ты не робот

Почему сравнение double и float приводит к неожиданному результату?

Возможный дубликат:
странный вывод в сравнении float с плавающим литералом

float f = 1.1;
double d = 1.1;
if(f == d) // returns false!

Почему это так?

4b9b3361

Ответ 1

Важные факторы, рассматриваемые с номерами 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

Ответ 2

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

#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.

Ответ 3

Как правило, вам не следует сравнивать поплавки с поплавками, удваивать до удваивания или плавать до удвоения с помощью ==.

Лучшая практика состоит в том, чтобы вычесть их и проверить, является ли абсолютное значение разницы менее маленьким эпсилон.

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

Ответ 4

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-разрядный)

Ответ 5

Поплавки и двойники хранятся в двоичном формате, который не может точно представлять каждое число (невозможно представить бесконечное число возможных разных чисел в конечном пространстве).

В результате они округляются. Поплавок должен округлить более чем вдвое, потому что он меньше, поэтому округление 1.1 до ближайшего действительного Float отличается от 1,1 округленным до ближайшего valud Double.

Чтобы узнать, какие числа действительны, поплавки и двойники см. Плавающая точка