¿En qué plataformas el número entero se divide por cero desencadenan una excepción de coma flotante?

En otra pregunta, alguien se preguntaba por qué estaban obteniendo un “error de punto flotante” cuando de hecho tenían un número entero dividido por cero en su progtwig C ++. Surgió una discusión al respecto, y algunos afirmaron que las excepciones de punto flotante nunca se plantean para el flotante dividido por cero, sino que solo surgen en el entero dividido por cero.

Esto me suena extraño, porque sé que:

  1. El código comstackdo MSVC en x86 y x64 en todas las plataformas de Windows informa una división interna por cero como “0xc0000094: División entera por cero”, y float divide por cero como 0xC000008E “División de punto flotante por cero” (cuando está habilitado)

  2. Las ISA IA-32 y AMD64 especifican #DE (excepción de división de enteros) como la interrupción 0. Las excepciones de coma flotante activan la interrupción 16 (coma flotante x87) o la interrupción 19 (coma flotante SIMD).

  3. Otro hardware tiene interrupciones similarmente diferentes ( por ejemplo, PPC aumenta 0x7000 en float-div-by-zero y no atrapa para int / 0 en absoluto).

  4. Nuestra aplicación desenmascara las excepciones de coma flotante para dividir por cero con el _controlfp_s intrínseco (en última instancia, stmxcsr op) y luego las captura para fines de depuración. Así que definitivamente he visto las excepciones de IEEE754 de división por cero en la práctica.

Así que llegué a la conclusión de que hay algunas plataformas que informan excepciones int como excepciones de punto flotante, como Linux x64 (elevando SIGFPE para todos los errores aritméticos, independientemente de la tubería ALU) .

¿Qué otros sistemas operativos (o tiempos de ejecución de C / C ++ si usted es el sistema operativo) informan el entero div-by-zero como una excepción de coma flotante?

No estoy seguro de cómo surgió la situación actual, pero actualmente es el caso de que el soporte de detección de excepciones FP sea muy diferente del número entero. Es común que la división entera atrape. POSIX requiere que levante SIGFPE si genera una excepción.

Sin embargo, puede determinar qué tipo de SIGFPE era, para ver que en realidad era una excepción de división. (No necesariamente dividido por cero, sin embargo: las trampas de división INT_MIN / -1 del complemento 2, y div y idiv también se atrapan cuando el cociente de la división 64b / 32b no cabe en el registro de salida 32b).

El manual de glibc explica que los sistemas BSD y GNU entregan un arg extra al manejador de señal para SIGFPE , que será FPE_INTDIV_TRAP para dividir por cero. POSIX documenta FPE_INTDIV_TRAP como un posible valor para el siginfo_t int si_code , en los sistemas donde siginfo_t incluye ese miembro.

IDK si Windows ofrece una excepción diferente en primer lugar, o si agrupa cosas en diferentes sabores de la misma excepción aritmética como lo hace Unix. Si es así, el controlador predeterminado decodifica la información adicional para decirle qué tipo de excepción era.

Tanto POSIX como Windows usan la frase “división por cero” para cubrir todas las excepciones de división de enteros, por lo que aparentemente se trata de una abreviatura común. Para las personas que conocen acerca de INT_MIN / -1 (con el complemento de 2) que es un problema, la frase “división por cero” puede tomarse como sinónimo de una excepción de división. La frase señala de inmediato el caso común para las personas que no saben por qué la división entera podría ser un problema.


Semántica de excepciones FP

Las excepciones de FP están enmascaradas por defecto para los procesos de espacio de usuario en la mayoría de los sistemas operativos / C ABI.

Esto tiene sentido, porque el punto flotante IEEE puede representar infinitos y tiene NaN para propagar el error a todos los cálculos futuros que usan el valor.

  • 0.0/0.0 => NaN
  • Si x es finito: x/0.0 => +/-Inf con el signo de x

Esto incluso permite que cosas como esta produzcan un resultado sensato cuando las excepciones están enmascaradas:

 double x = 0.0; double y = 1.0/x; // y = +Inf double z = 1.0/y; // z = 1/Inf = 0.0, no FP exception 

FP frente a la detección de errores enteros

La forma FP de detectar errores es bastante buena: cuando se enmascaran excepciones, establecen un indicador en el registro de estado FP en lugar de atrapar. (por ejemplo, MXCSR de x86 para instrucciones de SSE). El indicador permanece establecido hasta que se borre manualmente, por lo que puede verificar una vez (después de un bucle, por ejemplo) para ver qué excepciones ocurrieron, pero no dónde ocurrieron.

Ha habido propuestas para tener indicadores de desbordamiento de enteros “pegajosos” similares para registrar si el desbordamiento sucedió en cualquier punto durante una secuencia de cálculos. Permitir el enmascaramiento de las excepciones de divisiones enteras sería agradable en algunos casos, pero peligroso en otros casos (por ejemplo, en un cálculo de direcciones, debe atrapar en lugar de almacenar potencialmente en una ubicación falsa).

Sin embargo, en x86, detectar si se produjo un desbordamiento de enteros durante una secuencia de cálculos requiere colocar una twig condicional después de cada uno de ellos, porque los indicadores se sobrescriben. MIPS tiene una instrucción add que atrapará el desbordamiento firmado, y una instrucción sin firmar que nunca se detiene. Por lo tanto, la detección y el manejo de excepciones enteras están mucho menos estandarizados.


La división de enteros no tiene la opción de producir resultados NaN o Inf, por lo que tiene sentido que funcione de esta manera .

Cualquier patrón de bits enteros producido por división entera será incorrecto, porque representará un valor finito específico.

Sin embargo, en x86, la conversión de un valor de punto flotante fuera de rango a entero con cvtsd2si o cualquier instrucción de conversión similar produce el valor “entero indefinido” si se enmascara la excepción de “punto flotante no válido”. El valor es todo-cero, excepto el bit de signo. es decir, INT_MIN .

(Consulte los manuales de Intel, enlaces en la wiki de la etiqueta x86) .

¿Qué otros sistemas operativos (o tiempos de ejecución de C / C ++ si usted es el sistema operativo) informan el entero div-by-zero como una excepción de coma flotante?

La respuesta depende de si está en el espacio del kernel o en el espacio de usuario . Si se encuentra en el espacio del kernel, puede poner “i / 0” en kernel_main() , hacer que su manejador de interrupciones llame a un manejador de excepciones y detenga su kernel. Si está en el espacio de usuario, la respuesta depende de su sistema operativo y la configuración del comstackdor.

El hardware AMD64 especifica el entero dividido por cero como la interrupción 0, diferente de la interrupción 16 (excepción de coma flotante x87) y la interrupción 19 (excepción de coma flotante SIMD).

La excepción “Divide por cero” es para dividir por cero con la instrucción div . Discutir la FPU x87 está fuera del scope de esta pregunta.

Otro hardware tiene interrupciones similarmente diferentes (por ejemplo, PPC aumenta 0x7000 en float-div-by-zero y no atrapa para int / 0 en absoluto).

Más específicamente, 00700 se asigna al tipo de excepción “Progtwig”, que incluye una excepción habilitada de coma flotante. Tal excepción se plantea si se intenta dividir por cero usando una instrucción de coma flotante.

Por otro lado, la división entera es un comportamiento indefinido según PPC PEM:

8-53 divw

Si se intenta realizar cualquiera de las divisiones-0x8000_0000 ÷ -1 o ÷ 0, entonces los contenidos de rD no están definidos, al igual que los contenidos de los bits LT, GT y EQ del campo CR0 (si Rc = 1 ) En este caso, si OE = 1 entonces se establece OV.

Nuestra aplicación desenmascara las excepciones de coma flotante para dividir por cero con el _controlfp_s intrínseco (en última instancia, stmxcsr op) y luego las captura para fines de depuración. Así que definitivamente he visto las excepciones de IEEE754 de división por cero en la práctica.

Creo que es mejor invertir tu tiempo en dividir entre cero en tiempo de comstackción y no en tiempo de ejecución.

Para el espacio de usuario, esto ocurre en AIX ejecutándose con POWER, HP-UX ejecutándose en PA-RISC, Linux ejecutándose en x86-64, macOS ejecutándose en x86-64, Tru64 ejecutándose en Alpha y Solaris ejecutándose en SPARC.

Evitar las divisiones por cero en tiempo de comstackción es mucho mejor.