Skip to content

Instantly share code, notes, and snippets.

@fortune
Last active October 21, 2024 09:50
Show Gist options
  • Select an option

  • Save fortune/9a7182b98e290fab52fdd2a8a85ca65e to your computer and use it in GitHub Desktop.

Select an option

Save fortune/9a7182b98e290fab52fdd2a8a85ca65e to your computer and use it in GitHub Desktop.
単精度浮動小数点数についての調査メモ

単精度浮動小数点数についての調査メモ

単精度のビットパターンと表せる値の範囲

IEEE の単精度浮動小数点数規格では、32ビット(4バイト)のうち最上位ビットから

  • 1 ビットの符号ビット(0 が正、1 が負)
  • 8 ビットの指数部(バイアスが 127)
  • 23 ビットの仮数部

となっている。したがって、符号ビットを s, 指数部を e, 仮数部(のビットパターン)を f とすると、正規化されたものなら、

(-1)^s x 2^(e - 127) x 1.f

1 <= e <= 254 ならば正規化されている。e = 0 かつ f = 0 ならゼロを表すが符号ビットによって、通常のゼロか 負のゼロ かが決まる。

e = 0 かつ f != 0 の場合は正規化されていないが有効な値だ。非正規化数の場合は

(-1)^s x 2^(1-127) x 0.f

となる。指数が 0 - 127 = -127 ではなくて 1 - 127 = -126 になることに注意。

e = 255 の場合は f により無限大だったり NaN になる。

このあたりのことを C コード float_check.c で確認した。出力は次のようになる。

normalized_max_f = 340282346638528859811704183484516925440.00000000000000000000
normalized_min_f = -340282346638528859811704183484516925440.00000000000000000000
zero_f = 0.00000000000000000000
negative_zero_f = -0.00000000000000000000
unnormalized_f = 0.00000000000000000000000000000000000000000000140130
positive_infinite_f = inf
nan_f = nan

したがって、正規化数の(絶対値が)最小数は、指数部のビットパターンは最下位ビットのみ 1 で仮数の 23 ビットがすべて 0 であるから次の値であり、

1.00000000000000000000000 x 2^(-126)

一方、非正規化数の最小数は、指数部のビットパターンはすべて 0 で仮数部の 23 ビットは最下位ビットのみが 1 であるから次の値になる。

0.00000000000000000000001 x 2^(-126)

になる。非正規化数は正規化数の最小値よりも小さく、0 よりは大きいということになる。

単精度浮動小数点数の精度

仮数部が 23 ビットであり、これに 1 を足した 24 ビットが単精度浮動小数点数が保持できる 2 進での桁数である。ほぼ 2^24 = 10^7 だから、10進数なら 7 桁の精度を持つことになる。

したがって、8 桁の10進数は単精度浮動小数点数ではうまく表現できない場合がある。たとえば、

>>> 2**24
16777216

であり、この8桁の10進数は25ビットの2進数 1000000000000000000000000 に等しい。これは

2^24 x 1.000000000000000000000000(0 は24個)

であり、単精度浮動小数点数で表現すると、

  • 符号ビットは s = 0
  • 指数部は、e = 24 + 127 = 151
  • 仮数部は 23ビットの 0

である。実際、10進数 16777216 を単精度浮動小数点数にし、それのビットパターンを表示させるために次の Python コードを実行すると、

import numpy as np
import struct

x = np.float32(16777216)

# np.float32 -> bytes -> int
bytes_rep = struct.pack('>f', x)
int_rep = struct.unpack('>I', bytes_rep)[0]

# int -> binary string
bin_rep = format(int_rep, '032b')

print(f"Floating point value: {x}")
print(f"Binary representation: {bin_rep}")

次のような出力が得られる。

Floating point value: 16777216.0
Binary representation: 01001011100000000000000000000000

指数部の 10010111 は10進数の 151 であり、確かに合っている。

一方で 16777217 = 16777216 + 1 は 25 ビットの2進数 1000000000000000000000001 に等しい。これは

2^24 x 1.000000000000000000000001(小数点以下は 24個ある)

であり、単精度浮動小数点数にすると、仮数部が 23 ビットしかないので、16777216 と同じ表現になってしまう。実際、

import numpy as np
import struct

x = np.float32(16777217)

# np.float32 -> bytes -> int
bytes_rep = struct.pack('>f', x)
int_rep = struct.unpack('>I', bytes_rep)[0]

# int -> binary string
bin_rep = format(int_rep, '032b')

print(f"Floating point value: {x}")
print(f"Binary representation: {bin_rep}")

の出力は、

Floating point value: 16777216.0
Binary representation: 01001011100000000000000000000000

のように前と全く同じになってしまうのだ。

16777217 はうまく表せないが、16777218 は正確に表現できる。これは、25 ビットの2進数 1000000000000000000000010 に等しいので、仮数部の 23 ビットにうまく収まるからだ。

10 進小数と 2 進小数の不整合

整数であれば問題ないが、小数の場合、10進数では有限小数でも 2 進数だと循環小数になってしまうことがある。たとえば、10 進数の 0.1 は 2 進数だと

0.00011001100110011....

のように 0011 が無限に続いていく。これは、1.100110011001100110011..... x 2^(-4) だから、単精度浮動小数点数では

  • 符号ビット s = 0
  • 指数部は e = 127 - 4 = 123
  • 仮数部は f = 10011001100110011001101

になる。f の最後のビットは 0 になるはずだが、次のビットが 1 なので、それを切り上げている。実際、

import numpy as np
import struct

x = np.float32(0.1)

# np.float32 -> bytes -> int
bytes_rep = struct.pack('>f', x)
int_rep = struct.unpack('>I', bytes_rep)[0]

# int -> binary string
bin_rep = format(int_rep, '032b')

print(f"Floating point value: {x}")
print(f"Binary representation: {bin_rep}")

の出力は、

Floating point value: 0.10000000149011612
Binary representation: 00111101110011001100110011001101

となる。0.10000000149011612 が 10 進数表記だが、切り上げているので、その分、値が大きくなってしまっているのだ。

#include <stdio.h>
#include <string.h>
int main(void)
{
// 単精度浮動小数点数の正規化された最大数は、
//
// 符号ビットが 0
// 指数部が 254
// 仮数部が 23 ビットの 1
//
// だから、ビットパターンは、
//
// 0, 11111110, 11111111111111111111111
//
// となる。これを16進数にすると、
//
// 0111, 1111, 0111, 1111, 1111, 1111, 1111, 1111
//
// だから、7f7f ffff となる。
//
unsigned int x = 0x7f7fffff;
float normalized_max_f;
memcpy(&normalized_max_f, &x, sizeof(float));
// 単精度浮動小数点数の正規化された最小数は、
//
// 符号ビットが 1
// 指数部が 254
// 仮数部が 23 ビットの 1
//
// だから、ビットパターンは、
//
// 1, 11111110, 11111111111111111111111
//
// となる。これを16進数にすると、
//
// 1111, 1111, 0111, 1111, 1111, 1111, 1111, 1111
//
// だから、ff7f ffff となる。
//
x = 0xff7fffff;
float normalized_min_f;
memcpy(&normalized_min_f, &x, sizeof(float));
// 単精度浮動小数点数の通常のゼロは、
//
// 符号ビットが 0
// 指数部が 0
// 仮数部が 23 ビットの 0
//
// だ。
//
float zero_f;
x = 0;
memcpy(&zero_f, &x, sizeof(float));
// 単精度浮動小数点数の負のゼロは、
//
// 符号ビットが 1
// 指数部が 0
// 仮数部が 23 ビットの 0
//
// だから、16進表記は、
//
// 80 00 00 00
//
// だ。
//
float negative_zero_f;
x = 0x80000000;
memcpy(&negative_zero_f, &x, sizeof(float));
// 正規化されていない単精度浮動小数点数を使い、絶対値が最小となる正の数をつくってみる。この場合、
//
// 符号ビットは 0
// 指数部が 0
// 仮数部が最下位だけ 1 である 23 ビット
//
// だから、ビットパターンは、
//
// 0, 00000000, 00000000000000000000001
//
// だ。
//
float unnormalized_f;
x = 1;
memcpy(&unnormalized_f, &x, sizeof(float));
// 単精度浮動小数点数で正の無限大は
//
// 符号ビットが 0
// 指数部が 255
// 仮数部が 0
//
// だから、ビットパターンは、0, 11111111, 00000000000000000000000
// であり、0111, 1111, 1000, 0000, 0000, 0000, 0000, 0000 だから 16 進表記は、
// 7f 80, 00, 00 となる。
//
float positive_infinite_f;
x = 0x7f800000;
memcpy(&positive_infinite_f, &x, sizeof(float));
// 単精度浮動小数点数で NaN は
//
// 符号ビットは任意(とりあえず 0 としとこう)
// 指数部が 255
// 仮数部が not zero(とりあえず 1 としとこう)
//
// だから、ビットバターンは、0, 11111111, 00000000000000000000001
// であり、0111, 1111, 1000, 0000, 0000, 0000, 0000, 0001 だから 16 進表記は、
//
// 7f 80 00 01
//
// だ。
//
float nan_f;
x = 0x7f800001;
memcpy(&nan_f, &x, sizeof(float));
printf("normalized_max_f = %.20f\n", normalized_max_f);
printf("normalized_min_f = %.20f\n", normalized_min_f);
printf("zero_f = %.20f\n", zero_f);
printf("negative_zero_f = %.20f\n", negative_zero_f);
printf("unnormalized_f = %.50f\n", unnormalized_f);
printf("positive_infinite_f = %.20f\n", positive_infinite_f);
printf("nan_f = %.20f\n", nan_f);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment