Let's recap and expand upon what we currently know about them.
More ways to write a number
Imagine we need to write 1 billion. The obvious way is:
let billion = 1000000000;
But in real life we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like
"1bn" for a billion or
"7.3bn" for 7 billion 300 million. The same is true for most large numbers.
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes alert( 7.3e9 ); // 7.3 billions (7,300,000,000)
In other words,
"e" multiplies the number by
1 with the given zeroes count.
1e3 = 1 * 1000 1.23e6 = 1.23 * 1000000
Now let's write something very small. Say, 1 microsecond (one millionth of a second):
let ms = 0.000001;
Just like before, using
"e" can help. If we'd like to avoid writing the zeroes explicitly, we could say:
let ms = 1e-6; // six zeroes to the left from 1
If we count the zeroes in
0.000001, there are 6 of them. So naturally it's
// -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)
Hex, binary and octal numbers
alert( 0xff ); // 255 alert( 0xFF ); // 255 (the same, case doesn't matter)
Binary and octal numeral systems are rarely used, but also supported using the
let a = 0b11111111; // binary form of 255 let b = 0o377; // octal form of 255 alert( a == b ); // true, the same number 255 at both sides
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
let num = 255; alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111
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
alert( 123456..toString(36) ); // 2n9c
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
toStringin the example above, then we need to place two dots
..after it. If we placed a single dot:
One of the most used operations when working with numbers is rounding. There are several built-in functions for rounding:
Math.floor : Rounds down:
Math.ceil : Rounds up:
Math.round : Rounds to the nearest integer:
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:
For instance, we have
1.2345 and want to round it to 2 digits, getting only
There are two ways to do so:
let num = 1.23456; alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
let num = 12.34; alert( num.toFixed(1) ); // "12.3"
This rounds up or down to the nearest value, similar to
let num = 12.36; alert( num.toFixed(1) ); // "12.4"
Please note that result of
toFixedis a string. If the decimal part is shorter than required, zeroes are appended to the end:
let num = 12.34; alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
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 (they are zero for integer numbers), and 1 bit is for the sign.
If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:
alert( 1e500 ); // Infinity
What may be a little less obvious, but happens quite often, is the loss of precision.
Consider this (falsy!) test:
alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*
That's right, if we check whether the sum of
0.3, we get
Strange! What is it then if not
alert( 0.1 + 0.2 ); // 0.30000000000000004
Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts
$0.20 goods into their chart. 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 ones and zeroes. But fractions like
0.2 that look simple in the decimal numeric system are actually unending fractions in their binary form.
In other words, what is
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", so the number shows up as
0.3. But beware, the loss still exists.
We can see this in action:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
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, there're a number of ways:
We can round the result with the help of a method toFixed(n):
let sum = 0.1 + 0.2; alert( sum.toFixed(2) ); // 0.30
Please note that
toFixedalways 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:
let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3
We can temporarily turn numbers into integers for the maths and then revert it back. It works like this:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
This works because when we do
0.1 * 10 = 1and
0.2 * 10 = 2then both numbers become integers, and there's no precision loss.
If we were dealing with a shop, then the most radical solution would be to store all prices in cents and use no fractions at all. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely feasible, so the solutions above help avoid this pitfall.
Try running this:
// Hello! I'm a self-increasing number! alert( 9999999999999999 ); // shows 10000000000000000
-0. That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero. In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
Tests: isFinite and isNaN
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
alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true
But do we need this function? Can't we just use the comparison
=== NaN? Sorry, but the answer is no. The value
NaNis unique in that it does not equal anything, including itself:
alert( NaN === NaN ); // false
alert( isFinite("15") ); // true alert( isFinite("str") ); // false, because a special value: NaN alert( isFinite(Infinity) ); // false, because a special value: Infinity
let num = +prompt("Enter a number", ''); // will be true unless you enter Infinity, -Infinity or not a number alert( isFinite(num) );
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, it rarely matters, but these values technically are different.
In all other cases,
Object.is(a, b) is the same as
a === b.
Numeric conversion using a plus
Number() is strict. If a value is not exactly a number, it fails:
alert( +"100px" ); // NaN
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:
alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 alert( parseInt('12.3') ); // 12, only the integer part is returned alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
There are situations when
parseInt/parseFloat will return
NaN. It happens when no digits could be read:
alert( parseInt('a123') ); // NaN, the first symbol stops the process
The second argument of
alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255, without 0x also works alert( parseInt('2n9c', 36) ); // 123456
Other math functions
A few examples:
Math.random() : Returns a random number from 0 to 1 (not including 1)
alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...) /
Math.min(a, b, c...) : Returns the greatest/smallest from the arbitrary number of arguments.
alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1
Math.pow(n, power) : Returns
n raised the given power
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
To write big numbers:
"e"with the zeroes count to the number. Like:
123with 6 zeroes.
A negative number after
"e"causes the number to be divided by 1 with given zeroes. That's for one-millionth or such.
For different numeral systems:
Can write numbers directly in hex (
0x), octal (
0o) and binary (
parseInt(str, base)parses an integer from any numeral system with base:
2 ≤ base ≤ 36.
num.toString(base)converts a number to a string in the numeral system with the given
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.
- Make sure to remember there's a loss of precision when working with fractions.
More mathematical functions: