I wrote an article in November 2009 titled Faster isNaN() and a followup to it titled Even Faster isNaN() and continue to get comments on both, so today I’m doing a followup to bring together both articles and the many comments on them into one definitive article. (UPDATE: added Windows performance results)

In the first article, I derived a method of testing a Number against NaN that made use of the fact that no comparison operator (e.g. >, <) will evaluate to true when one of the operands is NaN. Here is that version:

!(val <= 0) && !(val > 0)

For the rest of the article, I'll refer to the above algorithm as the old algorithm. The new algorithm is much more simple:

val != val

This version was pointed out in a comment by skyboy and I have since seen this elsewhere. I'll refer to this version as the new algorithm. The reason it works is that, as with the old algorithm, it exploits the fact that no comparison operator will evaluate to true when one of the operands is NaN. Since both operands to the != operator are the same, there are only two cases: both NaN and neither NaN. So when val is NaN, this expression is true and we have therefore created another isNaN test.

The reverse of this is pretty simple, as pointed out in the comments on the last article:

val == val

This works for exactly the same reason that the isNaN works. Since thee only two cases are both NaN and both not NaN and NaN == NaN is false because, again, any operator will be false when one of its operands is NaN.

Next is another variant, also pointed out by skyboy, that will test for NaN or Infinity. The built-in isNaN does not check for Infinity, so this is an extra feature.

(n * 0) != 0

You can also take the reverse of this:

(n * 0) == 0

For the purposes of making utility functions (that you may inline with a tool like Apparat), you'll probably want to create both a isNaN and isNaNOrInfinity as well as a isNotNaN and isNotNaNOrInfinity so you can avoid the extra ! operator that you'd get by effectively writing !(val != val) or !((n * 0) != 0). So, here are the utility functions you need:

function isNaN(val:Number): Boolean
{
    return val != val;
}
function isNotNaN(val:Number): Boolean
{
    return val == val;
}
function isNaNOrInfinity(val:Number): Boolean
{
    return (val * 0) != 0;
}
function isNotNaNOrInfinity(val:Number): Boolean
{
    return (val * 0) == 0;
}

Now let's do a test to take a look at the performance of the new algorithm in comparison with the old algorithm and the built-in isNaN:

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class EvenFasterIsNaN extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function EvenFasterIsNaN()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			const NUM_ITERATIONS:int = 100000000;
			var i:int;
			var beforeTime:int;
			var local33:Number = 33;
			var localNaN:Number = NaN;
 
			log("Math.isNaN");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				isNaN(local33);
			}
			log("\t33: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				isNaN(localNaN);
			}
			log("\tNaN: " + (getTimer()-beforeTime));
 
			log("Old Algorithm:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				myOldIsNaN(local33);
			}
			log("\tfunction call 33: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				myOldIsNaN(localNaN);
			}
			log("\tfunction call NaN: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				!(local33 <= 0) && !(local33 > 0)
			}
			log("\tinline 33: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				!(localNaN <= 0) && !(localNaN > 0)
			}
			log("\tinline NaN: " + (getTimer()-beforeTime));
 
			log("New Algorithm:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				myNewIsNaN(local33);
			}
			log("\tfunction call 33: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				myNewIsNaN(localNaN);
			}
			log("\tfunction call NaN: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				local33 != local33;
			}
			log("\tinline 33: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				localNaN != localNaN;
			}
			log("\tinline NaN: " + (getTimer()-beforeTime));
		}
 
		private function myOldIsNaN(val:Number): Boolean
		{
			return !(val <= 0) && !(val > 0);
		}
 
		private function myNewIsNaN(val:Number): Boolean
		{
			return val != val;
		}
	}
}

The results I get on a 2.4 Ghz Intel Core i5 running Flash Player 10.1.102.64 on Mac OS X 10.6 are:

Algorithm Function Call Inline
isNaN 1993 1994 n/a n/a
Old Algorithm 950 944 218 210
New Algorithm 735 736 218 218

And here are the results I get on a 2.8 Ghz Intel Xeon W3530 running Flash Player 10.1.102.64 on Windows 7:

Algorithm Function Call Inline
isNaN 1519 1516 n/a n/a
Old Algorithm 893 889 203 206
New Algorithm 787 781 207 209

As you can see, the new algorithm is indeed quicker in its function call form. However, these speed gains are eliminated when the test is inlined. Still, the simplicity of the new algorithm is a nice improvement to have. If you're keeping your isNaN replacement in a function though, you should really go with the new algorithm as it is nearly 3x faster than the built-in isNaN. This is, as the title of this article states, even faster than the 2x speedup we got with the old algorithm.

One final note regarding a comment on the first article: the new algorithm does work with untyped variables:

var a:* = 33;
var b:* = NaN;
log(a != a); // false
log(b != b); // true