42

I use python 2.6

>>> hex(-199703103)
'-0xbe73a3f'

>>> hex(199703103)
'0xbe73a3f'

Positive and negative value are the same?

When I use calc, the value is FFFFFFFFF418C5C1.

5 Answers 5

72

Python's integers can grow arbitrarily large. In order to compute the raw two's-complement the way you want it, you would need to specify the desired bit width. Your example shows -199703103 in 64-bit two's complement, but it just as well could have been 32-bit or 128-bit, resulting in a different number of 0xf's at the start.

hex() doesn't do that. I suggest the following as an alternative:

def tohex(val, nbits):
  return hex((val + (1 << nbits)) % (1 << nbits))

print tohex(-199703103, 64)
print tohex(199703103, 64)

This prints out:

0xfffffffff418c5c1L
0xbe73a3fL
Sign up to request clarification or add additional context in comments.

3 Comments

Could you please explain what is going on when you're adding (1<<64)? Why does that need to be done?
@jathanism, the value (1<<64) is one larger than will fit in a 64-bit integer. Adding it to a negative number will turn it positive, as long as the negative number fits in 64 bits. If the original number was positive, the % will undo the effect of the addition.
I believe there is a bug in this implementation: tohex(-129,8) returns 0x7f, which is impossible since a 8-bit signed integer can only store numbers from (-1)* (2 ^7)= -128 to 2^7 - 1 = 127. Therefore the function should check if val is within this range before returns, if it is not, raise an exception (or something). This can make the code more robust, i think :)
22

Because Python integers are arbitrarily large, you have to mask the values to limit conversion to the number of bits you want for your 2s complement representation.

>>> hex(-199703103 & (2**32-1)) # 32-bit
'0xf418c5c1L'
>>> hex(-199703103 & (2**64-1)) # 64-bit
'0xfffffffff418c5c1L'

Python displays the simple case of hex(-199703103) as a negative hex value (-0xbe73a3f) because the 2s complement representation would have an infinite number of Fs in front of it for an arbitrary precision number. The mask value (2**32-1 == 0xFFFFFFFF) limits this:

FFF...FFFFFFFFFFFFFFFFFFFFFFFFF418c5c1
&                             FFFFFFFF
--------------------------------------
                              F418c5c1

6 Comments

Although concise, isn't the raising to a power costly compared with bit manipulation?
@swdev, py -m timeit "2**32-1" -> 0.0235 usec per loop, py -m timeit "2<<32-1" -> 0.0235 usec per loop. Don't assume. Always measure if you care :) Most likely the Python byte compiler generates the same load constant.
oops, make that (1<<32)-1...but you get the idea...Also easier to make mistakes. And I checked with the dis module and Python just generates a constant.
Also you could, inlieu of 2**32-1 just use the constant 0xFFFFFFFF for 32-bit and 0xFFFFFFFFFFFFFFFF for 64-bit if you are that concerned about timing. Or mask_32bit=(2**32-1) so that you don't have to do the calculation each time, just once.
@busfault per comment above speed is no different. Python calculates the constant once when generating byte code. No need to "optimize".
|
2

Adding to Marks answer, if you want a different output format, use

'{:X}'.format(-199703103 & (2**32-1))

Comments

0

For those who want leading zeros for positive numbers, try this:

val = 42
nbits = 16
'{:04X}'.format(val & ((1 << nbits)-1))

enter image description here

Thanks @tm1, for the inspiration!

1 Comment

'{:0{}X}'.format(val & ((1 << nbits)-1), int((nbits+3)/4)) will set the width correct.
0

Here's a little something I came up with today that prints the decimal and hex version of an int value. Of note, negative values are handled without having to specify a bit width.

def int2str_with_hex(value:int, 
                 hex_digits:int=0,
                 hex_limit:int=10,
                 hex_prefix:str=' (0x',
                 hex_suffix:str=')',
                 uppercase:bool=True,
                 all_neg_hex:bool=True) -> str:
"""Convert an int to a string with decimal and hex representations

- value: Value to display as string.
- hex_digits: Minimum number of hex digits to include
- hex_limit: inclusive lower limit of absolute(value) \
             from which to start adding hex value.
- hex_prefix: String to include between decimal and hex representations
- hex_suffix: String to include after hex representation
- uppercase: If true, hex value will use uppercase letter, else lowercase.
- all_neg_hex: If value is negative, hex is always included (hex_limit is ignored)

The minimum number of hex digits used in the represation of a
negative value is an even number (a complete 8-bits byte), based on
the number of bits used in the given value. If value of the 
hex_digits argument is greater than that number, then hex_digits
are used for the hex reprentation, which can be an odd or even number.

Positive values:
>>> int2str_with_hex(0)
'0'
>>> int2str_with_hex(0, hex_limit=0)
'0 (0x0)'
>>> int2str_with_hex(0, 3, 0)
'0 (0x000)'
>>> int2str_with_hex(0, 3, 0,'[',']')
'0[000]'
>>> int2str_with_hex(9)
'9'
>>> int2str_with_hex(10)
'10 (0xA)'
>>> int2str_with_hex(255, 3, 0,'[',']')
'255[0FF]'
>>> int2str_with_hex(255, 1, 0,'[',']', False)
'255[ff]'
>>> int2str_with_hex(65536)
'65536 (0x10000)'

Negative values:
>>> int2str_with_hex(-1)
'-1 (0xFF)'
>>> int2str_with_hex(-1, all_neg_hex=False)
'-1'
>>> int2str_with_hex(-1, hex_digits=4)
'-1 (0xFFFF)'
>>> int2str_with_hex(-1, hex_digits=4, uppercase=False)
'-1 (0xffff)'
>>> int2str_with_hex(-9)
'-9 (0xF7)'
>>> int2str_with_hex(-10)
'-10 (0xF6)'
>>> int2str_with_hex(-10, hex_digits=2)
'-10 (0xF6)'
>>> int2str_with_hex(-10, hex_digits=3)
'-10 (0xFF6)'
>>> int2str_with_hex(-300, hex_digits=3)
'-300 (0xFED4)'
>>> int2str_with_hex(-10, hex_digits=4)
'-10 (0xFFF6)'
>>> int2str_with_hex(-127, hex_digits=4)
'-127 (0xFF81)'
>>> int2str_with_hex(-128)
'-128 (0x80)'
>>> int2str_with_hex(-128, hex_digits=4)
'-128 (0xFF80)'
>>> int2str_with_hex(255)
'255 (0xFF)'

version: 1.0
author: info_dan on Stack Overflow
"""

fill:str = '0'
hex_value = value

if (value < 0):
    # Compute number of bits used by value
    nbits = 0
    hex_value = -hex_value
    while hex_value:
        nbits += 1
        hex_value >>= 1

    # Round up number of bits to fill a byte (8 bits)
    nbits = nbits + 7 - (nbits + 7) % 8

    # Compute 2's complement using found number of bits
    hex_value = value & (2**nbits-1)
    fill = 'F' if uppercase else 'f'
    if all_neg_hex: hex_limit = 0

return (f"{value}{hex_prefix}"
        f"{format(hex_value, 'X' if uppercase else 'x').rjust(hex_digits, fill)}"
        f"{hex_suffix}"
       ) if abs(value) >= hex_limit else f"{value}"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.