JavaScript's 64 bit Number type is perfectly adequate for most purposes but if you need very large numbers or more decimal places than it can represent then the math.js library's BigNumber type can come to your rescue. In this article I'll show the BigNumber type in action.
This project consists of an HTML file, a JavaScript file and a CSS file. These can be downloaded as a zip file or from the GitHub repository. I have also included the minimized version of the math.js library, math.min.js.
Source Code Links
The Limitations of the JavaScript Number Type
The JavaScript Number type is fine for most purposes both in terms of highest and lowest numbers, and maximum number of decimal places. However, it is restricted by how much you can squeeze into 64 bits so before looking at the main topic of this article, the math.js BigNumber, I'll briefly look at just what the JavaScript Number type's limits are.
JavaScript provides us with a couple of constants, Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER, as well as a method called Number.isSafeInteger which allows us to check if we think we might be stumbling into dangerous territory with very large numbers.
The first part of the JavaScript code that goes with this article shows these constants and method in use.
mathjsbignumber.js - part 1
"use strict" window.onload = function() { safeIntegers(); // BigInts(); // BigNumbers(); } function output(text) { const p = document.createElement("p"); p.innerHTML = text; document.body.appendChild(p); } function safeIntegers() { output("Safe Integers<br>-------------<br><br>"); output(`Number.MAX_SAFE_INTEGER = ${Number.MAX_SAFE_INTEGER}`); output(`Number.MIN_SAFE_INTEGER = ${Number.MIN_SAFE_INTEGER}`); const safePos = Math.pow(2, 53) - 1; const unsafePos = Math.pow(2, 53); const safeNeg = -(Math.pow(2, 53) - 1); const unsafeNeg = -(Math.pow(2, 53)); output("<br>"); output(`safePos = ${safePos}`); output(`Number.isSafeInteger(safePos) = ${Number.isSafeInteger(safePos)}`); output("<br>"); output(`unsafePos = ${unsafePos}`); output(`Number.isSafeInteger(unsafePos) = ${Number.isSafeInteger(unsafePos)}`); output("<br>"); output(`safeNeg = ${safeNeg}`); output(`Number.isSafeInteger(safeNeg) = ${Number.isSafeInteger(safeNeg)}`); output("<br>"); output(`unsafeNeg = ${unsafeNeg}`); output(`Number.isSafeInteger(unsafeNeg) = ${Number.isSafeInteger(unsafeNeg)}`); }
In onload there are three function calls, the second and third being commented out for the time being. After onload is a short function which writes text directly to the web page.
In safeIntegers we simply output Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER before creating a few consts to test for safety. Each of these is then output, followed by the result of calling Number.isSafeInteger with it. If you open mathjsbignumber.htm in your browser you'll see this.
Safe Integers
-------------
Number.MAX_SAFE_INTEGER = 9007199254740991
Number.MIN_SAFE_INTEGER = -9007199254740991
safePos = 9007199254740991
Number.isSafeInteger(safePos) = true
unsafePos = 9007199254740992
Number.isSafeInteger(unsafePos) = false
safeNeg = -9007199254740991
Number.isSafeInteger(safeNeg) = true
unsafeNeg = -9007199254740992
Number.isSafeInteger(unsafeNeg) = false
The JavaScript BigInt Type
JavaScript has its own partial solution to the problem of Number limits, the BigInt type. As its name implies it is an integer, not real or floating point type, and is not compatible with Math methods. It is therefore of limited usefulness but in the next method I'll briefly show it in use.
mathjsbignumber.js - part 2
function BigInts() { output("BigInt<br>------<br><br>"); const big1 = 9007199254740992n; // 9,007,199,254,740,992 const big2 = 9007199254740993n; // 9,007,199,254,740,993 output(`typeof(big1) = ${typeof(big1)}`); output(`<br>big1 = ${big1}`); output(`big2 = ${big2}`); const r = 3n / 2n; output(`<br>3n / 2n = ${3n / 2n}`); try { output("<br>Math.min(3n, 5n, 7n, 9n)"); output(Math.min(3n, 5n, 7n, 9n)); } catch (e) { output(e); } try { output("<br>Math.sign(64n)"); output(Math.sign(64n)); } catch (e) { output(e); } }
Firstly we create a couple of BigInts - this is done here by suffixing the number with n but you can also call the BigInt constructor, for example:
const bigNo = BigInt(9007199254740992);
Calling typeof on a BigInt returns bigint, and then the two consts are printed. Next we divide 3n by 2n just to demonstrate that this is an integer type; the result will be 1 with the decimal part being lost. Finally, just as a reminder that the Math methods don't work on BigInts I have tried it out with Math.min and Math.sign. These will both throw errors so these bits of code are in try/catch blocks.
If you scroll up to the onload function, comment out safeIntegers();, uncomment BigInts(); and refresh the web page you'll see this output.
BigInt ------ typeof(big1) = bigint big1 = 9007199254740992 big2 = 9007199254740993 3n / 2n = 1 Math.min(3n, 5n, 7n, 9n) TypeError: can't convert BigInt to number Math.sign(64n) TypeError: can't convert BigInt to number
It's interesting to see the error messages: the Math methods are attempting to convert the arguments to Number types but failing. This is just as well as they would lose precision if they were to do so.
I have to say I am not impressed with the BigInt type at all. It's a pretty lame effort with too many limitations to be taken seriously.
The Main Feature: the math.js BigNumber Type
The math.js library's BigNumber type can represent real or floating point numbers, and can be used with the library's methods. In the final function I will show it in use.
mathjsbignumber.js - part 3
function BigNumbers() { output("MathJS Big Numbers<br>==================<br><br>"); output(`Size of observable universe in kilometres: ${math.format(math.bignumber(880000000000000000000000000), {notation: 'fixed'})} (880,000,000,000,000,000,000,000,000)`); output(`<br>math.add(math.bignumber(10), math.bignumber(13)) = ${math.add(math.bignumber(10), math.bignumber(13))}`); output(`math.abs(math.bignumber(-128)) = ${math.abs(math.bignumber(-128))}`); output(`math.floor(math.bignumber(12.5589)) = ${math.floor(math.bignumber(12.5589))}`); output(`math.ceil(math.bignumber(12.5589)) = ${math.ceil(math.bignumber(12.5589))}`); output(`math.mod(math.bignumber(22), math.bignumber(10)) = ${math.mod(math.bignumber(22), math.bignumber(10))}`); output(`<br>math.sqrt(math.bignumber(2)) [default precision 64] = ${math.sqrt(math.bignumber(2))}`); math.config({ precision: 32 }); output(`math.sqrt(math.bignumber(2)) [precision 32] = ${math.sqrt(math.bignumber(2))}`); math.config({ precision: 128 }); output(`math.sqrt(math.bignumber(2)) [precision 128] = ${math.sqrt(math.bignumber(2))}`); const PlanckLength = math.bignumber(0.000000000000000000000000000000000016); output("<br>Planck Length<br>-------------"); output(PlanckLength); output(math.format(PlanckLength, {notation: 'fixed'})); const atomsInUniverse = math.bignumber(1e+80); output("<br>Atoms in universe<br>-----------------"); output(atomsInUniverse); output(math.format(atomsInUniverse, {notation: 'fixed'})); }
Firstly I have created a BigNumber with the size of observable universe in kilometres, and output the value with notation set to fixed; this will format the output in full rather than 8.8e+26. (Obviously light years would be a more sensible unit but this is just for demonstration purposes.)
Next we carry out a few operations on BigNumbers using math.js methods, something which you cannot do with JavaScript BigInts and Math methods, as we have just seen.
The next few lines of code demonstrate how to change the precision of BigNumbers, in this case with the square root of 2 which is an irrational number. The default is 64 significant digits but here I also use 32 and 128. Using BigNumbers is significantly slower than the Number type, becoming slower the more digits there are, so it's important to use only the precision you need. Note that even when set to 32 we still have about twice as many accurate decimal places than the Number type.
Finally I have created a couple of constants for actual very small and very large numbers. The first is the Planck length (in metres) which I won't attempt to describe here but you might like to read the Wikipedia article. Suffice to say it's very very very small!
The second number is the number of atoms in the Universe. The 1e+80 notation means 1080 and is an approximation. Obviously!
Comment out BigInts(); in onload, uncomment BigNumbers(); and reload the page. This is the result.
MathJS Big Numbers ================== Size of observable universe in kilometres: 880000000000000000000000000 (880,000,000,000,000,000,000,000,000) math.add(math.bignumber(10), math.bignumber(13)) = 23 math.abs(math.bignumber(-128)) = 128 math.floor(math.bignumber(12.5589)) = 12 math.ceil(math.bignumber(12.5589)) = 13 math.mod(math.bignumber(22), math.bignumber(10)) = 2 math.sqrt(math.bignumber(2)) [default precision 64] = 1.414213562373095048801688724209698078569671875376948073176679738 math.sqrt(math.bignumber(2)) [precision 32] = 1.4142135623730950488016887242097 math.sqrt(math.bignumber(2)) [precision 128] = 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248361 Planck Length ------------- 1.6e-35 0.000000000000000000000000000000000016 Atoms in universe ----------------- 1e+80 100000000000000000000000000000000000000000000000000000000000000000000000000000000
This article and source code has only provided an introduction to the topic with a few examples, but should get you up and running. This is a summary of using the math.js library's BigNumber type.
- Create a BigNumber with math.bignumber(880000000000000000000000000) or math.bignumber(1e+80)
- Set the number of significant digits to the lowest you need for optimum efficiency
- Use any of the math.js library's methods with BigNumbers: they will recognize when you pass BigNumber arguments and will return a BigNumber
- Use math.format . . . {notation: 'fixed'} if you want to see all the digits rather than something like 8.8e+26
External Resources
MAX_SAFE_INTEGER documentation on Mozilla
BigInt documentation on Mozilla
math.js library functions documentation