NaNの比較は要注意

先日JavascriptでlocalStorageから読み出した複数の情報を扱っていた時のことです。
localStorageにはキーと値の組み合わせで情報が保存されていますが、値はすべて文字列として保存されているため値の内数値のものだけを数値型に変換し、実際に文字列の物はそのままにするという処理を書いていました。
ざっくり下のような感じです。(localStorageから読み出す部分は省略しています)

//これをストレージから読み込んだデータの列とします。
var fromStorage = ["ABC","5","DEF","0.05","10","G","0"]; 
		
var result = []; //変換してこちらに保存
		
for(var i = 0; i < fromStorage.length; i++){
	result.push( parseFloat(fromStorage[i]) || fromStorage[i] );
}

ところがこの方法には欠点があり、変換したい文字列の中に"0"があると文字列に判定されてしまうことです。parseFloat("0")は数字の0を返しますが0の真偽値はfalseであるためです。次のように改変して確認してみます。

var fromStorage = ["ABC","5","DEF","0.05","10","G","0"]; 

var result = []; //変換してこちらに保存

for(var i = 0; i < fromStorage.length; i++){
	result.push( parseFloat(fromStorage[i]) || fromStorage[i] );
}

for(i = 0; i < result.length; i++){
	 document.write(result[i] + ":" + typeof(result[i]) + "<br>");
}

結果は

ABC:string
5:number
DEF:string
0.05:number
10:number
G:string
0:string

となり0が文字列のままになっています。そこで私は一番上の例を次のように書き変えました。

var fromStorage = ["ABC","5","DEF","0.05","10","G","0"]; 

var result = []; //変換してこちらに保存

for(var i = 0; i < fromStorage.length; i++){
	result.push( (parseFloat(fromStorage[i]) == NaN ) ? fromStorage[i] : parseFloat(fromStorage[i]) );
}

parseFloat()は文字列に対して使用するとNaNを返すので"parseFloat(fromStorage[i]) == NaN"がtrueなら文字列と判断しようということです。ところがこれがすべての間違いの始まりだったというわけです。typeをみてみます。

var fromStorage = ["ABC","5","DEF","0.05","10","G","0"]; 
		
var result = []; //変換してこちらに保存
		
for(var i = 0; i < fromStorage.length; i++){
	result.push( (parseFloat(fromStorage[i]) == NaN ) ? fromStorage[i] : parseFloat(fromStorage[i]) );
}

for(i = 0; i < result.length; i++){
	 document.write(result[i] + ":" + typeof(result[i]) + "<br>");
}

これの結果は

NaN:number
5:number
NaN:number
0.05:number
10:number
NaN:number
0:number

となり、すべての値が数値であると判断されてしまっています。つまり

parseFloat(fromStorage[i]) == NaN

がすべてfalseと判定されているわけです。

私は書き間違いかと思い、しばらく悩んで調べた結果次のことを初めて知りました。
NaNは何と比較してもfalseと判断される
ということのようです。

つまり

window.alert(NaN == NaN); //false

ということです。ちなみにその他の真偽値がfalseとなる値は同一の物と比較するとtrueになります。

window.alert(false == false);        //true
window.alert("" == "");              //true
window.alert(null == null);          //true
window.alert(undefined == undefined);//true

これは知らなければ嵌りますよねぇ(^_^;)
1時間くらい悩んでました・・・。
調べてみるとこれはJavascriptに限らない一般的な扱いのようですね→参考NaN - Wikipedia

ちなみに一番上のコードなのですが実はisNaN()という関数を使えばあっさり書けちゃいます。

var fromStorage = ["ABC","5","DEF","0.05","10","G","0"]; 

var result = []; //変換してこちらに保存

for(var i = 0; i < fromStorage.length; i++){
	result.push( isNaN(fromStorage[i]) ? fromStorage[i] : parseFloat(fromStorage[i]) );
}

for(i = 0; i < result.length; i++){
	 document.write(result[i] + ":" + typeof(result[i]) + "<br>");
}

これの結果は

ABC:string
5:number
DEF:string
0.05:number
10:number
G:string
0:number

となります。バッチリ。
最初からこれを知っていればこんなに悩まずに済みましたがNaNの比較の注意点を知ることができたという意味ではおかげで勉強になりました。