BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can’t safely exceed
(253-1)or be less than
-(253-1), as we mentioned earlier in the chapter Data types. As bigints are used in few special areas, we devote them a special chapter BigInt.
So here we’ll talk about regular numbers. Let’s expand our knowledge of them.
Imagine we need to write 1 billion. The obvious way is:
let billion = 1000000000;
We also can use underscore
_ as the separator:
let billion = 1_000_000_000;
Here the underscore
_ between digits, so it’s exactly the same one billion as above.
In real life though, we try to avoid writing long sequences of zeroes. We’re too lazy for that. We’ll try to write something like
"1bn" for a billion or
"7.3bn" for 7 billion 300 million. The same is true for most large numbers.
"e" to it and specifying the zeroes count:
In other words,
e multiplies the number by
1 with the given zeroes count.
1e3 === 1 * 1000; // e3 means *1000 1.23e6 === 1.23 * 1000000; // e6 means *1000000
Now let’s write something very small. Say, 1 microsecond (one millionth of a second):
let mсs = 0.000001;
Just like before, using
"e" can help. If we’d like to avoid writing the zeroes explicitly, we could write the same as:
let mcs = 1e-6; // five zeroes to the left from 1
If we count the zeroes in
0.000001, there are 6 of them. So naturally it’s
In other words, a negative number after
"e" means a division by 1 with the given number of zeroes:
// -3 divides by 1 with 3 zeroes 1e-3 === 1 / 1000; // 0.001 // -6 divides by 1 with 6 zeroes 1.23e-6 === 1.23 / 1000000; // 0.00000123 // an example with a bigger number 1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times
0x and then the number.
Binary and octal numeral systems are rarely used, but also supported using the
There are only 3 numeral systems with such support. For other numeral systems, we should use the function
parseInt (which we will see later in this chapter).
num.toString(base) returns a string representation of
num in the numeral system with the given
base can vary from
36. By default it’s
Common use cases for this are:
base=16 is used for hex colors, character encodings etc, digits can be
base=2 is mostly for debugging bitwise operations, digits can be
base=36 is the maximum, digits can be
A..Z. The whole latin alphabet is used to represent a number. A funny, but useful case for
36is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base
Please note that two dots in
123456..toString(36) is not a typo. If we want to call a method directly on a number, like
toString in the example above, then we need to place two dots
.. after it.
If we placed a single dot:
Also could write
One of the most used operations when working with numbers is rounding.
There are several built-in functions for rounding:
- Rounds down:
- Rounds up:
- Rounds to the nearest integer:
4, the middle case:
3.5rounds up to
Math.trunc(not supported by Internet Explorer)
- Removes anything after the decimal point without rounding:
Here’s the table to summarize the differences between them:
These functions cover all of the possible ways to deal with the decimal part of a number. But what if we’d like to round the number to
n-th digit after the decimal?
For instance, we have
1.2345 and want to round it to 2 digits, getting only
There are two ways to do so:
For example, to round the number to the 2nd digit after the decimal, we can multiply the number by
100, call the rounding function and then divide it back.
The method toFixed(n) rounds the number to
ndigits after the point and returns a string representation of the result.
This rounds up or down to the nearest value, similar to
Please note that the result of
toFixedis a string. If the decimal part is shorter than required, zeroes are appended to the end:
We can convert it to a number using the unary plus or a
Number()call, e.g write
Internally, a number is represented in 64-bit format IEEE-754, so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign.
If a number is really huge, it may overflow the 64-bit storage and become a special numeric value
What may be a little less obvious, but happens quite often, is the loss of precision.
Consider this (falsy!) equality test:
That’s right, if we check whether the sum of
0.3, we get
Strange! What is it then if not
Ouch! Imagine you’re making an e-shopping site and the visitor puts
$0.20 goods into their cart. The order total will be
$0.30000000000000004. That would surprise anyone.
But why does this happen?
A number is stored in memory in its binary form, a sequence of bits – ones and zeroes. But fractions like
0.2 that look simple in the decimal numeric system are actually unending fractions in their binary form.
0.1? It is one divided by ten
1/10, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third:
1/3. It becomes an endless fraction
So, division by powers
10 is guaranteed to work well in the decimal system, but division by
3 is not. For the same reason, in the binary numeral system, the division by powers of
2 is guaranteed to work, but
1/10 becomes an endless binary fraction.
There’s just no way to store exactly 0.1 or exactly 0.2 using the binary system, just like there is no way to store one-third as a decimal fraction.
The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don’t allow us to see that “tiny precision loss”, but it exists.
We can see this in action:
And when we sum two numbers, their “precision losses” add up.
0.1 + 0.2 is not exactly
The same issue exists in many other programming languages.
PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format.
Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method toFixed(n):
Please note that
toFixed always returns a string. It ensures that it has 2 digits after the decimal point. That’s actually convenient if we have an e-shopping and need to show
$0.30. For other cases, we can use the unary plus to coerce it into a number:
We also can temporarily multiply the numbers by 100 (or a bigger number) to turn them into integers, do the maths, and then divide back. Then, as we’re doing maths with integers, the error somewhat decreases, but we still get it on division:
So, multiply/divide approach reduces the error, but doesn’t remove it totally.
Sometimes we could try to evade fractions at all. Like if we’re dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut “tails” when needed.
Try running this:
This suffers from the same issue: a loss of precision. There are 64 bits for the number, 52 of them can be used to store digits, but that’s not enough. So the least significant digits disappear.
Another funny consequence of the internal representation of numbers is the existence of two zeroes:
That’s because a sign is represented by a single bit, so it can be set or not set for any number including a zero.
In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
Remember these two special numeric values?
-Infinity) is a special numeric value that is greater (less) than anything.
NaNrepresents an error.
They belong to the type
number, but are not “normal” numbers, so there are special functions to check for them:
isNaN(value)converts its argument to a number and then tests it for being
But do we need this function? Can’t we just use the comparison
=== NaN? Unfortunately not. The value
NaNis unique in that it does not equal anything, including itself:
isFinite(value)converts its argument to a number and returns
trueif it’s a regular number, not
isFinite is used to validate whether a string value is a regular number:
Please note that an empty or a space-only string is treated as
0 in all numeric functions including
Number.isNaN and Number.isFinite methods are the more “strict” versions of
isFinite functions. They do not autoconvert their argument into a number, but check if it belongs to the
number type instead.
trueif the argument belongs to the
numbertype and it is
NaN. In any other case it returns
alert( Number.isNaN(NaN) ); // true alert( Number.isNaN("str" / 2) ); // true // Note the difference: alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion
trueif the argument belongs to the
numbertype and it is not
NaN/Infinity/-Infinity. In any other case it returns
alert( Number.isFinite(123) ); // true alert( Number.isFinite(Infinity) ); // false alert( Number.isFinite(2 / 0) ); // false // Note the difference: alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123
In a way,
Number.isFinite are simpler and more straightforward than
isFinite functions. In practice though,
isFinite are mostly used, as they’re shorter to write.
There is a special built-in method
Object.is that compares values like
===, but is more reliable for two edge cases:
- It works with
Object.is(NaN, NaN) === true, that’s a good thing.
Object.is(0, -0) === false, technically that’s correct, because internally the number has a sign bit that may be different even if all other bits are zeroes.
In all other cases,
Object.is(a, b) is the same as
a === b.
Object.is (internally called SameValue).
Numeric conversion using a plus
Number() is strict. If a value is not exactly a number, it fails:
The sole exception is spaces at the beginning or at the end of the string, as they are ignored.
But in real life we often have values in units, like
"12pt" in CSS. Also in many countries the currency symbol goes after the amount, so we have
"19€" and would like to extract a numeric value out of that.
parseFloat are for.
They “read” a number from a string until they can’t. In case of an error, the gathered number is returned. The function
parseInt returns an integer, whilst
parseFloat will return a floating-point number:
There are situations when
parseInt/parseFloat will return
NaN. It happens when no digits could be read:
parseInt() function has an optional second parameter. It specifies the base of the numeral system, so
parseInt can also parse strings of hex numbers, binary numbers and so on:
A few examples:
Returns a random number from 0 to 1 (not including 1).
Math.max(a, b, c...)and
Math.min(a, b, c...)
Returns the greatest and smallest from the arbitrary number of arguments.
nraised to the given power.
There are more functions and constants in
Math object, including trigonometry, which you can find in the docs for the Math object.
To write numbers with many zeroes:
"e"with the zeroes count to the number. Like:
123e6is the same as
123with 6 zeroes
- A negative number after
"e"causes the number to be divided by 1 with given zeroes. E.g.
For different numeral systems:
- Can write numbers directly in hex (
0x), octal (
0o) and binary (
parseInt(str, base)parses the string
strinto an integer in numeral system with given
2 ≤ base ≤ 36.
num.toString(base)converts a number to a string in the numeral system with the given
For regular number tests:
isNaN(value)converts its argument to a number and then tests it for being
Number.isNaN(value)checks whether its argument belongs to the
numbertype, and if so, tests it for being
isFinite(value)converts its argument to a number and then tests it for not being
Number.isFinite(value)checks whether its argument belongs to the
numbertype, and if so, tests it for not being
For converting values like
100px to a number:
parseInt/parseFloatfor the “soft” conversion, which reads a number from a string and then returns the value they could read before the error.
- Round using
- Make sure to remember there’s a loss of precision when working with fractions.
More mathematical functions:
- See the Math object when you need them. The library is very small, but can cover basic needs.