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

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

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

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

Важными факторами, рассматриваемыми с помощью float или double чисел, являются:
Точность и округление


Точность:
Точность числа с плавающей запятой – это количество цифр, которое оно может представлять, не теряя при этом никакой информации.

Рассмотрим долю 1/3 . Десятичное представление этого числа равно 0.33333333333333… с 3- 0.33333333333333… до бесконечности. Для бесконечной длины требуется бесконечная память для точной точности, но для данных с float или double информацией обычно имеется только 4 или 8 байтов. Таким образом, точки с плавающей запятой и двойные числа могут хранить только определенное количество цифр, а остальные должны потеряться. Таким образом, нет четкого точного способа представления чисел с плавающей точкой или двойных чисел с числами, которые требуют большей точности, чем могут удерживать переменные.


Округление:
Существует неочевидная разница между binary и decimal (base 10) числами.
Рассмотрим долю 1/10 . В decimal форме это легко представить в виде 0.1 , а 0.1 можно рассматривать как легко представимое число. Однако в двоичном выражении 0.1 представляется бесконечной последовательностью: 0.00011001100110011…

Пример:

 #include  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  #include  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::epsilon()) { // ... } 

Одна из причин заключается в том, что числа с плавающей запятой являются (более или менее) двоичными дробями и могут приближать только число десятичных чисел. Многие десятичные числа обязательно должны быть преобразованы в повторяющиеся двоичные «десятичные знаки» или иррациональные числа. Это приведет к ошибке округления.

Из Википедии :

Например, 1/5 нельзя представить точно как число с плавающей запятой, используя двоичную базу, но может быть представлено точно с использованием десятичной базы.

В вашем конкретном случае float и double будут иметь различное округление для иррациональной / повторяющейся фракции, которая должна использоваться для представления 1.1 в двоичном формате. Вам будет трудно заставить их быть «равными» после того, как их соответствующие преобразования внесут разные уровни ошибки округления.

Код, который я дал выше, решает это, просто проверяя, находятся ли значения в пределах очень короткой дельта. Ваше сравнение меняется от «равны ли эти значения?» to “являются ли эти значения в пределах небольшой погрешности друг от друга?”

Также см. Этот вопрос: что является наиболее эффективным способом для плавающего и двойного сравнения?

Есть также множество других странностей о числах с плавающей запятой, которые нарушают простое сравнение равенства. Ознакомьтесь с этой статьей для описания некоторых из них:

http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

32-битный float IEEE 754 может хранить: 1.1000000238...
64-битный бит 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.

Чтобы увидеть, какие числа действительны, поплавки и удваивается, см. Floating Point

  • Необычное «предупреждение LNK4042» Visual Studio 2010,
  • Что случилось с тысячами предупреждений в стандартных заголовках в MSVC -Wall?
  • Как компиляторы обрабатывают массивы переменной длины
  • objective stdafx.h
  • Почему фатальная ошибка «LNK1104: невозможно открыть файл« C: \ Program.obj »возникает при компиляции проекта C ++ в Visual Studio?
  • Включение VLA (массивы переменной длины) в MS Visual C ++?
  • Почему разрешены статические константы?
  • Ошибка Weird MSC 8.0: «Значение ESP не было должным образом сохранено в вызове функции ...»
  • ошибка LNK2019: нерешенный внешний символ _WinMain @ 16, указанный в функции ___tmainCRTStartup
  • Почему происходит сбой этой программы: передача std :: string между DLL
  • Зачем нужен двойной слой косвенности для макросов?
  • Давайте будем гением компьютера.