Hex string to signed int in Python 3.2?

How do I convert a hex string to a signed int in Python 3.2?

The best I can come up with is

h = '9DA92DAB'
b = bytes(h, 'utf-8')
ba = binascii.a2b_hex(b)
print(int.from_bytes(ba, byteorder='big', signed=True))

Is there a simpler way? Unsigned is so much easier: int(h, 16)

BTW, the origin of the question is itunes persistent id - music library xml version and iTunes hex version


Solution 1:

In n-bit two's complement, bits have value:

bit 0 = 20
bit 1 = 21
bit n-2 = 2n-2
bit n-1 = -2n-1

But bit n-1 has value 2n-1 when unsigned, so the number is 2n too high. Subtract 2n if bit n-1 is set:

>>> def twos_complement(hexstr,bits):
...     value = int(hexstr,16)
...     if value & (1 << (bits-1)):
...         value -= 1 << bits
...     return value
...
>>> twos_complement('FFFE',16)
-2
>>> twos_complement('7FFF',16)
32767
>>> twos_complement('7F',8)
127
>>> twos_complement('FF',8)
-1

Solution 2:

import struct

For Python 3 (with comments' help):

h = '9DA92DAB'
struct.unpack('>i', bytes.fromhex(h))

For Python 2:

h = '9DA92DAB'
struct.unpack('>i', h.decode('hex'))

or if it is little endian:

h = '9DA92DAB'
struct.unpack('<i', h.decode('hex'))

Solution 3:

Here's a general function you can use for hex of any size:

import math

# hex string to signed integer
def htosi(val):
    uintval = int(val,16)
    bits = 4 * (len(val) - 2)
    if uintval >= math.pow(2,bits-1):
        uintval = int(0 - (math.pow(2,bits) - uintval))
    return uintval

And to use it:

h = str(hex(-5))
h2 = str(hex(-13589))
x = htosi(h)
x2 = htosi(h2)

Solution 4:

This works for 16 bit signed ints, you can extend for 32 bit ints. It uses the basic definition of 2's complement signed numbers. Also note xor with 1 is the same as a binary negate.

# convert to unsigned
x = int('ffbf', 16) # example (-65)
# check sign bit
if (x & 0x8000) == 0x8000:
    # if set, invert and add one to get the negative value, then add the negative sign
    x = -( (x ^ 0xffff) + 1)

Solution 5:

It's a very late answer, but here's a function to do the above. This will extend for whatever length you provide. Credit for portions of this to another SO answer (I lost the link, so please provide it if you find it).

def hex_to_signed(source):
    """Convert a string hex value to a signed hexidecimal value.

    This assumes that source is the proper length, and the sign bit
    is the first bit in the first byte of the correct length.

    hex_to_signed("F") should return -1.
    hex_to_signed("0F") should return 15.
    """
    if not isinstance(source, str):
        raise ValueError("string type required")
    if 0 == len(source):
        raise valueError("string is empty")
    sign_bit_mask = 1 << (len(source)*4-1)
    other_bits_mask = sign_bit_mask - 1
    value = int(source, 16)
    return -(value & sign_bit_mask) | (value & other_bits_mask)