Numbers

JavaScript has just one numeric type: number. This type includes both “integer” values and fractional decimal numbers. I say “integer” in quotes because it’s long been a criticism of JS that there are not true integers, as there are in other languages. That may change at some point in the future, but for now, we just have numbers for everything.

So, in JS, an “integer” is just a value that has no fractional decimal value. That is, 42.0 is as much an “integer” as 42.

Like most modern languages, including practically all scripting languages, the implementation of JavaScript’s numbers is based on the “IEEE 754” standard, often called “floating-point.” JavaScript specifically uses the “double precision” format (aka “64-bit binary”) of the standard.

There are many great write-ups on the Web about the nitty-gritty details of how binary floating-point numbers are stored in memory, and the implications of those choices. Because understanding bit patterns in memory is not strictly necessary to understand how to correctly use numbers in JS, we’ll leave it as an exercise for the interested reader if you’d like to dig further into IEEE 754 details.

Numeric Syntax

Number literals are expressed in JavaScript generally as base-10 decimal literals. For example:

  1. var a = 42;
  2. var b = 42.3;

The leading portion of a decimal value, if 0, is optional:

  1. var a = 0.42;
  2. var b = .42;

Similarly, the trailing portion (the fractional) of a decimal value after the ., if 0, is optional:

  1. var a = 42.0;
  2. var b = 42.;

Warning: 42. is pretty uncommon, and probably not a great idea if you’re trying to avoid confusion when other people read your code. But it is, nevertheless, valid.

By default, most numbers will be outputted as base-10 decimals, with trailing fractional 0s removed. So:

  1. var a = 42.300;
  2. var b = 42.0;
  3. a; // 42.3
  4. b; // 42

Very large or very small numbers will by default be outputted in exponent form, the same as the output of the toExponential() method, like:

  1. var a = 5E10;
  2. a; // 50000000000
  3. a.toExponential(); // "5e+10"
  4. var b = a * a;
  5. b; // 2.5e+21
  6. var c = 1 / a;
  7. c; // 2e-11

Because number values can be boxed with the Number object wrapper (see Chapter 3), number values can access methods that are built into the Number.prototype (see Chapter 3). For example, the toFixed(..) method allows you to specify how many fractional decimal places you’d like the value to be represented with:

  1. var a = 42.59;
  2. a.toFixed( 0 ); // "43"
  3. a.toFixed( 1 ); // "42.6"
  4. a.toFixed( 2 ); // "42.59"
  5. a.toFixed( 3 ); // "42.590"
  6. a.toFixed( 4 ); // "42.5900"

Notice that the output is actually a string representation of the number, and that the value is 0-padded on the right-hand side if you ask for more decimals than the value holds.

toPrecision(..) is similar, but specifies how many significant digits should be used to represent the value:

  1. var a = 42.59;
  2. a.toPrecision( 1 ); // "4e+1"
  3. a.toPrecision( 2 ); // "43"
  4. a.toPrecision( 3 ); // "42.6"
  5. a.toPrecision( 4 ); // "42.59"
  6. a.toPrecision( 5 ); // "42.590"
  7. a.toPrecision( 6 ); // "42.5900"

You don’t have to use a variable with the value in it to access these methods; you can access these methods directly on number literals. But you have to be careful with the . operator. Since . is a valid numeric character, it will first be interpreted as part of the number literal, if possible, instead of being interpreted as a property accessor.

  1. // invalid syntax:
  2. 42.toFixed( 3 ); // SyntaxError
  3. // these are all valid:
  4. (42).toFixed( 3 ); // "42.000"
  5. 0.42.toFixed( 3 ); // "0.420"
  6. 42..toFixed( 3 ); // "42.000"

42.toFixed(3) is invalid syntax, because the . is swallowed up as part of the 42. literal (which is valid — see above!), and so then there’s no . property operator present to make the .toFixed access.

42..toFixed(3) works because the first . is part of the number and the second . is the property operator. But it probably looks strange, and indeed it’s very rare to see something like that in actual JavaScript code. In fact, it’s pretty uncommon to access methods directly on any of the primitive values. Uncommon doesn’t mean bad or wrong.

Note: There are libraries that extend the built-in Number.prototype (see Chapter 3) to provide extra operations on/with numbers, and so in those cases, it’s perfectly valid to use something like 10..makeItRain() to set off a 10-second money raining animation, or something else silly like that.

This is also technically valid (notice the space):

  1. 42 .toFixed(3); // "42.000"

However, with the number literal specifically, this is particularly confusing coding style and will serve no other purpose but to confuse other developers (and your future self). Avoid it.

numbers can also be specified in exponent form, which is common when representing larger numbers, such as:

  1. var onethousand = 1E3; // means 1 * 10^3
  2. var onemilliononehundredthousand = 1.1E6; // means 1.1 * 10^6

number literals can also be expressed in other bases, like binary, octal, and hexadecimal.

These formats work in current versions of JavaScript:

  1. 0xf3; // hexadecimal for: 243
  2. 0Xf3; // ditto
  3. 0363; // octal for: 243

Note: Starting with ES6 + strict mode, the 0363 form of octal literals is no longer allowed (see below for the new form). The 0363 form is still allowed in non-strict mode, but you should stop using it anyway, to be future-friendly (and because you should be using strict mode by now!).

As of ES6, the following new forms are also valid:

  1. 0o363; // octal for: 243
  2. 0O363; // ditto
  3. 0b11110011; // binary for: 243
  4. 0B11110011; // ditto

Please do your fellow developers a favor: never use the 0O363 form. 0 next to capital O is just asking for confusion. Always use the lowercase predicates 0x, 0b, and 0o.

Small Decimal Values

The most (in)famous side effect of using binary floating-point numbers (which, remember, is true of all languages that use IEEE 754 — not just JavaScript as many assume/pretend) is:

  1. 0.1 + 0.2 === 0.3; // false

Mathematically, we know that statement should be true. Why is it false?

Simply put, the representations for 0.1 and 0.2 in binary floating-point are not exact, so when they are added, the result is not exactly 0.3. It’s really close: 0.30000000000000004, but if your comparison fails, “close” is irrelevant.

Note: Should JavaScript switch to a different number implementation that has exact representations for all values? Some think so. There have been many alternatives presented over the years. None of them have been accepted yet, and perhaps never will. As easy as it may seem to just wave a hand and say, “fix that bug already!”, it’s not nearly that easy. If it were, it most definitely would have been changed a long time ago.

Now, the question is, if some numbers can’t be trusted to be exact, does that mean we can’t use numbers at all? Of course not.

There are some applications where you need to be more careful, especially when dealing with fractional decimal values. There are also plenty of (maybe most?) applications that only deal with whole numbers (“integers”), and moreover, only deal with numbers in the millions or trillions at maximum. These applications have been, and always will be, perfectly safe to use numeric operations in JS.

What if we did need to compare two numbers, like 0.1 + 0.2 to 0.3, knowing that the simple equality test fails?

The most commonly accepted practice is to use a tiny “rounding error” value as the tolerance for comparison. This tiny value is often called “machine epsilon,” which is commonly 2^-52 (2.220446049250313e-16) for the kind of numbers in JavaScript.

As of ES6, Number.EPSILON is predefined with this tolerance value, so you’d want to use it, but you can safely polyfill the definition for pre-ES6:

  1. if (!Number.EPSILON) {
  2. Number.EPSILON = Math.pow(2,-52);
  3. }

We can use this Number.EPSILON to compare two numbers for “equality” (within the rounding error tolerance):

  1. function numbersCloseEnoughToEqual(n1,n2) {
  2. return Math.abs( n1 - n2 ) < Number.EPSILON;
  3. }
  4. var a = 0.1 + 0.2;
  5. var b = 0.3;
  6. numbersCloseEnoughToEqual( a, b ); // true
  7. numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false

The maximum floating-point value that can be represented is roughly 1.798e+308 (which is really, really, really huge!), predefined for you as Number.MAX_VALUE. On the small end, Number.MIN_VALUE is roughly 5e-324, which isn’t negative but is really close to zero!

Safe Integer Ranges

Because of how numbers are represented, there is a range of “safe” values for the whole number “integers”, and it’s significantly less than Number.MAX_VALUE.

The maximum integer that can “safely” be represented (that is, there’s a guarantee that the requested value is actually representable unambiguously) is 2^53 - 1, which is 9007199254740991. If you insert your commas, you’ll see that this is just over 9 quadrillion. So that’s pretty darn big for numbers to range up to.

This value is actually automatically predefined in ES6, as Number.MAX_SAFE_INTEGER. Unsurprisingly, there’s a minimum value, -9007199254740991, and it’s defined in ES6 as Number.MIN_SAFE_INTEGER.

The main way that JS programs are confronted with dealing with such large numbers is when dealing with 64-bit IDs from databases, etc. 64-bit numbers cannot be represented accurately with the number type, so must be stored in (and transmitted to/from) JavaScript using string representation.

Numeric operations on such large ID number values (besides comparison, which will be fine with strings) aren’t all that common, thankfully. But if you do need to perform math on these very large values, for now you’ll need to use a big number utility. Big numbers may get official support in a future version of JavaScript.

Testing for Integers

To test if a value is an integer, you can use the ES6-specified Number.isInteger(..):

  1. Number.isInteger( 42 ); // true
  2. Number.isInteger( 42.000 ); // true
  3. Number.isInteger( 42.3 ); // false

To polyfill Number.isInteger(..) for pre-ES6:

  1. if (!Number.isInteger) {
  2. Number.isInteger = function(num) {
  3. return typeof num == "number" && num % 1 == 0;
  4. };
  5. }

To test if a value is a safe integer, use the ES6-specified Number.isSafeInteger(..):

  1. Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
  2. Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
  3. Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true

To polyfill Number.isSafeInteger(..) in pre-ES6 browsers:

  1. if (!Number.isSafeInteger) {
  2. Number.isSafeInteger = function(num) {
  3. return Number.isInteger( num ) &&
  4. Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
  5. };
  6. }

32-bit (Signed) Integers

While integers can range up to roughly 9 quadrillion safely (53 bits), there are some numeric operations (like the bitwise operators) that are only defined for 32-bit numbers, so the “safe range” for numbers used in that way must be much smaller.

The range then is Math.pow(-2,31) (-2147483648, about -2.1 billion) up to Math.pow(2,31)-1 (2147483647, about +2.1 billion).

To force a number value in a to a 32-bit signed integer value, use a | 0. This works because the | bitwise operator only works for 32-bit integer values (meaning it can only pay attention to 32 bits and any other bits will be lost). Then, “or’ing” with zero is essentially a no-op bitwise speaking.

Note: Certain special values (which we will cover in the next section) such as NaN and Infinity are not “32-bit safe,” in that those values when passed to a bitwise operator will pass through the abstract operation ToInt32 (see Chapter 4) and become simply the +0 value for the purpose of that bitwise operation.