JavaScript: When a Number is not a Number

And it hit me again: JavaScripts loose typing that isn’t very loose when you think it is.

I wrote some functions, for example

Number.prototype.f = function(){
  var k = this;
   ...
  if(k.isInt())
    ...
};

The function isInt is the polyfill for ECMAScript 6 Number.isInteger as a Number prototype. Inside this prototype is a check for NaN and finiteness. It’s a very simple thing:

Number.prototype.isOk = function(){
  return ( !isNaN(this) && Number.isFinite(this) )?true:false;
};

Number.isFinite is a build-in to Firefox now and it is also the culprit here, it returns false even if it gets a good, finite number. There’s also a polyfill over at developers.mmozilla.org, so with a little name changing, I was able to find the problems.
The polyfill:

// Number.isFinite polyfill
// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isfinite
if (typeof Number.isFinite2 !== 'function') {
    Number.isFinite2 = function isFinite2(value) {
        // 1. If Type(number) is not Number, return false.
        if (typeof value !== 'number' ){
            return false;
        }
        // 2. If number is NaN, +∞, or βˆ’βˆž, return false.
        if ( value !== value || value === Infinity || value === -Infinity) {
            return false;
        }
        // 3. Otherwise, return true.
        return true;
    };
}

The summary to the function Number.isFinite at MDN states

In comparison to the global isFinite function, this method doesn’t forcibly convert the parameter to a number. This means only values of the type number, that are also finite, return true.

That’s what I wanted, no isFinite("123") returning true, good!

At least that’s what I thought.

You might have guessed it already: the “Number is an Object and not a Literal” train hit me again πŸ˜‰

But that’s why I wrote xtypeof. Here in its basic version:

function xtypeof(obj){
  var tmp = typeof obj;
  if( tmp !== "object"){
    return tmp;
  }
  else{
    var toString = Object.prototype.toString;
    tmp = toString.call(obj);
    if(tmp !== "[object Object]"){
      return tmp.slice(8, -1).toLowerCase();
    }
    return "object";
  }
}

So changing to that and all is well and peaceful again?

Nope, of course not. I found out, after some fiddling, that the clever way to test for NaN value !=== value isn’t so clever at all. After exchanging it with the global isNaN() it works as expected. Expected by me, that is πŸ˜‰

Number.isFinite2 = function(value) {
  // 1. If Type(number) is not Number, return false.
  if (xtypeof(value) !== 'number' ){
    return false;
  }
  // 2. If number is NaN, +∞, or βˆ’βˆž, return false.
  if ( isNaN(value) || value === Infinity || value === -Infinity) {
    return false;
  }
  // 3. Otherwise, return true.
  return true;
};

There is also a problem with the global isNaN() (not really a problem, I think) but that get’s caught by the xtypeof check.

A standard conforming check for IEEE-754 NaN would be the following:

// Shamelessly stolen from the SunPro code
/*
 * ====================================================
 * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
 *
 * Developed at SunPro, a Sun Microsystems, Inc. business.
 * Permission to use, copy, modify, and distribute this
 * software is freely granted, provided that this notice
 * is preserved.
 * ====================================================
 */
function isnan(x){
  var hx,lx;
  var double_int = new DataView(new ArrayBuffer(8));
  // needs nevertheless a check if it is really a number
  double_int.setFloat64(0, x);
  hx = double_int.getInt32(0);
  lx = double_int.getInt32(4);
  hx &= 0x7fffffff;
  hx |= (lx|(-lx))>>>31;
  hx = 0x7ff00000 - hx;
  return (hx>>>31)|0;
}
Number.prototype.foo = function(){
  var a = this;
  return isnan(a);
};

You don’t need a check if you use it this way, because a Number is always a number πŸ˜‰
Test:

(Number.NaN).foo()        /* 1 */
(123).foo()               /* 0 */
("123").foo()             /* throws exception: "123".foo is not a function */
Math.sqrt(-1).foo()       /* 1 */ /* IEEE 754 7.2g */
(0/0).foo()               /* 1 */ /* IEEE 754 7.2e */
(Infinity/Infinity).foo() /* 1 */ /* IEEE 754 7.2e */
(1/0).foo()               /* 0 */ /* IEEE 754 7.3  */

Yes, the last one is correct, too. Division by zero returns Infinity with the sign set as if it is an ordinary, finite rational (e.g.: if q/r -Infinity) as ruled by IEEE-754 and ECMAScript says to be in concordance to the standard.

If you watch the blogosphere you will see a lot of rants about ECMAScript’s isNaN but the single problem is the automatic conversion such that isNaN("123") returns false. That is a known problem of the principal design of JavaScript from the early days on and it is hard to get rid off now but with the to-come Number.isNaN you’ll get at least a work-around.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s