浮動小数点数のまとめ
概要
- 限られたビットの中で小数を表現する方法の一つが浮動小数点数
- 今日の実装は処理系、言語を問わずIEEE方式(IEEE 754)が標準
- とりあえずIEEE方式の倍精度浮動小数点数(C言語のdouble)の仕組みを覚えておけばいい
Wikipedia
浮動小数点数 - Wikipedia
IEEE 754 - Wikipedia
IEEE 754
IEEE 754: Standard for Binary Floating-Point Arithmetic
ビットの表現
小数を「(符号) 基数2の指数×仮数」で表現するのが基本的な考え方。
倍精度浮動小数点数の場合、64ビットを以下の3つの部分に分けて意味を持たせる。
Sign (符号部, 1bit)
- 0 なら +, 1 なら - を表す
Exponent (指数部, 11bits)
- 11ビットの符号無し整数で表現できるのは 0〜2047(=2^11-1)。
- 0(全てのビットが0)と2047(全てのビットが1)は特殊な用途(後述)で使用される。
- 1〜2046の範囲を、1023 のバイアス付き整数(ゲタ履き表現)で表す。
例えば符号無し整数の値が 1 であれば -1022、1023 であれば 0、2046 であれば 1023 を意味する。 - だいたい ±1000 と覚えておけば、ビット数を忘れても思い出しやすい
- 基数を 10 で考えると、だいたい 1e-308 〜 1e+308 の範囲を表せる
Significand (仮数部, 52bits)
- 指数部を掛け合わせる前の絶対値
- 整数部分が 1 になるように指数部を調整する(正規化)ので、整数部分の1は明らかであり表現しない。(hidden bitと呼ばれる)
- 例えば仮数が10進表記で 1.75 である場合は、2進表記だと 1.11。
整数部分を除外して、仮数部の表現は 11000000...(以下0が続く) となる。 - 0付近のごく小さい数を表すために、非正規化表現も用意されている。
(アンダーフローギャップを埋めるための重要な仕組み)
表現の種類
IEEE 754 では5種類の表現が定義されている。
s = sign (0 or 1)
q = exponent (指数部の符号無し整数表記)
c = significand (仮数部の符号無し整数表記) <=小数ではなく整数として見た場合の値
としたときの計算式と合わせて書くと以下のようになる。
種類 exponent(q) significand(c) 表している値 ゼロ 0 0 +0, -0 (符号の区別がある) 非正規化数 0 1〜 正規化数 1〜2046 0〜 無限大 2047 0 NaN(非数) 2047 1〜 基本的に符号の区別はない (詳細はWikipedia参照)
実装の確認
C言語でも可能だが、手っ取り早く Python で確認してみた。
(PyPIからbitarrayをインストールする)
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
import struct | |
from bitarray import bitarray | |
def double_to_bitstring(d): | |
b = bitarray() | |
b.frombytes(struct.pack('!d', d)) | |
s = b.to01() | |
return '%s %s %s' % (s[0], s[1:12], s[12:]) | |
def bitstring_to_double(bs): | |
return struct.unpack('!d', bitarray(bs).tobytes())[0] | |
def print_header(): | |
print(' ' * 19 + 'sign') | |
print('double'.center(18) + ': v <exponent > < ' + 'significant'.center(48) + ' >') | |
print('=' * 86) | |
def print_double(d): | |
print('%18s: ' % d + double_to_bitstring(d)) | |
if __name__ == '__main__': | |
print_header() | |
print_double(0.0) | |
# 負のゼロというものも存在する | |
print_double(-0.0) | |
print_double(2.0) | |
print_double(3.0) | |
print_double(1.0) | |
# 2進数の小数では有限で表現できないため、誤差が発生する | |
print_double(0.1) | |
# 非数 | |
print_double(float('nan')) | |
# 無限 | |
print_double(float('inf')) | |
print_double(float('-inf')) | |
# 正規化数の限界 | |
print_double(2.225073858507202e-308) | |
print_double(1.7976931348623157e+308) | |
# 非正規化数 | |
print_double(2.225073858507201e-308) | |
print_double(5e-324) | |
# バイナリからの逆引き | |
# print(bitstring_to_double('0' + '0' * 10 + '0' + '0' * 51 + '1')) | |
# print(bitstring_to_double('0' + '0' * 10 + '1' + '0' * 51 + '1')) | |
# print(bitstring_to_double('0' + '1' * 10 + '0' + '1' * 52)) | |
# print(bitstring_to_double('1' + '1' * 10 + '1' + '1' * 52)) | |
# print(bitstring_to_double('1' + '0' * 10 + '1' + '1' + '0' * 51)) | |
# print(bitstring_to_double('1' + '0' * 10 + '0' + '1' + '0' * 51)) |
出力例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | sign double : v <exponent > < significant > ====================================================================================== 0.0: 0 00000000000 0000000000000000000000000000000000000000000000000000 -0.0: 1 00000000000 0000000000000000000000000000000000000000000000000000 2.0: 0 10000000000 0000000000000000000000000000000000000000000000000000 3.0: 0 10000000000 1000000000000000000000000000000000000000000000000000 1.0: 0 01111111111 0000000000000000000000000000000000000000000000000000 0.1: 0 01111111011 1001100110011001100110011001100110011001100110011010 nan: 0 11111111111 1000000000000000000000000000000000000000000000000000 inf: 0 11111111111 0000000000000000000000000000000000000000000000000000 -inf: 1 11111111111 0000000000000000000000000000000000000000000000000000 2.22507385851e-308: 0 00000000001 0000000000000000000000000000000000000000000000000001 1.79769313486e+308: 0 11111111110 1111111111111111111111111111111111111111111111111111 2.22507385851e-308: 0 00000000000 1111111111111111111111111111111111111111111111111111 4.94065645841e-324: 0 00000000000 0000000000000000000000000000000000000000000000000001 |