skip to content

Why does `const r = 1.0 / 0.0` fail to compile in Go while a float64 variable divided by zero yields +Inf?

level: juniorimportance: must knowfreq 55%

answer

  1. two evaluators, two different rules
  2. one of them runs before the program does
  3. IEEE 754 only applies at run time
  4. nonzero over zero, then zero over zero
  5. check the result with math.IsInf and math.IsNaN

basics

~20 s

The compiler evaluates constant expressions itself and rejects a constant division by zero outright. Float64 variables divide at run time under IEEE 754, so a nonzero value over zero gives positive or negative infinity, and zero over zero gives NaN.

solid answer

~40 s

`1.0 / 0.0` is a constant expression, so the compiler folds it while building and reports `invalid operation: division by zero`; the declared type it is being assigned to makes no difference, which is why `const r`, `var r float64` and `r :=` all fail identically. Once a float64 variable is one of the operands the division is a machine instruction following IEEE 754: `1.0 / zero` is `+Inf`, `-1.0 / zero` is `-Inf`, and `zero / zero` is NaN. Nothing panics and no error is returned, so the value flows onward as an ordinary float64. Test the result with `math.IsNaN(v)` — comparing against `math.NaN()` never matches anything — and `math.IsInf(v, 0)` for an infinity of either sign.

code

go · 7 lines
go
// const r = 1.0 / 0.0 // invalid operation: division by zero

var zero float64
x := 1.0 / zero
y := zero / zero
fmt.Println(x, y, math.IsInf(x, 1), math.IsNaN(y))
// prints: +Inf NaN true true

go deeper

for a junior

Be ready to say what the three float64 results are: nonzero over zero is an infinity, zero over zero is NaN, and a literal division by zero in the source does not compile. Know the names math.IsInf and math.IsNaN.

for a middle

Explain the mechanism: constant expressions are folded by the compiler, so the error arrives at build time regardless of the target type, while any variable operand pushes the division to IEEE 754 arithmetic at run time.

for a senior

Show where you would place the guard in real code — at the divisor when a zero denominator is a named domain case, or on the result with math.IsNaN and math.IsInf before the value is stored, serialised or compared.

for a principal

Own the policy question: which values in your domain are allowed to be floats at all, whether non-finite values are rejected at ingest or tolerated, and how you keep that rule enforceable across teams rather than rediscovered per incident.

Go does floating-point arithmetic in two different places under two different rule sets, and that split is the whole answer. ### Constant expressions are the compiler's arithmetic `1.0` and `0.0` are constants, so `1.0 / 0.0` is a *constant expression* and the compiler is required to evaluate it while building the program. There is no value it can produce for a division by zero, so the language makes it illegal and the build stops with `invalid operation: division by zero`. This is not affected by the type on the left. All three of these fail the same way, because in each case the right-hand side is a constant expression that must be folded before any variable exists: - `const r = 1.0 / 0.0` - `var r float64 = 1.0 / 0.0` - `r := 1.0 / 0.0` That is a feature. A divisor that is literally zero in the source is always a mistake, and you find out before the binary exists. ### Variables are the machine's arithmetic As soon as one operand is a variable, the compiler emits a divide instruction and the result follows IEEE 754, the floating-point standard that `float32` and `float64` implement: - a finite nonzero value divided by zero is an infinity, with the sign taken from the signs of both operands (dividing by negative zero flips it); - zero divided by zero is NaN, "not a number"; - NaN in gives NaN out for every arithmetic operation. There is no panic and no error return. The result is an ordinary `float64` that keeps flowing through the program. ### Constructing and testing these values The `math` package is where Go exposes them: - `math.Inf(1)` and `math.Inf(-1)` build positive and negative infinity; `math.NaN()` builds a NaN. - `math.IsInf(v, 1)` tests for positive infinity, `math.IsInf(v, -1)` for negative, and `math.IsInf(v, 0)` for either. - `math.IsNaN(v)` tests for NaN, and it is the *only* correct test. Infinities compare normally, so `v == math.Inf(1)` happens to work, but NaN compares false against everything including itself, so `v == math.NaN()` is false no matter what `v` holds. `math.IsNaN` is implemented as `v != v` for exactly that reason. - `math.MaxFloat64` is the largest finite `float64`; infinity is larger than it, not equal to it. ### Why the distinction matters downstream Infinities and NaNs are contagious. `math.Inf(1) - math.Inf(1)`, `0 * math.Inf(1)` and `math.Inf(1) / math.Inf(1)` are all NaN, and once a NaN is in a running total the total is NaN forever. Worse, NaN makes threshold checks silently useless: for a NaN `v`, both `v > limit` and `v <= limit` are false, so a validation branch you wrote to catch outliers simply never fires. Division is not the only door. `math.Sqrt(-1)` and `math.Log(-1)` return NaN, and `strconv.ParseFloat` accepts the texts `"NaN"`, `"Inf"` and `"-Inf"`, so untrusted input can introduce one without any arithmetic at all. ### The practical rule Guard the divisor where you divide, or check the result immediately: 1. `if denom == 0` before the division when a zero denominator is a domain condition you can name (no revenue, no samples, no rows). 2. `math.IsNaN(v) || math.IsInf(v, 0)` on the result when the arithmetic is more complicated than a single division. 3. `math.Float64bits(v)` when you need to see exactly what a suspect value is in a log line — it prints the raw 64-bit pattern, which distinguishes a NaN from a genuine number and even separates negative zero from zero. A float division that produces an infinity is Go behaving correctly. Deciding what your program should do about it is your job, and the earliest point you can do that is the cheapest.

  • Why does `var f float64 = 1.0 / 0.0` fail too, given that f is a variable?
    Because the variable is irrelevant — the right-hand side is still a constant expression, and the compiler must evaluate it before the assignment exists. Only an operand that is itself a variable moves the division to run time, as in `1.0 / zero` where `zero` is a declared `float64`.
  • What does math.Inf(1) minus math.Inf(1) evaluate to?
    NaN. IEEE 754 leaves infinity minus infinity undefined, and the same is true of `0 * math.Inf(1)` and `math.Inf(1) / math.Inf(1)`. This is how an infinity produced by one division turns into a NaN two steps later, which is why it is worth catching infinities at the point they appear rather than at the end of a pipeline.
  • How do you check for positive infinity specifically rather than either sign?
    `math.IsInf(v, 1)`. The second argument is a sign selector: a positive value tests for +Inf, a negative value for -Inf, and zero for either. Because infinities compare normally, `v == math.Inf(1)` also works — the shortcut that does not exist for NaN.

The compiler is a bookkeeper who refuses to write down a division by zero at all; the CPU is a calculator that shrugs and answers "infinity" and lets you keep typing.

saying these in an interview costs you the question

  • Says a float64 divided by zero panics at run time
  • Claims Go returns an error value for a float division by zero
  • Thinks the compile error depends on the variable's declared type
  • Believes positive infinity equals math.MaxFloat64
  • Detects the zero-over-zero result by comparing against math.NaN()