NaN(Not a Number)詳細解説【発生条件、定数定義、比較方法、判定方法】

NaN(Not a Number)

NaN(Not a Number)とは非数を表す値のことです。NaNは数ではない値を表現する際に使われます。また例外的な結果や異常値であることを表現する際の値として使われることもあります。

NaNは非数を表すためだけではなく、無効な演算や不正な処理が行われたことを表すための値としても利用されています。

目次

NaNの発生条件

多くのプログラミング言語ではゼロ同士の除算0.0 / 0.0でNaNが発生します。NaNに対する算術演算の結果もNaNになります(NaNは伝播する)。また無限大を表すInfinity(Inf)の値との演算でNaNが発生することもあります。JavaScript等のスクリプト言語では9 * "a"1 + undefined等の非数となりうる演算処理でもNaNが発生します。

/* ゼロ同士の除算 */
printf("%f", 0.0 / 0.0);  // "nan"

/* NaNとの算術演算 */
printf("%f", NAN + 1);    // "nan"

/* 数学関数の定義域エラー */
printf("%f", asin(1.1));  // "nan" (区間外の逆三角関数)
printf("%f", sqrt(-0.1)); // "nan" (負数の平方根)
printf("%f", log(-0.1));  // "nan" (負数の対数)
printf("%f", fmod(1, 0)); // "nan" (ゼロ除算を伴う剰余演算)
// javascript: 1 % 0      //  NaN

/* Infとの算術演算 */
double inf = 1 / 0.0;
printf("%f", inf * 0);    // "nan"
printf("%f", inf / inf);  // "nan"
printf("%f", inf - inf);  // "nan"
printf("%f", inf + -inf); // "nan"

NaNの性質

NaNは以下の性質を持ちます。

  • NaNとの比較は等しくならない
  • NaNとの算術演算の結果はNaNになる
/* 等値比較、大小比較 */
NaN == 0;   // false
NaN > 0;    // false
NaN < 0;    // false
NaN == NaN; // false
NaN != NaN; // true

/* 算術演算 */
NaN + 99;   // NaN
NaN - NaN;  // NaN

qNaN (quiet NaN)

qNaN(quiet NaN)は演算によって伝播するNaNです。qNaNは浮動小数点例外を発生させません。qNaNはゼロ除算のような数学的に定義されていないような演算で発生する事があります。

sNaN (signaling NaN)

sNaN(signaling NaN)は発生時に浮動小数点例外を発生させるNaNです。sNaNは不正な演算を行った際に発生することがあります。たとえば、0による剰余(2 % 0, fmod(2, 0))はゼロ除算により解を定めることができないため、結果はsNaNとなり、同時に不正例外を発生させます。

浮動小数点例外

浮動小数点演算において発生する例外を浮動小数点例外(floating-point exception)と呼びます。浮動小数点例外には主に、ゼロ除算による例外やオーバーフロー、アンダーフローによる例外、不正確な結果の発生(丸め誤差など)、無効な演算を表す例外などが含まれます。

C言語やC++ではfenv.hヘッダのfetestexcept関数を用いて、発生した浮動小数点例外の種類を特定することが可能となっています。

// #include <fenv.h>

printf("%f", fmod(2, 0)); // "nan" (ゼロ除算を伴う剰余演算, 第二引数に無効な値を指定)
if (fetestexcept(FE_INVALID)) {
   puts("浮動小数点例外が発生しました");
}

feclearexcept(FE_ALL_EXCEPT); // 現在発生している例外を全てクリア

printf("%f", log(0)); // "-inf"
if (fetestexcept(FE_DIVBYZERO)) {
   puts("浮動小数点例外が発生しました");
}
printf("%f", exp(710));    // "inf" (オーバーフロー)
assert( fetestexcept(FE_ALL_EXCEPT) & (FE_INEXACT | FE_OVERFLOW) );

printf("%f\n", 1.0 / 3.0); // "0.333333" (情報落ち)
assert( fetestexcept(FE_ALL_EXCEPT) & FE_INEXACT );

double a = 0.5, b = 0.1;   // {a: 0.5, b: 0.10000000000000001}
double c = a - b;          // {c: 0.40000000000000002} (丸め誤差)
assert( fetestexcept(FE_ALL_EXCEPT) & FE_INEXACT );

浮動小数点例外の状態を表すフラグは以下の通りです。

定数浮動小数点例外の状態
FE_DIVBYZEROゼロ除算
FE_INEXACT不正確な結果
FE_INVALID無効な操作、不正な操作、未定義の操作
FE_OVERFLOWオーバーフローの発生
FE_UNDERFLOWアンダーフローの発生
FE_ALL_EXCEPT処理系が定義する全ての例外を表す際に指定

浮動小数点例外を意図的に発生させる場合にはferaiseexcept関数を用います。

/* 例外の追加 */
feraiseexcept(FE_DIVBYZERO);            // ゼロ除算による例外を起こす
feraiseexcept(FE_INVALID | FE_INEXACT); // 複数の例外を起こす

/* 例外の発生を確認 */
assert( fetestexcept(FE_INEXACT) );
assert( fetestexcept(FE_ALL_EXCEPT) & (FE_DIVBYZERO | FE_INVALID) );

/* 例外の解除 */
feclearexcept(FE_INEXACT);                // ゼロ除算のみクリア
feclearexcept(FE_DIVBYZERO | FE_INEXACT); // 複数の例外をクリア
feclearexcept(FE_ALL_EXCEPT);             // 全てクリア

NaNの定数定義

プログラミング言語の種類によってはNaNと言う定数が予め用意されていたり、Float型の名前空間内にNaNが定義されている場合もあります。

var nan = NaN;   // JavaScript
nan = Float::NAN // Ruby
float nan = NAN; // C言語
float nan = std::numeric_limits<float>::quiet_NaN(); // C++

NaNの定数定義(C言語)

C言語ではmath.hヘッダーにNANを表現するマクロが定義されています。マクロ定数NANの値はqNaN(quiet NaN)を表します。

// #include <math.h>
// #define NAN __builtin_nanf("0x7fc00000")

float nan = NAN;

NaNの定数定義(C++)

C++ではlimitsヘッダーのnumeric_limitsクラスにNaNを取得するための関数が定義されています。

種別関数名
qNaN (quiet NaN)std::numeric_limits<double>::quiet_NaN()
sNaN (signaling NaN)std::numeric_limits<double>::signaling_NaN()

quiet_NaN関数でNaNを取得できます。signaling_NaNは浮動小数点例外を発生させる特殊なNaN(sNaN)です。

// #include <limits>
// float quiet_NaN()     { return __builtin_nanf("");  }
// float signaling_NaN() { return __builtin_nansf(""); }

float q_nanf = std::numeric_limits<float>::quiet_NaN();
float s_nanf = std::numeric_limits<float>::signaling_NaN();

double q_nan = std::numeric_limits<double>::quiet_NaN();
double s_nan = std::numeric_limits<double>::signaling_NaN();

NaNの判定方法

NaNには、NaN同士の比較演算が等しくならないという特殊な性質があるため、非等値演算子によるNaNの判定も可能となっています。

float nan = NAN;
if (nan != nan) puts("YES I AM!");

ただし、基本的にはプログラミング言語側で定義されているisnan関数やisNaN関数を使うようにしましょう。

/* C言語 */
isnan(NAN);       // 1

/* C++ */
std::isnan(NAN);  // true

/* JavaScript */
Number.isNaN(NaN) // true

/* Ruby */
nan = Float::NAN
nan.nan?          // true

NaNの判定方法(C言語)

C言語ではmath.hヘッダーにNaN判定用のisnanマクロが定義されています。

// #include <math.h>
float nan = NAN;
printf("%d", isnan(nan)); // 1

NaNの判定方法(C++)

C++言語の場合はcmathヘッダーのstd::isnan()関数を利用することができます。

// #include <cmath>
float q_nan = std::numeric_limits<float>::quiet_NaN();
float s_nan = std::numeric_limits<float>::signaling_NaN();
if (std::isnan(q_nan) && std::isnan(s_nan))
   puts("Yes we NaN!");

NaNの判定方法(JavaScript)

JavaScriptの場合はisNaN関数が用意されていますが、厳格な比較が行われないという問題があるため、実際にはNaN !== NaNのイディオムがよく使われています。なおECMAScript 6(2015)ではより堅牢なNumber.isNaN()関数が用意されています。

// 型強制後の値で判定されてしまう
isNaN(NaN)       // true
isNaN({})        // true
isNaN("a")       // true
isNaN(undefined) // true
isNaN(false)     // false
isNaN(null)      // false
isNaN([])        // false
isNaN(0)         // false

// NaN判定イディオム
var nan = NaN
nan !== nan      // true
nan === nan      // false

var nan = "a";
typeof nan === "number" && isNaN(nan) // false

// ECMAScript 6
Number.isNaN(NaN) // true
Number.isNaN({})  // false
Number.isNaN("a") // false

NaNの整数変換

NaNの値をint型やlong型の整数に変換すると、NaNの情報が失われ、処理系依存の値に変化するため注意が必要です。Clangコンパイラでは各整数型の最小値に変換されることが確認できますが、他の処理系では異なる結果となる場合があります。

float nan = NAN;  // nan == NaN
int i = nan;      // i   == -2147483648
nan = i;          // nan == -2147483648.0

float nan = NAN;
printf("%d", (int)nan);   // "-2147483648" (INT_MIN == -2147483648)
printf("%ld", (long)nan); // "-9223372036854775808" (LONG_MIN)

NaNの評価

NaNに対する条件式の評価は真になることがほとんどですが、JavaScriptでは偽になるため注意が必要です。

/* C/C++ */
if (NAN) puts("yes");     // "yes"

/* Ruby */
Float::NAN ? "yes" : "no" // "yes"

/* JavaScript */
NaN ? "yes" : "no";       // "no"
~~NaN ? "yes" : "no";     // "yes"

NaNの内部表現

IEEE 754におけるNaNのデータ表現は、浮動小数点数型の指数部のビットを全て1、仮数部の値を0以外にする形で表されています。

内部表現(ビット)
qNaN【0.0 / 0.0, NAN, quiet_NaN()】
float0x7fc0000001111111110000000000000000000000
double0x7ff80000000000000111111111111000000000000000000000000000000000000000000000000000
sNaN【fmod(2, 0)】
float0xffc0000011111111110000000000000000000000
double0xfff80000000000001111111111111000000000000000000000000000000000000000000000000000
sNaN【signaling_NaN()】
float0x7fa0000001111111101000000000000000000000
double0x7ff40000000000000111111111110100000000000000000000000000000000000000000000000000
浮動小数点数型フォーマット
半精度浮動小数点数(float)(符号部 1bit)+(指数部 5bit)+(仮数部 10bit)
単精度浮動小数点数(double)(符号部 1bit)+(指数部 8bit)+(仮数部 23bit)
// #define NAN __builtin_nanf("0x7fc00000")
// float quiet_NaN()     { return __builtin_nanf("");  }
// float signaling_NaN() { return __builtin_nansf(""); }

double q_nan = std::numeric_limits<double>::quiet_NaN();
double s_nan = std::numeric_limits<double>::signaling_NaN();
double x_nan = 0.0 / 0.0;
double y_nan = fmod(2, 0);
double z_nan = NAN;

if (q_nan && s_nan && x_nan && y_nan && z_nan)
   printf("%f, %F", x_nan + y_nan, z_nan - z_nan); // "nan, NAN"

if (std::isnan(q_nan) && std::isnan(s_nan) &&
    std::isnan(x_nan) && std::isnan(y_nan) && std::isnan(z_nan))
   puts("Yes we NaN!"); // "Yes we NaN!"

整数への変換実験

NaNの整数変換については「# NaNの整数変換」を参考。

/* 処理系依存の型変換 in LLVM/Clang */
int  inan = std::numeric_limits<double>::quiet_NaN(); // (cvttsd2si)
inan;  // -2147483648 (INT_MIN)
long lnan = std::numeric_limits<double>::quiet_NaN(); // (cvttsd2si)
lnan;  // -9223372036854775808 (LONG_MIN)
int inanf = std::numeric_limits<float>::quiet_NaN();  // (cvttss2si)
inanf; // -2147483648 (INT_MIN)

NaNの演算実験

NaNの性質については「# NaNの性質」を参考。

double nan = NAN;

/* trueになる演算 */
if (nan != nan && nan - nan && nan + 999 &&
    nan != 999 && nan != 0x7ff8000000000000
) {
   puts("y"); // 表示される
}

/* falseになる演算 */
if (nan == nan ||    !nan     ||
    nan == 0x7ff8000000000000 ||
    nan <= nan ||  nan > nan  ||
    nan >= nan ||  nan < nan  ||
    nan <= 0   ||  nan >= 0
) {
   puts("n"); // 表示されない
}
広告