Faster isNaN()
You cannot directly check if a value is NaN by comparing with it. AS3, AS2, and JavaScript therefore provide a useful isNaN() function to do this very check. However, it is very slow. Today I’ll show you a workaround that results in a faster isNaN(): (UPDATE: see the definitive article on isNaN for much more!)
Consider this bit of code
!(val <= 0) && !(val > 0)
This is apparently a contradiction since a number cannot be both less than and greater than 0. If you read my article on Details of NaN, you may remember that any comparison with NaN is false. Therefore, if val is false, we will have:
!false && !false
Which is then:
true && true
Which is clearly true. We have therefore devised our own isNaN(). We can encapsulate it cleanly in a function:
function myIsNaN(val:Number): Boolean { return !(val <= 0) && !(val > 0); }
And we can also test its speed:
package { import flash.display.*; import flash.utils.*; import flash.text.*; /** * An app to test improved versions of isNaN() * @author Jackson Dunstan */ public class FastIsNaNTest extends Sprite { public function FastIsNaNTest() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); function log(msg:*): void { logger.appendText(msg + "\n"); } const NUM_ITERATIONS:int = 1000000; var i:int; var beforeTime:int; var local33:Number = 33; var localNaN:Number = NaN; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { isNaN(local33); } log("isNaN(33): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { isNaN(localNaN); } log("isNaN(NaN): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myIsNaN(local33); } log("myIsNaN(33): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myIsNaN(localNaN); } log("myIsNaN(NaN): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { !(local33 <= 0) && !(local33 > 0) } log("inline for 33: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { !(localNaN <= 0) && !(localNaN > 0) } log("inline for NaN: " + (getTimer()-beforeTime)); } private function myIsNaN(val:Number): Boolean { return !(val <= 0) && !(val > 0); } } }
Here are the results:
Environment | isNaN(33) | isNaN(NaN) | myIsNaN(33) | myIsNaN(NaN) | inline for 33 | inline for NaN |
---|---|---|---|---|---|---|
2.2 Ghz Intel Core 2 Duo, 2 GB RAM, Mac OS X 10.6 | 61 | 60 | 12 | 12 | 5 | 5 |
This shows myIsNaN() is five times as fast as isNaN() and an inlined version is twice as fast as that. For math-heavy code in performance-critical areas, this could be a major speed boost!
#1 by Valentin on November 11th, 2009 ·
damn, Adobe can’t even make their own stuff right
#2 by dVyper on November 11th, 2009 ·
Nicely done! :)
#3 by Jonnie on November 11th, 2009 ·
isNaN is one of those functions I never even considered testing. I’m not sure why since it makes so much sense with this simple alternative. I wonder what else is out there that’s faster… I’m sure you already saw them already, but check out the bitwise alternatives to division and the type casting alternatives to Math.ceil/Math.floor.
#4 by jackson on November 11th, 2009 ·
Stay tuned for Friday’s article! (and see my article on Math.ceil() in the meantime)
#5 by Simon Altschuler on November 11th, 2009 ·
Cool! However, theres an issue; myIsNaN(undefined) would also return true. I guess the simplest solution for that would be simply checking if the value is undefined (&& val != undefined). I don’t know if this would be a problem in a real world example, but it seems like that kind of bug that’ll make you go crazy :-)
#6 by jackson on November 11th, 2009 ·
isNaN(undefined) also returns true. Am I missing something? :)
#7 by Simon Altschuler on November 11th, 2009 ·
It might just be me missing the point (or the concept of undefined) :-) but wouldnt you consider it an issue if a function that is to test if a variable is NaN, also returns true if the given variable is undefined (eg an uninstantiated dynamic/untyped class)?
#8 by Simon Altschuler on November 11th, 2009 ·
Okay, I didnt read carefully enough, didnt realize isNaN also does it. Still seems odd to me though!
#9 by jackson on November 11th, 2009 ·
I think it’s because undefined is converted to a Number when isNaN is called since its parameter is a Number rather than a *, which would be required to accept undefined without converting it to null. Converting undefined to a Number results in NaN as this little test shows:
Hope that clears things up.
#10 by Simon Altschuler on November 11th, 2009 ·
Aha, I see. Thanks! :-)
#11 by Jonnie on November 11th, 2009 ·
Jackson, I submitted the issue to Adobe. You can vote it up here: https://bugs.adobe.com/jira/browse/ASC-3916
#12 by jackson on November 11th, 2009 ·
Thanks for filing this. I voted for it and attached the test source code.
#13 by Philippe on November 15th, 2009 ·
And what about: if (v === NaN)
#14 by jackson on November 15th, 2009 ·
All comparisons with NaN are false. See my article Details of NaN.
#15 by skyboy on August 6th, 2010 ·
if you have it strictly typed as a Number, then a != a is by far the fastest comparison for NaN; if it’s not then, only NaN will result in true (because !(NaN == NaN))
public static function isNaN2(a:Number):Boolean {
return a != a;
}
it’; about twice as fast as your test, and should also be faster in 10.1, but i haven’t tested it
#16 by Chen on December 16th, 2010 ·
Aren’t this tests should be compared against a static function that relies on a different namespace? (the question relates to the myCeil function as well)
#17 by jackson on December 16th, 2010 ·
The reason the
Math
functions, includingisNaN
andceil
, are so slow is because they carry the overhead of a static function call. The actual work of testing whether or not aNumber
is NaN is trivial, so inlining it or using a non-static function greatly speeds up the process. I suspect, but haven’t tested, that a staticmyIsNaN
would run about as fast asMath.isNaN
.#18 by skyboy on December 20th, 2010 ·
It actually isn’t in a different namespace, nor is it static, nor is it on the Math class:
http://hg.mozilla.org/tamarin-central/file/fbecf6c8a86f/core/actionscript.lang.as#l64
The global functions, isNaN selected.
http://hg.mozilla.org/tamarin-central/file/fbecf6c8a86f/core/Toplevel.cpp#l841
The C++ representation of global functions, isNaN selected.
http://hg.mozilla.org/tamarin-central/file/fbecf6c8a86f/core/MathUtils.cpp#l200
The actual isNaN function, in a class used everywhere in the AVM, but not directly through AS3.
http://hg.mozilla.org/tamarin-central/file/fbecf6c8a86f/core/MathClass.cpp#l41
The C++ code behind the Math class.
It appears the slow down occurs simply because the test is more complex than necessary, especially on *nix machines (scroll up in MathUtils).
#19 by jackson on December 20th, 2010 ·
My mistake.
isNaN
is not inMath
.These are good links! Assuming that
#ifdef UNIX
includes Mac OS X, it doesn’t seem that the alternate version has much of an effect. I’ve updated the definitive article with Windows 7 performance results, albeit on a different processor, and the results don’t seem wildly better, even though the processor is somewhat faster.#20 by skyboy on December 20th, 2010 ·
Not wildly better, but a 24% improvement, certainly not insignificant. Another CPU architecture would probably have different results as well; The joys of cross-platform programming.
#21 by ducDauge on May 18th, 2016 ·
Hi, for val={} the function returns true