Python中浮点数的二进制表示(位而非十六进制)

96
如何根据32位浮点数的IEEE 754表示获取一个由0和1组成的字符串?
例如,给定输入为1.00,结果应为'00111111100000000000000000000000'。
14个回答

97

你可以使用 struct 包来实现这个功能:

import struct
def binary(num):
    return ''.join('{:0>8b}'.format(c) for c in struct.pack('!f', num))

它将其打包为网络字节序浮点数,然后将每个结果字节转换为8位二进制表示并将它们连接在一起:

>>> binary(1)
'00111111100000000000000000000000'

编辑:有人请求扩展解释。我将使用中间变量扩展此内容,以便对每个步骤进行注释。

def binary(num):
    # Struct can provide us with the float packed into bytes. The '!' ensures that
    # it's in network byte order (big-endian) and the 'f' says that it should be
    # packed as a float. Alternatively, for double-precision, you could use 'd'.
    packed = struct.pack('!f', num)
    print 'Packed: %s' % repr(packed)

    # For each character in the returned string, we'll turn it into its corresponding
    # integer code point
    # 
    # [62, 163, 215, 10] = [ord(c) for c in '>\xa3\xd7\n']
    integers = [ord(c) for c in packed]
    print 'Integers: %s' % integers

    # For each integer, we'll convert it to its binary representation.
    binaries = [bin(i) for i in integers]
    print 'Binaries: %s' % binaries

    # Now strip off the '0b' from each of these
    stripped_binaries = [s.replace('0b', '') for s in binaries]
    print 'Stripped: %s' % stripped_binaries

    # Pad each byte's binary representation's with 0's to make sure it has all 8 bits:
    #
    # ['00111110', '10100011', '11010111', '00001010']
    padded = [s.rjust(8, '0') for s in stripped_binaries]
    print 'Padded: %s' % padded

    # At this point, we have each of the bytes for the network byte ordered float
    # in an array as binary strings. Now we just concatenate them to get the total
    # representation of the float:
    return ''.join(padded)

以下是几个示例的结果:

>>> binary(1)
Packed: '?\x80\x00\x00'
Integers: [63, 128, 0, 0]
Binaries: ['0b111111', '0b10000000', '0b0', '0b0']
Stripped: ['111111', '10000000', '0', '0']
Padded: ['00111111', '10000000', '00000000', '00000000']
'00111111100000000000000000000000'

>>> binary(0.32)
Packed: '>\xa3\xd7\n'
Integers: [62, 163, 215, 10]
Binaries: ['0b111110', '0b10100011', '0b11010111', '0b1010']
Stripped: ['111110', '10100011', '11010111', '1010']
Padded: ['00111110', '10100011', '11010111', '00001010']
'00111110101000111101011100001010'

1
我同意 @mgilson 的看法 -- 实际上,我更喜欢他的解决方案,但只使用一个最终的 replacerjust 到 32(或64)位,而不是每个字节都用一次。 - Dan Lecocq
1
@MarkRansom: bin(struct.unpack('!Q', struct.pack('!d', -1.))[0])[2:].zfill(64) 适用于双精度浮点数(64位浮点数)。 - jfs
16
在Python 3中,由于pack()返回一个字节对象,可以直接迭代产生整数,因此必须省略对ord()的调用。 - Rob Smallshire
8
一行Python 3代码:''.join('{:0>8b}'.format(c) for c in struct.pack('!f', num)),可以将浮点数num转换为32位二进制字符串。 - Cristian Ciupitu
2
@gciriani请将浮点格式(f)替换为双精度格式(d), 即''.join('{:0>8b}'.format(c) for c in struct.pack('!f', num)) - Cristian Ciupitu
显示剩余9条评论

41

这是一个很丑的例子...

>>> import struct
>>> bin(struct.unpack('!i',struct.pack('!f',1.0))[0])
'0b111111100000000000000000000000'

基本上,我只是使用了结构体模块将浮点数转换为整数...


这是稍微更好的一种方法,使用ctypes

>>> import ctypes
>>> bin(ctypes.c_uint32.from_buffer(ctypes.c_float(1.0)).value)
'0b111111100000000000000000000000'

基本上,我创建了一个float并使用相同的内存位置,但我将其标记为c_uint32c_uint32的值是一个Python整数,您可以在其上使用内置的bin函数。

注意:通过切换类型,我们也可以执行反向操作

>>> ctypes.c_float.from_buffer(ctypes.c_uint32(int('0b111111100000000000000000000000', 2))).value
1.0

对于双精度64位浮点数,我们可以使用同样的技巧,只需使用ctypes.c_doublectypes.c_uint64即可。


它依赖于sizeof(int) == sizeof(float)(使用'!' 强制 i格式为4个字节)。 ctypes.sizeof(ctypes.c_int) 可能取决于平台。 在Python 3.2+中有 int.from_bytes()函数。 - jfs
@J.F.Sebastian -- 我想我也假设bin返回IEEE标准表示... - mgilson
我想我也不知道。bin函数并不能保证输出的结果——只是一个Python可以处理的对象而已。如果sizeof(int) != sizeof(float),那就不是使用IEEE 754了(是吗?)。在那种情况下,由bin返回的比特模式也可能是任意的——例如由于不同字节序导致的比特被颠倒报告等等。符号位可能在其他地方,等等。 - mgilson
sizeof(int) != sizeof(float)问题与bin()无关(它适用于Python中无限制的整数)。为了支持负浮点数,请使用!I格式 - jfs
很抱歉这个帖子被顶起来了,但是针对ctypes变体,我必须进行更正:当处理负数时,结果完全错误,可能是由于Python在中间某个地方应用了2的补码或其他一些诡计。当使用数字-1.0尝试您的示例时,实际编码的IEEE 754结果为4.0('-0b1000000100000000000000000000000')...当使用ctypes.c_uint替换您的示例中的ctypes.c_int时,它似乎可以正确工作。编辑:对于快速响应表示非常感谢! :) - ToVine

35

使用bitstring模块找到了另一个解决方案。

import bitstring
f1 = bitstring.BitArray(float=1.0, length=32)
print(f1.bin)

输出:

00111111100000000000000000000000

6
这些天使用的是 f1.bin - Xander Dunn
2
可以从以下网站验证该值:https://www.h-schmidt.net/FloatConverter/IEEE754.html - Arighna
bitstring 链接已损坏。 - liang

20

为了完整起见,您可以使用numpy实现此操作:

f = 1.00
int32bits = np.asarray(f, dtype=np.float32).view(np.int32).item()  # item() optional
你可以使用 b 格式说明符打印带填充的内容。
print('{:032b}'.format(int32bits))

4
int32bits = np.float32(1.0).view(np.int32) 稍微整洁一点: - Aditya Sriram
1
如果你真正想要Python中的整数int,仍需要使用.item() - Eric
1
是的,没错。否则,你会得到一个 np.int32 对象。 - Aditya Sriram
2
依然整洁:int32bits = np.float32(1.0).view(np.int32).item() :) - Aditya Sriram
这是最佳答案,因为它适用于np.float16类型,在Python中没有本地等效项。 - Martin Ueding
显示剩余2条评论

11

使用这两个简单的函数(Python >=3.6),您可以轻松地将浮点数转换为二进制,反之亦然,适用于IEEE 754 binary64。

import struct

def bin2float(b):
    ''' Convert binary string to a float.

    Attributes:
        :b: Binary string to transform.
    '''
    h = int(b, 2).to_bytes(8, byteorder="big")
    return struct.unpack('>d', h)[0]


def float2bin(f):
    ''' Convert float to 64-bit binary string.

    Attributes:
        :f: Float number to transform.
    '''
    [d] = struct.unpack(">Q", struct.pack(">d", f))
    return f'{d:064b}'
例如:
print(float2bin(1.618033988749894))
print(float2bin(3.14159265359))
print(float2bin(5.125))
print(float2bin(13.80))

print(bin2float('0011111111111001111000110111011110011011100101111111010010100100'))
print(bin2float('0100000000001001001000011111101101010100010001000010111011101010'))
print(bin2float('0100000000010100100000000000000000000000000000000000000000000000'))
print(bin2float('0100000000101011100110011001100110011001100110011001100110011010'))

输出结果为:

0011111111111001111000110111011110011011100101111111010010100100
0100000000001001001000011111101101010100010001000010111011101010
0100000000010100100000000000000000000000000000000000000000000000
0100000000101011100110011001100110011001100110011001100110011010
1.618033988749894
3.14159265359
5.125
13.8

我希望你喜欢它,它对我来说完美无缺。


还有,为什么你在使用 struct.unpack('>Q', ...) 而不是 int.from_bytes(..., 'big') - Azat Ibrakov
你说得对,那个“decode”行不应该出现在代码里,我已经修改了。感谢你的提醒。关于你最后的评论,使用“struct”是因为函数接收的参数是浮点数而不是整数,而浮点数没有“to_bytes()”方法可用。如果你有更好的方法,请随时提出 :) - JavDomGom
谢谢您的回答!如果您有时间,能否请看一下这个例子?如果我理解正确,1.0 的浮点表示应该是 .100...0 * 2^(00000000001),其中指数有11位,尾数有52位。因此,我认为 1.0 的浮点表示应该是 00000000000110000000000000000000000000000000000000000000000000000。我尝试了 float2bin(1.0),结果是 0011111111110000000000000000000000000000000000000000000000000000。您能否解释一下这之间的区别? - user56202

10
这个问题可以通过分为两部分来更加清晰地解决。
第一步是将浮点数转换为具有等效位模式的整数:
import struct
def float32_bit_pattern(value):
    return sum(ord(b) << 8*i for i,b in enumerate(struct.pack('f', value)))

Python 3不需要使用ord将字节转换为整数,因此你需要稍微简化一下:

def float32_bit_pattern(value):
    return sum(b << 8*i for i,b in enumerate(struct.pack('f', value)))

接下来将整数转换为字符串:

def int_to_binary(value, bits):
    return bin(value).replace('0b', '').rjust(bits, '0')

现在将它们组合起来:

>>> int_to_binary(float32_bit_pattern(1.0), 32)
'00111111100000000000000000000000'

2
在Python 3.2+中,可以将float32_bit_pattern定义为lambda x: int.from_bytes(struct.pack("f", x), byteorder="little") - Janus Troelsen
无法编译。 - noobcoder
@noobcoder 抱歉,Python 3 不需要使用 ord 来转换 struct.pack 的输出。我忘记提到还需要导入 struct 模块。 - Mark Ransom

5
在丹的答案基础上跟进,附上适用于Python3的彩色版本:
import struct

BLUE = "\033[1;34m"
CYAN = "\033[1;36m"
GREEN = "\033[0;32m"
RESET = "\033[0;0m"


def binary(num):
    return [bin(c).replace('0b', '').rjust(8, '0') for c in struct.pack('!f', num)]


def binary_str(num):
    bits = ''.join(binary(num))
    return ''.join([BLUE, bits[:1], GREEN, bits[1:10], CYAN, bits[10:], RESET])


def binary_str_fp16(num):
    bits = ''.join(binary(num))
    return ''.join([BLUE, bits[:1], GREEN, bits[1:10][-5:], CYAN, bits[10:][:11], RESET])

x = 0.7
print(x, "as fp32:", binary_str(0.7), "as fp16 is sort of:", binary_str_fp16(0.7))

colored representation


2

浏览了很多类似的问题后,我编写了一些代码,希望能实现我想要的功能。

f = 1.00
negative = False
if f < 0:
    f = f*-1
    negative = True

s = struct.pack('>f', f)
p = struct.unpack('>l', s)[0]
hex_data =  hex(p)

scale = 16
num_of_bits = 32
binrep = bin(int(hex_data, scale))[2:].zfill(num_of_bits)
if negative:
    binrep = '1' + binrep[1:]

binrep 是结果。下面将对每一部分进行解释。


f = 1.00
negative = False
if f < 0:
    f = f*-1
    negative = True

如果数字为负数,则将其转换为正数,并将变量negative设置为false。这样做的原因是,正数和负数的二进制表示之间的差异仅在于第一位,这比弄清在处理负数时出现了什么问题更简单。


s = struct.pack('>f', f)                          #'?\x80\x00\x00'
p = struct.unpack('>l', s)[0]                     #1065353216
hex_data =  hex(p)                                #'0x3f800000'

s是二进制f的十六进制表示,但它不是我需要的漂亮形式。这就是

的作用。它是十六进制s的整数表示。然后进行另一种转换以获得漂亮的十六进制。


scale = 16
num_of_bits = 32
binrep = bin(int(hex_data, scale))[2:].zfill(num_of_bits)
if negative:
    binrep = '1' + binrep[1:]

scale是十六进制的基数。由于float类型占用32位,因此num_of_bits的值为32,稍后会使用它来填充额外的位置,以达到32位。从这个问题中获取了binrep的代码。如果数字为负数,只需更改第一位即可。


我知道这很丑陋,但我没有找到一个好的方法,而且我需要快速完成。欢迎评论。


1
bin(struct.unpack('!I', struct.pack('!f', -1.))[0])[2:].zfill(32) 支持正负浮点数。为了提高性能,您可以修改 b2a_bin(struct.pack('!f', -1.)) 以直接接受浮点数。 - jfs

2

虽然这比所要求的多一些,但当我找到这篇文章时正是我需要的。该代码将给出IEEE 754 32位浮点数的尾数、基数和符号。

import ctypes
def binRep(num):
    binNum = bin(ctypes.c_uint.from_buffer(ctypes.c_float(num)).value)[2:]
    print("bits: " + binNum.rjust(32,"0"))
    mantissa = "1" + binNum[-23:]
    print("sig (bin): " + mantissa.rjust(24))
    mantInt = int(mantissa,2)/2**23
    print("sig (float): " + str(mantInt))
    base = int(binNum[-31:-23],2)-127
    print("base:" + str(base))
    sign = 1-2*("1"==binNum[-32:-31].rjust(1,"0"))
    print("sign:" + str(sign))
    print("recreate:" + str(sign*mantInt*(2**base)))

binRep(-0.75)

输出:

bits: 10111111010000000000000000000000
sig (bin): 110000000000000000000000
sig (float): 1.5
base:-1
sign:-1
recreate:-0.75

当使用sqrt(2)验证此脚本时,似乎只给出了6位小数正确。是否有任何错误?2 ** .5 = 1.4142135623730951,但是您的脚本输出binRep(2 ** .5)如下,重新创建:1.4142135381698608。 - gciriani

1

将0到1之间的浮点数转换

def float_bin(n, places = 3): 
    if (n < 0 or n > 1):
        return "ERROR, n must be in 0..1"
    
    answer = "0."
    while n > 0:
        if len(answer) - 2 == places: 
            return answer
        
        b = n * 2
        if b >= 1:
            answer += '1'
            n = b - 1
        else:
            answer += '0'
            n = b
            
    return answer

1
我修复了代码:长度检查没有考虑前导零和小数点,b > 1 必须改为 b >= 1。测试结果:0 是 "0.",0.5 是 "0.1",0.25 是 "0.01",0.125 是 "0.001",0.1 是 "0.0001100110"(保留10位小数)。 - kol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接