Programming involves a really upsetting amount of numbers. We are perpetually finding ourselves dealing with numbers in the sorts of programs that seemingly have no just cause to expose us to arithmetic tedium. The greatest redeeming quality of numbers is the sense of possibility conveyed by the unlimited expanse of the number line – But machines mute even this glimmer of hope, offering instead a dim and claustrophobic view.
Here’s the thing: We treat our numbers in a most cruel manner, packing them into dark corners, shoving them through nanoscopically thin wires. They live in tiny boxes, because in computing, small is fast. And under these conditions, our numbers cannot handle much stress before they start bouncing off the walls.
Try starting with an
Int64 of 100 and repeatedly square it. You won’t get far.See Introduction to GHCi for a discussion of
λ> import Data.Int λ> 100 :: Int64 100 λ> it ^ 2 10000 λ> it ^ 2 100000000 λ> it ^ 2 10000000000000000 λ> it ^ 2 -8814407033341083648
Int64 is not a type for unbounded possibilities, not a realm in which to explore the vast glory of mathematics.
Int64 offers efficient and uninspiring service for tasks such as… counting.
λ> 1 :: Int64 1 λ> it + 1 2 λ> it + 1 3 λ> it + 1 4 λ> it + 1 5
For this sort of thing,
Int64 will pretty much stay in its lane. It takes a while to plus-one your way out of an
We do have an unbounded type! It’s called
Integer.Mnemonic: The type with the longer name can hold bigger numbers. Just as ‘Int’ is an abbrevation for ‘Integer’, types with ‘Int’ in their names are “abbreviated” notions of integerness. The
Integer type will neither give you up nor let you down.
λ> 100 :: Integer 100 λ> it ^ 2 10000 λ> it ^ 2 100000000 λ> it ^ 2 10000000000000000 λ> it ^ 2 10000000000000000000000000 0000000 λ> it ^ 2 10000000000000000000000000 00000000000000000000000000 0000000000000 λ> it ^ 2 10000000000000000000000000 00000000000000000000000000 00000000000000000000000000 000000000000000000000000000000000000000000000000000
It’s so nice, it makes you want to move to Integer Land, buy a little house there, plant some fruit trees, spend your evenings on the porch with a glass of wine looking out over the –
Snap out of it, you’re daydreaming! You’re not going to get Integers all the time, it’s just not realistic. In fact, it’s only going to get worse.
Do you know what the upper bound on
Int64 is? We said it was small, but that’s only in comparison to infinity. Really, it’s kind of an absurdly high number.
λ> maxBound :: Int64 9223372036854775807
A lot of situations that call for a number are situations where we know that the number won’t be nearly that high. Say you’ve got a cheap digital room thermometer that reports temperatures in whole degrees Fahrenheit. Maybe it’s going to give you an
Int16. That’s way smaller than
Int64, but this bad boy can still fit so many temperatures.
λ> minBound :: Int16 -32768 λ> maxBound :: Int16 32767
Programmers, hardware builders, network protocol designers – they’re all going to give you numbers, and they’re all going to require numbers from you. And here’s the snag: Because everybody is putting their numbers into the smallest box they can get away with, and because how small a box you can get away with depends on exactly what you’re doing, sooner or later you’re going to end up with a square peg and a round hole.
“We gotta find a way to make this fit into the hole for this” – from Apollo 13 (1995)
It is astonishing how casually we often see numbers abused as they are shoved from one box to another. The
fromIntegral function in the
Prelude module is one example of how numbers can get hurt.
fromIntegral :: (Integral a, Num b) => a -> b
This is an extremely polymorphic function – Both its input and output are type variables. So we’re going to need some type annotations in our demonstration, to ensure that the compiler knows what types we’re converting to and from.
Here’s how it works on a good day:
λ> fromIntegral (1243 :: Int64) :: Int16 1243
A number goes in, the same number comes out in new clothes as a different type. But what happens when the number doesn’t fit into its new clothes? Nothing good, I’ll tell you.
λ> fromIntegral (475891 :: Int64) :: Int16 17139 λ> fromIntegral (-1243 :: Int64) :: Word64 18446744073709550373
Don’t treat your numbers like this!
Generally the best tool for this kind of work is a function called
toIntegralSized :: Integral a, Bits a, ( Integral b, Bits b ) => -> Maybe b a
This function comes from a rather obscure place at the bottom of the
Data.Bits module in the
base package. You can also
Relude.Numeric find it in the
relude package, where it is included in the
Relude module and featured more prominently in the
λ> toIntegralSized (1243 :: Int64) :: Maybe Int16 Just 1243 λ> toIntegralSized (475891 :: Int64) :: Maybe Int16 Nothing
The output type is
Maybe because a reasonable conversion from one type of number to another is not always possible, owing to each type’s different boundaries. As the example above illustrates, if an
Int64 value is too large, an attempt to transfer it into a smaller
Int16 box may yield
Likewise, a negative integer will fail to convert to any “word” type; such things are only for non-negative numbers.
λ> import Data.Word λ> toIntegralSized (1243 :: Int64) :: Maybe Word64 Just 1243 λ> toIntegralSized (-1243 :: Int64) :: Maybe Word64 Nothing
toIntegralSized at your disposal, you can confidently tackle those pesky integer conversion tasks and shift your numbers around into whatever format is required of them. It doesn’t remove all your need to think, because you still have that
Nothing possibility to deal with, but you have an extremely versatile conversion function that never produces erroneous off-the-wall results.