Back to Home

Numbers

In modern JavaScript, there are two types of numbers: 1. Regular numbers in JavaScript are stored in 64-bit format IEEE-754, also known as “double precision floating point numbers”. These are numbers that we’re using most of the time, and we’ll talk about them in this chapter. 2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can’t safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter info:types. As bigints are used in a few special areas, we devote them to a special chapter info:bigint. So here we’ll talk about regular numbers. Let’s expand our knowledge of them.

More ways to write a number

Imagine we need to write 1 billion. The obvious way is: We also can use underscore _ as the separator: Here the underscore _ plays the role of the “syntactic sugar”, it makes the number more readable. The JavaScript engine simply ignores _ between digits, so it’s exactly the same one billion as above. In real life though, we try to avoid writing long sequences of zeroes. We’re too lazy for that. We’ll try to write something like “1bn” for a billion or “7.3bn” for 7 billion 300 million. The same is true for most large numbers. In JavaScript, we can shorten a number by appending the letter “e” to it and specifying the zeroes count: In other words, e multiplies the number by 1 with the given zeroes count. Now let’s write something very small. Say, 1 microsecond (one-millionth of a second): Just like before, using “e” can help. If we’d like to avoid writing the zeroes explicitly, we could write the same as: If we count the zeroes in 0.000001, there are 6 of them. So naturally it’s 1e-6. In other words, a negative number after “e” means a division by 1 with the given number of zeroes:

Hex, binary and octal numbers

Hexadecimal numbers are widely used in JavaScript to represent colors, encode characters, and for many other things. So naturally, there exists a shorter way to write them: 0x and then the number. For instance: Binary and octal numeral systems are rarely used, but also supported using the 0b and 0o prefixes: 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).

toString(base)

The method num.toString(base) returns a string representation of num in the numeral system with the given base. For example: The base can vary from 2 to 36. By default, it’s 10. Common use cases for this are: - base=16 is used for hex colors, character encodings etc, digits can be 0..9 or A..F. - base=2 is mostly for debugging bitwise operations, digits can be 0 or 1. - base=36 is the maximum, digits can be 0..9 or A..Z. The whole Latin alphabet is used to represent a number. A funny, but useful case for 36 is 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 36:

alert( 123456..toString(36) ); // 2n9c

Rounding

Math.floor
Rounds down: 3.1 becomes 3, and -1.1 becomes -2. Math.ceil
Rounds up: 3.1 becomes 4, and -1.1 becomes -1. Math.round
Rounds to the nearest integer: 3.1 becomes 3, 3.6 becomes 4. In the middle cases 3.5 rounds up to 4, and -3.5 rounds up to -3. Math.trunc (not supported by Internet Explorer)
Removes anything after the decimal point without rounding: 3.1 becomes 3, -1.1 becomes -1. Here’s the table to summarize the differences between them: These functions cover all of the possible ways to deal with the decimal part of a number. But what if we’d like to round the number to n-th digit after the decimal? For instance, we have 1.2345 and want to round it to 2 digits, getting only 1.23. There are two ways to do so:
  1. Multiply-and-divide. For example, to round the number to the 2nd digit after the decimal, we can multiply the number by 100, call the rounding function and then divide it back.
let num = 1.23456;
alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  1. The method toFixed(n) rounds the number to n digits after the point and returns a string representation of the result.
let num = 12.34;
alert( num.toFixed(1) ); // "12.3"

This rounds up or down to the nearest value, similar to Math.round:

let num = 12.36;
alert( num.toFixed(1) ); // "12.4"

Please note that the result of toFixed is 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

We can convert it to a number using the unary plus or a Number() call, e.g. write +num.toFixed(5).

Imprecise calculations

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, and 1 bit is for the sign. If a number is really huge, it may overflow the 64-bit storage and become a special numeric value Infinity: What may be a little less obvious, but happens quite often, is the loss of precision. Consider this (falsy!) equality test: That’s right, if we check whether the sum of 0.1 and 0.2 is 0.3, we get false. Strange! What is it then if not 0.3? Ouch! Imagine you’re making an e-shopping site and the visitor puts \(0.10 and \)0.20 goods into their cart. 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 bits - ones and zeroes. But fractions like 0.1, 0.2 that look simple in the decimal numeric system are actually unending fractions in their binary form. What is 0.1? It is one divided by ten 1/10, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: 1/3. It becomes an endless fraction 0.33333(3). 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", but it exists. We can see this in action: And when we sum two numbers, their "precision losses" add up. That's why 0.1 + 0.2 is not exactly 0.3. Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method toFixed(n): Please note that toFixed always 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: We also can temporarily multiply the numbers by 100 (or a bigger number) to turn them into integers, do the maths, and then divide back. Then, as we’re doing maths with integers, the error somewhat decreases, but we still get it on division: So, the multiply/divide approach reduces the error, but doesn’t remove it totally. Sometimes we could try to evade fractions at all. Like if we’re dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut “tails” when needed. // Hello! I’m a self-increasing number! alert( 9999999999999999 ); // shows 10000000000000000

Tests: isFinite and isNaN

Remember these two special numeric values? - Infinity (and -Infinity) is a special numeric value that is greater (less) than anything. - NaN represents 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 NaN:

alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true

But do we need this function? Can’t we just use the comparison === NaN? Unfortunately not. The value NaN is 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

Sometimes isFinite is used to validate whether a string value is a regular number: Please note that an empty or a space-only string is treated as 0 in all numeric functions including isFinite.

parseInt and parseFloat

Numeric conversion using a plus + or Number() is strict. If a value is not exactly a number, it fails: 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 “100px” or “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. That’s what parseInt and 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: There are situations when parseInt/parseFloat will return NaN. It happens when no digits could be read: alert( parseInt(‘0xff’, 16) ); // 255 alert( parseInt(‘ff’, 16) ); // 255, without 0x also works alert( parseInt(‘2n9c’, 36) ); // 123456

Other math functions

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…) and Math.min(a, b, c…)

Returns the greatest and 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 to the given power.

alert( Math.pow(2, 10) ); // 2 in power 10 = 1024

There are more functions and constants in Math object, including trigonometry, which you can find in the docs for the Math object.

Summary

To write numbers with many zeroes:

let billion = 1e9;  // 1 billion, literally: 1 and 9 zeroes

alert( 7.3e9 );  // 7.3 billions (same as 7300000000 or 7_300_000_000)
Example:

Follow the lesson from Microsoft Web-Dev-For-Beginners course

Tags: number