Float/double равенство с точным нулем

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

Пример:

****** a;
****** b;
****** c;
...
****** 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 всегда определять равенство с нулем. Я видел, что существует множество представлений для точного нуля в плавающей запятой? Как вы можете протестировать их все, не выполняя некоторую проверку эпсилона, которая мне не нужна (небольшой эпсилон будет игнорироваться в моем алгоритме)?

Что такое независимая проверка платформы?

EDIT:

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

****** result = numerator / denominator;

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

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

8 ответов

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

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

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


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

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

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


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


UPDATE (2016-01-04)

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

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

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

бинго,

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

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

Еще одна интересная идея, которую я нашел:

http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm

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

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

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


Вкратце, мы можем знать, что число с плавающей точкой равно ZERO, если мы знаем, что это формат.

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

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

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

В двоичных числах с плавающей запятой 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);
}
</stdio.h>

Результаты тестирования:

# равно нулю 0.000000 → 1  # равен нулю -0.000000 → 1  # равно нулю 1.200000 → 0

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

Позвольте проверить, когда float становится реальным ZERO с кодом.

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


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


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

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


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

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

licensed under cc by-sa 3.0 with attribution.