浮動小数比較の落とし穴

業務でリファクタリングを実施していた。演算結果が変わらないように毎回比較して違っていればエラーを吐くようにして、改良を進めていた。

if( std::abs( result - test ) > std::numeric_limits< double >::epsilon() ){
	std::cout << "error : " << result << " , " << test << std::endl;
}

浮動小数点は演算が一致する保証がないので、その差が非常に小さい値であるかどうかで評価する、まあ数値計算での基本ですね。
何の変哲もない上記のコード、実は大きな落とし穴があった。私はこれにはまってかなりの工数を無駄にしてしまった。

double型には普通の数値以外にいくつか特別な表現がある。例えば0割をした場合に∞になったりする。その特別表現の一つにNaNがある。NaNとは、Not a Numberという意味で、つまりは「数値ではない」という意味です。数学的には0/0等の不定形の演算を施すと出てくるおうです。
今回、改良中に何を間違ったのか結果がNaNを返すようになっていた。NaNの演算規則はよくわからないが、とりあえず数値比較演算は相手が何であれすべてfalseを返すっぽいです。なので上記のコードはresultとtestが異なるにも関わらずifをすり抜けてしまいます。エラーが出ないから演算結果が一致していると思って進めていて結果、全部やり直しになってしまいました。orz
とうわけで、上記コードをNaNにも対応できるように少し修正してみました。

if( !( std::abs( result - test ) <= std::numeric_limits< double >::epsilon() ) ){
	std::cout << "error : " << result << " , " << test << std::endl;
}

数学的には全く同じですが、NaNの場合は不等号が必ずfalseになるので、不等号の向きを逆にすることによりNaNの場合もifに入ることになります。

次回から気をつけます。