странный вывод по сравнению с float с литералом float - PullRequest
34 голосов
/ 03 декабря 2009
float f = 0.7;
if( f == 0.7 )
    printf("equal");
else
    printf("not equal");

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

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

Ответы [ 8 ]

49 голосов
/ 03 декабря 2009

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

  if(f == 0.7)

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

  if(f == 0.7f)

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

14 голосов
/ 03 декабря 2009

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

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

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

9 голосов
/ 12 июля 2016

Прежде всего, давайте заглянем внутрь числа с плавающей точкой. Я беру 0.1f, это 4 байта (двоичный 32), в шестнадцатеричном это
3D CC CC CD .
По стандарту 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 Calculator


**

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

**
Я беру число 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
Как видите, последние немного не равны. Это потому, что я сокращаю число. Процессор и компилятор знают, что что-то после Significand точности не может удержаться и просто устанавливают последний бит равным 1.

1 голос
/ 30 декабря 2018

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

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


EDIT

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

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) </p>

00110011001100110011010000000000....

больше

00110011001100110011001100110011....

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

1 голос
/ 26 декабря 2016

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

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

        }
1 голос
/ 21 сентября 2015

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

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

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

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

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

0 голосов
/ 10 февраля 2019

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

0 голосов
/ 05 февраля 2016

Учтите это:

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

если (0,7> a) , то здесь 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...