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():
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 · | Quote
damn, Adobe can’t even make their own stuff right
#2 by dVyper on November 11th, 2009 · | Quote
Nicely done! :)
#3 by Jonnie on November 11th, 2009 · | Quote
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 · | Quote
Stay tuned for Friday’s article! (and see my article on Math.ceil() in the meantime)
#5 by Simon Altschuler on November 11th, 2009 · | Quote
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 · | Quote
isNaN(undefined) also returns true. Am I missing something? :)
#7 by Simon Altschuler on November 11th, 2009 · | Quote
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 · | Quote
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 · | Quote
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 · | Quote
Aha, I see. Thanks! :-)
#11 by Jonnie on November 11th, 2009 · | Quote
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 · | Quote
Thanks for filing this. I voted for it and attached the test source code.
#13 by Philippe on November 15th, 2009 · | Quote
And what about: if (v === NaN)
#14 by jackson on November 15th, 2009 · | Quote
All comparisons with NaN are false. See my article Details of NaN.