Pythonで浮動小数点数floatと16進数表現の文字列を相互に変換
Pythonで浮動小数点数float
と16進数表現の文字列を相互に変換する方法を説明する。
- 標準ライブラリ
struct
,binascii
モジュールを使う方法 float
型のメソッドhex()
とfromhex()
を使う方法
の2つの方法があり、それぞれ変換できる16進数表現の形式が異なる。
Pythonのfloat
は他のプログラミング言語ではdouble
などと呼ばれることが多い倍精度浮動小数点数(64bit)だが、(1)の方法では32bitの16進数表現に変換する方法についても述べる。
ここでは、以下の内容について説明する。
- 2つの16進数表現
- シンプルな16進数表現
- pを含む16進数表現
- 浮動小数点数
float
とシンプルな16進数表現文字列との相互変換float
から16進数表現文字列- 2進数、8進数への変換
- 16進数表現文字列から
float
- 単精度浮動小数点数(32bit)の場合
- 浮動小数点数
float
とpを含む16進数表現文字列との相互変換float
から16進数表現文字列:hex()
- 16進数表現文字列から
float
:fromhex()
Pythonにおけるfloat
の範囲(最大値・最小値)については以下の記事を参照。
浮動小数点数float
ではなく整数int
を2進数、8進数、16進数の文字列にそれぞれ変換する方法は以下の記事を参照。
2つの16進数表現
シンプルな16進数表現
浮動小数点数は以下の3つのブロックに分かれる。
- 符号ビット (sign)
- 指数部 (exponent)
- 仮数部 (fraction)
Pythonの浮動小数点型float
は倍精度浮動小数点数(64bit)。それぞれのビット数は標準規格であるIEEE 754で以下のように定められている。
例えば10進数の42.195
をこの形式の16進数に変換すると0x4045 18f5 c28f 5c29
となる。
16進数から10進数への変換は、指数部が0x000
(0
または非正規化数)、0x7ff
(無限大またはNaN
)の特殊な場合を除き、以下の数式で計算される。exponent bias
は1023(0x3ff)
$$ (-1)^{sign} \times 2^{exponent - exponent \, bias} \times 1.fraction $$
詳細は以下のページを参照。
Pythonでの変換方法は後述する。
とりあえず確認したい場合は以下のWebページが便利。各ビットの割当などの詳細も表示されるので理解しやすい。ボタンが4つあるが、上の2つが単精度(32bit)で下の2つが倍精度(64bit)の16進数表現との変換に対応している。
pを含む16進数表現
以下のような形式もある。
[sign] ['0x'] integer ['.' fraction] ['p' exponent]
sign は必須ではなく、 + と - のどちらかです。 integer と fraction は 16 進数の文字列で、 exponent は 10 進数で符号もつけられます。大文字・小文字は区別されず、最低でも 1 つの 16 進数文字を整数部もしくは小数部に含む必要があります。この制限は C99 規格のセクション 6.4.4.2 で規定されていて、 Java 1.5 以降でも使われています。
組み込み型 — Python 3.7.1rc1 ドキュメント
ドキュメントにもあるように、Cの%a
書式や、JavaのDouble.toHexString
で取得できる形式と同じ。
例えば10進数の42.195
をこの形式の16進数表現に変換すると0x1.518f5c28f5c29p+5
となる。
Pythonでの変換方法は後述。
浮動小数点数floatとシンプルな16進数表現文字列との相互変換
floatから16進数表現文字列
float
をシンプルな16進数表現文字列に変換するには標準ライブラリのstruct
モジュールを使う。
float
の最大値を例とする。
import struct
import sys
f_max = sys.float_info.max
print(f_max)
# 1.7976931348623157e+308
struct.pack()
でfloat
をバイト列bytes
に変換する。
print(struct.pack('>d', f_max))
# b'\x7f\xef\xff\xff\xff\xff\xff\xff'
print(type(struct.pack('>d', f_max)))
# <class 'bytes'>
第一引数の>d
は書式文字列。
一文字目はバイトオーダーの指定で>
はビッグエンディアンを表す。二文字目のd
はdouble
(8byte = 64bitの浮動小数点数)を表す。第二引数の値を64bitビッグエンディアンの浮動小数点数としてバイト列に変換するという意味になる。
リトルエンディアンは<
。<d
とすると並びが逆になる。
print(struct.pack('<d', f_max))
# b'\xff\xff\xff\xff\xff\xff\xef\x7f'
書式文字列の詳細は公式ドキュメント参照。
これを一旦整数に戻す。バイト列から他のデータ型への変換はstruct.unpack()
を使う。64bitの整数であるQ
を書式文字列とする。struct.pack()
で指定した書式文字列とバイトオーダーを合わせる。
struct.unpack()
は複数の値を変換する関数なので、一つだけ変換する場合もタプルで返される。変換した値は[0]
で取り出せる。
print(struct.unpack('>Q', struct.pack('>d', f_max)))
# (9218868437227405311,)
print(type(struct.unpack('>Q', struct.pack('>d', f_max))))
# <class 'tuple'>
print(struct.unpack('>Q', struct.pack('>d', f_max))[0])
# 9218868437227405311
print(type(struct.unpack('>Q', struct.pack('>d', f_max))[0]))
# <class 'int'>
なお、書式文字列を64bitの浮動小数点数であるd
とすると当然ながらもとの値に戻る。
print(struct.unpack('>d', struct.pack('>d', f_max))[0])
# 1.7976931348623157e+308
Q
で整数に変換した値を16進数の文字列に変換するにはhex()
を使う。
print(hex(struct.unpack('>Q', struct.pack('>d', f_max))[0]))
# 0x7fefffffffffffff
print(type(hex(struct.unpack('>Q', struct.pack('>d', f_max))[0])))
# <class 'str'>
まとめて関数化するとfloat
から16進数の文字列への変換は以下のようになる。なお、Pythonのfloat
は64bitの浮動小数点数でほかのプログラミング言語のdouble
に相当するので、Pythonにはdouble
という型はないが便宜上double_to_hex
という関数名にしている。
def double_to_hex(f):
return hex(struct.unpack('>Q', struct.pack('>d', f))[0])
float
の範囲を超える値を指定してもエラーにはならないので注意。
print(double_to_hex(f_max))
# 0x7fefffffffffffff
print(double_to_hex(42.195))
# 0x404518f5c28f5c29
print(double_to_hex(1e500))
# 0x7ff0000000000000
print(double_to_hex(1e-500))
# 0x0
2進数、8進数への変換
16進数ではなく2進数や8進数に変換したい場合は、上で求めた16進数の値をint()
で整数に変換したあとでbin()
またはoct()
で2進数や8進数の文字列に変換すればいい。
print(int(double_to_hex(f_max), 16))
# 9218868437227405311
print(bin(int(double_to_hex(f_max), 16)))
# 0b111111111101111111111111111111111111111111111111111111111111111
print(oct(int(double_to_hex(f_max), 16)))
# 0o777577777777777777777
以下のような関数で直接変換することも可能。
def double_to_bin(f):
return bin(struct.unpack('>Q', struct.pack('>d', f))[0])
def double_to_oct(f):
return oct(struct.unpack('>Q', struct.pack('>d', f))[0])
print(double_to_bin(f_max))
# 0b111111111101111111111111111111111111111111111111111111111111111
print(double_to_oct(f_max))
# 0o777577777777777777777
bin()
, oct()
については以下の記事を参照。
16進数表現文字列からfloat
16進数表現文字列をfloat
に変換するには、まず文字列をバイト列に変換する。binascii.unhexlify()
を使う。
import struct
import binascii
f_max_s = '7fefffffffffffff'
print(binascii.unhexlify(f_max_s))
# b'\x7f\xef\xff\xff\xff\xff\xff\xff'
print(type(binascii.unhexlify(f_max_s)))
# <class 'bytes'>
上で説明したstruct.pack()
の書式文字列を>d
としてバイト列からfloat
に変換する(元の文字列のバイトオーダーがリトルエンディアンの場合は<d
)。
print(struct.unpack('>d', binascii.unhexlify(f_max_s)))
# (1.7976931348623157e+308,)
print(struct.unpack('>d', binascii.unhexlify(f_max_s))[0])
# 1.7976931348623157e+308
print(type(struct.unpack('>d', binascii.unhexlify(f_max_s))[0]))
# <class 'float'>
これを関数化すると以下のようになる。ここでも、Pythonにはdouble
という型はないが便宜上hex_tou_double
という関数名にしている。
def hex_to_double(s):
if s.startswith('0x'):
s = s[2:]
s = s.replace(' ', '')
return struct.unpack('>d', binascii.unhexlify(s))[0]
0x
がついている場合や空白で区切られた場合にも対応するようにしている。
print(hex_to_double('7fefffffffffffff'))
# 1.7976931348623157e+308
print(hex_to_double('0x7fefffffffffffff'))
# 1.7976931348623157e+308
print(hex_to_double('0x7fef ffff ffff ffff'))
# 1.7976931348623157e+308
print(hex_to_double('0x4045 18f5 c28f 5c29'))
# 42.195
無限大やNaN
、非正規化数にも対応する。
print(hex_to_double('7ff0000000000000'))
# inf
print(hex_to_double('7ff0000000000001'))
# nan
print(hex_to_double('0000000000000001'))
# 5e-324
文字数が多かったり少なかったりするとエラーとなる。
# print(hex_to_double('ffff ffff ffff ffff ff'))
# error: unpack requires a buffer of 8 bytes
# print(hex_to_double('ffff ffff ffff ff'))
# error: unpack requires a buffer of 8 bytes
単精度浮動小数点数(32bit)の場合
Pythonのfloat
は倍精度浮動小数点数(64bit)であるが、これを単精度浮動小数点数(32bit)の文字列を相互に変換したい場合は、上述の関数の書式文字列を変更すればOK。単精度浮動小数点数(32bit)はf
、32bitの整数はI
とする。
float
から単精度の16進数文字列。
def float_to_hex(f):
return hex(struct.unpack('>I', struct.pack('>f', f))[0])
print(float_to_hex(42.195))
# 0x4228c7ae
単精度の16進数文字列からfloat
。
def hex_to_float(s):
if s.startswith('0x'):
s = s[2:]
s = s.replace(' ', '')
return struct.unpack('>f', binascii.unhexlify(s))[0]
print(hex_to_float('0x4228c7ae'))
# 42.19499969482422
とりあえず確認したいだけであれば上で紹介したページが便利。
浮動小数点数floatとpを含む16進数表現文字列との相互変換
floatから16進数表現文字列: hex()
float
型のメソッドhex()
でfloat
をpを含む16進数表現の文字列に変換できる。
f = 256.0
print(f.hex())
# 0x1.0000000000000p+8
print(type(f.hex()))
# <class 'str'>
.
が続いて一見分かりにくいが、以下のようにも書ける。
print(256.0.hex())
# 0x1.0000000000000p+8
print(0.5.hex())
# 0x1.0000000000000p-1
print(42.195.hex())
# 0x1.518f5c28f5c29p+5
hex()
はfloat
のメソッドなので、整数型からは呼べない。例えば256
ではなく256.0
のようにfloat
型として記述する必要がある。
i = 256
# print(i.hex())
# AttributeError: 'int' object has no attribute 'hex'
16進数表現文字列からfloat: fromhex()
float
型のクラスメソッドfromhex()
でpを含む16進数表現の文字列をfloat
に変換できる。
s = '0x1.0000000000000p+8'
print(float.fromhex(s))
# 256.0
print(type(float.fromhex(s)))
# <class 'float'>
0x
や.<fraction>
, p<exponent>
の部分は省略可能。ただし、例えば0x
が無いと10進数と16進数で混乱してしまうので、あまり省略しすぎないほうがいい。
print(float.fromhex('0x1p+8'))
# 256.0
print(float.fromhex('1p+8'))
# 256.0
print(float.fromhex('0x100'))
# 256.0
print(float.fromhex('100'))
# 256.0
上の例のように、同じfloat
に変換される文字列は一意ではない。
参考までに各値と具体的な計算を示す。exponent
は10進数表記なので注意。
print(float.fromhex('0xf2.f8p-10'))
# 0.237274169921875
print((15 * 16**1 + 2 * 16**0 + 15 * 16**-1 + 8 * 16**-2) * 2**-10)
# 0.237274169921875
float
の最大値を変換すると以下の通り。
import sys
f_max = sys.float_info.max
print(f_max)
# 1.7976931348623157e+308
print(f_max.hex())
# 0x1.fffffffffffffp+1023
print(float.fromhex('0x1.fffffffffffffp+1023'))
# 1.7976931348623157e+308
これを超える値を示す文字列を指定するとオーバーフローする。
# print(float.fromhex('0x1.0000000000000p+1024'))
# OverflowError: hexadecimal value too large to represent as a float
# print(float.fromhex('0x2.0000000000000p+1023'))
# OverflowError: hexadecimal value too large to represent as a float
float
の正の最小の正規化数は以下の通り。
f_min = sys.float_info.min
print(f_min)
# 2.2250738585072014e-308
print(f_min.hex())
# 0x1.0000000000000p-1022
print(float.fromhex('0x1.0000000000000p-1022'))
# 2.2250738585072014e-308
正の最小の非正規化数(指数部が0x000
)は以下のように表せる。
print(float.fromhex('0x0.0000000000001p-1022'))
# 5e-324
print(format(float.fromhex('0x0.0000000000001p-1022'), '.17'))
# 4.9406564584124654e-324
更に小さい値を示す文字列を指定した場合はエラーにはならず0.0
になる。
print(float.fromhex('0x0.0000000000001p-1023'))
# 0.0