JavaScript has two kinds of numeric values:
This chapter covers numbers. Bigints are covered later in this book.
The type number
is used for both integers and floating point numbers in JavaScript:
98
123.45
However, all numbers are doubles, 64-bit floating point numbers implemented according to the IEEE Standard for Floating-Point Arithmetic (IEEE 754).
Integer numbers are simply floating point numbers without a decimal fraction:
> 98 === 98.0
true
Note that, under the hood, most JavaScript engines are often able to use real integers, with all associated performance and storage size benefits.
Let’s examine literals for numbers.
Several integer literals let us express integers with various bases:
// Binary (base 2)
assert.equal(0b11, 3); // ES6
// Octal (base 8)
assert.equal(0o10, 8); // ES6
// Decimal (base 10)
assert.equal(35, 35);
// Hexadecimal (base 16)
assert.equal(0xE7, 231);
Floating point numbers can only be expressed in base 10.
Fractions:
> 35.0
35
Exponent: eN
means ×10N
> 3e2
300
> 3e-2
0.03
> 0.3e2
30
Accessing a property of an integer literal entails a pitfall: If the integer literal is immediately followed by a dot, then that dot is interpreted as a decimal dot:
7.toString(); // syntax error
There are four ways to work around this pitfall:
7.0.toString()
(7).toString()
7..toString()
7 .toString() // space before dot
_
) as separators in number literals [ES2021]Grouping digits to make long numbers more readable has a long tradition. For example:
Since ES2021, we can use underscores as separators in number literals:
const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;
With other bases, grouping is important, too:
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;
We can also use the separator in fractions and exponents:
const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
The locations of separators are restricted in two ways:
We can only put underscores between two digits. Therefore, all of the following number literals are illegal:
3_.141
3._141
1_e12
1e_12
_1464301 // valid variable name!
1464301_
0_b111111000
0b_111111000
We can’t use more than one underscore in a row:
123__456 // two underscores – not allowed
The motivation behind these restrictions is to keep parsing simple and to avoid strange edge cases.
The following functions for parsing numbers do not support separators:
Number()
Number.parseInt()
Number.parseFloat()
For example:
> Number('123_456')
NaN
> Number.parseInt('123_456')
123
The rationale is that numeric separators are for code. Other kinds of input should be processed differently.
Table 18.1 lists JavaScript’s binary arithmetic operators.
Operator | Name | Example | |
---|---|---|---|
n + m | Addition | ES1 | 3 + 4 → 7 |
n - m | Subtraction | ES1 | 9 - 1 → 8 |
n * m | Multiplication | ES1 | 3 * 2.25 → 6.75 |
n / m | Division | ES1 | 5.625 / 5 → 1.125 |
n % m | Remainder | ES1 | 8 % 5 → 3 |
-8 % 5 → -3 |
|||
n ** m | Exponentiation | ES2016 | 4 ** 2 → 16 |
Table 18.1: Binary arithmetic operators.
%
is a remainder operator%
is a remainder operator, not a modulo operator. Its result has the sign of the first operand:
> 5 % 3
2
> -5 % 3
-2
For more information on the difference between remainder and modulo, see the blog post “Remainder operator vs. modulo operator (with JavaScript code)” on 2ality.
+
) and negation (-
)
Table 18.2 summarizes the two operators unary plus (+
) and negation (-
).
Operator | Name | Example | |
---|---|---|---|
+n | Unary plus | ES1 | +(-7) → -7 |
-n | Unary negation | ES1 | -(-7) → 7 |
Table 18.2: The operators unary plus (+
) and negation (-
).
Both operators coerce their operands to numbers:
> +'5'
5
> +'-12'
-12
> -'9'
-9
Thus, unary plus lets us convert arbitrary values to numbers.
++
) and decrementing (--
)
The incrementation operator ++
exists in a prefix version and a suffix version. In both versions, it destructively adds one to its operand. Therefore, its operand must be a storage location that can be changed.
The decrementation operator --
works the same, but subtracts one from its operand. The next two examples explain the difference between the prefix and the suffix version.
Table 18.3 summarizes the incrementation and decrementation operators.
Operator | Name | Example | |
---|---|---|---|
v++ | Increment | ES1 | let v=0; [v++, v] → [0, 1] |
++v | Increment | ES1 | let v=0; [++v, v] → [1, 1] |
v-- | Decrement | ES1 | let v=1; [v--, v] → [1, 0] |
--v | Decrement | ES1 | let v=1; [--v, v] → [0, 0] |
Table 18.3: Incrementation operators and decrementation operators.
Next, we’ll look at examples of these operators in use.
Prefix ++
and prefix --
change their operands and then return them.
let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);
let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);
Suffix ++
and suffix --
return their operands and then change them.
let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);
let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
We can also apply these operators to property values:
const obj = { a: 1 };
++obj.a;
assert.equal(obj.a, 2);
And to Array elements:
const arr = [ 4 ];
arr[0]++;
assert.deepEqual(arr, [5]);
Exercise: Number operators
exercises/numbers/is_odd_test.mjs
These are three ways of converting values to numbers:
Number(value)
+value
parseFloat(value)
(avoid; different than the other two!)
Recommendation: use the descriptive Number()
. Table 18.4 summarizes how it works.
x | Number(x) |
---|---|
undefined | NaN |
null | 0 |
boolean | false → 0 , true → 1 |
number | x (no change) |
bigint | -1n → -1 , 1n → 1 , etc. |
string | '' → 0 |
Other → parsed number, ignoring leading/trailing whitespace | |
symbol | Throws TypeError |
object | Configurable (e.g. via .valueOf() ) |
Table 18.4: Converting values to numbers.
Examples:
assert.equal(Number(123.45), 123.45);
assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);
assert.equal(Number(-123n), -123);
How objects are converted to numbers can be configured – for example, by overriding .valueOf()
:
> Number({ valueOf() { return 123 } })
123
Exercise: Converting to number
exercises/numbers/parse_number_test.mjs
Two number values are returned when errors happen:
NaN
Infinity
NaN
NaN
is an abbreviation of “not a number”. Ironically, JavaScript considers it to be a number:
> typeof NaN
'number'
When is NaN
returned?
NaN
is returned if a number can’t be parsed:
> Number('$$$')
NaN
> Number(undefined)
NaN
NaN
is returned if an operation can’t be performed:
> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN
NaN
is returned if an operand or argument is NaN
(to propagate errors):
> NaN - 3
NaN
> 7 ** NaN
NaN
NaN
NaN
is the only JavaScript value that is not strictly equal to itself:
const n = NaN;
assert.equal(n === n, false);
These are several ways of checking if a value x
is NaN
:
const x = NaN;
assert.equal(Number.isNaN(x), true); // preferred
assert.equal(Object.is(x, NaN), true);
assert.equal(x !== x, true);
In the last line, we use the comparison quirk to detect NaN
.
NaN
in ArraysSome Array methods can’t find NaN
:
> [NaN].indexOf(NaN)
-1
Others can:
> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN
Alas, there is no simple rule of thumb. We have to check for each method how it handles NaN
.
Infinity
When is the error value Infinity
returned?
Infinity is returned if a number is too large:
> Math.pow(2, 1023)
8.98846567431158e+307
> Math.pow(2, 1024)
Infinity
Infinity is returned if there is a division by zero:
> 5 / 0
Infinity
> -5 / 0
-Infinity
Infinity
as a default valueInfinity
is larger than all other numbers (except NaN
), making it a good default value:
function findMinimum(numbers) {
let min = Infinity;
for (const n of numbers) {
if (n < min) min = n;
}
return min;
}
assert.equal(findMinimum([5, -1, 2]), -1);
assert.equal(findMinimum([]), Infinity);
Infinity
These are two common ways of checking if a value x
is Infinity
:
const x = Infinity;
assert.equal(x === Infinity, true);
assert.equal(Number.isFinite(x), false);
Exercise: Comparing numbers
exercises/numbers/find_max_test.mjs
Internally, JavaScript floating point numbers are represented with base 2 (according to the IEEE 754 standard). That means that decimal fractions (base 10) can’t always be represented precisely:
> 0.1 + 0.2
0.30000000000000004
> 1.3 * 3
3.9000000000000004
> 1.4 * 100000000000000
139999999999999.98
We therefore need to take rounding errors into consideration when performing arithmetic in JavaScript.
Read on for an explanation of this phenomenon.
All remaining sections of this chapter are advanced.
In JavaScript, computations with numbers don’t always produce correct results – for example:
> 0.1 + 0.2
0.30000000000000004
To understand why, we need to explore how JavaScript represents floating point numbers internally. It uses three integers to do so, which take up a total of 64 bits of storage (double precision):
Component | Size | Integer range |
---|---|---|
Sign | 1 bit | [0, 1] |
Fraction | 52 bits | [0, 252−1] |
Exponent | 11 bits | [−1023, 1024] |
The floating point number represented by these integers is computed as follows:
(–1)sign × 0b1.fraction × 2exponent
This representation can’t encode a zero because its second component (involving the fraction) always has a leading 1. Therefore, a zero is encoded via the special exponent −1023 and a fraction 0.
To make further discussions easier, we simplify the previous representation:
The new representation works like this:
mantissa × 10exponent
Let’s try out this representation for a few floating point numbers.
To encode the integer 123, we use the mantissa 123 and multiply it with 1 (100):
> 123 * (10 ** 0)
123
To encode the integer −45, we use the mantissa −45 and, again, the exponent zero:
> -45 * (10 ** 0)
-45
For the number 1.5, we imagine there being a point after the mantissa. We use the negative exponent −1 to move that point one digit to the left:
> 15 * (10 ** -1)
1.5
For the number 0.25, we move the point two digits to the left:
> 25 * (10 ** -2)
0.25
In other words: As soon as we have decimal digits, the exponent becomes negative. We can also write such a number as a fraction:
For example:
> 15 * (10 ** -1) === 15 / (10 ** 1)
true
> 25 * (10 ** -2) === 25 / (10 ** 2)
true
These fractions help with understanding why there are numbers that our encoding cannot represent:
1/10
can be represented. It already has the required format: a power of 10 in the denominator.
1/2
can be represented as 5/10
. We turned the 2 in the denominator into a power of 10 by multiplying the numerator and denominator by 5.
1/4
can be represented as 25/100
. We turned the 4 in the denominator into a power of 10 by multiplying the numerator and denominator by 25.
1/3
cannot be represented. There is no way to turn the denominator into a power of 10. (The prime factors of 10 are 2 and 5. Therefore, any denominator that only has these prime factors can be converted to a power of 10, by multiplying both the numerator and denominator by enough twos and fives. If a denominator has a different prime factor, then there’s nothing we can do.)
To conclude our excursion, we switch back to base 2:
0.5 = 1/2
can be represented with base 2 because the denominator is already a power of 2.
0.25 = 1/4
can be represented with base 2 because the denominator is already a power of 2.
0.1 = 1/10
cannot be represented because the denominator cannot be converted to a power of 2.
0.2 = 2/10
cannot be represented because the denominator cannot be converted to a power of 2.
Now we can see why 0.1 + 0.2
doesn’t produce a correct result: internally, neither of the two operands can be represented precisely.
The only way to compute precisely with decimal fractions is by internally switching to base 10. For many programming languages, base 2 is the default and base 10 an option. For example:
BigDecimal
.
decimal
.
There are plans to add something similar to JavaScript: the ECMAScript proposal “Decimal”. Until that happens, we can use libraries such as big.js.
Integer numbers are normal (floating point) numbers without decimal fractions:
> 1 === 1.0
true
> Number.isInteger(1.0)
true
In this section, we’ll look at a few tools for working with these pseudo-integers. JavaScript also supports bigints, which are real integers.
The recommended way of converting numbers to integers is to use one of the rounding methods of the Math
object:
Math.floor(n)
: returns the largest integer i
≤ n
> Math.floor(2.1)
2
> Math.floor(2.9)
2
Math.ceil(n)
: returns the smallest integer i
≥ n
> Math.ceil(2.1)
3
> Math.ceil(2.9)
3
Math.round(n)
: returns the integer that is “closest” to n
with __.5
being rounded up – for example:
> Math.round(2.4)
2
> Math.round(2.5)
3
Math.trunc(n)
: removes any decimal fraction (after the point) that n
has, therefore turning it into an integer.
> Math.trunc(2.1)
2
> Math.trunc(2.9)
2
For more information on rounding, consult “Rounding” (§19.3).
These are important ranges of integer numbers in JavaScript:
>>>
): unsigned, [0, 232)
This is the range of integer numbers that are safe in JavaScript (53 bits plus a sign):
[–(253)+1, 253–1]
An integer is safe if it is represented by exactly one JavaScript number. Given that JavaScript numbers are encoded as a fraction multiplied by 2 to the power of an exponent, higher integers can also be represented, but then there are gaps between them.
For example (18014398509481984 is 254):
> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988
The following properties of Number
help determine if an integer is safe:
assert.equal(Number.MAX_SAFE_INTEGER, (2 ** 53) - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);
assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);
Exercise: Detecting safe integers
exercises/numbers/is_safe_integer_test.mjs
Let’s look at computations involving unsafe integers.
The following result is incorrect and unsafe, even though both of its operands are safe:
> 9007199254740990 + 3
9007199254740992
The following result is safe, but incorrect. The first operand is unsafe; the second operand is safe:
> 9007199254740995 - 10
9007199254740986
Therefore, the result of an expression a op b
is correct if and only if:
isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)
That is, both operands and the result must be safe.
Internally, JavaScript’s bitwise operators work with 32-bit integers. They produce their results in the following steps:
For each bitwise operator, this book mentions the types of its operands and its result. Each type is always one of the following two:
Type | Description | Size | Range |
---|---|---|---|
Int32 | signed 32-bit integer | 32 bits incl. sign | [−231, 231) |
Uint32 | unsigned 32-bit integer | 32 bits | [0, 232) |
Considering the previously mentioned steps, I recommend to pretend that bitwise operators internally work with unsigned 32-bit integers (step “computation”) and that Int32 and Uint32 only affect how JavaScript numbers are converted to and from integers (steps “input” and “output”).
While exploring the bitwise operators, it occasionally helps to display JavaScript numbers as unsigned 32-bit integers in binary notation. That’s what b32()
does (whose implementation is shown later):
assert.equal(
b32(-1),
'11111111111111111111111111111111');
assert.equal(
b32(1),
'00000000000000000000000000000001');
assert.equal(
b32(2 ** 31),
'10000000000000000000000000000000');
Operation | Name | Type signature | |
---|---|---|---|
~num | Bitwise Not, ones’ complement | Int32 → Int32 | ES1 |
Table 18.5: The bitwise Not operator.
The bitwise Not operator (table 18.5) inverts each binary digit of its operand:
> b32(~0b100)
'11111111111111111111111111111011'
This so-called ones’ complement is similar to a negative for some arithmetic operations. For example, adding an integer to its ones’ complement is always -1
:
> 4 + ~4
-1
> -11 + ~-11
-1
Operation | Name | Type signature | |
---|---|---|---|
num1 & num2 | Bitwise And | Int32 × Int32 → Int32 | ES1 |
num1 ¦ num2 | Bitwise Or | Int32 × Int32 → Int32 | ES1 |
num1 ^ num2 | Bitwise Xor | Int32 × Int32 → Int32 | ES1 |
Table 18.6: Binary bitwise operators.
The binary bitwise operators (table 18.6) combine the bits of their operands to produce their results:
> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'
Operation | Name | Type signature | |
---|---|---|---|
num << count | Left shift | Int32 × Uint32 → Int32 | ES1 |
num >> count | Signed right shift | Int32 × Uint32 → Int32 | ES1 |
num >>> count | Unsigned right shift | Uint32 × Uint32 → Uint32 | ES1 |
Table 18.7: Bitwise shift operators.
The shift operators (table 18.7) move binary digits to the left or to the right:
> (0b10 << 1).toString(2)
'100'
>>
preserves highest bit, >>>
doesn’t:
> b32(0b10000000000000000000000000000010 >> 1)
'11000000000000000000000000000001'
> b32(0b10000000000000000000000000000010 >>> 1)
'01000000000000000000000000000001'
b32()
: displaying unsigned 32-bit integers in binary notationWe have now used b32()
a few times. The following code is an implementation of it:
/**
* Return a string representing n as a 32-bit unsigned integer,
* in binary notation.
*/
function b32(n) {
// >>> ensures highest bit isn’t interpreted as a sign
return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
b32(6),
'00000000000000000000000000000110');
n >>> 0
means that we are shifting n
zero bits to the right. Therefore, in principle, the >>>
operator does nothing, but it still coerces n
to an unsigned 32-bit integer:
> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1
JavaScript has the following four global functions for numbers:
isFinite()
isNaN()
parseFloat()
parseInt()
However, it is better to use the corresponding methods of Number
(Number.isFinite()
, etc.), which have fewer pitfalls. They were introduced with ES6 and are discussed below.
Number.*
: data propertiesNumber.EPSILON
[ES6]
The difference between 1 and the next representable floating point number. In general, a machine epsilon provides an upper bound for rounding errors in floating point arithmetic.
Number.MAX_SAFE_INTEGER
[ES6]
The largest integer that JavaScript can represent unambiguously (253−1).
Number.MAX_VALUE
[ES1]
The largest positive finite JavaScript number.
Number.MIN_SAFE_INTEGER
[ES6]
The smallest integer that JavaScript can represent unambiguously (−253+1).
Number.MIN_VALUE
[ES1]
The smallest positive JavaScript number. Approximately 5 × 10−324.
Number.NaN
[ES1]
The same as the global variable NaN
.
Number.NEGATIVE_INFINITY
[ES1]
The same as -Number.POSITIVE_INFINITY
.
Number.POSITIVE_INFINITY
[ES1]
The same as the global variable Infinity
.
Number.*
: methodsNumber.isFinite(num)
[ES6]
Returns true
if num
is an actual number (neither Infinity
nor -Infinity
nor NaN
).
> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true
Number.isInteger(num)
[ES6]
Returns true
if num
is a number and does not have a decimal fraction.
> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false
Number.isNaN(num)
[ES6]
Returns true
if num
is the value NaN
:
> Number.isNaN(NaN)
true
> Number.isNaN(123)
false
> Number.isNaN('abc')
false
Number.isSafeInteger(num)
[ES6]
Returns true
if num
is a number and unambiguously represents an integer.
Number.parseFloat(str)
[ES6]
Coerces its parameter to string and parses it as a floating point number. For converting strings to numbers, Number()
(which ignores leading and trailing whitespace) is usually a better choice than Number.parseFloat()
(which ignores leading whitespace and illegal trailing characters and can hide problems).
> Number.parseFloat(' 123.4#')
123.4
> Number(' 123.4#')
NaN
Number.parseInt(str, radix=10)
[ES6]
Coerces its parameter to string and parses it as an integer, ignoring leading whitespace and illegal trailing characters:
> Number.parseInt(' 123#')
123
The parameter radix
specifies the base of the number to be parsed:
> Number.parseInt('101', 2)
5
> Number.parseInt('FF', 16)
255
Do not use this method to convert numbers to integers: coercing to string is inefficient. And stopping before the first non-digit is not a good algorithm for removing the fraction of a number. Here is an example where it goes wrong:
> Number.parseInt(1e21, 10) // wrong
1
It is better to use one of the rounding functions of Math
to convert a number to an integer:
> Math.trunc(1e21) // correct
1e+21
Number.prototype.*
(Number.prototype
is where the methods of numbers are stored.)
Number.prototype.toExponential(fractionDigits?)
[ES3]
fractionDigits
, we can specify, how many digits should be shown of the number that is multiplied with the exponent.
Example: number too small to get a positive exponent via .toString()
.
> 1234..toString()
'1234'
> 1234..toExponential() // 3 fraction digits
'1.234e+3'
> 1234..toExponential(5)
'1.23400e+3'
> 1234..toExponential(1)
'1.2e+3'
Example: fraction not small enough to get a negative exponent via .toString()
.
> 0.003.toString()
'0.003'
> 0.003.toExponential()
'3e-3'
Number.prototype.toFixed(fractionDigits=0)
[ES3]
Returns an exponent-free string representation of the number, rounded to fractionDigits
digits.
> 0.00000012.toString() // with exponent
'1.2e-7'
> 0.00000012.toFixed(10) // no exponent
'0.0000001200'
> 0.00000012.toFixed()
'0'
If the number is 1021 or greater, even .toFixed()
uses an exponent:
> (10 ** 21).toFixed()
'1e+21'
Number.prototype.toPrecision(precision?)
[ES3]
.toString()
, but precision
specifies how many digits should be shown overall.
precision
is missing, .toString()
is used.
> 1234..toPrecision(3) // requires exponential notation
'1.23e+3'
> 1234..toPrecision(4)
'1234'
> 1234..toPrecision(5)
'1234.0'
> 1.234.toPrecision(3)
'1.23'
Number.prototype.toString(radix=10)
[ES1]
Returns a string representation of the number.
By default, we get a base 10 numeral as a result:
> 123.456.toString()
'123.456'
If we want the numeral to have a different base, we can specify it via radix
:
> 4..toString(2) // binary (base 2)
'100'
> 4.5.toString(2)
'100.1'
> 255..toString(16) // hexadecimal (base 16)
'ff'
> 255.66796875.toString(16)
'ff.ab'
> 1234567890..toString(36)
'kf12oi'
Number.parseInt()
provides the inverse operation: it converts a string that contains an integer (no fraction!) numeral with a given base, to a number.
> Number.parseInt('kf12oi', 36)
1234567890