Fun with Numbers

Many programming languages - especially the "practical" ones - have their weird corner cases and inconsistencies when dealing with numbers as nicely pointed out by this xkcd comic.

This comes to a large extend from using numerical operators with non-numerical values (think + for String concatenation) and automatic coercion between numbers and Strings. These issues do not occur in Frege (nor in Haskell) where operators never work on a mixture of types.

No surprises with types
 1  +  1  -- Int plus
 1  + "1" -- Type error: "String is not an instance of Num"
"1" + []  -- Type error

However, there is some clever inference going on at compile time to make a programmer’s life easier. The type system would in principle not allow to write

1 + 1.0

because 1 is an Int and 1.0 is a Double and + requires both operands to be of the same type.

The compiler is smart enough to not insist on you writing 1.0 + 1.0 for this simple case of number literals. It sees what you meant by resolving the type constraints in the expression that 1 is used in. Let’s see another example that is a just a bit more advanced:

What type is this?
1 / 2

Again, 1 and 2 would be Int literals but the division operator is not defined for the Int type (or any other Integral type) in Frege. It requires a number type of the Real typeclass for both operands and Frege chooses the concrete type Double for you in this case (unlike Haskell, this default is fix). It follows:

1 / 2 == 0.5  -- type is Double
Note
This is very much unlike Java where 1/2 evaluates to 0.

This has some interesting consequences since the Java Double type handles many special cases in a nice manner.

Double is nice

The most common special case is division by zero. How is this handled in Frege?

Division by zero
1 / 0  -- Infinity
Warning
The String representation of a number is not always made up from digits only. Developers are usually aware that there are dots, minus, and the E character to consider but they often forget about Infinity and NaN (not a number).

And how do we calculate with Infinity? Well, when you add to infinity, the result is equally infinite. You can even add infinities to infinities. Division is not defined, though.

Calculating with Infinity
(1/0) + 1     -- Infinity
(1/0) * 3     -- Infinity
(1/0) + (1/0) -- Infinity
(1/0) * (1/0) -- Infinity
(1/0) / (1/0) -- NaN

And what do we gain from having Infinity as opposed to NaN? Well, with Infinity you have a chance of coming back to numbers that you can do normal calculations with:

Divide by Infinity
1 / (1/0)   -- 0.0

Wow, this is now definitely interesting and makes us ask for other corner cases of mathematics:

Corner cases
0 / 0       -- NaN
(1/0) / 0   -- Infinity
0 ^ 0       -- 1

This looks like we have violated some rules of mathematics.

Have we broken math?

In mathematics, division by zero is undefined. Likewise is division by infinity or zero to the power of zero.

However, computers calculate with approximations, they do applied numeric math at best. We use Math.PI and Math.E with limited precision. Heck, even square roots are only approximations:

The square of the root
import frege.prelude.Math
Math.sqrt 2 * Math.sqrt 2  -- 2.0000000000000004

And given that we have to deal with limited precision anyway, we can just as well take liberty to make operations like division by zero not blowing up our programs but behaving reasonably well within computational bounds.

results matching ""

    No results matching ""