在Python中将float.hex()值转换为二进制

9 投票
4 回答
11312 浏览
提问于 2025-04-17 03:44

我想知道怎么把 float.hex() 返回的结果转换成二进制,比如从 0x1.a000000000000p+2 转换成 110.1

有没有人能帮帮我?谢谢。

4 个回答

1

关于大数字的一个重要更新

.

下面的代码展示了我之前答案中的一个问题。
请注意,我把函数hexf2binf(floathex)的参数从h改成了floathex,这样就和unutbu的函数floathex_to_binary(floathex)中的参数一致了。

from decimal import Decimal,getcontext
getcontext.prec = 500
tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
com = dict((i,tin) for i,tin in enumerate(tinies,1))

def hexf2binf(floathex, tinies = tinies):
    fromh = float.fromhex(floathex)
    print 'fromh = float.fromhex(h)    DONE'
    print 'fromh ==',fromh
    print "str(float.fromhex(floathex)) ==",str(float.fromhex(floathex))

    a,_,p = str(float.fromhex(floathex)).partition('.')
    print 'before the dot ==',a
    print 'after the dot ==',p
    # the following part transforms the float after the dot into a binary after the dot
    bits = []
    pdec = Decimal('.'+p)
    for tin in tinies:
        if pdec-tin==0:
            bits.append('1')
            break
        elif pdec-tin>0:
            bits.append('1')
            pdec -= tin
        else:
            bits.append('0')
    pbin = ''.join(bits) # it's the binary after the dot
    # the float before the dot is easily transformed into a binary
    return '.'.join((bin(int(a)),pbin))



x = x = 123456789012345685803008.0
print '   x = {:f}'.format(x)
h = x.hex()
print '   h = x.hex() ==',h

print '\nENTERING hexf2binf(floathex) with h as argument'
v = hexf2binf(h)
print '\nhexf2binf(x)==',v

结果

   x = 123456789012345685803008.000000
   h = x.hex() == 0x1.a249b1f10a06dp+76

ENTERING hexf2binf(floathex) with h as argument
fromh = float.fromhex(h)    DONE
fromh == 1.23456789012e+23
str(float.fromhex(floathex)) == 1.23456789012e+23
before the dot == 1
after the dot == 23456789012e+23

hexf2binf(x)== 0b1.111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

问题出在这条指令上:str(float.fromhex(x)),在这条指令a,_,p = str(float.fromhex(x)).partition('.')中,对于一个大数字,它会生成float.fromhex(x)的科学计数法表示。
因此,点前的部分(a作为ante)和点后的部分(p作为post)都是错误的。

解决这个问题很简单:只需把不准确的指令替换成这个:

a,_,p = '{:f}'.format(float.fromhex(x)).partition('.')

.

注意:

在一台典型的运行Python的机器上,Python的浮点数有53位的精度可用,所以当你输入小数0.1时,内部存储的值是二进制分数 0.00011001100110011001100110011001100110011001100110011010 http://docs.python.org/tutorial/floatingpoint.html

这意味着,当你在代码中写一个大的浮点数时,它的内部表示实际上是对你写的值的一个近似。
这一点可以通过以下代码来说明:

x1 = 123456789012345685803008.0
print 'x1 == 123456789012345685803008.0'
h1 = x1.hex()
print 'h1 = x1.hex() ==',h1
y1 = float.fromhex(h1)
print 'y1 = float.fromhex(h1) == {:f}'.format(y1)
print

x2 = 123456789012345678901234.64655
print 'x2 == 123456789012345678901234.64655'
h2 = x2.hex()
print 'h2 = x2.hex() ==',h2
y2 = float.fromhex(h2)
print 'y2 = float.fromhex(h2) == {:f}'.format(y2)
print

结果

x1 == 123456789012345685803008.0
h1 = x1.hex() == 0x1.a249b1f10a06dp+76
y1 = float.fromhex(h1) == 123456789012345685803008.000000

x2 == 123456789012345678901234.64655
h2 = x2.hex() == 0x1.a249b1f10a06dp+76
y2 = float.fromhex(h2) == 123456789012345685803008.000000

变量h1h2的值是相同的,因为虽然在脚本中给标识符x1x2赋了不同的值,但对象x1x2在机器中用相同的近似表示。
123456789012345685803008.0的内部表示是123456789012345685803008.0的确切值,而123456789012345678901234.64655的内部表示也是这个值的近似,因此从x1x2推导出的h1h2给出了相同的值。

这个问题在我们用十进制表示数字时会出现,但如果我们直接用十六进制或二进制表示数字,就不会出现这个问题。

我想强调的是

我写了一个函数afterdotbinary2float(sbin, com = com)来验证hexf2binf( )产生的结果。这个验证在传递给hexf2binf( )的数字不大时效果很好,但由于大数字的内部近似(即有很多数字),我在想这个验证是否会失真。实际上,当一个大数字进入这个函数时,它已经被近似了:点后的数字被转化为一系列零;
如下所示:

from decimal import Decimal, getcontext
getcontext().prec = 500
tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
com = dict((i,tin) for i,tin in enumerate(tinies,1))


def afterdotbinary2float(sbin, com = com):
    '''Transforms a binary lying after a dot into a float after a dot'''
    if sbin.startswith('0b.') or sbin.startswith('.'):
        sbin = sbin.split('.')[1]
    if all(c in '01' for c in sbin):
        return sum(int(c)*com[i] for i,c in enumerate(sbin,1))
    else:
        return None



def hexf2binf(floathex, tinies = tinies):
    '''Transforms an hexadecimal float with a dot into a binary float with a dot'''
    a,_,p = '{:.400f}'.format(float.fromhex(floathex)).partition('.')
    # the following part transforms the float after the dot into a binary after the dot
    bits = []
    pdec = Decimal('.'+p)
    for tin in tinies:
        if pdec-tin==0:
            bits.append('1')
            break
        elif pdec-tin>0:
            bits.append('1')
            pdec -= tin
        else:
            bits.append('0')
    pbin = ''.join(bits) # it's the binary after the dot
    # the float before the dot is easily transformed into a binary
    return '.'.join((bin(int(a)),pbin))



for n in (123456789012345685803008.0, 123456789012345678901234.64655, Decimal('123456789012345.2546') ):
    print 'n == {:f}      transformed with its method hex() to:'.format(n)
    nhexed = n.hex()
    print 'nhexed = n.hex() ==',nhexed
    print '\nhexf2binf(nhexed) ==',hexf2binf(nhexed)
    print "\nVerification:\nbefore,_,after = hexf2binf(nhexed).partition('.')"
    before,_,after = hexf2binf(nhexed).partition('.')
    print 'before ==',before,'   after ==',after
    print 'int(before,2) ==',int(before,2)
    print 'afterdotbinary2float(after) ==',afterdotbinary2float(after)
    print '\n---------------------------------------------------------------\n'

结果

n == 123456789012345685803008.000000      transformed with its method hex() to:
nhexed = n.hex() == 0x1.a249b1f10a06dp+76

hexf2binf(nhexed) == 0b11010001001001001101100011111000100001010000001101101000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Verification:
before,_,after = hexf2binf(nhexed).partition('.')
before == 0b11010001001001001101100011111000100001010000001101101000000000000000000000000    after == 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int(before,2) == 123456789012345685803008
afterdotbinary2float(after) == 0E-399

---------------------------------------------------------------

n == 123456789012345685803008.000000      transformed with its method hex() to:
nhexed = n.hex() == 0x1.a249b1f10a06dp+76

hexf2binf(nhexed) == 0b11010001001001001101100011111000100001010000001101101000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Verification:
before,_,after = hexf2binf(nhexed).partition('.')
before == 0b11010001001001001101100011111000100001010000001101101000000000000000000000000    after == 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int(before,2) == 123456789012345685803008
afterdotbinary2float(after) == 0E-399

---------------------------------------------------------------

n == 123456789012345.2546      transformed with its method hex() to:

Traceback (most recent call last):
  File "I:\verfitruc.py", line 41, in <module>
    nhexed = n.hex()
AttributeError: 'Decimal' object has no attribute 'hex'

结论:用数字123456789012345685803008.0123456789012345678901234.64655进行测试没有区别,也没有意义。

所以,我想测试没有近似的数字,于是我传递了一个Decimal浮点数。正如你所看到的,问题在于这样的实例没有hex()方法。

.

最后,我对我的大数字函数并不完全确定,但在我修正了不准确的指令后,它对普通数字的处理是正确的。

.

更新

我在指令中添加了'.400':

a,_,p = '{:.400f}'.format(fromh).partition('.')

否则p的值可能会被截断,从而给出一个与传递给函数的数字略有不同的二进制表示。

我设置400是因为这是我为列表tinies定义的长度,这个列表包含了对应于1/2、1/4、1/8、1/16等的Decimal实例。

然而,虽然一个小数点后有超过400位的数字的情况很少见,但这个添加对我来说仍然不够满意:代码并不是绝对通用的,而unutbu的代码是通用的。

4

注意0x1.a000000000000p+2 的二进制形式并不是 101.1(更准确地说是 0b101.1
而是 0b110.1(在我的 Python 2.7 中,二进制数字是这样显示的)

.

首先,有一个很有用的方法是浮点数实例的 float.hex(),还有它的反向函数,浮点数类方法 float.fromhex()

fh =  12.34.hex()
print fh
print float.fromhex(fh)

结果

0x1.8ae147ae147aep+3  # hexadecimal representation of a float
12.34

"注意,float.hex() 是一个实例方法,而 float.fromhex() 是一个类方法。"

http://docs.python.org/library/stdtypes.html#float.fromhex

.

其次,我没有找到一个 Python 函数可以把浮点数的十六进制表示转换成二进制表示,也就是说带小数点的表示(也没有找到一个可以直接把浮点数的十进制表示转换成二进制的函数)。

所以我自己写了一个函数来实现这个功能。

这个函数首先把输入浮点数的十六进制表示转换成十进制表示(字符串形式)。

然后有两个问题:

  • 如何转换小数点前的部分?
    这部分是整数,可以很简单地用 bin() 来处理。

  • 如何转换小数点后面的部分??
    这个转换的问题在 SO 上被问过好几次,但我没理解那些解决方案,所以我写了自己的。

接下来,这就是你想要的函数,Qiang Li:

def hexf2binf(x):
    '''Transforms an hexadecimal float with a dot into a binary float with a dot'''
    a,_,p = str(float.fromhex(x)).partition('.')
    # the following part transforms the part after the dot into a binary after the dot
    tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
    bits = []
    pdec = Decimal('.'+p)
    for tin in tinies:
        if pdec-tin==0:
            bits.append('1')
            break
        elif pdec-tin>0:
            bits.append('1')
            pdec -= tin
        else:
            bits.append('0')
    pbin = ''.join(bits) # it's the binary after the dot
    # the integer before the dot is easily transformed into a binary
    return '.'.join((bin(int(a)),pbin))

.

为了进行验证,我写了一个函数,把二进制浮点数小数点后面的部分转换成它的十进制表示:

from decimal import Decimal, getcontext()

getcontext().prec = 500
# precision == 500 , to be large !

tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
com = dict((i,tin) for i,tin in enumerate(tinies,1))




def afterdotbinary2float(sbin, com = com):
    '''Transforms a binary lying after a dot into a float after a dot'''
    if sbin.startswith('0b.') or sbin.startswith('.'):
        sbin = sbin.split('.')[1]
    if all(c in '01' for c in sbin):
        return sum(int(c)*com[i] for i,c in enumerate(sbin,1))
    else:
        return None

.

.

最后,应用这些函数:

from decimal import Decimal

getcontext().prec = 500
# precision == 500 , to be large !


tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
com = dict((i,tin) for i,tin in enumerate(tinies,1))


def afterdotbinary2float(sbin, com = com):
    '''Transforms a binary lying after a dot into a float after a dot'''
    if sbin.startswith('0b.') or sbin.startswith('.'):
        sbin = sbin.split('.')[1]
    if all(c in '01' for c in sbin):
        return sum(int(c)*com[i] for i,c in enumerate(sbin,1))
    else:
        return None    



def hexf2binf(x):
    '''Transforms an hexadecimal float with a dot into a binary float with a dot'''
    a,_,p = str(float.fromhex(x)).partition('.')
    # the following part transforms the float after the dot into a binary after the dot
    tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
    bits = []
    pdec = Decimal('.'+p)
    for tin in tinies:
        if pdec-tin==0:
            bits.append('1')
            break
        elif pdec-tin>0:
            bits.append('1')
            pdec -= tin
        else:
            bits.append('0')
    pbin = ''.join(bits) # it's the binary after the dot
    # the float before the dot is easily transformed into a binary
    return '.'.join((bin(int(a)),pbin))



for n in (45.625 , 780.2265625 , 1022.796875):
    print 'n ==',n,'      transformed with its method hex() to:'
    nhexed = n.hex()
    print 'nhexed = n.hex() ==',nhexed
    print '\nhexf2binf(nhexed) ==',hexf2binf(nhexed)
    print "\nVerification:\nbefore,_,after = hexf2binf(nhexed).partition('.')"
    before,_,after = hexf2binf(nhexed).partition('.')
    print 'before ==',before,'   after ==',after
    print 'int(before,2) ==',int(before,2)
    print 'afterdotbinary2float(after) ==',afterdotbinary2float(after)
    print '\n---------------------------------------------------------------\n'

结果

n == 45.625       transformed with its method hex() to:
nhexed = n.hex() == 0x1.6d00000000000p+5

hexf2binf(nhexed) == 0b101101.101

Verification:
before,_,after = hexf2binf(nhexed).partition('.')
before == 0b101101    after == 101
int(before,2) == 45
afterdotbinary2float(after) == 0.625

---------------------------------------------------------------

n == 780.2265625       transformed with its method hex() to:
nhexed = n.hex() == 0x1.861d000000000p+9

hexf2binf(nhexed) == 0b1100001100.0011101

Verification:
before,_,after = hexf2binf(nhexed).partition('.')
before == 0b1100001100    after == 0011101
int(before,2) == 780
afterdotbinary2float(after) == 0.2265625

---------------------------------------------------------------

n == 1022.796875       transformed with its method hex() to:
nhexed = n.hex() == 0x1.ff66000000000p+9

hexf2binf(nhexed) == 0b1111111110.110011

Verification:
before,_,after = hexf2binf(nhexed).partition('.')
before == 0b1111111110    after == 110011
int(before,2) == 1022
afterdotbinary2float(after) == 0.796875

---------------------------------------------------------------

.

对于这两个数字

from decimal import Decimal

tinies = [ Decimal(1) / Decimal(2**i) for i in xrange(1,400)]
com = dict((i,tin) for i,tin in enumerate(tinies,1))


def hexf2binf(x, tinies = tinies):
    '''Transforms an hexadecimal float with a dot into a binary float with a dot'''
    a,_,p = str(float.fromhex(x)).partition('.')
    # the following part transforms the float after the dot into a binary after the dot
    bits = []
    pdec = Decimal('.'+p)
    for tin in tinies:
        if pdec-tin==0:
            bits.append('1')
            break
        elif pdec-tin>0:
            bits.append('1')
            pdec -= tin
        else:
            bits.append('0')
    pbin = ''.join(bits) # it's the binary after the dot
    # the float before the dot is easily transformed into a binary
    return '.'.join((bin(int(a)),pbin))


print hexf2binf('0x1.a000000000000p+2')
print
print hexf2binf('0x1.b5c2000000000p+1')

结果显示:

0b110.1

0b11.011010111000010000000000000000000000010000011111100001111111101001000110110000101101110000000001111110011100110101011100011110011101111010001011111001011101011000000011001111111010111011000101000111100100110000010110001000111010111101110111011111000100100110110101011100101001110011000100000000010001101111111010001110100100101110111000111111001011101101010011011111011001010011111111010101011010110
10

def float_to_binary(num):
    exponent=0
    shifted_num=num
    while shifted_num != int(shifted_num):        
        shifted_num*=2
        exponent+=1
    if exponent==0:
        return '{0:0b}'.format(int(shifted_num))
    binary='{0:0{1}b}'.format(int(shifted_num),exponent+1)
    integer_part=binary[:-exponent]
    fractional_part=binary[-exponent:].rstrip('0')
    return '{0}.{1}'.format(integer_part,fractional_part)

def floathex_to_binary(floathex):
    num = float.fromhex(floathex)
    return float_to_binary(num)


print(floathex_to_binary('0x1.a000000000000p+2'))    
# 110.1

print(floathex_to_binary('0x1.b5c2000000000p+1'))    
# 11.01101011100001

解释

float.fromhex 会返回一个浮点数 num。我们想要的是它的二进制表示。

{0:b}.format(...) 可以返回整数的二进制表示,但不能处理浮点数。

不过,如果我们把这个浮点数乘以足够多的2的幂,也就是把它的二进制表示向左移动足够的位置,我们就能得到一个整数 shifted_num

一旦得到了这个整数,我们就可以轻松搞定,因为现在可以用 {0:b}.format(...) 来处理了。

我们可以通过一些字符串切片的方法,重新插入小数点(呃,应该是二进制点?),这个方法是根据我们向左移动的位数(exponent)来进行的。

技术点:shifted_num 的二进制表示可能比 exponent 小。在这种情况下,我们需要在左边补充更多的0,以确保用 binary[:-exponent] 切片时不会为空。我们可以用 '{0:0{1}b}'.format(...) 来处理这个问题。格式字符串中的 0{1} 会把格式化后的字符串宽度设置为 {1},并在左边用0进行填充。({1} 会被 exponent 的值替换。)

撰写回答