Float special
An integer has nowhere to go past its limits: divide one by zero and the program stops. A float is different. It has a few values that are not ordinary numbers at all, and arithmetic that runs off the edge lands on one of them and carries on:
- Infinity,
Inf, when a result is too big to represent — or a non-zero number is divided by zero. - NaN, "not a number", when a result has no sensible value at all, such as zero divided by zero.
Neither is an error. The special value simply flows into everything computed from it. This lesson shows where they come from, and the one rule about them that catches everybody: how NaN compares.
Where the special values come from
let zero = 0.0;
PrintLine("1 / 0 {}", 1.0 / zero);
PrintLine("-1 / 0 {}", -1.0 / zero);
PrintLine("0 / 0 {}", zero / zero);
PrintLine("Max * 2 {}", float64::Max * 2.0);
PrintLine("Inf - Inf {}", float64::Infinity - float64::Infinity);
| Expression | Result | Why |
|---|---|---|
1.0 / 0.0 | Inf | The quotient grows without bound |
-1.0 / 0.0 | -Inf | The same, below zero |
0.0 / 0.0 | NaN | No single answer makes sense |
float64::Max * 2.0 | Inf | Too large to represent: it overflows upwards |
Inf - Inf | NaN | Again, no single answer |
Both values are also available by name, float64::Infinity and float64::NaN, from Core. Formatting with a precision leaves them alone — there are no digits to round — so {:.3} still prints Inf and NaN.
Compare an integer: 10 / n with a zero n stops the program with Panic: division by zero and the line it happened on. Integers have no special values to fall back on.
Infinity compares like a number
Infinity is larger than every finite number, and equal to itself:
let inf = float64::Infinity;
PrintLine("Inf > Max {}", inf > float64::Max);
PrintLine("Inf == Inf {}", inf == inf);
Both lines print true. No surprises there.
NaN compares false with everything
NaN is unordered. Every comparison with it is false — even with itself — except !=, which is true:
let nan = float64::NaN;
PrintLine("NaN == NaN {}", nan == nan);
PrintLine("NaN != NaN {}", nan != nan);
PrintLine("NaN < 1 {}", nan < 1.0);
PrintLine("NaN > 1 {}", nan > 1.0);
NaN is neither less than 1 nor greater than it, nor equal to anything. The rule makes NaN spread instead of hiding: a test such as if total > limit cannot be fooled into true by a NaN total. But it also means a test written as x == float64::NaN can never succeed.
Asking what a value is
The reliable way to ask is a function from Core:
PrintLine("IsNaN {}", IsNaN(nan));
PrintLine("IsInfinite {}", IsInfinite(inf));
PrintLine("IsFinite {}", IsFinite(float64::Max));
flowchart LR
x["A float64 value"] --> n{"IsNaN?"}
n -- "yes" --> isnan["NaN"]
n -- "no" --> i{"IsInfinite?"}
i -- "yes" --> isinf["Inf or -Inf"]
i -- "no" --> fin["An ordinary number:<br/>IsFinite is true"]IsFinite is true exactly when the value is neither NaN nor an infinity — the right check before you use a result as an ordinary number.
Two zeros
Zero has a sign too. -0.0 and 0.0 compare equal, yet dividing by them gives infinities of opposite signs:
let negativeZero = -0.0;
PrintLine("-0 == 0 {}", negativeZero == zero);
PrintLine("1 / -0 {}", 1.0 / negativeZero);
The first line prints true, the second -Inf. A negative zero turns up when a tiny negative result rounds to zero; it remembers which side it came from.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A float does not stop at its largest number. When a result is too big to represent, it becomes
// infinity, `Inf`; when a result has no sensible value at all, such as zero divided by zero, it
// becomes NaN, "not a number". Neither is an error: the arithmetic carries on and the special
// value flows into everything computed from it. Integers have neither, which is why an integer
// division by zero stops the program with "Panic: division by zero" and the line it happened on,
// while a float division by zero carries on.
//
// The surprise is in comparison. Infinity compares as you would expect, larger than every finite
// number. NaN compares false with everything, including itself, so `nan == nan` is `false` and
// `nan != nan` is `true`. A test written as `x == float64::NaN` can therefore never succeed; ask
// `Core::IsNaN` instead. Zero has a sign too: `-0.0 == 0.0`, yet dividing by them gives
// infinities of opposite signs.
import Core::{ IsFinite, IsInfinite, IsNaN, float64 };
import Io::PrintLine;
func Main() -> int {
let zero = 0.0;
// Where the special values come from.
PrintLine("1 / 0 {}", 1.0 / zero);
PrintLine("-1 / 0 {}", -1.0 / zero);
PrintLine("0 / 0 {}", zero / zero);
PrintLine("Max * 2 {}", float64::Max * 2.0);
PrintLine("Inf - Inf {}", float64::Infinity - float64::Infinity);
// A precision has no digits to round here, so the words come out unchanged.
PrintLine("rounded {:.3} {:.3}", 1.0 / zero, zero / zero);
// Infinity is ordered like a number.
let inf = float64::Infinity;
PrintLine("Inf > Max {}", inf > float64::Max);
PrintLine("Inf == Inf {}", inf == inf);
// NaN is unordered: every comparison with it is false, except `!=`.
let nan = float64::NaN;
PrintLine("NaN == NaN {}", nan == nan);
PrintLine("NaN != NaN {}", nan != nan);
PrintLine("NaN < 1 {}", nan < 1.0);
PrintLine("NaN > 1 {}", nan > 1.0);
// The reliable way to ask what a value is.
PrintLine("IsNaN {}", IsNaN(nan));
PrintLine("IsInfinite {}", IsInfinite(inf));
PrintLine("IsFinite {}", IsFinite(float64::Max));
// Two zeros that are equal and still different.
let negativeZero = -0.0;
PrintLine("-0 == 0 {}", negativeZero == zero);
PrintLine("1 / -0 {}", 1.0 / negativeZero);
return 0;
}
Besides Io, its Rux.toml lists Core under [Dependencies].
Run it
cd Examples/Numbers/FloatSpecial
rux run
1 / 0 Inf
-1 / 0 -Inf
0 / 0 NaN
Max * 2 Inf
Inf - Inf NaN
rounded Inf NaN
Inf > Max true
Inf == Inf true
NaN == NaN false
NaN != NaN true
NaN < 1 false
NaN > 1 false
IsNaN true
IsInfinite true
IsFinite true
-0 == 0 true
1 / -0 -Inf
Common mistakes
==.x == float64::NaN is always false, even when x is NaN — the compiler accepts it, and it simply never matches. Use IsNaN(x).1.0 / 0.0 is Inf, and the program carries on with it. Only an integer division by zero stops the program. Check a divisor that might be zero, or check the result with IsFinite.as never fails, so it has to produce something: NaN as int32 is 0, and Inf as int32 saturates to 2147483647. Neither says anything went wrong. Checked convert reports it.Try it yourself
- Compute
float32::Max * 2.0f32. Is afloat32infinity printed any differently? - Start from
let nan = zero / zero;and work outnan + 1.0,nan * 0.0andfloat64::Infinity * 0.0. Which are NaN? - Write
func SafeDivide(a: float64, b: float64) -> float64?that returnsnonewhen the result is not finite. - Import
IsNegativeZerofromCoreand use it to tell-0.0from0.0, which==cannot.
Learn more
- float64 in the Rux Reference
- Float — the float types and their precision
- Number limit —
Max,LowestandEpsilon - Math — functions whose answers outside their domain are NaN and infinities