c++ gcc floating-point divide-by-zero

Advertencia de C++: división del doble por cero



gcc floating-point (7)

En el estándar C ++, ambos casos son de comportamiento indefinido . Cualquier cosa puede suceder, incluido el formateo de su disco duro. No debe esperar o confiar en "return inf. Ok" o cualquier otro comportamiento.

El compilador aparentemente decide dar una advertencia en un caso y no en el otro, pero esto no significa que un código esté bien y el otro no. Es sólo un capricho de la generación de advertencias del compilador.

Desde el estándar C ++ 17 [expr.mul] / 4:

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

Caso 1:

#include <iostream> int main() { double d = 15.50; std::cout<<(d/0.0)<<std::endl; }

Se compila sin advertencias e imprime inf . OK, C ++ puede manejar la división por cero, ( verlo en vivo ).

Pero,

Caso 2:

#include <iostream> int main() { double d = 15.50; std::cout<<(d/0)<<std::endl; }

El compilador da la siguiente advertencia ( véalo en vivo ):

warning: division by zero [-Wdiv-by-zero] std::cout<<(d/0)<<std::endl;

¿Por qué el compilador da una advertencia en el segundo caso?

¿Es 0 != 0.0 ?

Editar:

#include <iostream> int main() { if(0 == 0.0) std::cout<<"Same"<<std::endl; else std::cout<<"Not same"<<std::endl; }

salida:

Same


Esto parece un error de gcc, la documentación para -Wno-div-by-zero dice claramente :

No advierta sobre la división de enteros en tiempo de compilación por cero. No se advierte sobre la división de punto flotante por cero, ya que puede ser una forma legítima de obtener infinitos y NaNs.

y después de las conversiones aritméticas habituales cubiertas en [expr.arith.conv] ambos operandos serán dobles :

Muchos operadores binarios que esperan operandos de tipo aritmético o de enumeración causan conversiones y producen tipos de resultados de una manera similar. El propósito es generar un tipo común, que también es el tipo del resultado. Este patrón se llama las conversiones aritméticas habituales, que se definen de la siguiente manera:

...

- De lo contrario, si cualquiera de los operandos es doble, el otro se convertirá en doble.

y [expr.mul] :

Los operandos de * y / tendrán un tipo de enumeración aritmética o sin ámbito; los operandos de% tendrán un tipo de enumeración integral o sin ámbito. Las conversiones aritméticas habituales se realizan en los operandos y determinan el tipo de resultado.

Con respecto a si la división de punto flotante por cero es un comportamiento indefinido y cómo la implementación diferente trata con eso parece ser mi respuesta aquí . TL; DR; Parece que gcc se ajusta al anexo F wrt a punto dividido por cero, por lo que no definido no juega un papel aquí. La respuesta sería diferente para clang.


La división de punto flotante por cero está bien definida por IEEE y da infinito (ya sea positivo o negativo según el valor del numerador (o NaN para ± 0) ).

Para los enteros, no hay forma de representar el infinito y el lenguaje define la operación para que tenga un comportamiento indefinido, por lo que el compilador intenta, de manera útil, alejarse de esa ruta.

Sin embargo, en este caso, dado que el numerador es un double , el divisor ( 0 ) también debe ascender a un doble y no hay razón para dar una advertencia aquí mientras no se emita una advertencia de 0.0 así que creo que este es un error del compilador.


La división de punto flotante por cero se comporta de manera diferente que la división de enteros por cero.

El estándar de punto flotante IEEE diferencia entre + inf y -inf, mientras que los enteros no pueden almacenar infinito. El resultado de la división entera por cero es un comportamiento indefinido. La división de punto flotante por cero está definida por el estándar de punto flotante y da como resultado + inf o -inf.


Mi mejor estimación para responder a esta pregunta en particular sería que el compilador emite una advertencia antes de realizar la conversión de int para double .

Entonces, los pasos serían así:

  1. Expresión de análisis
  2. Operador aritmético /(T, T2) , donde T=double , T2=int .
  3. Verifique que el valor std::is_integral<T2>::value sea true y b == 0 : esto activa la advertencia.
  4. Emitir advertencia
  5. Realizar conversión implícita de T2 a double
  6. Realizar una división bien definida (ya que el compilador decidió usar IEEE 754).

Esto es, por supuesto, una especulación y se basa en especificaciones definidas por el compilador. Desde el punto de vista estándar, estamos tratando con posibles comportamientos indefinidos.

Tenga en cuenta que este es el comportamiento esperado de acuerdo con la documentación de GCC
(por cierto, parece que esta bandera no se puede usar explícitamente en GCC 8.1)

-Wdiv por cero
Advertir sobre la división de enteros en tiempo de compilación por cero. Esto es por defecto Para inhibir los mensajes de advertencia, use -Wno-div-by-zero. No se advierte sobre la división de punto flotante por cero, ya que puede ser una forma legítima de obtener infinitos y NaNs.


No voy a entrar en la debacle UB / no UB en esta respuesta.

Solo quiero señalar que 0 y 0.0 son diferentes a pesar de que 0 == 0.0 evalúa como verdadero. 0 es un int literal y 0.0 es un double literal.

Sin embargo, en este caso, el resultado final es el mismo: d/0 es la división de punto flotante porque d es doble y, por lo tanto, 0 se convierte implícitamente en doble.


Yo diría que foo/0 y foo/0.0 no son lo mismo. A saber, el efecto resultante de la primera (división entera o división de punto flotante) depende en gran medida del tipo de foo , mientras que el mismo no es cierto para la segunda (siempre será una división de punto flotante).

Si alguno de los dos es UB es irrelevante. Citando el estándar:

El comportamiento indefinido permisible abarca desde ignorar la situación completamente con resultados impredecibles, a comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico) , hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

(Énfasis mío)

Considere la advertencia de " sugerir paréntesis alrededor de la asignación utilizada como valor de verdad ": la forma de decirle al compilador que realmente desea usar el resultado de una asignación es mediante la explícita y la adición de paréntesis alrededor de la asignación. La declaración resultante tiene el mismo efecto, pero le dice al compilador que sabe lo que está haciendo. Lo mismo se puede decir sobre foo/0.0 : Ya que le está diciendo explícitamente al compilador "Esto es una división de punto flotante" utilizando 0.0 lugar de 0 , el compilador confía en usted y no emitirá una advertencia.