— Diana MacDonald (@didoesdigital) June 20, 2019
Lots of Beyonce gifs ahead
We use numbers for a lot of things, but generally to…
There are many, many different types of number…
- Natural numbers (positive integers)
- Integers (negative and positive whole numbers)
- Rational numbers
- Irrational numbers
- Complex numbers
- Transcendental numbers
- Infinity and negative infinity
- Real numbers – a superset of all possible numbers
- …and many more
We can communicate numbers in many different ways. We can say eight and people may think of
— Klee Thomas (@kleeut) June 20, 2019
We have many notations: base 10, binary, octal, hex, scientific…
This limits the range we can display, although it’s in the quadrillions so generally not a big issue. We also consider precision, JS goes to 17 decimal points.
Everything on computers ends up in binary, which can’t do decimals. Floating point arithmetic has to work around this by encoding numbers. You can break down a 64 bit binary number into its components, the Sign (1 bit), Exponent (11 bits) and Significand precision (53 bits, 52 explicitly stored).
Fractions are tricky. 1/3 is a recurring decimal 0.33333…… so we round it. And you know this is painful when you are trying to create a three-column layout…
How big an issue is this? Depends what the number relates to. The rounding error if you were measuring a light year with 0.00000000000000004ly margin for error, that’s about as big as a bowling pin. In most applications it just isn’t big enough to be an issue; but in something like banking it can become a really big problem very quickly.
Range: size does matter. We make a trade off between range and precision.
If you type
1.7e308 into your JS console, you get
1.7e+308; but if you type in
1.8e308 you get infinity.
We have names for big numbers going up to centillion (a number followed by 303 zeros), and JS goes beyond that. It’s a massive range but we can’t display them all safely.
If you try…
Number.MAX_SAFE_INTEGER + 1
Number.MAX_SAFE_INTEGER + 2
Number.MAX_SAFE_INTEGER + 3
Number.MAX_SAFE_INTEGER + 4
…this will not do what you would expect! You get:
9007199254740991 9007199254740992 9007199254740992 9007199254740994 9007199254740996
Meggan ran into this problem while working on a very large music database at Jaxta. There was an error where the main artist on a listing was coming up as the featured artist. Which wasn’t right, even if Meggan does contend that Double Beyonce isn’t a bug!
The core of the problem was that two artist resources were coming back with the same ID. How was this possible? The actual IDs were returned as different numbers from the back end; but since they were beyond MAX_SAFE_INTEGER, JS was turning them into the same number. Solution was to move IDs to a 128-bit UUID strings.
So, long term what can JS do about this? Enter
BigInt. A new numeric primitive for JS, allowing representation of numbers beyond
MAX_SAFE_INTEGER. This can be expressed with
n or by explicit cast:
We can do arithmetic with BigInts, noting it will still round to whole integers. Can’t do mixed type operations, although we can compare
0n == 0 and sort mixed numbers.
Plus if you try this…
let bigNumber = BigInt(Number.MAX_SAFE_INTEGER)
bigNumber + 1n
bigNumber + 2n
bigNumber + 3n
bigNumber + 4n
bigNumber + 5n
Numbers in JS can be confusing, but when you understand why they were implemented that way it’s easier to figure out the bugs.
Things to look out for:
- we only have 64 bits, so round numbers
- remember MAX_SAFE_INTEGER