Можно ли сравнивать float с нулем?

141
03 апреля 2019, 13:10

Все мы знаем, что к float нельзя (точнее не рекомендуется) применять операцию ==. Почему - думаю сами знаете. Но что насчет сравнения с 0? То есть, допустим, у нас есть какая-то переменная, проинициализированная нулем при создание, а далее, возможно, что ей будет присвоено какое-то значение, а может и не будет. Соответственно, если значение переменной равно нулю - то она не была проинициализирована. Так как при инициализации нулем все биты в переменной будут выставлены в 0, то я не вижу причин почему бы не сравнивать float (в данном случае) с нулем. Так ли это?

Answer 1

Если известно, что число представлено точно, а именно:

  1. Нет никакой вычислительной погрешности
  2. Константа сама представима в соответствующем типе без погрешности

то сравнивать на равенство можно.

В остальных случаях сравнение нежелательно и может привести к неправильному результату.

Answer 2

Не рекомендуется сравнивать вычисленный float на равенство с нулем.

Не рекомендуется сравнивать никакой float, ни вычисленный, ни константный на равенство с нулем (и на равенство двух float между собой).
Для константного float сравнение будет работать правильно, но это не общий случай.

Предположим есть код.

float n=0;
float a1;
float a2;
//здесь a1 и a2 получили какие-то значения, 
//возможно пришедшие из внешнего мира (например считанные с АЦП)
float delta=a1-a2;
if(delta==n){
//do something1
}
else{
//do something2
}

Когда вычисляется разность delta a1 и a2 могут быть равны с точностью до точности типа float.
Но разность будет не равна нулю и соответственно, сравнение на равенство нулю не пройдет.
И алгоритм пойдет по другой ветке.

В качестве delta может быть не простая разность, а сколь угодно сложные вычисления.
В которых будут и ошибки округления, которые дадут +-1 к точности float.

Поэтому не рекомендуется.

Для двух констант, конечно, это будет работать.
Причем и для ненулевых констант тоже будет работать (на одной и той же платформе конечно).

Например:

float n1=0;
float n2=0;
    if(n1==n2){
    //do something1
    }
    else{
    //do something2
    }

Или пример для ненулевых констант:

float n1=1.1;
float n2=1.1;
    if(n1==n2){
    //do something1
    }
    else{
    //do something2
    }

Но в общем случае, когда float получен после вычислений, на конце у float будет +-1 точности float.
А может и больше, чем +-1 точности float.

UPD1:

И что такое "+-1 точности float" - это какой-то необщепринятый термин

Что-то я не помню, какой для этого есть общепринятый термин.
Может быть "машинный эпсилон" это термин для этого, то есть константа FLT_EPSILON из float.h.
А может быть FLT_MIN это термин для этого.
Наверное все-таки FLT_EPSILON.

UPD2:

Возможная подпрограмма сравнения на равенство для float (если сделать из нее шаблон, то подойдет и для других форматов с плавающей точкой):

bool compare_fl_point(float a1, float a2, float delta=FLT_EPSILON){
float delta1=a1-a2;
if(delta1<0) delta1=-delta1;
if(delta1>delta) return false; else return true;
}

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

UPD3:

EPSILON работает несколько иначе – Kromster 1 час назад

Эпсилон здесь только для примера в качестве дефолтного значения.
Более того, сравнивать с эпсилоном бессмысленно, так как эпсилон (по определению) всегда меньше или равен любому float числу.
На месте эпсилона должна быть точность входных данных в данной конкретной задаче.
Обычно точность вычислений выбирают так, чтобы она была как минимум в 10 раз выше, чем точность входных данных.

UPD4:

А вообще-то нужно погуглить этот вопрос.
Тут двух мнений быть не может, есть правила, написанные разработчиками стандарта на float.
Там однозначно говорится, как надо сравнивать float на равенство.
Я погуглил, но сходу выпало только такие же безответственные базары разработчиков на форумах, как и здесь.
Должен быть стандарт, в котором все это прописано.
Можно еще попробовать у Кнута посмотреть, может быть у него есть.

UPD5:

А вот, нашел что-то похожее на статью по теме:

https://habr.com/post/112953/

Там есть и про сравнение float на равенство.
И даже отсылка к какой-то импортной статье.

UPD6:

Решил провести вычислительный эксперимент, чтобы посмотреть, как появляются ошибки округления float.
Для этого написал программу, проверяющую известное тождество sin(x)^2+cos(x)^2=1;

Вот программа:

// Example program
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
const int numpoint=10;
int main(){
    int i;
    for(i=0;i!=numpoint;++i){
        float arg=3.14/((float)i+1.0);
    float s=sin(arg);
    float c=cos(arg);
    float one=s*s+c*c;
    float delta=one-1.0;
    bool flag;
    if(delta==0.0) flag=true; else flag=false;
    cout<<endl<<i<<". delta="<<delta<<" flag="<<flag<<" arg="<<arg;
        }
return 0;        
}

Результат работы программы:

0. delta=0 flag=1 arg=3.14
1. delta=0 flag=1 arg=1.57
2. delta=0 flag=1 arg=1.04667
3. delta=0 flag=1 arg=0.785
4. delta=0 flag=1 arg=0.628
5. delta=0 flag=1 arg=0.523333
6. delta=-5.96046e-08 flag=0 arg=0.448571
7. delta=-5.96046e-08 flag=0 arg=0.3925
8. delta=-5.96046e-08 flag=0 arg=0.348889
9. delta=-5.96046e-08 flag=0 arg=0.314 

Видно, что при некоторых аргументах возникает delta не равная нулю и оператор сравнения delta с нулем

if(delta==0.0) flag=true; else flag=false;

выдает значение false.

Все это проверялось на http://cpp.sh/

READ ALSO
OpenProcess не возвращает хэндл процесса

OpenProcess не возвращает хэндл процесса

OpenProcess не возвращает хэндл процесса, при этом hwnd и procID не равен NULL

143
Задача &ldquo;Игра&rdquo; C++

Задача “Игра” C++

Условие задачи на картинкеКод что я написал ниже: Почему-то всегда если ввожу большое число(около 1 * 10^10) выдает результат 17:174

130
С3863: Тип массива char[64] является неоднозначным

С3863: Тип массива char[64] является неоднозначным

Примитивная программа, при компилировании которой выходит две ошибки:

135
Создание самостоятельного файла в Clion

Создание самостоятельного файла в Clion

Пытаюсь создать exe файл через ClionНо при запуске exe файла, выдает ошибку о нехватке dll файла

136