language agnostic - ¿El flotador tiene un cero negativo?(-0f)
language-agnostic floating-point (10)
Los números de punto flotante IEEE tienen un bit asignado para indicar el signo, lo que significa que técnicamente puede tener diferentes representaciones binarias de cero (+0 y -0). ¿Existe una operación aritmética que podría hacer, por ejemplo, en C, que dé como resultado un valor de punto flotante cero negativo?
Esta pregunta está inspirada en otra que cuestionó si se puede comparar de manera segura 0.0f
usando ==
, y me pregunté si habría otras formas de representar cero que harían que float1 == 0.0f
rompiera por valores aparentemente perfectamente iguales.
[Editar] Por favor, ¡no comente sobre la seguridad de comparar flotadores para la igualdad! No estoy tratando de agregar a ese cubo desbordante de preguntas duplicadas.
¿Existe una operación aritmética que podría hacer, por ejemplo, en C, que dé como resultado un valor de punto flotante cero negativo?
Por supuesto:
float negativeZero = -10.0e-30f * 10.0e-30f;
El resultado matemáticamente preciso de la multiplicación no se puede representar como un valor de punto flotante, por lo que se redondea al valor representable más cercano, que es -0.0f
.
La semántica del cero negativo está bien definida por el estándar IEEE-754; la única forma real observable en la que su comportamiento difiere del de cero en la expresión aritmética es que si se divide por ella, obtendrá un signo diferente de infinito. Por ejemplo:
1.f / 0.f --> +infinity
1.f / -0.f --> -infinity
Las comparaciones y las sumas y restas con -0.f
dan el mismo resultado que con +0.f
(en el modo de redondeo predeterminado). La multiplicación puede conservar el signo de cero, pero como se señaló, generalmente no es observable.
Hay algunas funciones de la biblioteca matemática cuyo comportamiento puede variar según el signo de cero. Por ejemplo:
copysignf(1.0f, 0.0f) --> 1.0f
copysignf(1.0f,-0.0f) --> -1.0f
Esto es más común en las funciones complejas:
csqrtf(-1.0f + 0.0f*i) --> 0.0f + 1.0f*i
csqrtf(-1.0f - 0.0f*i) --> 0.0f - 1.0f*i
En general, sin embargo, no debería preocuparse por el cero negativo.
-lm tiene la función signbit () disponible para indicar si un valor es negativo (incluido -0)
De acuerdo con el estándar, existe un cero negativo, pero es igual a un cero positivo. Para casi todos los propósitos, los dos se comportan de la misma manera y muchos consideran que la existencia de un negativo es un detalle de implementación. Hay, sin embargo, algunas funciones que se comportan de manera muy diferente, a saber, división y atan2
:
#include <math.h>
#include <stdio.h>
int main() {
double x = 0.0;
double y = -0.0;
printf("%.08f == %.08f: %d/n", x, y, x == y);
printf("%.08f == %.08f: %d/n", 1 / x, 1 / y, 1 / x == 1 / y);
printf("%.08f == %.08f: %d/n", atan2(x, y), atan2(y, y), atan2(x, y) == atan2(y, y));
}
El resultado de este código es:
0.00000000 == -0.00000000: 1
1.#INF0000 == -1.#INF0000: 0
3.14159265 == -3.14159265: 0
Esto significaría que el código manejaría correctamente ciertos límites sin la necesidad de un manejo explícito. No es seguro que confiar en esta función para valores cercanos a los límites sea una buena idea, ya que un simple error de cálculo puede cambiar el signo y hacer que el valor esté lejos de ser correcto, pero aún puede aprovecharlo si evita los cálculos que lo harían. cambiar el signo.
Debes tener cuidado al hacer comparaciones de igualdad usando flotadores. Recuerda, estás intentando representar un valor decimal en un sistema binario.
¿Es seguro verificar que los valores de punto flotante sean iguales a 0?
Si debe comparar los valores de punto flotante, le sugiero que use algún tipo de tolerancia que sea aceptable para usted float1 <= toleranceVal && float1 >= toleranceVal2
o multiplíquelo por un factor de diez y conviértalo como un entero. if (!(int)(float1 * 10000)) { .. some stuff .. }
Hay un par de operaciones aritméticas simples que resultan en una respuesta cero negativa (al menos en los sistemas i386 / x64 / ARMv7 / ARMv8 en los que lo probé):
- -1 * 0
- 0 / -1
Estos me sorprendieron cuando estaba escribiendo un optimizador para simplificar expresiones aritméticas. La optimización de " a = b * 0 " a " a = 0 " dará como resultado una respuesta incorrecta (+0) si b resulta ser negativo (la respuesta correcta es -0).
Sí, float tiene un cero negativo, pero no , no tiene que preocuparse por esto al comparar valores de punto flotante.
La aritmética de punto flotante se define para funcionar correctamente en casos especiales.
Sí, los float
tienen un cero negativo al igual que otros tipos de punto flotante IEEE, como el double
(en sistemas con punto flotante IEEE). Hay un ejemplo here en Octave de cómo crearlos; las mismas operaciones funcionan en C. El operador ==
trata a +0 y -0 de la misma manera, y así los ceros negativos no rompen ese tipo de comparación.
Sí, puede tener un +0 y -0 y esos son patrones de bits diferentes (debería fallar la prueba de igualdad). Nunca debe usar == con flotador, ciertamente no IEEE. <o> están bien. Hay muchas otras preguntas y discusiones de SO sobre este tema, por lo que no voy a entrar aquí.
Sí, se puede firmar el cero, pero el estándar requiere un cero positivo y negativo para probar como igual
este float1 == 0.0f
nunca es realmente una comparación segura.
si tienes algo como
float x = 0.0f;
for (int i = 0; i < 10; i++) x += 0.1f;
x -= 1.0f;
assert (x == 0.0f);
fallará aunque aparentemente se supone que es 0.