Странный вывод в сравнении float с float literal

float f = 0.7;
if( f == 0.7 )
    printf("equal");
else
    printf("not equal");

Почему вывод not equal?

Почему это происходит?

Ответы

Ответ 1

Это происходит потому, что в вашем заявлении

  if(f == 0.7)

0,7 рассматривается как двойное. Попробуйте 0.7f, чтобы убедиться, что значение рассматривается как float:

  if(f == 0.7f)

Но, как предложил Майкл в комментариях ниже, вы никогда не должны проверять точное равенство значений с плавающей запятой.

Ответ 2

Этот ответ в дополнение к существующим: обратите внимание, что 0.7 не представляется точно либо как float (или как double). Если бы он был представлен точно, тогда при конвертировании в float и затем в double не было бы потери информации, и у вас не было бы этой проблемы.

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

Все нецелые числа, которые могут быть представлены точно, имеют 5 как их последнюю десятичную цифру. К сожалению, обратное неверно: некоторые цифры имеют 5 как их последнюю десятичную цифру и не могут быть представлены точно. Малые целые числа могут быть представлены точно, а деление на степень 2 преобразует число, которое может быть представлено в другое, которое может быть представлено, до тех пор, пока вы не войдете в область денормализованных чисел.

Ответ 3

Прежде всего, пусть заглянет внутрь числа с плавающей точкой. Я принимаю 0.1f, это 4 байта длиной (binary32), в шестнадцатеричном формате 3D CC CC CD.
По стандарту IEEE 754, чтобы преобразовать его в десятичное, мы должны сделать следующее:

введите описание изображения здесь
В двоичном 3D CC CC CD есть
0 01111011 1001100 11001100 11001101
здесь первая цифра - бит Знака. 0 означает (-1) ^ 0, что наше число положительно. Второй 8 бит - это показатель. В двоичном виде это 01111011 - в десятичном значении 123. Но реальный показатель равен 123-127 (всегда 127) = -4, это означает, что нам нужно умножить число, которое мы получим на 2 ^ (- 4).
Последние 23 байта - это значение Significand. Там первый бит умножим на 1/(2 ^ 1) (0,5), второй на 1/(2 ^ 2) (0,25) и т.д. Здесь мы получаем:


введите описание изображения здесь введите описание изображения здесь

Нам нужно добавить все числа (мощность 2) и добавить к нему 1 (всегда 1, по стандарту). Это
1,60000002384185791015625
Теперь умножим это число на 2 ^ (- 4), это из показателя экспоненты. Мы просто делим число выше на 2 четыре раза:
0,100000001490116119384765625
Я использовал MS Calculator


**

Теперь вторая часть. Преобразование из десятичного в двоичный.

**
Я принимаю число 0,1
Это легко, потому что нет целой части. Бит первого знака - 0. Exponent и Significand, я буду рассчитывать сейчас. Логика умножается на 2 целое число (0,1 * 2 = 0,2), и если оно больше 1 вычитания и продолжается.
введите описание изображения здесь
И номер .00011001100110011001100110011, стандарт говорит, что мы должны сдвинуться влево, прежде чем мы получим 1. (что-то). Как вы видите, нам нужно 4 смены, начиная с этого числа, вычисляя Экспонента (127-4 = 123). И теперь значение Significand - 10011001100110011001100 (и есть потерянные биты).
Теперь целое число. Знак бит 0 Экспонент - 123 (01111011), а значение Significand 10011001100110011001100, и все это - 00111101110011001100110011001100 сравните его с теми, что мы имеем в предыдущей главе
00111101110011001100110011001101
Как вы видите, бит последнего не равен. Это потому, что я усекаю число. CPU и компилятор знают, что это то, что после того, как значение Significand не может быть выполнено, и просто установите последний бит в 1.

Ответ 4

Проблема, с которой вы столкнулись, заключается в том, что, как отмечали другие комментаторы, как правило, небезопасно тестировать точную эквивалентность между поплавками, поскольку ошибки инициализации или ошибки округления в вычислениях могут приводить к незначительным различиям, что приведет к тому, что оператор == return false.

Лучшая практика - сделать что-то вроде

float f = 0.7;
if( fabs(f - 0.7) < FLT_EPSILON )
    printf("equal");
else
    printf("not equal");

Предполагая, что FLT_EPSILON был определен как соответствующее небольшое значение float для вашей платформы.

Поскольку ошибки округления или инициализации вряд ли превысят значение FLT_EPSILON, это даст вам надежный тест эквивалентности, который вы ищете.

Ответ 5

Многие ответы в Интернете делают ошибку, глядя на разницу между номерами с плавающей запятой, это справедливо только для особых случаев, надежный способ - посмотреть на относительную разницу, как показано ниже:

      // Floating point comparison:

        bool CheckFP32Equal(float referenceValue, float value)
        {
           const float fp32_epsilon = float(1E-7);
           float abs_diff = std::abs(referenceValue - value);

           // Both identical zero is a special case
           if( referenceValue==0.0f && value == 0.0f)
              return true;

           float rel_diff = abs_diff / std::max(std::abs(referenceValue) , std::abs(value) ); 

           if(rel_diff < fp32_epsilon)
                 return true;
           else 
                 return false;

        }

Ответ 6

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

int fun1 ( void )
{
      float x=0.7;
      if(x==0.7) return(1);
      else       return(0);
}
int fun2 ( void )
{
      float x=1.1;
      if(x==1.1) return(1);
      else       return(0);
}
int fun3 ( void )
{
      float x=1.0;
      if(x==1.0) return(1);
      else       return(0);
}
int fun4 ( void )
{
      float x=0.0;
      if(x==0.0) return(1);
      else       return(0);
}
int fun5 ( void )
{
      float x=0.7;
      if(x==0.7f) return(1);
      else       return(0);
}
float fun10 ( void )
{
    return(0.7);
}
double fun11 ( void )
{
    return(0.7);
}
float fun12 ( void )
{
    return(1.0);
}
double fun13 ( void )
{
    return(1.0);
}

Disassembly of section .text:

00000000 <fun1>:
   0:   e3a00000    mov r0, #0
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e3a00000    mov r0, #0
   c:   e12fff1e    bx  lr

00000010 <fun3>:
  10:   e3a00001    mov r0, #1
  14:   e12fff1e    bx  lr

00000018 <fun4>:
  18:   e3a00001    mov r0, #1
  1c:   e12fff1e    bx  lr

00000020 <fun5>:
  20:   e3a00001    mov r0, #1
  24:   e12fff1e    bx  lr

00000028 <fun10>:
  28:   e59f0000    ldr r0, [pc]    ; 30 <fun10+0x8>
  2c:   e12fff1e    bx  lr
  30:   3f333333    svccc   0x00333333

00000034 <fun11>:
  34:   e28f1004    add r1, pc, #4
  38:   e8910003    ldm r1, {r0, r1}
  3c:   e12fff1e    bx  lr
  40:   66666666    strbtvs r6, [r6], -r6, ror #12
  44:   3fe66666    svccc   0x00e66666

00000048 <fun12>:
  48:   e3a005fe    mov r0, #1065353216 ; 0x3f800000
  4c:   e12fff1e    bx  lr

00000050 <fun13>:
  50:   e3a00000    mov r0, #0
  54:   e59f1000    ldr r1, [pc]    ; 5c <fun13+0xc>
  58:   e12fff1e    bx  lr
  5c:   3ff00000    svccc   0x00f00000  ; IMB

Почему fun3 и fun4 возвращают одного, а не других? почему fun5 работает?

Это о языке. Язык говорит, что 0,7 является двойным, если вы не используете этот синтаксис 0,7f, то это единственный. Так

  float x=0.7;

двойной 0,7 преобразуется в один и сохраняется в х.

  if(x==0.7) return(1);

В языке говорится, что мы должны повысить точность, чтобы сингл в x преобразовывался в double и сравнивался с double 0,7.

00000028 <fun10>:
  28:   e59f0000    ldr r0, [pc]    ; 30 <fun10+0x8>
  2c:   e12fff1e    bx  lr
  30:   3f333333    svccc   0x00333333

00000034 <fun11>:
  34:   e28f1004    add r1, pc, #4
  38:   e8910003    ldm r1, {r0, r1}
  3c:   e12fff1e    bx  lr
  40:   66666666    strbtvs r6, [r6], -r6, ror #12
  44:   3fe66666    svccc   0x00e66666

однолокальный 3f333333 двухлокальный 3fe6666666666666

Как отметил Александр, если ответ остается IEEE 754,

seeeeeeeefffffffffffffffffffffff

И двойной

seeeeeeeeeeeffffffffffffffffffffffffffffffffffffffffffffffffffff

с 52 битами дроби, а не 23, что имеет сингл.

00111111001100110011... single
001111111110011001100110... double

0 01111110 01100110011... single
0 01111111110 01100110011... double

Так же, как 1/3 в базе 10 - это 0,3333333... навсегда. У нас здесь повторяющийся узор 0110

01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.

И вот ответ.

  if(x==0.7) return(1);

x содержит 01100110011001100110011 в качестве своей дроби, когда она преобразуется обратно в удвоенную дробь

01100110011001100110011000000000....

который не равен

01100110011001100110011001100110...

но здесь

  if(x==0.7f) return(1);

что продвижение не происходит, сравниваются одни и те же битовые комбинации.

Почему 1.0 работает?

00000048 <fun12>:
  48:   e3a005fe    mov r0, #1065353216 ; 0x3f800000
  4c:   e12fff1e    bx  lr

00000050 <fun13>:
  50:   e3a00000    mov r0, #0
  54:   e59f1000    ldr r1, [pc]    ; 5c <fun13+0xc>
  58:   e12fff1e    bx  lr
  5c:   3ff00000    svccc   0x00f00000  ; IMB

0011111110000000...
0011111111110000000...

0 01111111 0000000...
0 01111111111 0000000...

В обоих случаях дробь равна нулю. Таким образом, преобразование из двойного в одинарный для двойного не приводит к потере точности. Он преобразует из одинарной в удвоенную точно, и битовое сравнение двух значений работает.

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

Почему не было показано в этом ответе. 0.7 не работает 1.0 работает. Почему 0,7 сбоя не было показано. Дублирующий вопрос 1.1 также терпит неудачу.


РЕДАКТИРОВАТЬ

Здесь можно вывести равных из проблемы, это уже другой вопрос, на который уже дан ответ, но это та же проблема, которая также имеет первоначальный шок "что за...".

int fun1 ( void )
{
      float x=0.7;
      if(x<0.7) return(1);
      else       return(0);
}
int fun2 ( void )
{
      float x=0.6;
      if(x<0.6) return(1);
      else       return(0);
}

Disassembly of section .text:

00000000 <fun1>:
   0:   e3a00001    mov r0, #1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e3a00000    mov r0, #0
   c:   e12fff1e    bx  lr

Почему один показывает как меньше, а другой не меньше? Когда они должны быть равны.

Сверху мы знаем историю 0,7.

01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.

01100110011001100110011000000000....

меньше чем.

01100110011001100110011001100110...

0.6 - это другой повторяющийся шаблон 0011, а не 0110.

но при преобразовании из двойного в единичное или в целом, когда оно представлено как единое IEEE 754.

00110011001100110011001100110011.... double 52 bits.
00110011001100110011001 is NOT the fraction for single
00110011001100110011010 IS the fraction for single

IEEE 754 использует режимы округления, округления вверх, округления вниз или округления до нуля. Компиляторы имеют тенденцию округляться по умолчанию. Если вы помните округление в начальной школе 12345678, если бы я хотел округлить до 3-й цифры сверху, это было бы 12300000, но округлилось бы до следующей цифры 1235000, если цифра после 5 или больше, чем округление вверх. 5 - это 1/2 от 10, основание (десятичное число) в двоичном виде 1 равно 1/2 от основания, поэтому, если цифра после позиции, которую мы хотим округлить, равна 1, то округление в большую сторону не выполняется. Таким образом, для 0,7 мы не округлились, для 0,6 мы округлились.

И теперь легко увидеть, что

00110011001100110011010

преобразуется в двойной из-за (х <0,7)

00110011001100110011010000000000....

больше, чем

00110011001100110011001100110011....

Таким образом, не говоря уже об использовании равных, проблема все еще представляет себя 0,7 - двойная 0,7f - одиночная, операция повышается с максимальной точностью, если они различаются.

Ответ 7

Рассмотрим это:

int main()
{
    float a = 0.7;
    if(0.7 > a)
        printf("Hi\n");
    else
        printf("Hello\n");
    return 0;
}

if (0.7 > a) здесь a - переменная с плавающей точкой, а 0.7 - двойная константа. Двойная константа 0.7 больше, чем переменная float a. Следовательно, условие if выполняется и печатает 'Hi'

Пример:

int main()
{
    float a=0.7;
    printf("%.10f %.10f\n",0.7, a);
    return 0;
}

Вывод:
0,7000000000 0,6999999881

Ответ 8

Если вы измените тип данных f на double, он будет печататься равным. Это связано с тем, что константы с плавающей запятой хранятся в двойном формате, а не с плавающим длинным, двойная точность высока, а плавающее имеет менее точное значение, двойное значение сохраняется в 64- битном двоичном и плавающем значение хранится в 32- битном двоичном формате, будет совершенно ясно, если вы увидите метод преобразования чисел с плавающей запятой в двоичное преобразование.