## Definitive isNaN()

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

#1 by Hannes on December 20th, 2010 · | Quote

doesn’t this break when there are getters with side effects involved?

#2 by jackson on December 20th, 2010 · | Quote

It’s not the

`isNaN`

that’s broken, it’s any kind of macro expansion in that case. For example, a simple function to double the input value would be broken:#3 by

Henke37on December 20th, 2010 · | QuoteYes, yes it does, if you use the inline variant.

But it was broken to begin with. Getters should not have side effects.

#4 by skyboy on December 20th, 2010 · | Quote

Per the source code of the AVM,

`(n * 0) == 0`

is actually a replacement for isFinite rather than isNaN:http://hg.mozilla.org/tamarin-central/file/fbecf6c8a86f/core/Toplevel.cpp#l858

#5 by

erikon April 8th, 2011 · | QuoteI think the code for two functions under “So, here are the utility functions you need:” need “n” changed to “val”?

#6 by jackson on April 8th, 2011 · | Quote

Good catch! I’ve updated the article.

#7 by skyboy on October 8th, 2011 · | Quote

I optimized the old variant with bitwise operators and int conversions to get this; doubled up on the results, same algo behind them:

1ms per 100,000,000 over != and isNaN

~27% improvement over && on older machines

#8 by skyboy on October 8th, 2011 · | Quote

(I hate that anti-HTML filter.)

#9 by

Daniel Sigurdssonon December 13th, 2011 · | QuoteThis also works for the inverse of isNaN

In cases where it is very slow to get the value of val (e.g. in cases of getter functions) this should be an even faster approach.

#10 by

Frankon December 3rd, 2013 · | QuoteThese results are still valid for AIR 3.9. Just stumbled across this very easy performance optimisation and gained a whopping 10% throughout our application. Pretty significant improvement for such a small thing. Thanks you very much for this!