🔢

IEEE 754 (ISO/IEC 60559) 浮動小数点の主要な値まとめ

に公開

はじめに

IEEE 754 (ISO/IEC 60559)浮動小数点の特にbinary32, binary64について以下のような主要な値とそのビット表現がパッと見て分かるように表にしてまとめる。また、それぞれに関連したC/C++の定数を紹介する。

  • ±0
  • ±1
  • 非正規化数の最小・最大値
  • 正規化数の最小・最大値
  • 機械イプシロン
  • 型の最小値・最大値
  • 正確に整数を表現できる限界
  • 上記のそれぞれにおける1ULP
  • 無限大
  • NaN

最後に、実際の値とビット表現を出力するプログラムをC++で作成し、結果を確認する。

前提知識

詳細は、Wikipedia[1][2]などが分かりやすいため、そちらを当たってほしい。

IEEE 754浮動小数点ではradix=2(基数)の二進浮動小数点形式に対して、数値をsignbit(符号部),exponent(指数部),fraction(仮数部)に分割し、exponentのビット列が全て0または1でない限りにおいて

(-1)^\mathit{signbit} \times 2^{\mathit{exponent}-\mathit{bias}} \times (1.\mathit{fraction})_2

を表現し、これを正規化数という。

最後の(1.\mathit{fraction})_2は、fractionのビットをそのまま2進小数と見なしている。仮数部の先頭は必ず1.から始まることから、fractionに含めずにビット数を節約している(ケチ表現)。たとえば、ケチ表現を含めて(1.1001)_2のような仮数部であれば、1 \times 2^0 + 1 \times 2^{-1} + 0 \times 2^{-2} + 0 \times 2^{-3} + 1 \times 2^{-4}を表現する。

fractionをケチ表現を含んだビット数をp(=precision)とすると、fractionの最下位ビットが1変化したときに生じる浮動小数点の差はexponentが前後で変わらない前提で、

2^{\mathit{exponent}-\mathit{bias}-p+1}

となる。これが、その値の前後において浮動小数点が表現できる最小の間隔であり、1ULPと呼ぶ。1ULPは0から遠ざかるほど大きくなる。

浮動小数点は下記のように大別される。

  • ゼロ:exponentが0でかつ、fraction部も0である数。signbitが1である場合は、-0である。このように、0と-0の二種類のゼロがある。
    • 0と-0は算術演算上は等しいと判定される。すなわち0 == -0
  • 正規化数:exponentが全て0ないしは1でない数。fraction部の有効桁数が一定で保証される。
  • 非正規化数:exponentが全て0の数。下記の0に非常に近い数字を表す。fractionの有効桁数が正規化数よりも小さくなるため、精度が悪化する。
    (-1)^\mathit{signbit} * \mathit{radix}^{-\mathit{bias}+1} * (0.fraction)_2
  • 無限大:exponentが全て1でかつfractionが全て0の数。IEEE 754においては無限大は数であって、後述の非数(nan)ではない。signbit=1であれば、負の無限大を意味する
  • NaN:非数。exponentが全て1でかつfractionが非0の数。数でないことを表す。fraction部は任意の表現を持って良い。NaNは、他の数とは異なり自分自身とは等しくないと評価される。すなわち、あるNaNであるxに対して x != x
    • さらに仮数部の最上位ビットが0である場合はsignaling NaN、1である場合はquiet NaNである。

C/C++との対応関係

C/C++の代表的な浮動小数点であるfloatdoubleは、言語規格上はIEEE 754への準拠を求めていない。従って、FLT_MAXなどの各種浮動小数点に関する定数については実装定義であって、理論的には後述する内容と異なる値が得られる可能性がある。しかしながら、多くの場合はfloatはbinary32、doubleはbinary64で実装されていることが多く、これを前提として実装されたプログラムは多い。

C++03以降においては、std::numeric_limits::is_iec559によってIEEE 754への準拠を確認することができる。しかしながらこれがtrueであっても、理論上はfloatがbinary16やbinary64であっても構わないため、より厳密を期すのであればビット幅なども確認したほうがよい。

C++23以降においては、std::float32_tstd::float64_tがbinary32とbinary64に対応することが保証される。ただし、floatdoubleがbinary32、binary64で実装されていたとしても、C++の言語規格上、std::float32_tstd::float64_tがそれぞれfloatdoubleのエイリアスとして定義されることは禁じられており、異なる型として区別される。

以降、floatはbinary32、doubleはbinary64で実装されていると仮定し、話を進める。

binary32

  • radix=2であり、CではFLT_RADIX、C++ではstd::numeric_limits<float>::radixに対応する。
  • fractionは23bitであり、ケチ表現と合わせて基数表現で24桁となるため、24がCではFLT_MANT_DIG、C++ではstd::numeric_limits<float>::digitsに定義される。
  • biasは127である。

したがって、

  • exponent = 0のとき (ゼロと非正規化数)
    (-1)^\mathit{signbit} \times 2^{-126} \times (0.\mathit{fraction})_2 =(-1)^\mathit{signbit} \times 2^{-149} \times \mathit{fraction}
  • 0 < exponent < 255のとき (正規化数)
    (-1)^\mathit{signbit} \times 2^{\mathit{exponent}-127} \times (1.\mathit{fraction})_2 = (-1)^\mathit{signbit} \times 2^{\mathit{exponent}-127} \times (1 + \mathit{fraction} \times 2^{-23})

ビット表現したものを正の整数として見たときに、最も小さい順から並べて特記すべき内容を列挙する。余白の都合上、ヘッダーを省略しているがs=signbit、e=exponent、f=fractionであり、括弧はビット数である。便宜上、二進数表記 0b...、10進数表記、16進数表記 0x...を併用する。

s e(8) f(23) 内容
0 0 0b00..00 0
0 0 0b00..01 2^{-126} \times 2^{-23} = 2^{-149} = 1.40129...\times10^{-45} 最小の正の非正規化数であり、かつ最小の正の数。また、最大の正の非正規化数と最小の正の正規化数との差でもある。CではFLT_TRUE_MIN、C++ではstd::numeric_limits<float>::denorm_min()が対応する。
0 0 0b11..11 2^{-126} \times (1 - 2^{-23}) = 1.17549421...\times10^{-38} 最大の正の非正規化数
0 1 0b00..00 2^{-126} = 1.17549435...\times10^{-38} 最小の正の正規化数。CではFLT_MIN、C++ではstd::numeric_limits<float>::min()に対応する。\mathit{radix}^{-125-1}が基数の累乗で表現できる最小の正の正規化数となることから、CではFLT_MIN_EXP、C++ではstd::numeric_limits<float>::min_exponentに-125が定義される。また、10^{-37}が10の累乗で表現される最小の正の正規化数となることから、CではFLT_MIN_10_EXP、C++ではstd::numeric_limits<float>::min_exponent10に-37が定義される。
0 104 0b00..00 2^{-23} = 1.192092...\times10^{-7} 後述するようにこれが機械イプシロンである。CではFLT_EPSILON、C++ではstd::numeric_limits<float>::epsilon()に対応する。ここからも分かるように機械イプシロンは正確に表現できる。また最小の正の数(最小の正の非正規化数)ではない。
0 127 0b00..00 2^0 = 1 0や1を含め、後述するように16777216までの整数は全て正確に表現できる。
0 127 0b00..01 1 + 2^{-23} = 1.000000119209... 1より大きい最小の数。これと1との差がいわゆる機械イプシロンとして定義される。つまり、1の1ULPとは機械イプシロンと同義である。
0 150 0b00..00 2^{23}=8388608 1ULPが1である最小の正の数である。1ULPはexponentが増えるごとに倍倍に増えていくことから、これ以降の数は全て正確に整数である。
0 150 0b11..11 2^{24}-1=16777215 正確に表現できる最大の奇数。1ULPが1である最後の数。
0 151 0b00..00 2^{24}=16777216 ここまではすべての整数が正確に表現できるが、1ULPが2であるため、これ以降は正確に表現できる整数が限られてくる。したがって、16bit整数はbinary32に正確に変換できるが、32bit整数はこれ以降は正確に変換できない。
0 0xFE = 254 0b11..11 2^{127} \times (2 - 2^{-23})=2^{128} - 2^{104}=3.4028...\times10^{38} = [3] 最大の有限の数であり、かつ正確に表現できる最大の偶数。CではFLT_MAX、C++ではstd::numeric_limits<float>::max()が対応する。\mathit{radix}^{128-1}が基数の累乗で表現できる最大の正の正規化数となることから、128がCではFLT_MAX_EXP、C++ではstd::numeric_limits<float>::max_exponentとして定義される。また、10^{38}が10の累乗で表現される最大の正の正規化数となることから、CではFLT_MAX_10_EXP、C++ではstd::numeric_limits<float>::max_exponent10に38が定義される。ここからも分かるように、正確に表現できる最大の奇数と偶数の間には大きな差がある。この数を整数型に変換する場合、64bitでも足りず128bitを必要とするため現代のPCでは組み込み型では多くの場合は対応できず、多倍長整数を必要とする。1ULPは2^{104}である。
0 0xFF = 255 0b00..00 \infty 正の無限大。CではINFINITY、C++ではstd::numeric_limits<float>::infinity()が対応する。
0 0xFF = 255 0b00..01 NaN signaling NaN
0 0xFF = 255 0b01..11 NaN ここまで全てsignaling NaN
0 0xFF = 255 0b10..00 NaN quiet NaN
0 0xFF = 255 0b11..11 NaN ここまで全てquiet NaN
1 0 0b00...00 -0 負のゼロ
1 0 0b00...01 -2^{-126} \times 2^{-23} = -2^{-149} = -1.40129...\times10^{-45} 最大の負の非正規化数であり、かつ最大の負の数である。
1 0 0b11..11 -2^{-126} \times (1 - 2^{-23}) = -1.17549421...\times10^{-38} 最小の負の非正規化数
1 1 0b00..00 -2^{-126} = -1.17549435...\times10^{-38} 最小の負の正規化数
1 104 0b00..00 -2^{-23} = -1.192092...\times10^{-7} 負の機械イプシロン
1 127 0b00..00 -2^0 = -1
1 127 0b00..01 -(1 + 2^{-23}) = -1.000000119209... -1より小さい最大の数。
1 150 0b00..00 2^{23}=-8388608 1ULPが1の最大の負の数。これ以降の数は全て整数である。
1 150 0b11..11 -(2^{24}-1)=-16777215 正確に表現できる最小の奇数。1ULPが1の最小の数。
1 151 0b00..00 -2^{24}=-16777216 ここまでの整数は正確に表現できる。
1 0xFE = 254 0b11..11 -2^{127} \times (2 - 2^{-23})=-(2^{128} - 2^{104}) = - [3:1] 最小の有限の数であり、かつ最小の正確に表現できる偶数。C++ではstd::numeric_limits<float>::lowest()が対応する。
1 0xFF = 255 0b00..00 -\infty 負の無限大
1 0xFF = 255 0b00..01 NaN signaling NaN
1 0xFF = 255 0b01..11 NaN ここまで全てsignaling NaN
1 0xFF = 255 0b10..01 NaN quiet NaN
1 0xFF = 255 0b11..11 NaN ここまで全てquiet NaN

binary64

  • radix=2であり、CではDBL_RADIX、C++ではstd::numeric_limits<double>::radixに対応する。
  • fractionは52bitであり、ケチ表現と合わせて基数表現で53桁となるため、53がCではDBL_MANT_DIG、C++ではstd::numeric_limits<double>::digitsに定義される。
  • bias=1023である。

したがって、

  • exponent = 0のとき (ゼロと非正規化数)
    (-1)^\mathit{signbit} \times 2^{-1022} \times (0.\mathit{fraction})_2 = (-1)^\mathit{signbit} \times 2^{-1074} \times \mathit{fraction}
  • 0 < exponent < 2047のとき (正規化数)
    (-1)^\mathit{signbit} \times 2^{\mathit{exponent}-1023} \times (1.\mathit{fraction})_2 = (-1)^\mathit{signbit} \times 2^{\mathit{exponent}-1023} \times (1 + \mathit{fraction} \times 2^{-52})
s e(11) f(52) 内容
0 0 0b00..00 0
0 0 0b00..01 2^{-1022} \times 2^{-52} = 2^{-1074} = 4.94065...\times10^{-324} 最小の正の非正規化数であり、かつ最小の正の数。また、最大の正の非正規化数と最小の正の正規化数との差でもある。CではDBL_TRUE_MIN、C++ではstd::numeric_limits<double>::denorm_min()が対応する。
0 0 0b11..11 2^{-1022} \times (1 - 2^{-52}) = 2.225073858507200889...\times10^{-308} 最大の正の非正規化数
0 1 0b00..00 2^{-1022} = 2.22507385850720138...\times10^{-308} 最小の正の正規化数。CではDBL_MIN、C++ではstd::numeric_limits<double>::min()に対応する。\mathit{radix}^{-1021-1}が基数の累乗で表現できる最小の正の正規化数となることから、CではDBL_MIN_EXP、C++ではstd::numeric_limits<double>::min_exponentに-1021が定義される。また、10^{-307}が10の累乗で表現される最小の正の正規化数となることから、CではDBL_MIN_10_EXP、C++ではstd::numeric_limits<double>::min_exponent10に-307が定義される。
0 971 0b00..00 2^{-52} = 2.220446...\times10^{-16} 機械イプシロン。CではDBL_EPSILON、C++ではstd::numeric_limits<double>::epsilon()に対応する。
0 1023 0b00..00 2^0 = 1
0 1023 0b00..01 1 + 2^{-52} = 1.00000000000000022... 1より大きい最小の数。これと1との差がいわゆる機械イプシロンとして定義される。
0 1075 0b00..00 2^{52}=4503599627370496 1ULPが1である最小の正の数である。これ以降の数は全て整数である。
0 1075 0b11..11 2^{53}-1=9007199254740991 正確に表現できる最大の奇数。1ULPが1である最後の数。
0 1076 0b00..00 2^{53}=9007199254740992 ここまではすべての整数が正確に表現できるが、1ULPが2であるため、これ以降は正確に表現できる整数が限られてくる。したがって、32bit整数はbinary64に正確に変換できるが、64bit整数はこれ以降は正確に変換できない。
0 0x7FE = 2046 0b11..11 2^{1023} \times (2 - 2^{-52})=2^{1024} - 2^{971}=1.797693...\times10^{308} = [4] 最大の有限の数であり、かつ正確に表現できる最大の偶数。CではDBL_MAX、C++ではstd::numeric_limits<double>::max()が対応する。\mathit{radix}^{1024-1}が基数の累乗で表現できる最大の正の正規化数となることから、CではDBL_MAX_EXP、C++ではstd::numeric_limits<double>::max_exponentに1024が定義される。また、10^{308}が10の累乗で表現される最大の正の正規化数となることから、CではDBL_MAX_10_EXP、C++ではstd::numeric_limits<double>::max_exponent10に308が定義される。脚注に正確な値を載せたように非常に大きな数字であり、整数型で表現するとした場合1024bit必要となる。1ULPは2^{971}である。
0 0x7FF = 2047 0b00..01 NaN signaling NaN
0 0x7FF = 2047 0b01..11 NaN ここまで全てsignaling NaN
0 0x7FF = 2047 0b10..00 NaN quiet NaN
0 0x7FF = 2047 0b11..11 NaN ここまで全てquiet NaN
1 0 0b00...00 -0 負のゼロ
1 0 0b00..01 -2^{-1022} \times 2^{-52} = -2^{-1074} = -4.94065...\times10^{-324} 最大の負の非正規化数であり、かつ最大の負の数
1 0 0b11..11 -2^{-1022} \times (1 - 2^{-52}) = -2.225073858507200889...\times10^{-308} 最小の負の非正規化数
1 1 0b00..00 -2^{-1022} = -2.22507385850720138...\times10^{-308} 最大の負の正規化数
1 971 0b00..00 -2^{-52} = -2.220446...\times10^{-16} 負の機械イプシロン。
1 1023 0b00..00 -2^0 = -1
1 1023 0b00..01 -(1 + 2^{-52}) = -1.00000000000000022... -1より小さい最大の数
1 1075 0b00..00 -2^{52}=-4503599627370496 1ULPが1である最大の負の数である。これ以降の数は全て整数である。
1 1075 0b11..11 -(2^{53}-1)=-9007199254740991 正確に表現できる最小の奇数
1 1076 0b00..00 -2^{53}=-9007199254740992 ここまではすべての整数が正確に表現できるが、これ以降は正確に表現できる整数が限られてくる。
1 0x7FE = 2046 0b11..11 -2^{1023} \times (2 - 2^{-52})=-(2^{1024} - 2^{971})=-1.797693...\times10^{308} = -[4:1] 最小の有限の数であり、かつ正確に表現できる最小の偶数。C++ではstd::numeric_limits<double>::lowest()が対応する。
1 0x7FF = 2047 0b00..01 NaN signaling NaN
1 0x7FF = 2047 0b01..11 NaN ここまで全てsignaling NaNである
1 0x7FF = 2047 0b10..00 NaN quiet NaN
1 0x7FF = 2047 0b11..11 NaN ここまで全てquiet NaNである

FLT_MAX、DBL_MAXマクロと正確な数値との関係

FLT_MAXとDBL_MAXの正確な値は[3:2][4:2]に記載した通りであるが、

// <math.h>
#define FLT_MAX 3.402823466e+38F
#define DBL_MAX 1.7976931348623158e+308

のように、実装上は端折ってマクロが定義されていることがある(実際の定義は処理系依存である)。これは一見すると異なる数値ではあるものの、仮数部の桁数を考慮すると、floatでは2^{-23} = 1.19..*10^{-7}で約7桁、doubleでは2^{-52} = 2.22..*10^{-16}で約16桁が10進数の有効桁数であるため、これ以上に正確な数値を記載しても意味はないからである。

従って、3.402823466e+38F3.402823466*10^{38}ではなく、1.7976931348623158e+3081.7976931348623158*10^{308}ではない。(補足:3.402823466e+38Fは小数点とFのサフィックスがあることからfloatのリテラル値であり、1.7976931348623158e+308は小数点のみあることからdoubleのリテラル値である。)

細かく見ると、DBL_MAXの正確な値は1.79769313486231570...\times10^{308}であるから、上述の例であれば1.7976931348623158e+308よりも1.7976931348623157e+308のほうがより正確かもしれない(処理系によってはこれで定義されるものもあるようである)。ただ、これはコンパイラのコンパイル時の丸めモードに依存すると考えられるため、ユーザーとしてはこれらのマクロ定義は勝手にせずに、処理系が提供するものに従うべきであろう。(たとえば切り上げがコンパイル時の丸めモードとして設定された処理系では、1.7976931348623158e+308を無限大としてしまう可能性が考えられる。)

C++17でbinary32を可視化する

#include <iostream>
#include <bitset>
#include <cstring>
#include <cstdint>
#include <utility>
#include <limits>
#include <climits>
#include <iomanip>

static_assert(sizeof(float) * CHAR_BIT == 32);
static_assert(std::numeric_limits<float>::is_iec559);
static_assert(std::numeric_limits<float>::digits == 24);
static_assert(std::numeric_limits<float>::radix == 2);
static_assert(std::numeric_limits<float>::max_exponent == 128);

void print_float_bits(float f) {
    std::uint32_t u;
    std::memcpy(&u, &f, sizeof(float));
    std::cout << f << "\n";
    const std::bitset<32> bits(u);
    for (int i{31}; i >= 0; i--) {
        std::cout << bits[i];
        if (i==31 || i==23) {
            std::cout << " ";
        }
    }
    std::cout << "\n";
}

int main() {
    const std::pair<std::string, float> print_list[] {
        {"zero", 0.0f},
        {"minus zero", -0.0f},
        {"denorm_min", std::numeric_limits<float>::denorm_min()},
        {"min", std::numeric_limits<float>::min()},
        {"epsilon", std::numeric_limits<float>::epsilon()},
        {"one", 1.0f},
        {"one + epsilon", 1.0f + std::numeric_limits<float>::epsilon()},
        {"16777215.0f", 16777215.0f},
        {"16777216.0f", 16777216.0f},
        {"16777217.0f (not precise)", 16777217.0f},
        {"max", std::numeric_limits<float>::max()},
        {"340282346638528859811704183484516925440.0f (= max)", 340282346638528859811704183484516925440.0f},
        {"infinity", std::numeric_limits<float>::infinity()},
        {"signaling NaN", std::numeric_limits<float>::signaling_NaN()},
        {"quiet NaN", std::numeric_limits<float>::quiet_NaN()}
    };

     std::cout << std::setprecision(10);

    for (const auto & e : print_list) {
        std::cout << e.first << "\n";
        print_float_bits(e.second);
        std::cout << "\n";
    }
}

x86-64 gcc 15.1での出力結果は下記の通りだった。

zero
0
0 00000000 00000000000000000000000

minus zero
-0
1 00000000 00000000000000000000000

denorm_min
1.401298464e-45
0 00000000 00000000000000000000001

min
1.175494351e-38
0 00000001 00000000000000000000000

epsilon
1.192092896e-07
0 01101000 00000000000000000000000

one
1
0 01111111 00000000000000000000000

one + epsilon
1.000000119
0 01111111 00000000000000000000001

16777215.0f
16777215
0 10010110 11111111111111111111111

16777216.0f
16777216
0 10010111 00000000000000000000000

16777217.0f (not precise)
16777216
0 10010111 00000000000000000000000

max
3.402823466e+38
0 11111110 11111111111111111111111

340282346638528859811704183484516925440.0f (= max)
3.402823466e+38
0 11111110 11111111111111111111111

infinity
inf
0 11111111 00000000000000000000000

signaling NaN
nan
0 11111111 01000000000000000000000

quiet NaN
nan
0 11111111 10000000000000000000000

16777217.0fの結果について補足する。16777217を正確に表現できる前後のbinary32浮動小数点は16777216または16777218である(1ULPが2である)。この場合、どちらも同じ1だけ差があるがどちらが選ばれるべきだろうか。結論として、これはデフォルトの浮動小数点の丸めモードがRound to NearestでかつroundTiesToEven[5]となっているからだと考えられる。丸めモードにはいくつかのオプションがあるため、この結果は処理系依存である。また、C/C++では丸めモードをfesetroundによって指定できるがこれはランタイムオプションであり、コンパイル時の丸めモードを指定するオプションについてはgccやclangでは見つからなかった。

またsignaling NaNとquiet NaNについては複数の表現があるため、得られるビット表現については処理系依存である。実際、x64 msvc v19.43 VS17.13では下記を得た。よく見るとsignaling NaNの仮数部の最上位ビットが1となっており、MSVCはIEEE 754規格に厳密には適合していないと考えられる。

...
signaling NaN
nan
0 11111111 10000000000000000000001

quiet NaN
nan
0 11111111 10000000000000000000000
脚注
  1. https://ja.wikipedia.org/wiki/単精度浮動小数点数 ↩︎

  2. https://ja.wikipedia.org/wiki/倍精度浮動小数点数 ↩︎

  3. 340282346638528859811704183484516925440 ↩︎ ↩︎ ↩︎

  4. 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 ↩︎ ↩︎ ↩︎

  5. https://en.wikipedia.org/wiki/IEEE_754-1985#Rounding_floating-point_numbers ↩︎

Discussion