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で以下のように定められています。 倍精度浮動小数点数 - Wikipedia

例えば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 $$ 詳細は以下のページを参照。

倍精度浮動小数点数 - 指数部の符号化方式 - Wikipedia

Pythonでの変換方法は後述します。 とりあえず確認したい場合は以下のWebページが便利です。各ビットの割当などの詳細も表示されるので理解しやすいです。ボタンが4つあるが、上の2つが単精度(32bit)で下の2つが倍精度(64bit)の16進数表現との変換に対応しています。

Floating Point to Hex Converter

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モジュールを使います。

struct --- バイト列をパックされたバイナリデータとして解釈する — Python 3.7.1rc1 ドキュメント

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 --- バイト列をパックされたバイナリデータとして解釈する — Python 3.7.1rc1 ドキュメント

これを一旦整数に戻す。バイト列から他のデータ型への変換は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()を使います。

binascii.unhexlify() --- バイナリデータと ASCII データとの間での変換 — Python 3.7.1rc1 ドキュメント

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)の文字列を相互に変換したい場合は、上述の関数の書式文字列を変更すれば問題ありません。単精度浮動小数点数(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

とりあえず確認したいだけであれば上で紹介したページが便利です。

Floating Point to Hex Converter

浮動小数点数floatとpを含む16進数表現文字列との相互変換

floatから16進数表現文字列: hex()

float型のメソッドhex()でfloatをpを含む16進数表現の文字列に変換できます。

組み込み型 float.hex() — Python 3.7.1rc1 ドキュメント

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に変換できます。

組み込み型 float.fromhex() — Python 3.7.1rc1 ドキュメント

s = '0x1.0000000000000p+8'

print(float.fromhex(s))
# 256.0

print(type(float.fromhex(s)))
# <class 'float'>

0xや., pの部分は省略できます。ただし、例えば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
Last Updated: 6/26/2019, 10:34:03 PM