Плавающее / двойное равенство с точным нулем - PullRequest
8 голосов
/ 03 марта 2012

У меня есть алгоритм, который использует floats или doubles для выполнения некоторых вычислений.

Пример:

double a;
double b;
double c;
...
double result = c / (b - a);
if ((result > 0) && (result < small_number))
{
    // result is relevant...
} else {
    // result not required...
}

Теперь я обеспокоен тем, что (b - a) может быть нулевым,Если оно близко к нулю, но не к нулю, это не имеет значения, потому что result будет вне диапазона, чтобы быть полезным, и я уже обнаруживаю это (когда (b - a) приближается к нулю, result приближается к +/- inf, который не находится в диапазоне 0 - small_number ...)

Но если результат (b - a) точно равен нулю, я ожидаю, что из-за деления на ноль произойдет нечто зависящее от платформы.Я мог бы изменить выражение if на:

if ((!((b-a) == 0.0)) && ((result = c/(b-a)) > 0) && (result < small_number)) {

, но я не знаю, будет ли (b-a) == 0.0 всегда обнаруживать равенство с нулем.Я видел, есть несколько представлений для точного нуля с плавающей точкой?Как вы можете протестировать их все, не выполняя проверку эпсилон, которая мне не нужна (небольшой эпсилон будет игнорироваться в моем алгоритме)?

Какой независимый от платформы способ проверки?

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

Не уверен, что это было достаточно ясно для людей.По сути, я хочу знать, как найти, если выражение типа:

double result = numerator / denominator;

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

Достаточно ли ( (denominator==0.0) || (denominator==-0.0) ) ? "Will 'throw'" : "Won't 'throw'";?

Ответы [ 8 ]

9 голосов
/ 03 марта 2012

Это зависит от того, как b и a получили свои значения.Ноль имеет точное представление в формате с плавающей запятой, но большая проблема - почти нулевые значения.Всегда было бы безопасно проверить:

if (abs(b-a) > 0.00000001  && ...

Где 0,00000001 - это любое значение, которое имеет смысл.

4 голосов
/ 21 мая 2013

Вот как вы это делаете: вместо проверки на (result < small_number) вы проверяете на

(abs(c) < abs(b - a) * small_number)

Тогда все твои проблемы исчезнут! Вычисление c/(b-a) никогда не будет переполнено, если этот тест пройден.

2 голосов
/ 11 сентября 2015

Я думаю, вы можете использовать fpclassify(-0.0) == FP_ZERO.Но это полезно только в том случае, если вы хотите проверить, вставил ли кто-то какой-либо ноль в переменную типа float.Как уже говорили многие, если вы хотите проверить результат расчета, вы можете получить значения, очень близкие к нулю, из-за характера представления.

1 голос
/ 17 декабря 2015

Короче говоря, мы можем знать, что плавающее число равно нулю точно, если мы знаем, что оно представляет формат.


На практике , мы сравниваем x с небольшим номером.И если x меньше этого числа, мы думаем, что x такое же, как ZERO функционально (но в большинстве случаев наше маленькое число все еще больше нуля).Этот метод очень прост, эффективен и может работать на разных платформах.


На самом деле , float и double представлены в специальном формате, и широко используемым является IEEE 754 в текущем оборудовании, котороеразделил число на биты знака, экспоненты и мантиссы (значимого).

Итак, если мы хотим проверить, точно ли число с плавающей точкой равно НУЛЬ , мы можем проверить, является ли экспонента и мантиссаНОЛЬ, см. здесь .

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

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

#include <stdio.h>   

typedef union {
          float f;
          struct {
              unsigned int mantissa : 23;
              unsigned int exponent : 8;
              unsigned int sign :     1;
          } parts;
} float_cast;


int isZero(float num) {

    int flag = 0;
    float_cast data;
    data.f = num;

    // Check both exponent and mantissa parts
    if(data.parts.exponent == 0u && data.parts.mantissa == 0u) {
       flag = 1;
    } else {
       flag = 0;
   }

   return(flag);
}


int main() {

   float num1 = 0.f, num2 = -0.f, num3 = 1.2f;

   printf("\n is zero of %f -> %d", num1, isZero(num1));
   printf("\n is zero of %f -> %d", num2, isZero(num2));
   printf("\n is zero of %f -> %d", num3, isZero(num3));

   return(0);
}

Результаты теста:

# равно нулю 0,000000 -> 1
# равно нулю -0,000000 -> 1
# равно нулю 1.200000 -> 0


Дополнительные примеры:

Давайте проверим, когда float станет действительным НУЛЕМ с кодом.

void test() {
     int i =0;
     float e = 1.f, small = 1.f;
     for(i = 0; i < 40; i++) {
         e *= 10.f;
         small = 1.f/e;
         printf("\nis %e zero? : %d", small, isZero(small));
     }
     return;
}


is 1.0000e-01 zero? : NO
is 1.0000e-02 zero? : NO
is 1.0000e-03 zero? : NO
is 1.0000e-04 zero? : NO
is 1.0000e-05 zero? : NO
is 1.0000e-06 zero? : NO
is 1.0000e-07 zero? : NO
is 1.0000e-08 zero? : NO
is 1.0000e-09 zero? : NO
is 1.0000e-10 zero? : NO
is 1.0000e-11 zero? : NO
is 1.0000e-12 zero? : NO
is 1.0000e-13 zero? : NO
is 1.0000e-14 zero? : NO
is 1.0000e-15 zero? : NO
is 1.0000e-16 zero? : NO
is 1.0000e-17 zero? : NO
is 1.0000e-18 zero? : NO
is 1.0000e-19 zero? : NO
is 1.0000e-20 zero? : NO
is 1.0000e-21 zero? : NO
is 1.0000e-22 zero? : NO
is 1.0000e-23 zero? : NO
is 1.0000e-24 zero? : NO
is 1.0000e-25 zero? : NO
is 1.0000e-26 zero? : NO
is 1.0000e-27 zero? : NO
is 1.0000e-28 zero? : NO
is 1.0000e-29 zero? : NO
is 1.0000e-30 zero? : NO
is 1.0000e-31 zero? : NO
is 1.0000e-32 zero? : NO
is 1.0000e-33 zero? : NO
is 1.0000e-34 zero? : NO
is 1.0000e-35 zero? : NO
is 1.0000e-36 zero? : NO
is 1.0000e-37 zero? : NO
is 1.0000e-38 zero? : NO
is 0.0000e+00 zero? : YES   <-- 1e-39
is 0.0000e+00 zero? : YES   <-- 1e-40
0 голосов
/ 13 сентября 2013

Для epsilon существует стандартное определение шаблона std :: numeric_limits :: epsilon ().Я предполагаю, что проверка разницы больше, чем std :: numeric_limits :: epsilon (), должна быть достаточно безопасной, чтобы защитить от деления на ноль.Здесь нет зависимости от платформы.

0 голосов
/ 20 мая 2013

Я верю, что (b-a)==0 будет истинным именно в тех случаях, когда c/(b-a) потерпит неудачу из-за того, что (b-a) равно нулю. Математика с плавающей запятой - это сложно, но, на мой взгляд, сомнение в этом преувеличивает. Также я считаю, что (b-a)==0 будет эквивалентно b!=a.

Различать положительный и отрицательный 0 также не обязательно. Смотрите, например здесь Имеет ли float отрицательный ноль? (-0f)

0 голосов
/ 03 марта 2012

ОБНОВЛЕНИЕ (2016-01-04)

Я получил несколько отрицательных голосов по этому ответу, и мне было интересно, стоит ли его просто удалить.Похоже, что консенсус (https://meta.stackexchange.com/questions/146403/should-i-delete-my-answers)) заключается в том, что удаление ответов следует выполнять только в крайних случаях.

Итак, мой ответ неверен. Но я полагаю, что оставляю его, потому что он предусматриваетИнтересный мысленный эксперимент типа «думай из коробки».

===============

Bingo,

Вы говоритеВы хотите знать, если ba == 0.

Другой способ посмотреть на это - определить, будет ли a == b. Если a равно b, то ba будет равно 0.

Другойинтересная идея, которую я нашел:

http://www.cygnus -software.com / documents / Сравнение плаваний / Сравнение% 20floating% 20point% 20numbers.htm

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

if (*(int*)&b == *(int*)&a)

Затем вы сравниваете целые числа, а не числа с плавающей запятой. Может, это поможет?Может и нет. Удачи!

0 голосов
/ 03 марта 2012

Вы можете попробовать

if ((b-a)!=(a-b) && ((result = c/(b-a)) > 0) && (result < small_number))) {
...
...