golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.69k stars 17.62k forks source link

spec: floating-point division by zero shouldn't be implementation defined #43577

Open ianlancetaylor opened 3 years ago

ianlancetaylor commented 3 years ago

The spec says `The result of a floating-point or complex division by zero is not specified beyond the IEEE-754 standard; whether a run-time panic occurs is implementation-specific. This dates back to https://golang.org/cl/1081041, which was a general change about division by zero.

I think this is incorrect. Division by zero is fully defined by IEEE-754. There should not be a run-time panic for floating-point division by zero. This should be fully defined, not implementation-defined.

CC @griesemer

griesemer commented 3 years ago

This doesn't say that it's undefined, it just says it's not defined beyond the IEEE-754 standard. Specifically (https://en.wikipedia.org/wiki/IEEE_754#Exception_handling):

The standard defines five exceptions, each of which returns a default value and has a corresponding status flag that is raised when the exception occurs.[e] No other exception handling is required, but additional non-default alternatives are recommended (see § Alternate exception handling).

While this section (later on) defines that division-by-zero results in an infinity, it also says: "additional non-default alternatives are recommended".

The additional alternatives (https://en.wikipedia.org/wiki/IEEE_754#Alternate_exception_handling) include traps, etc.

I think this is the original reason why we didn't pin this down more precisely. In the past, on some FPUs, whether a trap happens upon division-by-zero depended on the FPU flag register. Unless I am misremembering, some OS didn't use to save/restore that register upon context switches, making in impractical to rely on a specific flag setting but the one provided by the OS. Consequently, requiring a specific behavior (no traps upon division by zero) was basically impossible to guarantee (albeit in practice, no "reasonable" program would ever change that register).

These problems may not exist anymore (to be investigated).

ianlancetaylor commented 3 years ago

We can guarantee that division by zero does not trap, because we can set the floating-point control word on program startup. Or on systems where that is impossible because the floating-point control word is not saved on context switches, we can compile a floating-point division to check for a zero divisor. But the floating-point control word is always saved on context switches, because anything else would break threaded C code.

In C, if FE_DIVBYZERO is defined in <fenv.h>, then the system can control whether it traps on division-by-zero or simply returns an infinity (or NaN for 0.0 / 0.0). I checked every glibc target. FE_DIVBYZERO is defined for all of them except nios2 and microblaze. For nios2 it is omitted because division by zero always produces infinity/NaN, and never traps. For Microblaze the flag is omitted because on Microblaze you can either enable all floating-point exceptions or none; you can't enable specific exceptions.

In practice I am not aware of any system that defaults to trapping on floating-point division by zero. All systems I know of simply produce a floating-point infinity.

josharian commented 3 years ago

we can set the floating-point control word on program startup.

Can we guarantee it stays set? I am reminded of https://github.com/golang/go/issues/8623

ianlancetaylor commented 3 years ago

We can ensure it stays set by forcing it on cgo calls and callbacks if necessary. (But, again, all systems I know of do what we want anyhow.)