¿Qué operaciones y funciones en +0.0 y -0.0 dan diferentes resultados aritméticos?

En C, cuando se admite ±0.0 , -0.0 o +0.0 asignado a un double general no hace diferencia aritmética . Aunque tienen patrones de bits diferentes, se comparan aritméticamente como iguales.

 double zp = +0.0; double zn = -0.0; printf("0 == memcmp %d\n", 0 == memcmp(&zn, &zp, sizeof zp));// --> 0 == memcmp 0 printf("== %d\n", zn == zp); // --> == 1 

Inspire por un comentario de @Pascal Cuoq , estoy buscando algunas funciones más en el estándar C que brinden resultados aritméticamente diferentes.

Nota: Muchas funciones, como sin() , devuelven +0.0 desde f(+0.0) y -0.0 desde f(-0.0) . Pero estos no proporcionan resultados aritméticos diferentes. Además, los 2 resultados no deberían ser ambos NaN .

Hay algunas operaciones y funciones estándar que forman respuestas numéricamente diferentes entre f(+0.0) y f(-0.0) .

Diferentes modos de redondeo u otras implementaciones de coma flotante pueden dar resultados diferentes.

 #include  double inverse(double x) { return 1/x; } double atan2m1(double y) { return atan2(y, -1.0); } double sprintf_d(double x) { char buf[20]; // sprintf(buf, "%+f", x); Changed to e sprintf(buf, "%+e", x); return buf[0]; // returns `+` or `-` } double copysign_1(double x) { return copysign(1.0, x); } double signbit_d(double x) { int sign = signbit(x); // my compile returns 0 or INT_MIN return sign; } double pow_m1(double x) { return pow(x, -1.0); } void zero_test(const char *name, double (*f)(double)) { double fzp = (f)(+0.0); double fzn = (f)(-0.0); int differ = fzp != fzn; if (fzp != fzp && fzn != fzn) differ = 0; // if both NAN printf("%-15s f(+0):%-+15e %sf(-0):%-+15e\n", name, fzp, differ ? "!=" : "==", fzn); } void zero_tests(void) { zero_test("1/x", inverse); zero_test("atan2(x,-1)", atan2m1); zero_test("printf(\"%+e\")", sprintf_d); zero_test("copysign(x,1)", copysign_1); zero_test("signbit()", signbit_d); zero_test("pow(x,-odd)", pow_m1);; // @Pascal Cuoq zero_test("tgamma(x)", tgamma); // @vinc17 @Pascal Cuoq } 

 Output: 1/xf(+0):+inf != f(-0):-inf atan2(x,-1) f(+0):+3.141593e+00 != f(-0):-3.141593e+00 printf("%+e") f(+0):+4.300000e+01 != f(-0):+4.500000e+01 copysign(x,1) f(+0):+1.000000e+00 != f(-0):-1.000000e+00 signbit() f(+0):+0.000000e+00 != f(-0):-2.147484e+09 pow(x,-odd) f(+0):+inf != f(-0):-inf tgamma(x) f(+0):+inf != f(-0):+inf 

Notas:
tgamma(x) apareció == en mi máquina gcc 4.8.2, pero correctamente != en otros.

rsqrt() , AKA 1/sqrt() es una función estándar de C futura. Puede / no también puede funcionar.

double zero = +0.0; memcpy(&zero, &x, sizeof x) double zero = +0.0; memcpy(&zero, &x, sizeof x) puede mostrar que x es un patrón de bits diferente de +0.0 pero x aún podría ser un +0.0 . Creo que algunos formatos FP tienen muchos patrones de bits que son +0.0 y -0.0 . TBD.

Esta es una respuesta automática proporcionada por https://stackoverflow.com/help/self-answer .

La función rsqrt IEEE 754-2008 (que estará en el futuro estándar ISO C) devuelve ± ∞ en ± 0, lo cual es bastante sorprendente. Y tgamma también devuelve ± ∞ en ± 0. Con MPFR, mpfr_digamma devuelve lo opuesto a ± ∞ en ± 0.

Pienso en este método, pero no puedo verificarlo antes del fin de semana, por lo que alguien podría hacer algunos experimentos con esto, si le gusta, o simplemente decirme que no tiene sentido:

  • Genera un -0.0f. Debería ser posible generar estáticamente asignando una pequeña constante negativa que desborda la representación de flotación.

  • Asigna esta constante a un doble volátil y vuelve a flotar.

    Al cambiar la representación de bits 2 veces, supongo que la representación de bits estándar específica del comstackdor para -0.0f ahora está en la variable. El comstackdor no puede ser más listo que yo, porque un valor totalmente diferente podría estar en la variable volátil entre esas 2 copias.

  • compare la entrada a 0.0f. Para detectar si tenemos un caso de 0.0f / -0.0f

  • si es igual, asigne la variable doble entrada volitale, y luego vuelva a flotar.

    De nuevo supongo que ahora tiene una representación de comstackdor estándar para 0.0f

  • acceda a los patrones de bits por un sindicato y compárelos, para decidir si es -0.0f

El código podría ser algo así como:

 typedef union { float fvalue; /* assuming int has at least the same number of bits as float */ unsigned int bitpat; } tBitAccess; float my_signf(float x) { /* assuming double has smaller min and other bit representation than float */ volatile double refitbits; tBitAccess tmp; unsigned int pat0, patX; if (x < 0.0f) return -1.0f; if (x > 0.0f) return 1.0f; refitbits = (double) (float) -DBL_MIN; tmp.fvalue = (float) refitbits; pat0 = tmp.bitpat; refitbits = (double) x; tmp.fvalue = (float) refitbits; patX = tmp.bitpat; return (patX == pat0)? -1.0f : 1.0f; } 
  • No es una función estándar, o un operador, sino una función que debe diferenciar entre signos de -0.0 y 0.0.
  • Se basa (principalmente) en la suposición de que el proveedor del comstackdor no utiliza patrones de bits diferentes para -0.0f como resultado del cambio de formatos, incluso si el formato de coma flotante lo permitiera, y si esto es así, es independiente del elegido. patrón de bits.
  • Para los formatos de punto flotante que tienen un patrón exacto para -0.0f, esta función debe hacer el truco sin conocimiento del orden de bits en ese patrón.
  • Las otras suposiciones (sobre el tamaño de los tipos, etc.) se pueden manejar con los precomstackdores en las constantes float.h.

Editar: En un segundo pensamiento: si podemos forzar el valor comparando a (0.0 || -0.0) por debajo del número de coma flotante denormal (subnormal) más pequeño representable o su contraparte negativa, y no hay un segundo patrón para -0.0f (exacto ) en el formato FP, podríamos dejar el casting en doble volátil. (Pero tal vez mantenga la flotación volátil, para asegurar que con los denormales desactivados el comstackdor no pueda hacer ningún truco elegante, ignorar las operaciones, que reduzca aún más el valor absoluto de las cosas que comparan igual a 0.0).

El Código entonces podría verse así:

 typedef union { float fvalue; /* assuming int has at least the same number of bits as float */ unsigned int bitpat; } tBitAccess; float my_signf(float x) { volatile tBitAccess tmp; unsigned int pat0, patX; if (x < 0.0f) return -1.0f; if (x > 0.0f) return 1.0f; tmp.fvalue = -DBL_MIN; /* forcing something compares equal to 0.0f below smallest subnormal - not sure if one abs()-factor is enough */ tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue); pat0 = tmp.bitpat; tmp.fvalue = x; tmp.fvalue = tmp.fvalue * fabsf(tmp.fvalue); patX = tmp.bitpat; return (patX == pat0)? -1.0f : 1.0f; } 

Es posible que esto no funcione con los métodos de redondeo sofisticados, que evitan el redondeo de valores negativos hacia -0.0.