странный вывод по сравнению с плавающим литералом

StackOverflow https://stackoverflow.com/questions/1839422

Вопрос

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

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

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

Это было полезно?

Решение

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

  if(f == 0.7)

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

  if(f == 0.7f)

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

Другие советы

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

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

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

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

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


enter image description here enter image description here

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


**

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

**
я беру цифру 0,1
Это легко, потому что нет целой части.Первый знаковый бит – это 0.Точность экспоненты и мантиссы я сейчас вычислю.Логика заключается в умножении на 2 целых числа (0,1*2=0,2), и если оно больше 1, вычитаем и продолжаем.
enter image description here
И число .00011001100110011001100110011, стандарт говорит, что мы должны сдвинуться влево, прежде чем получим 1.(что-то).Как видите, нам нужно 4 смены, исходя из этого числа. Экспонента(127-4=123).И точность мантиссы теперь равна
10011001100110011001100(и есть потерянные биты).
Теперь целое число.Знаковый бит 0 Экспонента равна 123 (01111011) и точность значащей величины 10011001100110011001100 и все это
00111101110011001100110011001100 давайте сравним это с теми, что у нас есть из предыдущей главы
00111101110011001100110011001101
Как видите, последние биты не равны.Это потому, что я сокращаю число.ЦП и компилятор знают, что это что-то после точности значимости, и просто устанавливают последний бит в 1.

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

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

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

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

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

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

      // 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;

        }

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

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 преобразуется в одинарный и сохраняется в x.

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

Язык говорит, что нам нужно повысить точность, чтобы одиночное число в x преобразулось в двойное и сравнивалось с двойным 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

Single 3F333333 Double 3Fe6666666666666

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

вижуееееэфффффффффффффффффффффффф

И двойной это

вижуааааааааааааааааааааааааааааааааааааааааааааааааааааааааааа

с 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 содержит 011001100110011001100111111111111111111111 год, когда это преобразуется обратно, чтобы удвоить долю

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

преобразовано в двойное из-за (x<0,7)

00110011001100110011010000000000....

больше, чем

00110011001100110011001100110011....

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

Учти это:

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

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

Пример:

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

Выход:
0.7000000000 0.6999999881

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top