On which platforms does integer divide by zero trigger a floating point exception?
Asked Answered
C

3

19

In another question, someone was wondering why they were getting a "floating point error" when in fact they had an integer divide-by-zero in their C++ program. A discussion arose around this, with some asserting that floating point exceptions are in fact never raised for float divide by zero, but only arise on integer divide by zero.

This sounds strange to me, because I know that:

  1. MSVC-compiled code on x86 and x64 on all Windows platforms reports an int divide by zero as "0xc0000094: Integer division by zero", and float divide by zero as 0xC000008E "Floating-point division by zero" (when enabled)

  2. IA-32 and AMD64 ISAs specify #DE (integer divide exception) as interrupt 0. Floating-point exceptions trigger interrupt 16 (x87 floating-point) or interrupt 19 (SIMD floating-point).

  3. Other hardware have similarly different interrupts (eg PPC raises 0x7000 on float-div-by-zero and doesn't trap for int/0 at all).

  4. Our application unmasks floating-point exceptions for divide-by-zero with the _controlfp_s intrinsic (ultimately stmxcsr op) and then catches them for debugging purposes. So I have definitely seen IEEE754 divide-by-zero exceptions in practice.

So I conclude that there are some platforms that report int exceptions as floating point exceptions, such as x64 Linux (raising SIGFPE for all arithmetic errors regardless of ALU pipe).

What other operating systems (or C/C++ runtimes if you are the operating system) report integer div-by-zero as a floating point exception?

Copaiba answered 16/5, 2016 at 20:18 Comment(12)
CPU interrupts are not not directly visible to userspace applications, since they are received by the kernel. What your program sees depends on how the OS handles the interrupt.Fleurdelis
@KerrekSB Indeed. I am asking, "what OSes report integer divide by zero as floating point exception?"Copaiba
You are? Your questions say What other C or C++ runtimesTice
@Tice I've fixed the question. I'm used to thinking of this in terms of C runtime because when I first dealt with this sort of thing I was writing kernel-level code.Copaiba
"What other operating systems/runtimes..." is a very broad question. But since you ask, MSVC 9.0 (Windows 7) hangs, although the compiler issues a warning if it knows the divisor is 0.Warmongering
Since the question is tagged x86-64, are you only looking for OS's on that hardware?Rapeseed
You seem to be asking how undefined behaviour is defined.Boart
@AlanStokes I am asking how specific platforms deal with a specific interrupt defined by hardware.Copaiba
I think it's OK to ask how implementation behaves on UB because implementation is free to define certain UBs as implementation defined, as an extension to the standard.Pentylenetetrazol
I think this has been common behavior on most of the Unix systems I've used over the past 25 years.Bunchy
On a side note with x86 : What some may call an integer division by zero exception may not always be accurate. That same exception can get thrown if integer division overflows. I prefer to call it integer division exception.Marin
On Linux/Unix for legacy reasons floating point and integer exceptions gets mapped to user land signal SIGFPE . The name SIGFPE is legacy "Floating point exception" by name only. It really just means "arithmetic exception" (including both integer and floating point)Marin
J
18

I'm not sure how the current situation came to be, but it's currently the case that FP exception detection support is very different from integer. It's common for integer division to trap. POSIX requires it to raise SIGFPE if it raises an exception at all.

However, you can sort out what kind of SIGFPE it was, to see that it was actually a division exception. (Not necessarily divide-by-zero, though: 2's complement INT_MIN / -1 division traps, and x86's div and idiv also trap when the quotient of 64b/32b division doesn't fit in the 32b output register. But that's not the case on AArch64 using sdiv.)

The glibc manual explains that BSD and GNU systems deliver an extra arg to the signal handler for SIGFPE, which will be FPE_INTDIV_TRAP for divide by zero. POSIX documents FPE_INTDIV_TRAP as a possible value for siginfo_t's int si_code field, on systems where siginfo_t includes that member.

IDK if Windows delivers a different exception in the first place, or if it bundles things into different flavours of the same arithmetic exception like Unix does. If so, the default handler decodes the extra info to tell you what kind of exception it was.

POSIX and Windows both use the phrase "division by zero" to cover all integer division exceptions, so apparently this is common shorthand. For people that do know about about INT_MIN / -1 (with 2's complement) being a problem, the phrase "division by zero" can be taken as synonymous with a divide exception. The phrase immediately points out the common case for people that don't know why integer division might be a problem.


FP exceptions semantics

FP exceptions are masked by default for user-space processes in most operating systems / C ABIs.

This makes sense, because IEEE floating point can represent infinities, and has NaN to propagate the error to all future calculations using the value.

  • 0.0/0.0 => NaN
  • If x is finite: x/0.0 => +/-Inf with the sign of x

This even allows things like this to produce a sensible result when exceptions are masked:

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 vs. integer error detection

The FP way of detecting errors is pretty good: when exceptions are masked, they set a flag in the FP status register instead of trapping. (e.g. x86's MXCSR for SSE instructions). The flag stays set until manually cleared, so you can check once (after a loop for example) to see which exceptions happened, but not where they happened.

There have been proposals for having similar "sticky" integer-overflow flags to record if overflow happened at any point during a sequence of computations. Allowing integer division exceptions to be masked would be nice in some cases, but dangerous in other cases (e.g. in an address calculation, you should trap instead of potentially storing to a bogus location).

On x86, though, detecting if integer overflow happened during a sequence of calculations requires putting a conditional branch after every one of them, because flags are just overwritten. MIPS has an add instruction that will trap on signed overflow, and an unsigned instruction that never traps. So integer exception detection and handling is a lot less standardized.


Integer division doesn't have the option of producing NaN or Inf results, so it makes sense for it to work this way.

Any integer bit pattern produced by integer division will be wrong, because it will represent a specific finite value.

However, on x86, converting an out-of-range floating point value to integer with cvtsd2si or any similar conversion instruction produces the "integer indefinite" value if the "floating-point invalid" exception is masked. The value is all-zero except the sign bit. i.e. INT_MIN.

(See the Intel manuals, links in the tag wiki.

Jacal answered 17/5, 2016 at 3:16 Comment(3)
Forgive me I'm so new at this, but can we use QNaNs to point to where the exceptions happened so we can still check once but see where those exceptions happened? @Peter I ask b/c of the last line in the first paragraph of "FP vs. integer error detection."Fidelis
@Modulo: In theory yes, if the hardware chooses to copy the instruction pointer into the QNaN payload if neither input was NaN. Then NaN propagation (when one of inputs already is NaN) should keep that payload unchanged, IIRC. I'm not sure if actual hardware (like x86) does this, though.Jacal
@RobertHoughton That's a model that's deliberately allowed by IEEE 754, but I don't think anyone has ever implemented it outside of some hobby projects. Peter mentioned nan propagation rules in his comment, which introduce some complexity--when an operation has a nan input, the result should be the same nan quieted in most cases, but this is not a requirement of IEEE 754. When an operation has multiple nan inputs, there's no specified rule for how to choose one. It would require some hardware codesign to make such a system work, but it's definitely feasible.Greeneyed
P
2

What other operating systems (or C/C++ runtimes if you are the operating system) report integer div-by-zero as a floating point exception?

The answer depends on whether you are in kernel space or user space. If you are in kernel space, you can put "i / 0" in kernel_main(), have your interrupt handler call an exception handler and halt your kernel. If you're in user space, the answer depends on your operating system and compiler settings.

AMD64 hardware specifies integer divide by zero as interrupt 0, different from interrupt 16 (x87 floating-point exception) and interrupt 19 (SIMD floating-point exception).

The "Divide-by-zero" exception is for dividing by zero with the div instruction. Discussing the x87 FPU is outside the scope of this question.

Other hardware have similarly different interrupts (eg PPC raises 0x7000 on float-div-by-zero and doesn't trap for int/0 at all).

More specifically, 00700 is mapped to exception type "Program", which includes a floating-point enabled exception. Such an exception is raised if trying to divide-by-zero using a floating point instruction.

On the other hand, integer division is undefined behavior per the PPC PEM:

8-53 divw

If an attempt is made to perform either of the divisions—0x8000_0000 ÷ –1 or ÷ 0, then the contents of rD are undefined, as are the contents of the LT, GT, and EQ bits of the CR0 field (if Rc = 1). In this case, if OE = 1 then OV is set.

Our application unmasks floating-point exceptions for divide-by-zero with the _controlfp_s intrinsic (ultimately stmxcsr op) and then catches them for debugging purposes. So I have definitely seen IEEE754 divide-by-zero exceptions in practice.

I think your time is better spent catching divide by zero at compile-time rather than at run-time.

Pawsner answered 16/5, 2016 at 21:25 Comment(0)
P
0

For userspace, this happens on AIX running on POWER, HP-UX running on PA-RISC, Linux running on x86-64, macOS running on x86-64, Tru64 running on Alpha and Solaris running on SPARC.

Avoiding divides-by-zero at compile time is much better.

Plumbago answered 1/2, 2017 at 17:13 Comment(2)
Are you sure that Windows x86-64 reports an integer divide by zero as a floating-point exception? I have observed that it reports 0xc0000094 (Integer division by zero) and not 0xC000008E (Floating-point division by zero). Is it configuration specific?Copaiba
Whoops, forgot I wrote some infrastructure to make that happen. Changed.Plumbago

© 2022 - 2024 — McMap. All rights reserved.