Numbers in JavaScript

In JavaScript, there are several number types that you can work with. The most common number types are:

  1. Integer: Integers are whole numbers without a fractional component.
  2. Floating-Point Number: Also known as "float" or "double," floating-point numbers are numbers with a fractional component.
  3. NaN: Stands for "Not-a-Number." It represents an unrepresentable or undefined value resulting from an invalid operation.
  4. Infinity: Represents positive infinity, which is the result of a mathematical operation that exceeds the largest representable number.
  5. Negative Infinity: Represents negative infinity, which is the result of a mathematical operation that exceeds the lowest representable number.


Integer

The simplest way to represent a number in JavaScript is using the decimal integer format, which can be directly entered as shown below:

let intNum = 55; // integer

Integers can be represented in JavaScript using octal (base 8) or hexadecimal (base 16) literals. Octal literals start with a zero (0) followed by a sequence of octal digits (0 through 7). If a number outside this range is encountered in the literal, the leading zero is ignored, and the number is treated as a decimal, as shown in the following examples:

let octalNum1 = 070; // octal for 56
let octalNum2 = 079; // invalid octal - interpreted as 79
let octalNum3 = 08; // invalid octal - interpreted as 8

To represent a hexadecimal literal in JavaScript, you simply start with '0x' (case-insensitive), followed by any number of hexadecimal digits (0 through 9 and A through F). The letters can be in uppercase or lowercase. Here's an example:

let hexNum1 = 0xA;  // hexadecimal for 10
let hexNum2 = 0x1f; // hexadecimal for 31


Floating-Point Number

To represent a floating-point value in JavaScript, you need to include a decimal point and at least one number after it. While it's not necessary to have an integer before the decimal point, it's recommended. Here are some examples:

let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // valid, but not recommended

Since storing floating-point values requires twice as much memory as storing integers, JavaScript always tries to convert values into integers whenever possible. If there are no digits after the decimal point, the number is treated as an integer. Similarly, if the number being represented is a whole number (e.g., 1.0), it will be converted into an integer. Here's an example:

let floatNum1 = 1.; // missing digit after decimal - interpreted as integer 1
let floatNum2 = 10.0; // whole number - interpreted as integer 10

To represent very large or very small numbers in JavaScript, you can use e-notation. E-notation is a way to show a number multiplied by 10 raised to a certain power. In JavaScript, e-notation follows the format of a number (either integer or floating-point) followed by the letter E (uppercase or lowercase), and then the power of 10 to multiply by. Take a look at this example:

let floatNum = 3.459e7; // equal to 34590000

In this example, floatNum is equal to 34,590,000 even though it is represented in a more compact form using e-notation. The notation essentially says, "Take 3.459 and multiply it by 107".

E-notation is also handy for expressing very small numbers, like 0.00000000000000003, which can be written more concisely as 3e-17.


You should avoid comparing floating-point numbers directly due to potential precision errors caused by their binary representation. These precision errors arise from the fact that certain decimal numbers cannot be precisely represented in binary form. Let's consider a simple example:

let num1 = 0.1;
let num2 = 0.2;
let sum = num1 + num2;

console.log(sum); // Output: 0.30000000000000004
console.log(sum == 0.3); // Output: false

In this example, the variables num1 and num2 represent the floating-point numbers 0.1 and 0.2, respectively. When we add these numbers together and store the result in the variable sum, the actual result is a slightly imprecise value: 0.30000000000000004.

Now, if we compare sum to the decimal number 0.3 using the equality operator ==, the result will be false, even though mathematically, they should be equal.



Range of Values

In JavaScript, the smallest number that can be stored is represented by Number.MIN_VALUE, which is approximately 5e–324 on most browsers. On the other hand, the largest number is represented by Number.MAX_VALUE, which is approximately 1.7976931348623157e+308 on most browsers. If a calculation yields a number that falls outside JavaScript's numeric range, it is automatically assigned the special value of Infinity.

Negative numbers that exceed the representable range are denoted as -Infinity (negative infinity), while positive numbers beyond the range are simply denoted as Infinity (positive infinity). You can easily obtain the values of positive and negative Infinity in JavaScript by using Number.NEGATIVE_INFINITY and Number.POSITIVE_INFINITY.

If a calculation results in either a positive or negative Infinity, you cannot use that value for any more calculations because Infinity doesn't have a numeric representation suitable for further calculations. To check if a value falls within the valid numeric range (between the minimum and maximum values), you can use the isFinite() function. This function will only return true if the argument is within the valid range, as demonstrated in this example:

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); // false


NaN

In JavaScript, there's a special numeric value called NaN, which stands for "Not a Number". It's used to indicate when an operation that should return a number fails, instead of throwing an error. For instance, dividing any number by 0 usually leads to an error in many other programming languages, stopping the code. However, in JavaScript, dividing a number by 0 will result in NaN, which allows the rest of the processing to continue without halting the code execution.

The value NaN has two unique characteristics. First, if you perform any operation involving NaN (like NaN divided by 10), the result will always be NaN. Second, NaN is not equal to any other value, even NaN itself. For instance, the following comparison will return false:

console.log(NaN == NaN); // false

To handle this situation, JavaScript offers the isNaN() function. This function takes one argument, which can be any type of data, and checks if the value is "not a number". When a value is passed to isNaN(), it tries to convert it into a number. Some non-numeric values can be directly converted to numbers, like the string "10" or a Boolean value. However, if the value cannot be converted into a number, the function returns true. Let's look at an example:

console.log(isNaN(NaN));    // true
console.log(isNaN(10));     // false - 10 is a number
console.log(isNaN("10"));   // false - can be converted to number 10
console.log(isNaN("blue")); // true - cannot be converted to a number
console.log(isNaN(true));   // false - can be converted to number 1


Number Conversions

There are three functions to turn non-numeric values into numbers: Number() casting function, parseInt() function, and parseFloat() function. The Number() function can be used on any data type, while the other two functions are specifically used to convert strings into numbers. Each of these functions reacts differently to the same input.

The Number() function converts values according to these rules:

  • If the input is a Boolean value (true or false), true is converted to 1, and false is converted to 0.
  • If the input is already a number, it returns the same number
  • If the input is null, it returns 0
  • If the input is undefined, it returns NaN
  • If the input is a string, the following rules are applied:
    • If the string contains only numbers, with or without a plus or minus sign in front, it will always be converted to a decimal number. For example, Number("1") becomes 1, Number("123") becomes 123, and Number("011") becomes 11 (note that leading zeros are ignored)
    • If the string contains a valid floating-point format, like "1.1", it will be converted into the correct floating-point numeric value (once again, leading zeros are ignored)
    • If the string contains a valid hexadecimal format, such as "0xf", it is converted into an integer that matches the hexadecimal value.
    • If the string is empty (contains no characters), it is converted to 0
    • If the string contains anything other than these previous formats, it is converted into NaN
  • When applied to objects, the valueOf() method is called and the returned value is converted based on the previously described rules. If that conversion results in NaN, the toString() method is called and the rules for converting strings are applied.
let num1 = Number("Hello world!");  // NaN
let num2 = Number("");              // 0
let num3 = Number("000011");        // 11
let num4 = Number(true);            // 1

Due to the complexities of the Number() function when converting strings, the parseInt() function is usually a better choice, especially for dealing with integers. The parseInt() function carefully examines the string to determine if it matches a number pattern. It ignores any leading white spaces until it encounters the first non-white space character. If this first character is not a number, a minus sign, or a plus sign, parseInt() always returns NaN. This means that the empty string will return NaN (unlike Number(), which returns 0).

On the other hand, if the first character is a number, plus, or minus sign, the conversion continues to the second character and goes on until it reaches the end of the string or encounters a non-numeric character. For example, "1234blue" is converted to 1234 because "blue" is completely ignored. Similarly, "22.5" will be converted to 22 because the decimal is not a valid integer character.

The parseInt() function also recognizes the various integer formats (decimal, octal, and hexadecimal). This means when the string begins with "0x", it is interpreted as a hexadecimal integer; if it begins with "0" followed by a number, it is interpreted as an octal value.

Let's look at some examples of conversions to better understand what happens:

let num1 = parseInt("1234blue");    // 1234
let num2 = parseInt("");            // NaN
let num3 = parseInt("0xA");         // 10 - hexadecimal
let num4 = parseInt(22.5);          // 22
let num5 = parseInt("70");          // 70 - decimal
let num6 = parseInt("0xf");         // 15 - hexadecimal

To make things less confusing, parseInt() offers a second argument called "radix", which specifies the number system used in the value you're parsing. For example, if you know that the value is in hexadecimal format, you can pass 16 as the radix to ensure proper parsing. Here's an illustration:

let num = parseInt("0xAF", 16); // 175

By providing the hexadecimal radix (16), you can skip the "0x" prefix, and the conversion will still work correctly. Here's how it goes:

let num1 = parseInt("AF", 16); // 175
let num2 = parseInt("AF");     // NaN

In the first case, when using "AF" with radix 16, parseInt() converts it to the decimal number 175 successfully. However, in the second case, since the radix is not specified, parseInt() cannot determine the correct conversion and returns NaN (Not a Number).

Passing in a radix can greatly change the outcome of the conversion. Consider the following:

let num1 = parseInt("10", 2);   // 2 - parsed as binary
let num2 = parseInt("10", 8);   // 8 - parsed as octal
let num3 = parseInt("10", 10);  // 10 - parsed as decimal
let num4 = parseInt("10", 16);  // 16 - parsed as hexadecimal

It's a good idea to include a radix when using parseInt() because if you leave it off, parseInt() will decide how to interpret the input on its own, which can lead to errors.


The parseFloat() function works similarly to parseInt(), starting at the beginning of the string and analyzing each character. It keeps parsing the string until it reaches the end or encounters an invalid character for a floating-point number. When parsing, the first occurrence of a decimal point is valid, but if there's a second decimal point, it becomes invalid, and everything after it is ignored. For example, "22.34.5" will be converted to 22.34.

Another difference in parseFloat() is that initial zeros are always ignored. This function will recognize any of the floating-point formats discussed earlier, as well as the decimal format (leading zeros are always ignored). Hexadecimal numbers always become 0. Because parseFloat() parses only decimal values, there is no radix mode.

Additionally, if the string represents a whole number (no decimal point or only a zero after the decimal point), parseFloat() returns an integer. Let's see some examples to illustrate these points:

let num1 = parseFloat("1234blue");  // 1234 - integer
let num2 = parseFloat("0xA");       // 0
let num3 = parseFloat("22.5");      // 22.5
let num4 = parseFloat("22.34.5");   // 22.34
let num5 = parseFloat("0908.5");    // 908.5
let num6 = parseFloat("3.125e7");   // 31250000