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でない限りにおいて
を表現し、これを正規化数という。
最後の
fractionをケチ表現を含んだビット数をp(=precision)とすると、fractionの最下位ビットが1変化したときに生じる浮動小数点の差はexponentが前後で変わらない前提で、
となる。これが、その値の前後において浮動小数点が表現できる最小の間隔であり、1ULPと呼ぶ。1ULPは0から遠ざかるほど大きくなる。
浮動小数点は下記のように大別される。
- ゼロ:exponentが0でかつ、fraction部も0である数。signbitが1である場合は、-0である。このように、0と-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++の代表的な浮動小数点であるfloat
とdouble
は、言語規格上は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_t
とstd::float64_t
がbinary32とbinary64に対応することが保証される。ただし、float
とdouble
がbinary32、binary64で実装されていたとしても、C++の言語規格上、std::float32_t
とstd::float64_t
がそれぞれfloat
とdouble
のエイリアスとして定義されることは禁じられており、異なる型として区別される。
以降、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 |
0b00..01 |
最小の正の非正規化数であり、かつ最小の正の数。また、最大の正の非正規化数と最小の正の正規化数との差でもある。CではFLT_TRUE_MIN、C++ではstd::numeric_limits<float>::denorm_min() が対応する。 |
|
0 |
0 |
0b11..11 |
最大の正の非正規化数 | |
0 |
1 |
0b00..00 |
最小の正の正規化数。CではFLT_MIN 、C++ではstd::numeric_limits<float>::min() に対応する。FLT_MIN_EXP 、C++ではstd::numeric_limits<float>::min_exponent に-125が定義される。また、FLT_MIN_10_EXP 、C++ではstd::numeric_limits<float>::min_exponent10 に-37が定義される。 |
|
0 |
104 |
0b00..00 |
後述するようにこれが機械イプシロンである。CではFLT_EPSILON 、C++ではstd::numeric_limits<float>::epsilon() に対応する。ここからも分かるように機械イプシロンは正確に表現できる。また最小の正の数(最小の正の非正規化数)ではない。 |
|
0 |
127 |
0b00..00 |
0や1を含め、後述するように16777216までの整数は全て正確に表現できる。 | |
0 |
127 |
0b00..01 |
1より大きい最小の数。これと1との差がいわゆる機械イプシロンとして定義される。つまり、1の1ULPとは機械イプシロンと同義である。 | |
0 |
150 |
0b00..00 |
1ULPが1である最小の正の数である。1ULPはexponentが増えるごとに倍倍に増えていくことから、これ以降の数は全て正確に整数である。 | |
0 |
150 |
0b11..11 |
正確に表現できる最大の奇数。1ULPが1である最後の数。 | |
0 |
151 |
0b00..00 |
ここまではすべての整数が正確に表現できるが、1ULPが2であるため、これ以降は正確に表現できる整数が限られてくる。したがって、16bit整数はbinary32に正確に変換できるが、32bit整数はこれ以降は正確に変換できない。 | |
0 |
0xFE = 254
|
0b11..11 |
|
最大の有限の数であり、かつ正確に表現できる最大の偶数。CではFLT_MAX 、C++ではstd::numeric_limits<float>::max() が対応する。FLT_MAX_EXP 、C++ではstd::numeric_limits<float>::max_exponent として定義される。また、FLT_MAX_10_EXP 、C++ではstd::numeric_limits<float>::max_exponent10 に38が定義される。ここからも分かるように、正確に表現できる最大の奇数と偶数の間には大きな差がある。この数を整数型に変換する場合、64bitでも足りず128bitを必要とするため現代のPCでは組み込み型では多くの場合は対応できず、多倍長整数を必要とする。1ULPは |
0 |
0xFF = 255
|
0b00..00 |
正の無限大。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 |
負のゼロ | |
1 |
0 |
0b00...01 |
最大の負の非正規化数であり、かつ最大の負の数である。 | |
1 |
0 |
0b11..11 |
最小の負の非正規化数 | |
1 |
1 |
0b00..00 |
最小の負の正規化数 | |
1 |
104 |
0b00..00 |
負の機械イプシロン | |
1 |
127 |
0b00..00 |
||
1 |
127 |
0b00..01 |
-1より小さい最大の数。 | |
1 |
150 |
0b00..00 |
1ULPが1の最大の負の数。これ以降の数は全て整数である。 | |
1 |
150 |
0b11..11 |
正確に表現できる最小の奇数。1ULPが1の最小の数。 | |
1 |
151 |
0b00..00 |
ここまでの整数は正確に表現できる。 | |
1 |
0xFE = 254
|
0b11..11 |
|
最小の有限の数であり、かつ最小の正確に表現できる偶数。C++ではstd::numeric_limits<float>::lowest() が対応する。 |
1 |
0xFF = 255
|
0b00..00 |
負の無限大 | |
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 |
0b00..01 |
最小の正の非正規化数であり、かつ最小の正の数。また、最大の正の非正規化数と最小の正の正規化数との差でもある。CではDBL_TRUE_MIN、C++ではstd::numeric_limits<double>::denorm_min() が対応する。 |
|
0 |
0 |
0b11..11 |
最大の正の非正規化数 | |
0 |
1 |
0b00..00 |
最小の正の正規化数。CではDBL_MIN 、C++ではstd::numeric_limits<double>::min() に対応する。DBL_MIN_EXP 、C++ではstd::numeric_limits<double>::min_exponent に-1021が定義される。また、DBL_MIN_10_EXP 、C++ではstd::numeric_limits<double>::min_exponent10 に-307が定義される。 |
|
0 |
971 |
0b00..00 |
機械イプシロン。CではDBL_EPSILON 、C++ではstd::numeric_limits<double>::epsilon() に対応する。 |
|
0 |
1023 |
0b00..00 |
||
0 |
1023 |
0b00..01 |
1より大きい最小の数。これと1との差がいわゆる機械イプシロンとして定義される。 | |
0 |
1075 |
0b00..00 |
1ULPが1である最小の正の数である。これ以降の数は全て整数である。 | |
0 |
1075 |
0b11..11 |
正確に表現できる最大の奇数。1ULPが1である最後の数。 | |
0 |
1076 |
0b00..00 |
ここまではすべての整数が正確に表現できるが、1ULPが2であるため、これ以降は正確に表現できる整数が限られてくる。したがって、32bit整数はbinary64に正確に変換できるが、64bit整数はこれ以降は正確に変換できない。 | |
0 |
0x7FE = 2046
|
0b11..11 |
|
最大の有限の数であり、かつ正確に表現できる最大の偶数。CではDBL_MAX 、C++ではstd::numeric_limits<double>::max() が対応する。DBL_MAX_EXP 、C++ではstd::numeric_limits<double>::max_exponent に1024が定義される。また、DBL_MAX_10_EXP 、C++ではstd::numeric_limits<double>::max_exponent10 に308が定義される。脚注に正確な値を載せたように非常に大きな数字であり、整数型で表現するとした場合1024bit必要となる。1ULPは |
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 |
負のゼロ | |
1 |
0 |
0b00..01 |
最大の負の非正規化数であり、かつ最大の負の数 | |
1 |
0 |
0b11..11 |
最小の負の非正規化数 | |
1 |
1 |
0b00..00 |
最大の負の正規化数 | |
1 |
971 |
0b00..00 |
負の機械イプシロン。 | |
1 |
1023 |
0b00..00 |
||
1 |
1023 |
0b00..01 |
-1より小さい最大の数 | |
1 |
1075 |
0b00..00 |
1ULPが1である最大の負の数である。これ以降の数は全て整数である。 | |
1 |
1075 |
0b11..11 |
正確に表現できる最小の奇数 | |
1 |
1076 |
0b00..00 |
ここまではすべての整数が正確に表現できるが、これ以降は正確に表現できる整数が限られてくる。 | |
1 |
0x7FE = 2046
|
0b11..11 |
|
最小の有限の数であり、かつ正確に表現できる最小の偶数。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では
従って、3.402823466e+38F
は1.7976931348623158e+308
は3.402823466e+38F
は小数点とFのサフィックスがあることからfloatのリテラル値であり、1.7976931348623158e+308
は小数点のみあることからdoubleのリテラル値である。)
細かく見ると、DBL_MAXの正確な値は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
の結果について補足する。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
-
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 ↩︎ ↩︎ ↩︎
-
https://en.wikipedia.org/wiki/IEEE_754-1985#Rounding_floating-point_numbers ↩︎
Discussion