浮動小数点数の詳細

文字列化する関数を書くんでもない限り別に知らなくてもいいんだけど

float でも double でも別に指数部と仮数部のビット幅が違うだけでその解釈は同じです

MSB.............................LSB
S EXPONENT MANTISSA
符号 指数部 仮数部

浮動小数点数は基本的には次のように評価されます

±1.仮数部×2指数部
== ±( 2指数部 + 仮数部の最上位ビット*2指数部-1 +...+ 仮数部の最下位ビット*2指数部-仮数部のビット数 )

仮数

仮数部は最上位ビットが 2-1(==0.5)で最下位ビットが 2-仮数部のビット数 になります

正規数(普通の値)では最上位ビットの上に暗黙の 20(==1)を仮定します
例えば仮数部が 23ビットである IEEE 754 単精度 float で仮数部が 10100000000000000000000 のとき、この仮数部の値自体は 2-1 + 2-3 で 0.675 になります

10100000000000000000000
== 1*2-1 + 0*2-2 + 1*2-3 + 0*2-4 ...
== 2-1 + 2-3
== 0.5 + 0.125
== 0.675

正規数(普通の値)ではさらに暗黙の 20(==1)があるのでこれを足して仮数部は実際には 1.675 と評価されることになります

1.10100000000000000000000
== 1*20 + ( 1*2-1 + 0*2-2 + 1*2-3 + 0*2-4 ... )
== 20 + ( 2-1 + 2-3 )
== 1.0 + ( 0.5 + 0.125 )
== 1.675

非正規数ではこの暗黙の 20(==1)は仮定されません

指数部

指数部は2の補数表現ではなくバイアス/エクセス表現になっています
上半分が正の数、真ん中が 0、下半分が負の数となります
最小値(全ビット 0 )と最大値(全ビット 1 )は特別扱いになっています
指数部が最大値(全ビット 1 )のときは NaN か ±∞ になります
指数部が最小値(全ビット 0 )のときは 0 か非正規数になります

指数部が 8 ビットである IEEE 754 単精度 float では次のように解釈されます

11111111NaN または ∞
11111110 254-127 +127
11111101 253-127 +126
11111100 252-127 +125
...
...
...
10000010 130-127 +3
10000001 129-127 +2
10000000 128-127 +1
01111111 127-127 0
01111110 126-127 -1
01111101 125-127 -2
01111100 124-127 -3
...
...
...
00000003 3-127 -124
00000002 2-127 -125
00000001 1-127 -126
00000000非正規数 または 0

普通の数

普通の数である正規数は次のように評価されます

±1.仮数部×2指数部
== ±( 2指数部 + 仮数部の最上位ビット*2指数部-1 +...+ 仮数部の最下位ビット*2指数部-仮数部のビット数 )

正規数の最大値は指数部の最下位ビット以外 1 で最下位ビットが 0 (指数部の最大値-1)で仮数部は全ビット 1
正規数の最小値は指数部の最下位ビット以外 0 で最下位ビットが 1 (指数部の最小値+1)で仮数部は全ビット 0
最大値

x 1.......10 1.................1

最小値

x 0.......01 0.................0

FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX などがこのビットパターンになっています

特別な数

指数部が全ビット 1 で仮数部が 0 のときは ∞
符号ビットによって ±∞
+∞

0 1........1 0.................0

-∞

1 1........1 0.................0
NaN

指数部が全ビット 1 で仮数部が 0 以外のときは NaN
Intel の場合はさらに仮数部の最上位ビットが 1 かどうかで QNaN と SNaN に分かれる
仮数部の最上位ビットが 1 だったら QNaN・・・だったかな?
SNaN

x 1........1 0x................x

QNaN

x 1........1 1x................x

指数部と仮数部が共に 0 のときは 0
符号ビットによって ±0
0

0 0........0 0.................0

-0

1 0........0 0.................0

負の 0 と 0 のビットパターンは異なりますが、C/C++ で負の 0 と 0 を浮動小数点数として区別することはできません

( 0.0f == -0.0f ) → TRUE
( 0.0f <= -0.0f ) → TRUE
( 0.0f >= -0.0f ) → TRUE
( 0.0f != -0.0f ) → FALSE
( 0.0f < -0.0f ) → FALSE
( 0.0f > -0.0f ) → FALSE

Visual Studio 2008 より前の Visual C++ では -0.0 と書いても負の 0 とは評価されないので気をつけましょう

例えば次のように -0.0f を代入しても f0n は 0.0f(==0x00000000)で初期化されます

float	f0n = -0.0f;

非正規数

指数部が 0 で仮数部が 0 以外のときは非正規数

x 0........0 x.................x

非正規数は次のように評価されます

±0.仮数部×2指数部
== ±( 仮数部の最上位ビット*2指数部-1 +...+ 仮数部の最下位ビット*2指数部-仮数部のビット数 )

非正規数では正規数と違い仮数部に暗黙の 20 を仮定しません
また非正規数の指数部は 0 ですが 1 のときと同じ値として評価されます
例えば指数部が 8 ビットである IEEE 754 単精度 float では非正規数の指数部は -126 であるとみなされますから値全体としては

±0.仮数部×2-126
== ±( 仮数部の最上位ビット*2-126-1 +...+ 仮数部の最下位ビット*2-126-仮数部のビット数 )

と評価されます

これらのことから非正規数は 0.0 〜 正規数の最小値までの精度を向上させます

大きさの関係はこんな感じになります
0.0 < 非正規数の中の最小値 < 非正規数の中の最大値 < 正規数の最小値
0.0

x 0.......00 0................00

非正規数の中で最も小さい値

x 0.......00 0................01

非正規数の中で最も大きい値

x 0.......00 1.................1

正規数の最小値

x 0.......01 0.................0
Epsilon

Epsilon は 1.0 より大きい最小の正規数と 1.0 との差です
1.0 は 20 なので仮数部は 0 です
20

0 01.....1 0.................0

これより大きい最小の正規数は仮数部に 1 を足したものですね
20 + 20-仮数部のビット数

0 01.....1 0................01

20 + 20-仮数部のビット数 と 20 の差なのですから、つまり Epsilon は 2-仮数部のビット数 です

例えば仮数部が 23ビットである IEEE 754 単精度 float では Epsilon は 2-23 になります
2-23 は 2 のべき乗なので仮数部は 0 になります。指数部は float のバイアス値である 127 を足して -23+127 で 104 = 0x68 = 0110 1000 になります
FLT_EPSILON

0 01101000 0.................0

1.0f + FLT_EPSILON

0 01111111 0................01

1.0f

0 01111111 0.................0

仮数部が 52ビットである IEEE 754 倍精度 double では Epsilon は 2-52 になります
2-52 は 2 のべき乗なので仮数部は 0 になります。指数部は double のバイアス値である 1023 を足して -52+1023 で 971 = 0x3CB = 011 1100 1011 になります
DBL_EPSILON

0 01111001011 0.................0

1.0 + DBL_EPSILON

0 01111111111 0................01

1.0

0 01111111111 0.................0

ところで、この Epsilon という値は「ある基準値とその基準値よりも大きい最小の正規数との差」ですから、基準が 1.0 でない一般の場合で言うと 2n と 2n + 2n-仮数部のビット数 の差ということになります
このとき精度としては 2-仮数部のビット数 ということで変わりありませんが、値としては 2n-仮数部のビット数 ですから、例えば仮数部が 23ビットである IEEE754 単精度 float でこの基準を 256.0f に置いてみると、256.0f つまり 28 に対する Epsilon は 28-23 で 2-15 であるということになります
2-15

0 01110000 0.................0

256.0f + 2-15

0 10000111 0................01

256.0f

0 10000111 0.................0

ビットパターンを見てもお判りの通り 256.0f と 256.0f + 2-15 の差は 1 しかありませんから、この2つの数の間には表現可能な数は存在しません
従って 256.0f に 2-15 より小さい値を足しても意味がありません

× 256.0f に FLT_EPSILON ( 2-23 ) を足しても意味が無い

float	f = sample_distance( foo, bar );
if( f <= 256.0f + FLT_EPSILON ){
	.
	.
	.
}

具体的には

具体的にはこんな感じ

fp32(s23e8) - IEEE 754 単精度 float
#define	IEEE754_FLOAT_SIGN_BITS				1
#define	IEEE754_FLOAT_EXPONENT_BITS			8
#define	IEEE754_FLOAT_MANTISSA_BITS			23
#define	IEEE754_FLOAT_SIGN_MASK				(0x80000000UL)
#define	IEEE754_FLOAT_EXPONENT_MASK			(0x7f800000UL)
#define	IEEE754_FLOAT_MANTISSA_MASK			(0x007fffffUL)
#define	IEEE754_FLOAT_QNAN					(0x00400000UL)
#define	IEEE754_FLOAT_POSITIVE_INFINITE		(0x7f800000UL)
#define	IEEE754_FLOAT_NEGATIVE_INFINITE		(0xff800000UL)
#define	IEEE754_FLOAT_POSITIVE_ZERO			(0x00000000UL)
#define	IEEE754_FLOAT_NEGATIVE_ZERO			(0x80000000UL)
#define	IEEE754_FLOAT_MAX					(0x7f7fffffUL)	// <  2<sup>+128</sup>
#define	IEEE754_FLOAT_MIN					(0x00800000UL)	// == 2<sup>-126</sup>
#define	IEEE754_FLOAT_EPSILON				(0x34000000UL)	// == 2<sup>-23</sup>
#define	IEEE754_FLOAT_ISMINUS( fl )			((fl) & IEEE754_FLOAT_SIGN_MASK)
#define	IEEE754_FLOAT_ISZERO( fl )			( !((fl) & ~IEEE754_FLOAT_SIGN_MASK))
#define	IEEE754_FLOAT_FINITE( fl )			(((fl) & IEEE754_FLOAT_EXPONENT_MASK)!=IEEE754_FLOAT_EXPONENT_MASK)
#define	IEEE754_FLOAT_NORMALIZED( fl )		((((fl) & IEEE754_FLOAT_EXPONENT_MASK)!=IEEE754_FLOAT_EXPONENT_MASK)&& ((fl) & IEEE754_FLOAT_EXPONENT_MASK))
#define	IEEE754_FLOAT_ISNAN( fl )			((((fl) & IEEE754_FLOAT_EXPONENT_MASK)==IEEE754_FLOAT_EXPONENT_MASK)&& ((fl) & IEEE754_FLOAT_MANTISSA_MASK))
#define	IEEE754_FLOAT_SIGN( fl )			(((fl) >> (IEEE754_FLOAT_EXPONENT_BITS+IEEE754_FLOAT_MANTISSA_BITS)) & 0x00000001UL)
#define	IEEE754_FLOAT_EXPONENT( fl )		( !IEEE754_FLOAT_NORMALIZED((fl)) ? (IEEE754_FLOAT_EXPONENT_MIN+1) : ((((fl) & IEEE754_FLOAT_EXPONENT_MASK)>>IEEE754_FLOAT_MANTISSA_BITS)+IEEE754_FLOAT_EXPONENT_MIN))
#define	IEEE754_FLOAT_MANTISSA( fl )		( !IEEE754_FLOAT_NORMALIZED((fl)) ? ((fl) & IEEE754_FLOAT_MANTISSA_MASK) : (((fl) & IEEE754_FLOAT_MANTISSA_MASK)|(1UL<<IEEE754_FLOAT_MANTISSA_BITS)))
#define	IEEE754_FLOAT_EXPONENT_DIGITS		38
#define	IEEE754_FLOAT_EXPONENT_MAX			128
#define	IEEE754_FLOAT_EXPONENT_MIN			(-127)	// 本当は -126 だけど。んー
#define	IEEE754_FLOAT_EXPONENT_RANGE		(IEEE754_FLOAT_EXPONENT_MAX-IEEE754_FLOAT_EXPONENT_MIN+1)
#define	IEEE754_FLOAT_MANTISSA_DIGITS		7
#define	IEEE754_FLOAT_POW2( exponent )		((((exponent)-IEEE754_FLOAT_EXPONENT_MIN)<<IEEE754_FLOAT_MANTISSA_BITS)&IEEE754_FLOAT_EXPONENT_MASK)
fp64(s52e11) - IEEE 754 倍精度 double
#define	IEEE754_DOUBLE_SIGN_BITS			1
#define	IEEE754_DOUBLE_EXPONENT_BITS		11
#define	IEEE754_DOUBLE_MANTISSA_BITS		52
#define	IEEE754_DOUBLE_SIGN_MASK			(0x8000000000000000ULL)
#define	IEEE754_DOUBLE_EXPONENT_MASK		(0x7ff0000000000000ULL)
#define	IEEE754_DOUBLE_MANTISSA_MASK		(0x000fffffffffffffULL)
#define	IEEE754_DOUBLE_QNAN					(0x0008000000000000ULL)
#define	IEEE754_DOUBLE_POSITIVE_INFINITE	(0x7ff0000000000000ULL)
#define	IEEE754_DOUBLE_NEGATIVE_INFINITE	(0xfff0000000000000ULL)
#define	IEEE754_DOUBLE_POSITIVE_ZERO		(0x0000000000000000ULL)
#define	IEEE754_DOUBLE_NEGATIVE_ZERO		(0x8000000000000000ULL)
#define	IEEE754_DOUBLE_MAX					(0x7fefffffffffffffULL)	// <  2<sup>+1024</sup>
#define	IEEE754_DOUBLE_MIN					(0x0010000000000000ULL)	// == 2<sup>-1022</sup>
#define	IEEE754_DOUBLE_EPSILON				(0x3cb0000000000000ULL)	// == 2<sup>-52</sup>
#define	IEEE754_DOUBLE_ISMINUS( fl )		((fl) & IEEE754_DOUBLE_SIGN_MASK)
#define	IEEE754_DOUBLE_ISZERO( fl )			( !((fl) & ~IEEE754_DOUBLE_SIGN_MASK))
#define	IEEE754_DOUBLE_FINITE( fl )			(((fl) & IEEE754_DOUBLE_EXPONENT_MASK)!=IEEE754_DOUBLE_EXPONENT_MASK)
#define	IEEE754_DOUBLE_NORMALIZED( fl )		((((fl) & IEEE754_DOUBLE_EXPONENT_MASK)!=IEEE754_DOUBLE_EXPONENT_MASK)&& ((fl) & IEEE754_DOUBLE_EXPONENT_MASK))
#define	IEEE754_DOUBLE_ISNAN( fl )			((((fl) & IEEE754_DOUBLE_EXPONENT_MASK)==IEEE754_DOUBLE_EXPONENT_MASK)&& ((fl) & IEEE754_DOUBLE_MANTISSA_MASK))
#define	IEEE754_DOUBLE_SIGN( fl )			(((fl) >> (IEEE754_DOUBLE_EXPONENT_BITS+IEEE754_DOUBLE_MANTISSA_BITS)) & 0x0000000000000001ULL)
#define	IEEE754_DOUBLE_EXPONENT( fl )		( !IEEE754_DOUBLE_NORMALIZED((fl)) ? (IEEE754_DOUBLE_EXPONENT_MIN+1) : ((((fl) & IEEE754_DOUBLE_EXPONENT_MASK)>>IEEE754_DOUBLE_MANTISSA_BITS)+IEEE754_DOUBLE_EXPONENT_MIN))
#define	IEEE754_DOUBLE_MANTISSA( fl )		( !IEEE754_DOUBLE_NORMALIZED((fl)) ? ((fl) & IEEE754_DOUBLE_MANTISSA_MASK) : (((fl) & IEEE754_DOUBLE_MANTISSA_MASK)|(1ULL<<IEEE754_DOUBLE_MANTISSA_BITS)))
#define	IEEE754_DOUBLE_EXPONENT_DIGITS		308
#define	IEEE754_DOUBLE_EXPONENT_MAX			1024
#define	IEEE754_DOUBLE_EXPONENT_MIN			(-1023)	// 本当は -1022 だけど。んー
#define	IEEE754_DOUBLE_EXPONENT_RANGE		(IEEE754_DOUBLE_EXPONENT_MAX-IEEE754_DOUBLE_EXPONENT_MIN+1)
#define	IEEE754_DOUBLE_MANTISSA_DIGITS		16
#define	IEEE754_DOUBLE_POW2( exponent )		((((exponent)-IEEE754_DOUBLE_EXPONENT_MIN)<<IEEE754_DOUBLE_MANTISSA_BITS)&IEEE754_DOUBLE_EXPONENT_MASK)

ここで 64ビットの整数リテラルを 1ULL のように書いていますが VC6.0 と VC7.0 では ULL の代わりに I64 というサフィックスを使うので下記のようなマクロを書いておいて

// __MSC_VER
//  VC8.0 : 1400
//  VC7.1 : 1310
//  VC7.0 : 1300
//  VC6.0 : 1200
#if	!defined( _ULL )
#if	defined( _MSC_VER ) &&( _MSC_VER < 1310 )
#define	_ULL( n )	n ## I64
#else // _MSC_VER
#define	_ULL( n )	n ## ULL
#endif // _MSC_VER
#endif // _ULL

こんなふうにしてもいいかもしれません

#define	IEEE754_DOUBLE_POSITIVE_INFINITE	_ULL(0x7ff0000000000000)
fp16(s10e5)
#define	FP16_SIGN_BITS				1
#define	FP16_EXPONENT_BITS			5
#define	FP16_MANTISSA_BITS			10
#define	FP16_SIGN_MASK				(0x8000)
#define	FP16_EXPONENT_MASK			(0x7c00)
#define	FP16_MANTISSA_MASK			(0x03ff)
#define	FP16_QNAN					(0x0200)
#define	FP16_POSITIVE_INFINITE		(0x7c00)
#define	FP16_NEGATIVE_INFINITE		(0xfc00)
#define	FP16_POSITIVE_ZERO			(0x0000)
#define	FP16_NEGATIVE_ZERO			(0x8000)
#define	FP16_MAX					(0x7bff)	// <  2<sup>+16</sup>
#define	FP16_MIN					(0x0400)	// == 2<sup>-14</sup>
#define	FP16_EPSILON				(0x1400)	// == 2<sup>-10</sup>
#define	FP16_ISMINUS( fl )			((fl) & FP16_SIGN_MASK)
#define	FP16_ISZERO( fl )			( !((fl) & ~FP16_SIGN_MASK))
#define	FP16_FINITE( fl )			(((fl) & FP16_EXPONENT_MASK)!=FP16_EXPONENT_MASK)
#define	FP16_NORMALIZED( fl )		((((fl) & FP16_EXPONENT_MASK)!=FP16_EXPONENT_MASK)&& ((fl) & FP16_EXPONENT_MASK))
#define	FP16_ISNAN( fl )			((((fl) & FP16_EXPONENT_MASK)==FP16_EXPONENT_MASK)&& ((fl) & FP16_MANTISSA_MASK))
#define	FP16_SIGN( fl )				(((fl) >> (FP16_EXPONENT_BITS+FP16_MANTISSA_BITS)) & 0x00000001)
#define	FP16_EXPONENT( fl )			( !FP16_NORMALIZED((fl)) ? (FP16_EXPONENT_MIN+1) : ((((fl) & FP16_EXPONENT_MASK)>>FP16_MANTISSA_BITS)+FP16_EXPONENT_MIN))
#define	FP16_MANTISSA( fl )			( !FP16_NORMALIZED((fl)) ? ((fl) & FP16_MANTISSA_MASK) : (((fl) & FP16_MANTISSA_MASK)|(1<<FP16_MANTISSA_BITS)))
#define	FP16_EXPONENT_DIGITS		5
#define	FP16_EXPONENT_MAX			16
#define	FP16_EXPONENT_MIN			(-15)	// 本当は -14 だけど。んー
#define	FP16_EXPONENT_RANGE			(FP16_EXPONENT_MAX-FP16_EXPONENT_MIN+1)
#define	FP16_MANTISSA_DIGITS		4
#define	FP16_POW2( exponent )		((((exponent)-FP16_EXPONENT_MIN)<<FP16_MANTISSA_BITS)&FP16_EXPONENT_MASK)