Double the Beyoncé: Navigating Numbers in JavaScript

What happens when the numbers you’re working with are too big for JavaScript? How do numbers behave beyond JavaScript’s integer limit?

In a talk which promises to be more interesting than your typical high school math class, Meggan takes you down the rabbit hole that is numbers in JavaScript. We’ll explore how they’re implemented, how we can best write, format, convert & calculate them, and what to do when we’re forced to handle really big (or really small) numbers.

Lots of Beyonce gifs ahead #sorrynotsorry

We use numbers for a lot of things, but generally to…

  • Count
  • Measure
  • Label
  • Identify

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 8, , आठ

We have many notations: base 10, binary, octal, hex, scientific…

JavaScript follows the IEE-754 standard for floating point arithmetic. This is why 0.1 + 0.2 !== 0.3 in JS (and many other languages that do unexpected things due to floating point maths).

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.

Number.MAX_VALUE
Number.MIN_VALUE

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
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:

100n
BigInt(100)

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

…you get the numbers you expect! Yay! This is a really important new feature for JavaScript. Sadly though, browser support is some way off.

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
  • BigInt is coming!

@megganeturner