Explicit type conversion

Explicit type conversion

In many cases JS automatically convert values into appropriate data types if necessary. Sometimes however, you will run into a case where you need to explicitly convert a value to an appropriate type. In figure 1 two numbers, given by the user through a HTML form, need to be added. The output value however, is a concatenation of the two numbers as strings instead of an addition of numbers. To make this little application work properly, the input values need to be explicitly converted to numbers first. The example in figure 1 contains JavaScript you may not be familiar with yet. This example just serves to point out that explicit conversion to numbers is often needed when performing mathematical operations on values from a string-based source, such as HTML form elements.


<form>
	<input name="firstNumber" type="number" value="1" />
	<span>+</span>
	<input name="secondNumber" type="number" value="1" />
	<span>=</span>
	<output name="outputNumber" for="firstNumber secondNumber"></output>
</form>  
<script>
	'use strict';
	const formElements = document.querySelector("form").elements;
	const firstNumber = formElements["firstNumber"];
	const secondNumber = formElements["secondNumber"];
	const outputNumber = formElements["outputNumber"];
	function outputFunction() { outputNumber.value = firstNumber.value + secondNumber.value; }
	
	outputFunction();
	firstNumber.addEventListener('input', outputFunction);
	secondNumber.addEventListener('input', outputFunction);
</script>	

Result:

+ =
Fig.1 - Two input numbers are concatenated as strings, instead of being added as numbers. To fix this, firstNumber.value and secondNumber.value need to be explicitly converted to numbers before adding.

The shortest way to convert a non-number to a number is to use the unary plus operator: +true === 1. Another way is to multiply by 1: (1 * "") === 0 (or divide by 1 or subtract zero or...). In fact, both of these operations use JavaScript's implicit type conversion, or type coercion, as described previously. So, explicit conversion using implicit conversion 🙄. Also bitwise operators coerce to numbers, so you can also use methods like ~~x or x>>0 to explicitly convert x to a number.

To convert a non-boolean value to a boolean value you can use a double NOT operator (!!).


console.log(+"   10   "); // logs: 10
console.log(1 * null); // logs: 0

console.log(!!1); // logs: true

The general way to explicitly convert primitive values is to use the wrapper object constructor functions. But do not use instances of wrapper objects to convert values. Converting a value to a number is done by using Number(value), converting to a boolean is done by Boolean(value) etc.


let myNum = new Number("2"); // do not use this!

console.log(Number("2")); // logs: 2
console.log(Number("   10   ")); // logs: 10
console.log(Number("1  0")); // logs: NaN
console.log(Number("hello")); // logs: NaN
console.log(Number("10g")); // logs: NaN
console.log(Number("")); // logs: 0
console.log(Number("123e-2"))  // logs: 1.23
console.log(Number("-Infinity"))  // logs: -Infinity // typeof -Infinity is number!
console.log(Number("null")); // logs: NaN
console.log(Number(null)); // logs: 0
console.log(Number(undefined)); // logs: NaN
console.log(Number(true)); // logs: 1
console.log(Number(false)); // logs: 0

console.log(Number('' + null)); // logs: NaN // ('' + null) returns "null" as a string which is then converted to a number, resulting in NaN.
console.log(Number('2' + 2)); // logs: 22 // String 22 converts to number 22.
console.log(Number("" + "")); // logs: 0 // ("" + "") returns an empty string which then converts to 0.

// The falsy values:
console.log(Boolean(0)); // logs: false
console.log(Boolean(0n)); // logs: false
console.log(Boolean("")); // logs: false
console.log(Boolean(null)); // logs: false
console.log(Boolean(undefined)); // logs: false
console.log(Boolean(NaN)); // logs: false

console.log(Boolean(1)); // logs: true
console.log(Boolean("0")); // logs: true
console.log(Boolean(1e100)); // logs: true
console.log(Boolean('hello')); // logs: true

console.log(String(true)); // logs: "true" // a string.
console.log(String(null)); // logs: "null" // a string.

console.log(BigInt(55)); // logs: 55n

Converting to strings

A (less reliable) alternative to the String() constructor function is the toString() method that both the number and boolean wrapper objects provide (see chapter Object to primitive conversion). Method toString() takes an optional parameter to indicate a radix or base of a numeral system. This way toString() can also be used to convert base 10 numbers to numeral representations in an another base, returned as a string.


console.log(15.toString()); // logs: SyntaxError // 15 is conceived as an identifier, and identifiers cannot start with a digit.	
	
let varToStr = 3.14;	
console.log(varToStr.toString()); // logs: "3.14" // as a string.
varToStr = true;
console.log(varToStr.toString()); // logs: "true" // as a string.
varToStr = null;
console.log(varToStr.toString()); // logs: TypeError // null has no wrapper object constructor and thus no toString() method.
varToStr = undefined;
console.log(varToStr.toString()); // logs: TypeError // undefined also has no properties or methods.

varToStr = 10;
console.log(varToStr.toString(2)); // logs: "1010" // converted to a binary (base 2) number, returned as a string.

Converting to numbers

Explicit number conversion using Number() or unary + also converts non-negative binary, octal and hexadecimal integers into decimal integers. In addition, it converts numbers containing underscores (numeric separators) and numbers represented in the scientific e-notation into the plain decimal number format (if possible).


console.log(+0xFACE); // logs: 64206
console.log(Number(0xFACE)); // logs: 64206
console.log(+0b11111111)  // logs: 255
console.log(Number(0b11111111)); // logs: 255
console.log(+0o144)  // logs: 100
console.log(Number(0o144))  // logs: 100
console.log(+1_234.56)  // logs: 1234.56
console.log(Number(1_234.56))  // logs: 1234.56
console.log(+123e-2)  // logs: 1.23
console.log(Number(123e-2))  // logs: 1.23

console.log(+123e19)  // logs: 1.23e+21
console.log(Number(123e19))  // logs: 1.23e+21

Explicit number conversion returns 0 when converting an empty string ("", including a string with only spaces). In some cases it may not be desirable when an empty string converts to number zero, for instance when a user left an input field open. The same thing holds for converting value null (Number(null) === 0).

To "fix" this you can use something like:


function toNumber(value) {
  if (typeof value === 'number') { return value; } // if value is a number, no conversion
  else if (value === null) { return NaN }
  else if (typeof value === 'string' && value.trim().length === 0) { return NaN; } // check for empty string. trim() removes whitespace from both sides of a string.
  else { return +value; } // in all other cases return the value converted to a number
}
console.log(toNumber("2e10")); // logs: 20000000000
console.log(toNumber("  ")); // logs: NaN
console.log(toNumber("123p")); // logs: NaN
console.log(toNumber(null)); // logs: NaN
console.log(toNumber(true)); // logs: 1

The Number object also provides separate methods to convert a string to a number. The method Number.parseFloat() converts its argument to a number (which in JS is a floating point number), only a little bit different than when using the Number() constructor function.

Number.parseFloat() does return NaN to an empty string and to null. It also converts boolean values (true, false) to NaN. It converts to a number if the first non-whitespace character can be converted to a number, even if following characters are non-digits. In some applications this may be undesirable.


console.log(Number.parseFloat("2e10")); // logs: 20000000000
console.log(Number.parseFloat("  ")); // logs: NaN
console.log(Number.parseFloat("123p")); // logs: 123 // in many applications you would want this to return NaN.
console.log(Number.parseFloat(null)); // logs: NaN
console.log(Number.parseFloat(true)); // logs: NaN

The method Number.parseInt() parses a string argument to an integer (a whole number floating point). It converts the string to a number up until it encounters a character that is not a part of a number. Next it truncates the number to an integer value (it removes any fractional digits). Preceding and succeeding white spaces in a string are ignored. It returns NaN if the (first) argument cannot be converted to a number at all, this includes the empty string "". It also returns NaN if the (first) argument is not a string. An optional second argument is allowed to indicate the radix or base of the numeral system the number should be converted in.


console.log(Number.parseInt("  3.99  ")); // logs: 3
console.log(Number.parseInt("3.01")); // logs: 3
console.log(Number.parseInt(" -15.99 ")); // logs: -15

console.log(Number.parseInt("23e100")); // logs: 23 // while 23e100 IS an integer!
console.log(Number.parseInt("23*2")); // logs: 23
console.log(Number.parseInt("-23px")); // logs: -23
console.log(Number.parseInt("$25")); // logs: NaN

console.log(Number.parseInt("Infinity")); // logs: NaN
console.log(Number.parseInt("")); // logs: NaN

console.log(Number.parseInt(NaN)); // logs: NaN
console.log(Number.parseInt(null)); // logs: NaN
console.log(Number.parseInt(undefined)); // logs: NaN
console.log(Number.parseInt(true)); // logs: NaN
console.log(Number.parseInt(false)); // logs: NaN

console.log(Number.parseInt(101, 2)); // logs: 5
console.log(Number.parseInt(102, 3)); // logs: 11

Like Number.parseFloat(), Number.parseInt() converts the string to a number up until it encounters a character that is not a part of a number. In some situations this may be undesirable, in other situations this may be very useful, for example when parsing CSS unit values:


<div id="myContainer" style="width:200px;"></div>

const containerWidth = document.querySelector('#myContainer').style.width;
console.log(containerWidth);               // logs: 200px
console.log(parseInt(containerWidth, 10)); // logs: 200
console.log(Number(containerWidth));       // logs: NaN

Number.parseInt() has a nasty quirk. Truncating large or small numbers (actual numbers instead of strings) may produce unexpected results.


console.log(Number.parseInt("0.0000004")); // logs: 0
console.log(Number.parseInt(0.0000004)); // logs: 4 // incorrect truncation

console.log(Number.parseInt("1000000000000000000000")); // logs: 1e+21
console.log(Number.parseInt(1000000000000000000000)); // logs: 1 // incorrect truncation

The Math object provides better options for truncating or rounding floating point numbers to integers. They coerce parameters into a number. Method Math.trunc() truncates a number to an integer. Methods Math.floor() and Math.ceil() also round-off to an integer, but in different ways than Math.trunc().


function toInt(value) {
  if (typeof value === 'string' && value.trim().length === 0) { return NaN; } // check for empty string. trim() removes whitespace from both sides of a string.
  else { return Math.trunc(value); } // in all other cases return the value converted to a number and truncate.
}

console.log(toInt("15e2")); // logs: 1500
console.log(toInt("15px")); // logs: NaN //!
console.log(toInt("")); // logs: NaN // !
console.log(toInt("  ")); // logs: NaN // !
console.log(toInt("Infinity")); // logs: Infinity // !
console.log(toInt("1000000000000000000000")); // logs: 1e+21
console.log(toInt(1000000000000000000000)); // logs: 1e+21

console.log(toInt(null)); // logs: 0 // !
console.log(toInt(undefined)); // logs: NaN
console.log(toInt(true)); // logs: 1 // !
console.log(toInt(false)); // logs: 0 // !

console.log(toInt("  -3.99  ")); // logs: -3
console.log(toInt(3.99)); // logs: 3

console.log(toInt("-3.99")); // logs: -3
console.log(Math.floor("-3.99")); // logs: -4
console.log(Math.ceil("-3.99")); // logs: -3

console.log(toInt("3.99")); // logs: 3
console.log(Math.floor("3.99")); // logs: 3
console.log(Math.ceil("3.99")); // logs: 4

Validating numbers

In many cases you need to check that a value is a valid number. You can easily do this by using if (typeof value === 'number') { }. However, also value NaN (is of data type number) would validate in this case. We can 'fix' this by using a function like:


// function returns true or false:
function isValidNumber(value) {
  return typeof value === 'number' && !Number.isNaN(value);
}

console.log(isValidNumber("NaN")); // logs: false
console.log(isValidNumber(NaN)); // logs: false
console.log(isValidNumber(0/0)); // logs: false
console.log(isValidNumber(null)); // logs: false
console.log(isValidNumber(true)); // logs: false
console.log(isValidNumber(2.5e6)); // logs: true
console.log(isValidNumber(undefined)); // logs: false
console.log(isValidNumber(-Infinity)); // logs: true

Additionally we likely want to check if the number is finite as well. The Number.isFinite() method returns false if the argument is not a number, if it is (positive or negative) Infinity, or if it is NaN. In all other cases it returns true. We can replace the return value of the function in the above example by Number.isFinite():


function isValidNumber(value) {
  return Number.isFinite(value);
}

console.log(isValidNumber("NaN")); // logs: false
console.log(isValidNumber(NaN)); // logs: false
console.log(isValidNumber(0/0)); // logs: false
console.log(isValidNumber(null)); // logs: false
console.log(isValidNumber(true)); // logs: false
console.log(isValidNumber(2.5e6)); // logs: true
console.log(isValidNumber(undefined)); // logs: false
console.log(isValidNumber(-Infinity)); // logs: false

BTW: Of course it is also possible to directly use Number.isFinite(value);. However, wrapping it in a function isValidNumber is clearer and more sustainable. It makes it clear that you are validating some input and when in the future validation requires some additional check, you can simply add it to the function isValidNumber, instead of replacing Number.isFinite(value) everywhere it is used.

In other instances we may need to check if a value (e.g. user input) is an integer. To check if a number is an integer we can use Number.isInteger(). Number.isInteger() always returns false if its argument is not a number, including NaN and Infinity.


console.log(Number.isInteger(2099999999999999)); // logs: true
console.log(Number.isInteger(1/2)); // logs: false
console.log(Number.isInteger("15")); // logs: false
console.log(Number.isInteger(" ")); // logs: false
console.log(Number.isInteger(15e2)); // logs: true
console.log(Number.isInteger(-0)); // logs: true
console.log(Number.isInteger(NaN)); // logs: false
console.log(Number.isInteger(true)); // logs: false
console.log(Number.isInteger(null)); // logs: false
console.log(Number.isInteger(undefined)); // logs: false
console.log(Number.isInteger(-Infinity)); // logs: false

Summarised

Number conversion & validation


function toNumber(value) {
  // conversion only needed if data type is not a number:
  if (!(typeof value === 'number')) {
    // optional exceptions to standard conversion:  
    if (value === null) { value = NaN } // check for null.
    else if (typeof value === 'string' && value.trim().length === 0) { value = NaN; } // check for empty string. trim() removes whitespace from both sides of a string.
    //
	else { value = +value; } // standard conversion in all other cases
  }
  if (Number.isFinite(value)) { return value; } // if valid number, return value
  else { throw new TypeError("Input cannot be converted into a number."); }
}

function printNumber(num) {
  num = toNumber(num);
  console.log(num);
}

printNumber(13); // logs: 13
printNumber("2.5e6"); // logs: 2500000
printNumber(0/0); // TypeError: Input cannot be converted into a number.
printNumber(true); // logs: 1 // It does convert booleans!
printNumber(null); // TypeError: Input cannot be converted into a number.
printNumber(undefined); // TypeError: Input cannot be converted into a number.
printNumber(" "); // TypeError: Input cannot be converted into a number.
printNumber(Infinity); // TypeError: Input cannot be converted into a number.

function isValidNumber(value) {
  return Number.isFinite(value);
}

Integer conversion


function toInt(value) {
  // optional exceptions to standard conversion:
  if (typeof value === 'string' && value.trim().length === 0) { value = NaN; } // check for empty string. trim() removes whitespace from both sides of a string.
  if (typeof value === 'boolean') { value = NaN; } // check for boolean values true and false
  else if (value === null) { value = NaN } // check for null.
  //
  else { value = Math.trunc(value); } // in all other cases conversion using trunc().  
  if (Number.isInteger(value)) { return value } 
  else { throw new TypeError("Input cannot be converted into an integer."); }
}

function printNumber(num) {
  num = toInt(num);
  console.log(num);
}

printNumber(2099999999999999); // logs: 2099999999999999
printNumber(1/2); // logs: 0
printNumber("15"); // logs: 15
printNumber(15e2); // logs: 1500
printNumber(-0); // logs: -0
printNumber(" "); // TypeError: Input cannot be converted into an integer.
printNumber(NaN); // TypeError: Input cannot be converted into an integer.
printNumber(true); // TypeError: Input cannot be converted into an integer.
printNumber(null); // TypeError: Input cannot be converted into an integer.
printNumber(undefined); // TypeError: Input cannot be converted into an integer.
printNumber(-Infinity); // TypeError: Input cannot be converted into an integer.