DEV Community

Discussion on: Explain BigInt Like I'm Five

Collapse
ikirker profile image
Ian Kirker

Floating Point Numbers

So, computers can represent numbers in a variety of ways. Anything works, as long as the computer knows how to do the things it needs to do to the numbers, like add them together, or print them out.

JavaScript normally represents all numbers as "floating-point numbers". This means that it stores a value between -2 and +2 (the mantissa), and then a number that it uses as a power of two to multiply that by (the exponent).

The useful thing about this is that it can represent a big range of numbers, but the accuracy it can store them at depends on how big the numbers are.

If you keep adding 1 to a number and you're using this type of representation, eventually you'll find that the 1 you're adding is too small to make a difference to the representation value!

This has caused bugs in the past, because the underlying system JavaScript is running on has another type of number that it uses for some important things: integers. These can't represent as big a range as floating-point numbers using the same number of memory bits, but always have the same level of accuracy: 1. If you add 1 to an integer it will go up by 1, and you can't add a fraction to it.

One notable example was when node.js had some problems because the integer labels for files stored on disk were too big to store accurately with floating-point numbers, so node.js was accessing the wrong files...

Overflows

When you want to store a number on a computer, you reserve a number of memory bits. You can only store a number of a certain size in that number of bits, and if you try to go over, you either get an exception, where the computer notices and tries to stop what you're doing, or you get a special-cased value, where the computer notices and puts a special type of value in the memory, or the numbers wrap around, losing some information, and getting an incorrect value.

These can each cause their own class of problems, but in any case, you don't want them to happen.

BigInt

To let you use accurate, whole numbers as large as you want, you need a special adjustable type of representation: one where if you need to store a larger number, a hidden layer asks for more memory to store it in, then keeps track of how many bits of memory and how many digits your large number has.

So, for example, if I want to store the number 384.

As a floating-point number, JavaScript's normal type, we store:


{ 1.5 times 2 to the power of 8 }

As a normal integer, we would store:

{ binary number: 110000000 }

As a (decimal version of a) BigInt, we could store:

{ 2 chunks of digits, they are: 3, and 84}.

But because they'd be binary numbers, it would work out as:

{ 2 chunks of digits, they are binary numbers: 1, and 10000000 }

(384 is 256 + 128).

So then, you also have to have a library of special ways to do arithmetic that know about how you've written these numbers.

If I want to multiply 12 by 16, I have to do something like:


{1 chunk of digits, it is: 12} * {1 chunk of digits, it is: 16}

I will need at least one chunk to store the result, so let's request that.

I know how to multiply two one-chunk numbers, that is: 192

This number is too big to fit in one chunk: I need an extra chunk to store the result, so request an extra chunk of memory.


Result is {2 chunks of digits, they are: 1, and 92}.

Result

This is a bit slower than using normal maths, but lets you handle numbers of any size: you can just keep requesting more chunks of memory to store them in. You still can't handle fractions, but you can pretend that all your numbers are only worth 100th of what they say they are, to get around that problem. (Fixed-point arithmetic.)

The BigInt library adds on a way to write these multi-chunk numbers, and a library of ways to work on them.