simple representar punto para operaciones numeros numero notación normalizada mantisa flotante exponente estandar doble coma c++ floating-point language-lawyer undefined-behavior divide-by-zero

c++ - representar - El comportamiento de la división de coma flotante por cero



precision simple y doble (7)

Citando cppreference :

Si el segundo operando es cero, el comportamiento no está definido, excepto que si se está produciendo una división de coma flotante y el tipo admite la aritmética de punto flotante IEEE (vea std::numeric_limits::is_iec559 ), entonces:

  • si un operando es NaN, el resultado es NaN

  • dividir un número distinto de cero en ± 0.0 da el infinito correctamente firmado y se levanta FE_DIVBYZERO

  • dividir 0.0 por 0.0 da NaN y FE_INVALID es levantado

Aquí estamos hablando de división de punto flotante, por lo que en realidad está definido por la implementación si la división double por cero no está definida.

Si std::numeric_limits<double>::is_iec559 es true , y es std::numeric_limits::is_iec559 , entonces el comportamiento está bien definido y produce los resultados esperados.

Una apuesta bastante segura sería lanzar un:

static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");

... cerca de tu código.

Considerar

#include <iostream> int main() { double a = 1.0 / 0; double b = -1.0 / 0; double c = 0.0 / 0; std::cout << a << b << c; // to stop compilers from optimising out the code. }

Siempre he pensado que a será + Inf, b será -Inf, y c será NaN. Pero también escucho rumores de que, estrictamente hablando, el comportamiento de la división de punto flotante por cero no está definido y, por lo tanto, el código anterior no puede considerarse como C ++ portátil. (Eso teóricamente borra la integridad de mi pila de código de millones de líneas más. Vaya).

¿Quién es correcto?

Tenga en cuenta que estoy contento con la implementación definida , pero estoy hablando de cometer un gato, estornudo de demonios comportamiento indefinido aquí.


División por cero, tanto el número entero como el punto flotante son comportamientos indefinidos [expr.mul]p4 :

El operador binario produce el cociente, y el operador binario% produce el resto de la división de la primera expresión por el segundo. Si el segundo operando de / o% es cero, el comportamiento no está definido. ...

Aunque la implementación puede admitir opcionalmente el Anexo F, que tiene una semántica bien definida para la división de coma flotante por cero.

Podemos ver en este reporte de fallas de clang clang sanitizer que la división de coma flotante IEC 60559 por cero no define que aunque la macro __STDC_IEC_559__ está definida, está siendo definida por los encabezados del sistema y al menos para clang no es compatible con el Anexo F y así para clang sigue siendo un comportamiento indefinido:

El anexo F del estándar C (soporte de IEC 60559 / IEEE 754) define la división de punto flotante por cero, pero clang (instantánea de Debian 3.3 y 3.4) lo considera indefinido. Esto es incorrecto:

El soporte para el Anexo F es opcional, y no lo apoyamos.

#if STDC_IEC_559

Esta macro está siendo definida por los encabezados de nuestro sistema, no por nosotros; esto es un error en los encabezados de tu sistema. (FWIW, GCC tampoco es totalmente compatible con el Anexo F, IIRC, por lo que ni siquiera es un error específico de Clang).

Ese informe de errores y otros dos informes de fallos UBSan: la división de puntos flotantes por cero no está indefinida y el clang debería apoyar el Anexo F de ISO C (IEC 60559 / IEEE 754) indica que gcc se ajusta al Anexo F con respecto al punto flotante dividido por cero .

Aunque estoy de acuerdo en que no corresponde a la biblioteca de C definir incondicionalmente STDC_IEC_559 , el problema es específico de clang. GCC no admite completamente el Anexo F, pero al menos su intención es respaldarlo por defecto y la división está bien definida con él si el modo de redondeo no se modifica. En la actualidad, no es compatible con IEEE 754 (al menos las funciones básicas como el manejo de la división por cero) se considera un mal comportamiento.

Este es un apoyo adicional de la semántica gcc de matemática de coma flotante en la wiki de GCC que indica que -fno-signaling-nans es el valor predeterminado que está de acuerdo con la documentación de opciones de optimización de gcc que dice:

El valor predeterminado es -fno-signaling-nans.

Es interesante notar que UBSan para clang se predetermina a incluir float-divide-by-zero bajo -fsanitize = indefinido mientras que gcc no lo hace :

Detecta la división de punto flotante por cero. A diferencia de otras opciones similares, -fsanitize = float-divide-by-zero no está habilitado por -fsanitize = undefined, ya que la división de punto flotante por cero puede ser una forma legítima de obtener infinitos y NaN.

Véalo en vivo para sonar y vivir para gcc .


El estándar C ++ no fuerza el estándar IEEE 754, porque eso depende principalmente de la arquitectura del hardware.

Si el hardware / compilador implementa correctamente el estándar IEEE 754, la división proporcionará el esperado INF, -INF y NaN, de lo contrario ... depende.

Undefined significa, la implementación del compilador decide, y hay muchas variables para eso, como la arquitectura del hardware, la eficiencia de generación de código, la pereza del desarrollador del compilador, etc.

Fuente:

El estándar de C ++ establece que una división por 0.0 undefined está undefined

Estándar C ++ 5.6.4

... Si el segundo operando de / o% es cero, el comportamiento no está definido

Estándar C ++ 18.3.2.4

... static constexpr bool is_iec559;

... 56. Verdadero si y solo si el tipo cumple con la norma IEC 559.217

... 57. Significativo para todos los tipos de punto flotante.

Detección C ++ de IEEE754:

La biblioteca estándar incluye una plantilla para detectar si IEEE754 es compatible o no:

static constexpr bool is_iec559;

#include <numeric> bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();

¿Qué ocurre si IEEE754 no es compatible?

Depende, generalmente una división por 0, desencadenar una excepción de hardware y hacer que la aplicación finalice.


En [expr] / 4 tenemos

Si durante la evaluación de una expresión, el resultado no está matemáticamente definido o no está en el rango de valores representables para su tipo, el comportamiento no está definido . [Nota: la mayoría de las implementaciones existentes de C ++ ignoran los desbordamientos de enteros. El tratamiento de la división por cero, formando un resto usando un divisor cero, y todas las excepciones de punto flotante varían entre las máquinas, y generalmente es ajustable por una función de biblioteca. -Finalizar nota]

Énfasis mío

Entonces, según el estándar, este es un comportamiento indefinido. Continúa diciendo que algunos de estos casos son manejados por la implementación y son configurables. Por lo tanto, no indicará que se trata de una implementación definida, pero sí le permite saber que las implementaciones definen parte de este comportamiento.


En cuanto a la pregunta del remitente "¿Quién es correcto?", Está perfectamente bien decir que ambas respuestas son correctas. El hecho de que el estándar C describa el comportamiento como "indefinido" NO INDICA lo que realmente hace el hardware subyacente; simplemente significa que si desea que su programa sea significativo de acuerdo con el estándar que usted puede asumir, el hardware realmente implementa esa operación. Pero si se está ejecutando en un hardware que implementa el estándar IEEE, descubrirá que la operación de hecho está implementada, con los resultados estipulados por el estándar IEEE.


Esto también depende del entorno de coma flotante.

cppreference tiene detalles: http://en.cppreference.com/w/cpp/numeric/fenv (sin embargo, no hay ejemplos).

Esto debería estar disponible en la mayoría de los entornos C ++ 11 y C99 de escritorio / servidor. También hay variaciones específicas de la plataforma que son anteriores a la estandarización de todo esto.

Esperaría que la habilitación de excepciones haga que el código se ejecute más lentamente, por lo que probablemente por esta razón la mayoría de las plataformas que conozco desactivan las excepciones de forma predeterminada.


La división por 0 es un comportamiento indefinido .

De la sección 5.6 del estándar de C ++ (C ++ 11) :

El operador binario produce el cociente, y el operador binario % produce el resto de la división de la primera expresión por el segundo. Si el segundo operando de / o % es cero, el comportamiento no está definido. Para operandos integrales, el operador / produce el cociente algebraico con cualquier parte fraccional descartada; si el cociente a/b es representable en el tipo de resultado, (a/b)*b + a%b es igual a a .

No se hace distinción entre enteros y operandos de coma flotante para el operador / . La norma solo establece que dividir por cero no está definido sin tener en cuenta los operandos.