15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【C++】isnan()に注意! nan, Inf, -nan(ind)の判定について

Last updated at Posted at 2020-01-24

目的

  • 数値計算が可能か を判定する際に、 isnan() の使用が妥当かを検証する
    • 検証に使う関数は、 isnan()以外に、isinf()isfinite()

結論

  • 数値計算が可能かどうかを判定する場合は、isfinite()の使用が良い
    • isnan()の場合、無限大を検出できないため

対応表

  • 各関数と各非数の対応関係を表にまとめた
    • 太字 の箇所は、私の肌感覚と異なる判定(違和感を感じる箇所)
数値 inf nan -nan(ind) 備考
isnan False False True True infを検出してくれない
isinf False True False False nanを検出してくれない
isfinite True False False False nan, inf どちらも検出可

isnan()を使ったコードで発生したバグ

  • 実際の業務で発見したバグ
    • 分野: 機械学習案件の数値計算

      • ※説明のため、非常に単純化した例を示します。
    • 以下のような足し算関数add()を定義する

      • プログラマーは、isnan()を設置していたので、不正な値が入っても安心できると考えていた
      • しかし、この関数では、引数x,yに無限大が入ってしまった時に検出できない。
    • サンプルコードの例では、何かの拍子に y = 無限大が入ってしまったと仮定して、話を進めます。

bug.cpp
#include <iostream>
#include <string>
#include <cmath>
#include <limits>
#include "classA.h"

double add(double x, double y){
    // x, yが数値計算可能か判定して欲しい...
    if(std::isnan(x) || std::isnan(y)){
        return 0;
    }

    return x + y;
}

int main(void){
    double x = 1.0;
    double y = INFINITY; // 何かの拍子に無限大が入る

    std::cout << "add(x,y) = " << add(x,y) << std::endl;
    
    return 0;
}
実行結果
    add(x,y) = inf
  • 引数にはfloatを入れてるし、isnan()で検出できるはずなのに...??

    • このように、プログラマーの意図と反した値が帰ってきた。
  • 実行〜バグ発見までの経緯

    • 実際に走らせると、実行時によく分からない値が返却される
    • コンパイルエラーや実行時エラーは、吐かれない。
    • しかし、なぜかAIの予測結果がおかしい...
    • 計算は何層にも渡っているため、1層ずつ値を確認...
    • 発見まで非常に苦労した。

所感

  • 機械学習など、絶対的な正解がなくデバッグが難しい分野で上記のような勘違いをすると、問題の発見が非常に難しくなります。
  • 筆者は、isfinite()を積極的に使った方がよいと考えています。

(おまけ)検証プログラム

  • 以下は、おまけです。
    • 対応表を作るために作成したコードを乗せます。
test.cpp
#include <iostream>
#include <string>
#include <cmath>
#include <limits>

int main(void){
    float num = 1;
    float inf = INFINITY;
    float nan = NAN;    
    float nan_ind = inf - inf;

    // isnanの判定
    std::cout << "isnan(num) = " << std::isnan(num) << "\t\t" 
        << "isnan(inf) = " << std::isnan(inf) << "\t\t"  
        << "isnan(nan) = " << std::isnan(nan) << "\t\t" 
        << "isnan(nan_ind) = " << std::isnan(nan_ind) << std::endl;

    // isinfの判定
    std::cout << "isinf(num) = " << std::isinf(num) << "\t\t" 
        << "isinf(inf) = " << std::isinf(inf) << "\t\t" 
        << "isinf(nan) = " << std::isinf(nan) << "\t\t" 
        << "isinf(nan_ind) = " << std::isinf(nan_ind) << std::endl;
    
    // isfiniteの判定
    std::cout << "isfinite(num) = " << std::isfinite(num) << "\t" 
        << "isfinite(inf) = " << std::isfinite(inf) << "\t" 
        << "isfinite(nan) = " << std::isfinite(nan) << "\t" 
        << "isfinite(nan_ind) = " << std::isfinite(nan_ind) << std::endl;

    return 0;
}

出力

a.out
isnan(num) = 0		isnan(inf) = 0		isnan(nan) = 1		isnan(nan_ind) = 1
isinf(num) = 0		isinf(inf) = 1		isinf(nan) = 0		isinf(nan_ind) = 0
isfinite(num) = 1	isfinite(inf) = 0	isfinite(nan) = 0	isfinite(nan_ind) = 0
  • ※ 1 = True, 0 = False

処理系

  • OS: Windows10 Pro
  • CPU: Intel(R) Core(TM) i7-6700K
  • コンパイラ: VS2017 標準コンパイラ

参考ページ

15
11
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?