浮動小数点数のまとめ
概要
- 限られたビットの中で小数を表現する方法の一つが浮動小数点数
- 今日の実装は処理系、言語を問わず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をインストールする)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
0 件のコメント:
コメントを投稿