Not a Number: a quiet horror?

I’d love to hear your thoughts about this article that I wrote today, in reply to @joeerl’s earlier tweet. :slight_smile:

5 Likes

Nice article -

I was in the middle of writing a lecture for next week and stumbled over
a talk called “Beyond Floating Point: Next generation computer
arithmetic” by John Gustafson

Well worth watching

It reminded me that floats are a mess, for example
Arithmetic is NOT associative (over floats)

ie A + (B+C) /= (A+B) + C

for example:

$ iex
iex(1)> a = 1 + (2+3)
6
iex(2)> b = (1+2) + 3
6
iex(3)> a == b
true

But oh dear

iex(4)> a = 0.1 + (0.2 + 0.3)
0.6
iex(5)> b = (0.1 + 0.2) + 0.3
0.6000000000000001
iex(6)> a == b
false

As regards quiet NaN - here’s some stupid JS code

a = 1/0;
b = a/a;
c = a + b * 100;
d = c*1234*b;
console.log('js is wonderful');
// more nonsense
e = 34*b+d/a;
console.log('and e = ' + e);

If we run this in node we see this:

$ node silent2.js
js is wonderful
and e = NaN

The rot sets in line 1

a = 1/0

When I was at school my math teacher said “you can’t divide by zero”
so by my mind this is nonsense and the program should crash immediately.

Js thinks this is ‘Infinity’ - now we say

b = a/a

Which Js thinks is NaN (not a number) (and quite right too)

c = a + b * 100 = NaN

(Yes - rubbish in rubbish out - but we don’t know yet)

We first get a clue that something is wrong at the end of the program
when we print e - but we haven’t a clue where the error occurred.

Just for fun try this in C, C++, Haskell, python, ruby etc.
(I did) - the statically compiled languages language all get this wrong
(ie behave like JS) - the dynamic language are far better.

There’s a deeper reason why C etc. try not to crash and use a few bits
in the float to represent a NaN - this is because in sequential languages with a single thread if your thread crashes all is lost. In Erlang/Elixir we have
loads of processes to play with so if a few crash we don’t care.

My view is Erlang/elixir should crash immediately if anything goes wrong
and let some other linked process clean up.

In the processor after any arithmetic instruction a number of condition flags
get set (indicating overflow etc.) it is absurd that these are ingnored
by many compiled languages - incorrectly handling arithmetic errors has lead
to many software disasters …

Cheers

/Joe

8 Likes

In my pay-the bills language (ILE-RPG), a=1/0 won’t even compile (regardless of whether the 0 is a literal or a constant). If you try to divide by a 0 variable at runtime, it crashes.

Seems like reasonable behavior to me.

2 Likes

Thanks a lot for your reply, @joeerl!
I have yet to watch the video (it is very late here now; I’ll watch it and create a longer reply tomorrow).

An important thing to note is that IEEE 754 floats do not implement simple arithmetic with normal real numbers, but rather the arithmetic of working with limits(Called, if I am not mistaken, Differential Calculus):

Division by zero indeed is not possible. But approaching zero from the positive or negative side by moving x arbitrarily close to zero is. And this is why Infinity and negative Infinity exist. Of course, in the mathematical sense you cannot say x = Infinity but only x -> Infinity, but in programming language land, this subtle distinction has been lost.
For many common functions in calculus, it makes sense that Infinity can both be an output of a function, as well as sometimes an input.

I think the main reason why ‘all these languages get this wrong’ is that they all are built around the same standard (and therefore are able to communicate more easily). I do want to note that at least for Ruby and Python the behaviour is exactly the same as it is for C, C++, Haskell, even Julia and Rust. Lua also. Less surprising might be that Go and Java (and with it most JVM-targetting languages) also fall in this group. Keep in mind that most unityped languages require 0.0 to catch up on the fact that something is a float rather than an integer.

1 Like

I think the question should be, if a float is really a good abstraction for most programs nowadays. I’m personally convinced a rational type would be much more useful for a vast majority of applications. The rational type should feature exact calculation and indeed fail as soon as anything goes wrong. Floats should be a “fringe” tool you only use where you need “lolspeed” - and then you sacrifice correctness for this speed and accept infinities, nans and all the other nasty stuff.

7 Likes

I completely agree with this.

3 Likes

but 140 (or was it 256)

280 now. ^.^;

Good article though, but I think it’d be more descriptive if it had some examples of how and why floats are bad, especially with NaN’s, rather than just text. :slight_smile:

Ooo, added to my Watch Later list. ^.^

Rust handles this all a lot better than most languages, as you specify what you want to happen, like if you don’t want integer to overflow but instead error, or return an option result, or allow overflow, or or or…

Exactly this, or fixed-point (I love fixed-point, especially with operators that return ‘lost’ parts that you have to handle)!

2 Likes

If you are in danger of NaN you should probably not want ‘Float’ rather ‘Maybe Float’.

Rationals are also a mostly good idea - amazing that they are not more widely used - probably due to historical performance worries…

So a ‘Maybe Rational’ type is probably best for most purposes.

2 Likes