- 5. New number and Math features
- 5.1 Overview
- 5.2 New integer literals
- 5.3 New static Number properties
- 5.4 New Math functionality
- 5.5 FAQ: numbers
Please support this book: buy it (PDF, EPUB, MOBI) or donate
5. New number and Math features
5.1 Overview
5.1.1 New integer literals
You can now specify integers in binary and octal notation:
> 0xFF // ES5: hexadecimal
- 255
- > 0b11 // ES6: binary
- 3
- > 0o10 // ES6: octal
- 8
5.1.2 New Number properties
The global object Number
gained a few new properties:
Number.EPSILON
for comparing floating point numbers with a tolerance for rounding errors.Number.isInteger(num)
checks whethernum
is an integer (a number without a decimal fraction):
> Number.isInteger(1.05)
- false
- > Number.isInteger(1)
- true
- > Number.isInteger(-3.1)
- false
- > Number.isInteger(-3)
- true
- A method and constants for determining whether a JavaScript integer is safe (within the signed 53 bit range in which there is no loss of precision):
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAX_SAFE_INTEGER
Number.isNaN(num)
checks whethernum
is the valueNaN
. In contrast to the global functionisNaN()
, it doesn’t coerce its argument to a number and is therefore safer for non-numbers:
> isNaN('???')
- true
- > Number.isNaN('???')
- false
- Three additional methods of
Number
are mostly equivalent to the global functions with the same names:Number.isFinite
,Number.parseFloat
,Number.parseInt
.
5.1.3 New Math methods
The global object Math
has new methods for numerical, trigonometric and bitwise operations. Let’s look at four examples.
Math.sign()
returns the sign of a number:
> Math.sign(-8)
- -1
- > Math.sign(0)
- 0
- > Math.sign(3)
- 1
Math.trunc()
removes the decimal fraction of a number:
> Math.trunc(3.1)
- 3
- > Math.trunc(3.9)
- 3
- > Math.trunc(-3.1)
- -3
- > Math.trunc(-3.9)
- -3
Math.log10()
computes the logarithm to base 10:
> Math.log10(100)
- 2
Math.hypot()
Computes the square root of the sum of the squares of its arguments (Pythagoras’ theorem):
> Math.hypot(3, 4)
- 5
5.2 New integer literals
ECMAScript 5 already has literals for hexadecimal integers:
> 0x9
- 9
- > 0xA
- 10
- > 0x10
- 16
- > 0xFF
- 255
ECMAScript 6 brings two new kinds of integer literals:
- Binary literals have the prefix
0b
or0B
:
> 0b11
- 3
- > 0b100
- 4
- Octal literals have the prefix
0o
or0O
(that’s a zero followed by the capital letter O; the first variant is safer):
> 0o7
- 7
- > 0o10
- 8
Remember that the Number
method toString(radix)
can be used to see numbers in a base other than 10:
> 255..toString(16)
- 'ff'
- > 4..toString(2)
- '100'
- > 8..toString(8)
- '10'
(The double dots are necessary so that the dot for property access isn’t confused with a decimal dot.)
5.2.1 Use case for octal literals: Unix-style file permissions
In the Node.js file system module, several functions have the parameter mode
. Its value is used to specify file permissions, via an encoding that is a holdover from Unix:
- Permissions are specified for three categories of users:
- User: the owner of the file
- Group: the members of the group associated with the file
- All: everyone
- Per category, the following permissions can be granted:
- r (read): the users in the category are allowed to read the file
- w (write): the users in the category are allowed to change the file
- x (execute): the users in the category are allowed to run the file That means that permissions can be represented by 9 bits (3 categories with 3 permissions each):
User | Group | All | |
---|---|---|---|
Permissions | r, w, x | r, w, x | r, w, x |
Bit | 8, 7, 6 | 5, 4, 3 | 2, 1, 0 |
The permissions of a single category of users are stored in 3 bits:
Bits | Permissions | Octal digit |
---|---|---|
000 | ––– | 0 |
001 | ––x | 1 |
010 | –w– | 2 |
011 | –wx | 3 |
100 | r–– | 4 |
101 | r–x | 5 |
110 | rw– | 6 |
111 | rwx | 7 |
That means that octal numbers are a compact representation of all permissions, you only need 3 digits, one digit per category of users. Two examples:
- 755 = 111,101,101: I can change, read and execute; everyone else can only read and execute.
- 640 = 110,100,000: I can read and write; group members can read; everyone can’t access at all.
5.2.2 Number.parseInt() and the new integer literals
Number.parseInt()
(which does the same as the global function parseInt()
) has the following signature:
Number
.
parseInt
(
string
,
radix
?
)
5.2.2.1 Number.parseInt(): hexadecimal number literals
Number.parseInt()
provides special support for the hexadecimal literal notation – the prefix 0x
(or 0X
) of string
is removed if:
radix
is missing or 0. Thenradix
is set to 16. As a rule, you should never omit theradix
.radix
is 16. For example:
> Number.parseInt('0xFF')
- 255
- > Number.parseInt('0xFF', 0)
- 255
- > Number.parseInt('0xFF', 16)
- 255
In all other cases, digits are only parsed until the first non-digit:
> Number.parseInt('0xFF', 10)
- 0
- > Number.parseInt('0xFF', 17)
- 0
5.2.2.2 Number.parseInt(): binary and octal number literals
However, Number.parseInt()
does not have special support for binary or octal literals!
> Number.parseInt('0b111')
- 0
- > Number.parseInt('0b111', 2)
- 0
- > Number.parseInt('111', 2)
- 7
- > Number.parseInt('0o10')
- 0
- > Number.parseInt('0o10', 8)
- 0
- > Number.parseInt('10', 8)
- 8
If you want to parse these kinds of literals, you need to use Number()
:
> Number('0b111')
- 7
- > Number('0o10')
- 8
Number.parseInt()
works fine with numbers that have a different base, as long as there is no special prefix and the parameter radix
is provided:
> Number.parseInt('111', 2)
- 7
- > Number.parseInt('10', 8)
- 8
5.3 New static Number properties
This section describes new properties that the constructor Number
has picked up in ECMAScript 6.
5.3.1 Previously global functions
Four number-related functions are already available as global functions and have been added to Number
, as methods: isFinite
and isNaN
, parseFloat
and parseInt
. All of them work almost the same as their global counterparts, but isFinite
and isNaN
don’t coerce their arguments to numbers, anymore, which is especially important for isNaN
. The following subsections explain all the details.
5.3.1.1 Number.isFinite(number)
Number.isFinite(number)
determines whether number
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
The advantage of this method is that it does not coerce its parameter to number (whereas the global function does):
> Number.isFinite('123')
- false
- > isFinite('123')
- true
5.3.1.2 Number.isNaN(number)
Number.isNaN(number)
checks whether number
is the value NaN
.
One ES5 way of making this check is via !==
:
> const x = NaN;
- > x !== x
- true
A more descriptive way is via the global function isNaN()
:
> const x = NaN;
- > isNaN(x)
- true
However, this function coerces non-numbers to numbers and returns true
if the result is NaN
(which is usually not what you want):
> isNaN('???')
- true
The new method Number.isNaN()
does not exhibit this problem, because it does not coerce its arguments to numbers:
> Number.isNaN('???')
- false
5.3.1.3 Number.parseFloat and Number.parseInt
The following two methods work exactly like the global functions with the same names. They were added to Number
for completeness sake; now all number-related functions are available there.
5.3.2 Number.EPSILON
Especially with decimal fractions, rounding errors can become a problem in JavaScript3. For example, 0.1 and 0.2 can’t be represented precisely, which you notice if you add them and compare them to 0.3 (which can’t be represented precisely, either).
> 0.1 + 0.2 === 0.3
- false
Number.EPSILON
specifies a reasonable margin of error when comparing floating point numbers. It provides a better way to compare floating point values, as demonstrated by the following function.
function
epsEqu
(
x
,
y
)
{
return
Math
.
abs
(
x
-
y
)
<
Number
.
EPSILON
;
}
console
.
log
(
epsEqu
(
0.1
+
0.2
,
0.3
));
// true
5.3.3 Number.isInteger(number)
JavaScript has only floating point numbers (doubles). Accordingly, integers are simply floating point numbers without a decimal fraction.
Number.isInteger(number)
returns true
if number
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
5.3.4 Safe integers
JavaScript numbers have only enough storage space to represent 53 bit signed integers. That is, integers i in the range −253 < i < 253 are safe. What exactly that means is explained momentarily. The following properties help determine whether a JavaScript integer is safe:
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAXSAFE_INTEGER
The notion of _safe integers centers on how mathematical integers are represented in JavaScript. In the range (−253, 253) (excluding the lower and upper bounds), JavaScript integers are safe: there is a one-to-one mapping between them and the mathematical integers they represent.
Beyond this range, JavaScript integers are unsafe: two or more mathematical integers are represented as the same JavaScript integer. For example, starting at 253, JavaScript can represent only every second mathematical integer:
> Math.pow(2, 53)
- 9007199254740992
- > 9007199254740992
- 9007199254740992
- > 9007199254740993
- 9007199254740992
- > 9007199254740994
- 9007199254740994
- > 9007199254740995
- 9007199254740996
- > 9007199254740996
- 9007199254740996
- > 9007199254740997
- 9007199254740996
Therefore, a safe JavaScript integer is one that unambiguously represents a single mathematical integer.
5.3.4.1 Static Number properties related to safe integers
The two static Number
properties specifying the lower and upper bound of safe integers could be defined as follows:
Number
.
MAX_SAFE_INTEGER
=
Math
.
pow
(
2
,
53
)
-
1
;
Number
.
MIN_SAFE_INTEGER
=
-
Number
.
MAX_SAFE_INTEGER
;
Number.isSafeInteger()
determines whether a JavaScript number is a safe integer and could be defined as follows:
Number
.
isSafeInteger
=
function
(
n
)
{
return
(
typeof
n
===
'number'
&&
Math
.
round
(
n
)
===
n
&&
Number
.
MIN_SAFE_INTEGER
<=
n
&&
n
<=
Number
.
MAX_SAFE_INTEGER
);
}
For a given value n
, this function first checks whether n
is a number and an integer. If both checks succeed, n
is safe if it is greater than or equal to MIN_SAFE_INTEGER
and less than or equal to MAX_SAFE_INTEGER
.
5.3.4.2 When are computations with integers correct?
How can we make sure that results of computations with integers are correct? For example, the following result is clearly not correct:
> 9007199254740990 + 3
- 9007199254740992
We have two safe operands, but an unsafe result:
> Number.isSafeInteger(9007199254740990)
- true
- > Number.isSafeInteger(3)
- true
- > Number.isSafeInteger(9007199254740992)
- false
The following result is also incorrect:
> 9007199254740995 - 10
- 9007199254740986
This time, the result is safe, but one of the operands isn’t:
> Number.isSafeInteger(9007199254740995)
- false
- > Number.isSafeInteger(10)
- true
- > Number.isSafeInteger(9007199254740986)
- true
Therefore, the result of applying an integer operator op
is guaranteed to be correct only if all operands and the result are safe. More formally:
isSafeInteger
(
a
)
&&
isSafeInteger
(
b
)
&&
isSafeInteger
(
a
op
b
)
implies that a op b
is a correct result.
5.4 New Math functionality
The global object Math
has several new methods in ECMAScript 6.
5.4.1 Various numerical functionality
5.4.1.1 Math.sign(x)
Math.sign(x)
returns:
-1
ifx
is a negative number (including-Infinity
).0
ifx
is zero4.+1
ifx
is a positive number (includingInfinity
).NaN
ifx
isNaN
or not a number. Examples:
> Math.sign(-8)
- -1
- > Math.sign(3)
- 1
- > Math.sign(0)
- 0
- > Math.sign(NaN)
- NaN
- > Math.sign(-Infinity)
- -1
- > Math.sign(Infinity)
- 1
5.4.1.2 Math.trunc(x)
Math.trunc(x)
removes the decimal fraction of x
. Complements the other rounding methods Math.floor()
, Math.ceil()
and Math.round()
.
> Math.trunc(3.1)
- 3
- > Math.trunc(3.9)
- 3
- > Math.trunc(-3.1)
- -3
- > Math.trunc(-3.9)
- -3
You could implement Math.trunc()
like this:
function
trunc
(
x
)
{
return
Math
.
sign
(
x
)
*
Math
.
floor
(
Math
.
abs
(
x
));
}
5.4.1.3 Math.cbrt(x)
Math.cbrt(x)
returns the cube root of x
(∛x).
> Math.cbrt(8)
- 2
5.4.2 Using 0 instead of 1 with exponentiation and logarithm
A small fraction can be represented more precisely if it comes after zero. I’ll demonstrate this with decimal fractions (JavaScript’s numbers are internally stored with base 2, but the same reasoning applies).
Floating point numbers with base 10 are internally represented as mantissa × 10exponent. The mantissa has a single digit before the decimal dot and the exponent “moves” the dot as necessary. That means if you convert a small fraction to the internal representation, a zero before the dot leads to a smaller mantissa than a one before the dot. For example:
- (A) 0.000000234 = 2.34 × 10−7. Significant digits: 234
- (B) 1.000000234 = 1.000000234 × 100. Significant digits: 1000000234 Precision-wise, the important quantity here is the capacity of the mantissa, as measured in significant digits. That’s why (A) gives you higher precision than (B).
Additionally, JavaScript represents numbers close to zero (e.g. small fractions) with higher precision.
5.4.2.1 Math.expm1(x)
Math.expm1(x)
returns Math.exp(x)-1
. The inverse of Math.log1p()
.
Therefore, this method provides higher precision whenever Math.exp()
has results close to 1. You can see the difference between the two in the following interaction:
> Math.expm1(1e-10)
- 1.00000000005e-10
- > Math.exp(1e-10)-1
- 1.000000082740371e-10
The former is the better result, which you can verify by using a library (such as decimal.js) for floating point numbers with arbitrary precision (“bigfloats”):
>
var
Decimal
=
require
(
'decimal.js'
)
.
config
(
{
precision
:
50
}
);
>
new
Decimal
(
1e-10
)
.
exp
()
.
minus
(
1
)
.
toString
()
'1.000000000050000000001666666666708333333e-10'
5.4.2.2 Math.log1p(x)
Math.log1p(x)
returns Math.log(1 + x)
. The inverse of Math.expm1()
.
Therefore, this method lets you specify parameters that are close to 1 with a higher precision. The following examples demonstrate why that is.
The following two calls of log()
produce the same result:
> Math.log(1 + 1e-16)
- 0
- > Math.log(1 + 0)
- 0
In contrast, log1p()
produces different results:
> Math.log1p(1e-16)
- 1e-16
- > Math.log1p(0)
- 0
The reason for the higher precision of Math.log1p()
is that the correct result for 1 + 1e-16
has more significant digits than 1e-16
:
> 1 + 1e-16 === 1
- true
- > 1e-16 === 0
- false
5.4.3 Logarithms to base 2 and 10
5.4.3.1 Math.log2(x)
Math.log2(x)
computes the logarithm to base 2.
> Math.log2(8)
- 3
5.4.3.2 Math.log10(x)
Math.log10(x)
computes the logarithm to base 10.
> Math.log10(100)
- 2
5.4.4 Support for compiling to JavaScript
Emscripten pioneered a coding style that was later picked up by asm.js: The operations of a virtual machine (think bytecode) are expressed in static subset of JavaScript. That subset can be executed efficiently by JavaScript engines: If it is the result of a compilation from C++, it runs at about 70% of native speed.
The following Math
methods were mainly added to support asm.js and similar compilation strategies, they are not that useful for other applications.
5.4.4.1 Math.fround(x)
Math.fround(x)
rounds x
to a 32 bit floating point value (float
). Used by asm.js to tell an engine to internally use a float
value.
5.4.4.2 Math.imul(x, y)
Math.imul(x, y)
multiplies the two 32 bit integers x
and y
and returns the lower 32 bits of the result. This is the only 32 bit basic math operation that can’t be simulated by using a JavaScript operator and coercing the result back to 32 bits. For example, idiv
could be implemented as follows:
function
idiv
(
x
,
y
)
{
return
(
x
/
y
)
|
0
;
}
In contrast, multiplying two large 32 bit integers may produce a double that is so large that lower bits are lost.
5.4.5 Bitwise operations
Math.clz32(x)
Counts the leading zero bits in the 32 bit integerx
.
> Math.clz32(0b01000000000000000000000000000000)
- 1
- > Math.clz32(0b00100000000000000000000000000000)
- 2
- > Math.clz32(2)
- 30
- > Math.clz32(1)
- 31
Why is this interesting? Quoting “Fast, Deterministic, and Portable Counting Leading Zeros” by Miro Samek:
Counting leading zeros in an integer number is a critical operation in many DSP algorithms, such as normalization of samples in sound or video processing, as well as in real-time schedulers to quickly find the highest-priority task ready-to-run.
5.4.6 Trigonometric methods
Math.sinh(x)
Computes the hyperbolic sine ofx
.Math.cosh(x)
Computes the hyperbolic cosine ofx
.Math.tanh(x)
Computes the hyperbolic tangent ofx
.Math.asinh(x)
Computes the inverse hyperbolic sine ofx
.Math.acosh(x)
Computes the inverse hyperbolic cosine ofx
.Math.atanh(x)
Computes the inverse hyperbolic tangent ofx
.Math.hypot(…values)
Computes the square root of the sum of the squares of its arguments (Pythagoras’ theorem):
> Math.hypot(3, 4)
- 5
5.5 FAQ: numbers
5.5.1 How can I use integers beyond JavaScript’s 53 bit range?
JavaScript’s integers have a range of 53 bits. That is a problem whenever 64 bit integers are needed. For example: In its JSON API, Twitter had to switch from integers to strings when tweet IDs became too large.
At the moment, the only way around that limitation is to use a library for higher-precision numbers (bigints or bigfloats). One such library is decimal.js.
Plans to support larger integers in JavaScript exist, but may take a while to come to fruition.