Как оператор C == определяет, равны ли два значения с плавающей запятой? - PullRequest
7 голосов
/ 07 июня 2011

Сегодня я отслеживал, почему моя программа получала некоторые неожиданные ошибки несоответствия контрольной суммы, в некотором написанном мною коде, который сериализует и десериализует значения с плавающей точкой IEEE-754, в формате, который включает 32-битное значение контрольной суммы (который вычисляется путем запуска алгоритма типа CRC над байтами массива с плавающей запятой).

После небольшого разбора головы я понял, что проблема в том, что 0.0f и -0.0f имеют разные биты-patterns (0x00000000 против 0x00000080 (little-endian), соответственно), но они считаются эквивалентными оператором равенства C ++.Итак, ошибки несоответствия контрольной суммы произошли потому, что мой алгоритм вычисления контрольной суммы уловил разницу между этими двумя битовыми шаблонами, в то время как некоторые другие части моей кодовой базы (которые используют тестирование на равенство с плавающей запятой, а не просматривали значения побайтнобайт) не делал этого различия.

Хорошо, достаточно справедливо - я, наверное, должен был бы знать лучше, чем проводить тестирование на равенство с плавающей точкой.

Но это заставило меня задуматься, а есть лидругие значения с плавающей точкой IEEE-754, которые считаются равными (согласно оператору C ==), но имеют разные битовые комбинации?Или, другими словами, как именно оператор == определяет, равны ли два значения с плавающей точкой?Я новичок, хотя он делал что-то вроде memcmp () на своих битовых шаблонах, но, очевидно, это более нюанс, чем это.

Вот пример кода того, что я имею в виду, на случай, если я не понял выше.

#include <stdio.h>

static void PrintFloatBytes(const char * title, float f)
{
   printf("Byte-representation of [%s] is: ", title);
   const unsigned char * p = (const unsigned char *) &f;
   for (int i=0; i<sizeof(f); i++) printf("%02x ", p[i]);
   printf("\n");
}

int main(int argc, char ** argv)
{
   const float pzero = -0.0f;
   const float nzero = +0.0f;
   PrintFloatBytes("pzero", pzero);
   PrintFloatBytes("nzero", nzero);
   printf("Is pzero equal to nzero?  %s\n", (pzero==nzero)?"Yes":"No");
   return 0;
}

Ответы [ 3 ]

13 голосов
/ 07 июня 2011

Используются правила равенства IEEE-754.

  • -0 == +0
  • NaN != NaN
2 голосов
/ 07 июня 2011

Для платформ Windows эта ссылка имеет :

  • Деление на 0 дает +/- INF, кроме 0/0, что приводит к NaN.
  • log (+/-) 0 производит -INF. log отрицательного значения (кроме -0) дает NaN.
  • Взаимный квадратный корень (rsq) или квадратный корень (sqrt) отрицательного числа производит NaN. Исключение составляет -0; sqrt (-0) выдает -0, а rsq (-0) выдает -INF.
  • INF - INF = NaN
  • (+/-) INF / (+/-) INF = NaN
  • (+/-) INF * 0 = NaN
  • NaN (любой OP) любое значение = NaN
  • Сравнения EQ, GT, GE, LT и LE, когда один или оба операнда являются NaN, возвращает FALSE.
  • Сравнения игнорируют знак 0 (поэтому +0 равно -0).
  • NE сравнения, когда один или оба операнда имеют значение NaN, возвращает TRUE.
  • Сравнение любого значения, отличного от NaN, с +/- INF возвращает правильный результат.
1 голос
/ 07 июня 2011

точное сравнение. Вот почему лучше избегать == как тест на поплавках. Это может привести к неожиданным и тонким ошибкам.

Стандартный пример этого кода:

 float f = 0.1f;

 if((f*f) == 0.01f)
     printf("0.1 squared is 0.01\n");
 else
     printf("Surprise!\n");

потому что 0.1 не может быть представлен точно в двоичном виде (это повторение независимо от того, что, черт возьми, вы называете дробным двоичным файлом) 0.1*0.1 не будет точно 0.01 - и, следовательно, тест на равенство не будет работать.

Численные аналитики очень долго об этом беспокоятся, но для первого приближения полезно определить значение - APL назвало его FUZZ - то, насколько близко должны быть равны два числа с плавающей точкой. Таким образом, вы можете, например, #define FUZZ 0.00001f и проверить

 float f = 0.1f;

 if(abs((f*f)-0.01f) < FUZZ)
     printf("0.1 squared is 0.01\n");
 else
     printf("Surprise!\n");
...