DEV Community

Cover image for JavaScript In Depth - isFinite & IsNaN Functions
Kobby Owen
Kobby Owen

Posted on

JavaScript In Depth - isFinite & IsNaN Functions

Javascript is not a language for the unwary - Kobby Owen

Just before you stop reading, I know what you are thinking! "Who would read an article about these simple functions ?. These are basic functions every beginner of Javascript can quickly master, and easily learn to use. There is no need for an article about this!". While this is true, their behavior may be somewhat surprising, especially when dealing with non-number arguments. Learning more about their implementation will give you an in-depth knowledge of JavaScript and its core implementation.

If you can answer the following questions, then you may stop reading this article. If not, I suggest you keep reading, since you will learn a lot from studying these functions and their implementation.

  1. Why is isNaN(new Date()) false and isNaN(Date()) true
  2. Why is isFinite(null) true and isFinite(undefined) false
  3. Why is isFinite(" ") true and isFinite("a") false
  4. Why is isNaN("Infinity") true and isNaN("infinity") false
  5. Under what conditions do isNaN and isFinite throw a TypeError
  6. What is the value of isNaN(" 23 "), isNaN(23), isNaN(" 23." ), isNaN("12 .")

isFinite function determines if the passed argument is finite value. It checks if its argument is not NaN, or negative infinity or positive positive infinity.

isNaN on the other hand determines whether the argument passed is a NaN or not. This function is necessary because of the nature of NaN. NaN is the only floating point value that does not compare to itself. This behavior is so true that, ECMAScript documentation suggests that one of the reliable ways to check for NaN is the expression(x === x), which returns false only if x is a NaN.
Mostly, to determine if a number is okay to be used in arithmetic operation without few surprises, you should find yourself using isFinite more than isNaN, since isFinite checks for NaN values and goes on to check for infinite values. In cases where infinite values are legitimately allowed to participate in operations, isNaN will be the best function to use.

Implementation Details

The first thing isNaN and isFinite function does is to try to convert its argument to a Number. This conversion is done using an ECMAScript internal function which is not exposed to the developer. It is this internal function that forms the core of these two functions, and is therefore worth studying. For the purpose of the article, lets call this function ToNumber function. This function is used a lot in ECMAScript behind the scenes. Understanding how it works, will give you a lot of understanding about the results of most operations in JavaScript. In an attempt to explain this internal function, we will use many helper functions and explain other internal methods used in ECMAScript that helps the ToNumber function to perform its conversion. I will use to top down approach to explain the implementation of this function.

ToNumber Internal Function

ToNumber function takes a single argument, which is the argument to convert. To convert the argument, it takes the following step.

  1. If the argument is undefined, it returns NaN
  2. If the argument is null , it returns 0
  3. If the argument is a number, it returns it
  4. If the argument is a BigInt , throw a TypeError
  5. If the argument is a Symbol, throw a TypeError
  6. If the argument is a String, call another internal method(StringToNumber)
  7. If the argument is an Object, call another internal method(ToPrimitive) and pass its result through ToNumber function again.

NB. Step 7 involves 2 steps, it calls a helper function to convert the object to a primitive value, preferably a number, and call the ToNumber function recursively on its return value. The astute reader may reason at this point that this can cause an infinite recursion. That is not the case, because ECMAScript makes sure the return of ToPrimitive is not another object.

Now lets look at the two helper functions used by ToNumber to aid the conversion of its argument.

StringToNumber Internal Function

StringToNumber function simply parses its string argument and converts it to a number. One important thing to note about this function is the kind of input the parser accepts. The parser allows for optional white-space before and after the main string decimal characters. Any invalid character present in the argument, no matter where it is, will cause the parser to return NaN, and consequently the function too. Invalid characters include any character that is not part of the set [+ - E e .]. These valid non decimal characters are however allowed to appear only once. Making it appear twice will cause the function to return NaN. The function however recognizes the "Infinity" and returns the mathematical representation of it. An optional + or - is allowed before the decimal characters. They should however be the first non white-space character, if it exists in the sequence except it is being used before an E or e. An empty string, or a string full of white-space will cause the function to return the number 0. The following examples demonstrates the use of the function.


function StringToNumber( argument ){
    /** implementation code **/
}

StringToNumber(" 23") // 23
StringToNumber(" 23 ") // 23
StringToNumber("+23.5") // 23.5 
StringToNumber("+ 23.5") // NaN ( space after the plus sign)
StringToNumber("-23.5") // -23.5 
StringToNumber("23.2.3") // NaN
StringToNumber("23ab") //NaN
StringToNumber("Infinity") // Infinity 
StringToNumber("-Infinity") // -Infinity
StringToNumber("+Infinity") // Infinity 
StringToNumber("ab") //NaN
StringToNumber("NaN") 
/**NaN ( not because the phrase NaN can be parsed , but because the characters N a N cannot be represented as a number) **/

StringToNumber("23E-14") //23E-14
StringToNumber("23E -14") //NaN ( space after E. )

Enter fullscreen mode Exit fullscreen mode

ToPrimitive Internal Function

The last function to examine before we can proceed is ToPrimitive method. This method takes an input and converts it to a primitive type, basically a number or a string. The function also takes an optional argument called hint. The hint argument can either be [default, number or string]. When the function is called, it first checks if the input is an object. If it is and it defines a Symbol.toPrimitive method, it is called on the object while passing "number" as a hint to the function. If the method returns an object ( null not included ), a TypeError is thrown, otherwise its value is returned. If the object does not define its Symbol.ToPrimitive, it looks for two methods on the object, ie toString and valueOf. If the hint is a number, valueOf is called first, else toString is called first, and the other is called next. When the function to be called first is resolved, it is checked if it exists on the object or any of its bases, if it exists, and its return value when called is not an object, it returns it results. The second function, which is based on the value passed to the hint argument is called next. Its value is returned if is is not an object. If both methods returns an object, a TypeError is thrown by the function.

If you did not understand these functions, here are their implementation in JavaScript( note JavaScript). In a real ECMAScript implementation, these functions are probably implemented in C/C++.


function StringToNumber( argument ){
    const res = argument.trim()
    // return 0 for empty string after stripping space characters
    if ( res.length === 0 ) return 0
    return Number(res)
}

function OrdinaryToPrimitive( input, hint){
    let methodNames = []
    if ( hint === "string" )
        methodNames = ["toString", "toValueOf"]
    else 
        methodNames = ["valueOf", "toString"]

    for ( const name of methodNames) {
        if ( typeof name === "function" ){
            const res = input[name]()
            if ( typeof res !== 'object' || res === null) 
                return res 
        }
    }
    throw TypeError
}

function ToPrimitive( input, hint){

    if ( typeof input === "object" ){
        if ( input[Symbol.toPrimitive] !== undefined ){
            if ( hint === undefined ) hint = 'default'
            const res = input[Symbol.toPrimitive]( hint )
            if ( typeof res !== 'object' || res === null) 
                return res 
            throw TypeError 
        }
        else{
            if ( hint === undefined ) hint = "number"
            return OrdinaryToPrimitive(input, hint)
        }
    }
    return input 



}

function ToNumber( argument ) {

    switch( typeof argument) {
        case 'undefined' : 
            return NaN
        case 'number' : 
            return argument 
        case 'bigint': case 'symbol': 
            throw TypeError 
        case 'string' : 
            return StringToNumber(argument)
        case 'object':{
            if (argument === null ) 
                return 0
            const hint = "number"
            const primitive = ToPrimitive(argument, hint)
            return ToNumber(primitive)  
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

There are a few things to note here. ToPrimitive delegates to another method called OrdinaryToPrimitive if the input provided does not defined Symbol.toPrimitive method.

isNaN and isFinite

Now that we understand these internal functions. Let us go back to our functions.
isNaN first converts its argument to a number using the ToNumber method and checks for NaN. If the result of that conversion is a NaN, true is return otherwise false is returned.
isFinite also first converts its argument to a number using the same ToNumber method. If then proceeds to check if the result of that conversion is not a NaN or -Infinity or Infinity.
There is nothing interesting about these functions apart from the internal method that it calls to convert its argument before checking it. ToNumber internal methods are used by a lot of JavaScript functions including parseInt to convert its radix argument., All functions defined on the global Math object calls the function on its arguments before it starts processing the result, it is used by Date.UTC to convert it parameters into acceptable values and almost all the setter methods on the Date object ( example setHours, setMonth, setYear) and almost all methods and functions that operates with numbers. Understanding how this internal method works will save you from opening your jaws wide while you stare at the screen trying to understand the return values of some functions. Try to take a moment to go through this internal method one more time. Now let us answer the five questions at the beginning of the article, which you should be able to answer if you paid enough attention to it.

Question 1

Why is isNaN(new Date()) false and isNaN(Date()) true

Answer

The result of new Date() is an object. When that object is passed to isNaN as an argument, ToPrimitive is called to convert it to a primitive value, preferably a number. This ends up calling valueOf method on the object and returning its results, which is a number. This number is then checked for NaN , which is ultimately false. The result of Date() on the other hand is a string that represents the current time. This string is passed to StringToNumber internal method by ToNumber. The result is a string that cannot be parsed into a number, thus returning NaN. isNaN proceeds to check the result of this conversion and finds that its NaN and ultimately return true

Question 2

Why is isFinite(null) true and isFinite(undefined) false

Answer

ToNumber converts null to 0 and undefined to NaN, thus the return values of isFinite when called with these two values

Question 3

Why is isFinite(" ") true and isFinite("a") false

Answer

Both arguments are strings, so ToNumber calls StringToNumber internal method on them. Empty strings after trimming white spaces causes the method to return 0. Thus the first isFinite call is the result of checking if 0 is a finite number, which it is. "a" on the other hand returns NaN when converted.

Question 4

Why is isNaN("Infinity") true and isNaN("infinity") false

Answer

StringToNumber recognizes the string "Infinity" , "-Infinity", "-Infinity". It rightly returns Infinity and the result is checked whether its NaN, which ends up being false. Infinity is not NaN.
"infinity" on the other hand is not recognized, neither can it be parsed as a number. It returns NaN as a result of the conversion.

Question 5.

Under what conditions do isNaN and isFinite throw a TypeError

Answer

If their argument in either a BigInt, Symbol or they defined toString and valueOf which both returns an object instead of a primitive value like a string or a number

Question 6.

What is the value of isNaN(" 23 "), isNaN(23), isNaN(" 23." ), isNaN("12 .")

Answer

isNaN(" 23 ") is false
isNaN("23.") is false
isNaN("12 .") is true

Top comments (1)

Collapse
 
programistka profile image
Iwona Kubowicz • Edited

Great article, but I have two concerns:

  1. In relation to "Why is isNaN("Infinity") true and isNaN("infinity") false" shouldn't it be the opposite? Actually isNaN("Infinity") does return false and isNaN("infinity") does return true.
  2. Something is wrong with the statement "StringToNumber recognizes de string "Infinity", "-Infinity", "-Infinity"". What did you really mean here?