Precision of new Math APIs

Math.log1p

The built-in function Math.log1p(x) calculates the value of the expression Math.log(1 + x). At first glance it may seem strange to include such a simple helper, but its purpose is not convenience but to preserve precision when x is very small. Take a look at the following chart:

Note how once we reach input values smaller than around 10-13 we start losing significant amounts of precision while Math.log1p does not. To understand why we have to look at how floating point works. Floating point values are stored in 3 components - a sign bit, a significand, and an exponent. The value of the floating point is determined by evaluating sign * significand * 10exponent. In JavaScript, floats are 64 bits wide (also known as double precision floats) and allocate 1 bit for the sign, 11 bits for the exponent, leaving 52 bits for the significand.

So now let's look at our calculation of Math.log(1 + x) when x is very small. Since we have a very tiny number less than 1, wecan express all the leading zeroes as a negative exponent and reserve all of the significand to express precision following the first non-zero digit. But when we add 1 to this number, we end up with a number very slightly more than 1. In this case, we need to set the exponent to 0, forcing us to use the significand to encode all of the subsequent 0's after the initial 1. This leaves us significantly fewer bits available for precision since most of the significand is now dedicated to encoding the 0's following the initial 1.

To illustrate this problem further, you can evaluate 0 + 1E-100 === 0 as compared to 1 + 1E-100 === 1 and note that the former is true while the latter is not. This is the same issue - JavaScript floats do not have sufficient bits to represent a number slightly more than 1, but slightly more than 0 is no problem because we can use a negative exponent.

Math.expm1

The built-in function Math.expm1(x) calculates the value of the expression Math.pow(Math.E, x) - 1.

Again note that doing this calculation by hand results in significant loss of precision when x is very small.