开发者

Converting float.hex() value to binary in Python

I am wondering how to convert the result returned by float.hex() to binary, for exa开发者_StackOverflow中文版mple, from 0x1.a000000000000p+2 to 110.1.

Can anyone please help? Thanks.


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

Explanation:

float.fromhex returns a float num. We'd like its binary representation.

{0:b}.format(...) returns binary representations of integers, but not floats.

But if we multiply the float by enough powers of 2, that is, shift the binary representation to the left enough places, we end up with an integer, shifted_num.

Once we have that integer, we are home free, because now we can use {0:b}.format(...).

We can re-insert the decimal point (err, binary point?) by using a bit of string slicing based on the number of places we had shifted to the left (exponent).

Technical point: The number of digits in the binary representation of shifted_num may be smaller than exponent. In that case, we need to pad the binary representation with more 0's on the left, so binary slicing with binary[:-exponent] won't be empty. We manage that with '{0:0{1}b}'.format(...). The 0{1} in the format string sets the width of the formated string to {1}, padded on the left with zeros. (The {1} gets replaced by the number exponent.)


Note that the binary form of 0x1.a000000000000p+2 isn't 101.1 (or more exactly 0b101.1 )
but 0b110.1 (in my Python 2.7, binary numbers are displayed like that)

.

First, a useful method of float instances float.hex() and its inverse function, a float class method float.fromhex()

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

result

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

"Note that float.hex() is an instance method, while float.fromhex() is a class method."

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

.

Secondly, I didn't find a Python's function to transform an hexadecimal representation of a float into a binary representation of this float, that is to say with a dot ( nor than one to transform directly a decimal representation of a float into a binary one).

So I created a function for that purpose.

Before hand, this function transforms the hexadecimal representation into a decimal representation (string) of the input float.

Then there are two problems:

  • how to transform the part before the dot ?
    This part being an integer, it's easy to use bin()

  • how to transform the part after the dot ??
    The problem of this transformation has been asked several times on SO, but I didn't understand the solutions, so I wrote my own.

Then, here's the function you wish, 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))

.

In order to perform verification, I wrote a function to transform the part of a binary float after a dot into its decimal representation:

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

.

.

Finally, applying these functions:

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'

result

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

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

.

For the two numbers:

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')

as a result, this displays:

0b110.1

0b11.011010111000010000000000000000000000010000011111100001111111101001000110110000101101110000000001111110011100110101011100011110011101111010001011111001011101011000000011001111111010111011000101000111100100110000010110001000111010111101110111011111000100100110110101011100101001110011000100000000010001101111111010001110100100101110111000111111001011101101010011011111011001010011111111010101011010110


Given the a hexadecimal string h, you can find the corresponding float with

x = float.fromhex(h)

So really you're interesting in being able to produce a "fixed point" binary representation of any float. Its possible there is no finite representation so you probably want to restrict the length it can be. (eg the binary representation of math.pi wouldn't end ...)

So something like the following might work

def binaryRepresentation(x, n=8):
    # the base and remainder ... handle negatives as well ...
    base = int(x)
    fraction = abs(int(round( (x - base) * (2**n) )))

    # format and remove redundant zeros
    return "{0:b}.{1:b}".format(base, fraction).rstrip("0")


Big EDIT about big numbers

.

The following code shows a problem with my solution in my other answer.
Note that I changed the parameter of my function hexf2binf(floathex) from h to floathex, to make it the same as the parameter used by unutbu in his function 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

result

   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

The problem is due to the instruction str(float.fromhex(x)) in the instruction a,_,p = str(float.fromhex(x)).partition('.') that produces , for a big number, a representation of float.fromhex(x) with an exponent.
Then THE PARTS BEFORE THE DOT (a as ante) AND AFTER THE DOT (p as post) ARE FALSE.

Correcting this is easy: replacing the inaccurate instruction with this one:

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

.

Nota bene:

On a typical machine running Python, there are 53 bits of precision available for a Python float, so the value stored internally when you enter the decimal number 0.1 is the binary fraction 0.00011001100110011001100110011001100110011001100110011010 http://docs.python.org/tutorial/floatingpoint.html

That means that when a big value for a float is written in a code, its internal representation is in fact an approximation of the written value.
That is shown by the following code:

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

result

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

Values of h1 and h2 are the same because, though different values are assigned to identifiers x1 and x2 in the script, the OBJECTS x1 and x2 are represented with the same approximation in the machine.
The internal representation of 123456789012345685803008.0 is the exact value of 123456789012345685803008.0 and is the internal representation of 123456789012345678901234.64655 but its approximation, hence deduction of h1 and h2 from x1 and x2 gives the same value to h1 and h2.

This problem exists when we write a number in decimal representation in a script. It doesn't exist when we write a number directly in hexadecimal or binary representation.

What I wanted to underline

is that I wrote a function afterdotbinary2float(sbin, com = com) to perform verification on the results yielded by hexf2binf( ). This verification works well when the number passed to hexf2binf( ) isn't big, but because of the internal approximation of big numbers (= having a lot of digits), I wonder if this verification isn't distorted. Indeed, when a big number arrives in the function, it has already been approximated : the digits after the dot have been transformed into a series of zeros;
as it is shown here after:

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'

result

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'

Conclusion: testing with numbers 123456789012345685803008.0 and 123456789012345678901234.64655 makes no difference and no interest.

So, I wanted to test un-approximated numbers, and I passed a Decimal float number. As you see, the problem is that such an instance hasn't the hex() method.

.

Finally, I'm not entirely sure of my function for the big numbers, but it works correctly for common numbers after I corrected the inaccurate instruction.

.

EDIT

I added '.400' in the instruction:

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

otherwise the value of p could be truncated, thus giving a binary representation of a slightly different number than the one passed to the function.

I put 400 because it is the length I defined for the list tinies that contains the Decimal instances corresponding to 1/2 , 1/4 , 1/8 , 1/16 etc

However, though it is rare that a number with more than 400 digits after the comma has any sense, this adding remains unsatisfactory for me: the code isn't absolutely general, which is the case of unutbu 's code.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜