24

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

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

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
foosion
  • 7,619
  • 25
  • 65
  • 102
  • The two lines b= and ba= can be replaced with ba=bytes.fromhex(h). See Lennart's comment below. – foosion Jul 18 '11 at 11:21

5 Answers5

51

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

print(twos_complement('FFFE', 16))
print(twos_complement('7FFF', 16))
print(twos_complement('7F', 8))
print(twos_complement('FF', 8))

Output:

-2
32767
127
-1
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • bits = hexstr.__len__() so it is not necessary to pass bits in as a parameter. Unless, for example, you want to have hexstr = 'FF' represent a 32 bit value. – DeanM Jul 15 '19 at 16:39
  • 1
    @DeanM I assume you really mean `len(hexstr) * 4`, and yes the number could be missing leading zeros so I was being explicit about the number of bits in the answer. – Mark Tolonen Jul 15 '19 at 16:48
17
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'))
kichik
  • 33,220
  • 7
  • 94
  • 114
4

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)
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)
amarchiori
  • 1,329
  • 1
  • 8
  • 3
1

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)
Joe Marley
  • 179
  • 6