2012-03-03 9 views
8

Tengo un algoritmo que usa floats o doubles para realizar algunos cálculos.igualdad flotante/doble con cero exacto

Ejemplo:

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

Ahora, yo estoy preocupado por (b - a) podría ser cero. Si está cerca de cero pero no es cero, no importa porque el result estará fuera de rango para ser útil, y ya detecto que (como (b - a) se aproxima a cero, result se acercará a +/- inf, que no está en el rango 0 - small_number ...)

Pero si el resultado de (b - a) es exactamente cero, espero que algo dependiente de la plataforma ocurra debido a la división por cero. Podría cambiar la declaración if a:

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

pero yo no sé si (b-a) == 0.0 detectará siempre igual a cero. He visto que hay múltiples representaciones para el cero exacto en coma flotante. ¿Cómo puedes probarlos a todos sin hacer un chequeo épsilon, que no necesito (un pequeño epsilon será ignorado en mi algoritmo)?

¿Cuál es la plataforma de manera independiente para comprobar?

EDIT:

No estoy seguro de si era lo suficientemente claro para la gente. Básicamente quiero saber cómo encontrar si una expresión como:

double result = numerator/denominator; 

dará lugar a una excepción de coma flotante, una excepción de la CPU, una señal del sistema operativo o alguna otra cosa .... sin realizar la operación y ver si se "lanzará" ... porque detectar un "tiro" de esta naturaleza parece ser complicado y específico de la plataforma.

¿Es suficiente ((denominator==0.0) || (denominator==-0.0)) ? "Will 'throw'" : "Won't 'throw'";?

+1

Incluso si 'b - a' no es exactamente igual a cero, la operación' c/(b - a) 'podría todavía desbordamiento y enviar el valor de' +/- INF'. – Mysticial

+0

@Mystical Eso está bien, pondría el resultado fuera del rango de interés para mi problema. – Bingo

+8

Es difícil encontrar una implementación que no utilice IEEE punto flotante. Entonces, creo que es seguro suponer que la división por '0' solo arrojará' + INF' o '-INF'? ¿Alguien me quiere corregir si estoy equivocado? – Mysticial

Respuesta

9

Depende de cómo b y a obtuvieron sus valores. Zero tiene una representación exacta en formato de coma flotante, pero el problema más grande sería casi cero valores. Siempre sería seguro para comprobar:

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

Dónde 0,00000001 es cualquier valor que tiene sentido.

+5

¿Por qué exactamente un número casi cero pero no suficiente podría ser un problema? Mi algoritmo ya rechaza los resultados de un denominador casi cero pero no cero ... – Bingo

+0

@Bingo: No veo tu algoritmo rechazando 'b - a' con un valor de' 1e-300'. Tal prueba cuesta poco y es un gran seguro para resistir la transformación del código, incluso si una versión de él no puede producir ese valor. – wallyk

+1

el punto es que cualquier cosa dividida por casi cero es grande ... fuera del rango de '0' a' small_number' que es el rango en el que estoy interesado. – Bingo

-1

Usted podría intentar

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

No estoy seguro, pero ¿está seguro de que '(b-a)' y '(a-b)' produciría la misma versión de cero en coma flotante? – Bingo

0

ACTUALIZACIÓN (2016-01-04)

que he recibido algunos downvotes en esta respuesta, y me preguntaba si yo debería eliminarlo. Parece que el consenso (https://meta.stackexchange.com/questions/146403/should-i-delete-my-answers) es que la eliminación de respuestas solo debe hacerse en casos extremos.

Entonces, mi respuesta es incorrecta. Pero supongo que lo dejo porque ofrece un interesante tipo de experimento mental "pensar fuera de la caja".

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

bingo,

Usted dice que quiere saber si == 0. ba

Otra manera de mirar esto es para determinar si a == b. Si a es igual a b, entonces b bis será igual a 0.

Otra idea interesante que encontré:

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

Esencialmente, se toma las variables de coma flotante que tiene y le dice al compilador para reinterpretarlas (bit de bits) como enteros con signo, como en el siguiente:

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

enteros Luego se están comparando, y los puntos no flotantes. Quizás eso ayude? Tal vez no. ¡Buena suerte!

+2

Votaba esto si no fuera por el tema de fundición bastante feo. 'if (a! = b) {/ * las matemáticas * /}' son lo suficientemente buenas para este caso; cualquier otra cosa es exagerada. – cHao

+0

Gracias por su respuesta. La idea de la igualdad vale la pena pensar. Sería bueno para una solución más general cuando el denominador no fue el resultado de una resta. Además, creo que la comparación de bit a bit (que es el enfoque entero) es el problema aquí. Las diferentes representaciones de cero tendrían diferentes patrones de bits de todos modos. – Bingo

+3

No solo '* (int *) & b == * (int *) & a' rompe las reglas de alias estrictas, pero hace que' + 0.' parezca diferente de '-0'. No veo ningún sentido para usar esta comparación aquí. –

0

Creo que (b-a)==0 será verdadero exactamente en esos casos cuando el c/(b-a) fallaría porque (b-a) es cero. Las matemáticas float son complicadas, pero cuestionar esto es exagerar en mi opinión. También creo que el (b-a)==0 va a ser equivalente a b!=a.

Distinguir positivo 0 negativo tampoco es necesario. Ver p. aquí Does float have a negative zero? (-0f)

+0

En relación con "el orden de evaluación de las expresiones secundarias en su condición" no se define en C++ ", en' e1 && e2', el orden de evaluación ** está ** definido. 'e2' siempre se evalúa después de' e1' y solo si 'e1' se evalúa como verdadero/distinto de cero. –

+0

¡Tienes razón! http://stackoverflow.com/questions/7112282/order-of-evaluation-of-operands Eliminaré esa parte. El resto de la respuesta la dejo aunque la parte eliminada fue la razón por la que decidí responder ... –

+0

La división con coma flotante cero está bien definida si el compilador sigue los estándares IEEE. [Lea más en SO] (http://stackoverflow.com/questions/12617408/a-few-things-about-division-by-zero-in-c) o lea [el estándar de punto flotante IEEE] (http://en.wikipedia.org/wiki/Division_by_zero#In_computer_arithmetic)! – DanielTuzes

4

Así es como se hace: en lugar de la comprobación de (result < small_number), comprueba si hay

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

Entonces todos tus problemas desaparecen! El cálculo de c/(b-a) nunca se desbordará si se aprueba esta prueba.

0

Para epsilon, existe una definición de plantilla estándar std :: numeric_limits :: epsilon(). Creo que comprobar la diferencia para ser más grande que std :: numeric_limits :: epsilon() debería ser lo suficientemente seguro como para proteger contra la división por cero. No hay dependencia de la plataforma aquí, supongo.

+0

Esta respuesta no tiene sentido. 'std :: numeric_limits :: epsilon()' no es para este uso, mide el ULP justo arriba de '1.0'. Y cuando se divide por 'x', la división por cero solo ocurre cuando' x == 0' (puede ocurrir desbordamiento, pero esto ya se ha señalado). –

+0

Correcto, no es una respuesta al problema original, solo un comentario relacionado con épsilon. Debería haberlo puesto como comentario, pero muchos posts anteriores se referían a esto. Pero tu comentario es correcto. Más sobre esto [http://en.cppreference.com/w/cpp/types/numeric_limits/epsilon] – mcjoan

2

Supongo que puede usar fpclassify(-0.0) == FP_ZERO. Pero esto solo es útil si desea verificar si alguien puso algún tipo de cero en la variable de tipo flotante. Como ya se ha dicho, si desea verificar el resultado del cálculo, puede obtener valores muy cercanos a cero debido a la naturaleza de la representación.

+0

Comente una razón para la votación negativa. Prometo que es solo para entender mi error. – ony

0

En resumen, podemos saber que un número flotante es CERO exactamente si sabemos que representa el formato.


En la práctica, comparamos x con un número pequeño. Y si x es menor que este número, creemos que x es igual que CERO funcionalmente (pero la mayoría de las veces nuestro pequeño número es aún mayor que cero). Este método es muy fácil, eficiente y puede cruzar plataforma.


En realidad, la float y double han sido presentados por formato especial, y el se utiliza es IEEE 754 en el hardware actual, que divide el número en bits (mantisa) mantisa signo, exponente y.

lo tanto, si queremos comprobar si un número flotante es CERO exactamente, podemos comprobar si ambos exponente y mantisa es cero, ver here.

En IEEE 754 números de punto flotante binario, valores cero están representados por el exponente sesgado y significand ser ambos cero. El cero negativo tiene el bit de signo establecido en uno.

Tome float por ejemplo, podemos escribir un código simple para extraer el exponente y el bit de mantisa y luego verificarlo.

#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); 
} 

Resultados del ensayo:

# es igual a cero de 0,000000 -> 1
# es cero del -0.000000 -> 1
# es cero de 1,200000 -> 0


Más ejemplos:

Comprobemos cuando el float se convierte en CERO real con el código.

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