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.
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
(1<<64)? Why does that need to be done?(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.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
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.(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.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.Adding to Marks answer, if you want a different output format, use
'{:X}'.format(-199703103 & (2**32-1))
For those who want leading zeros for positive numbers, try this:
val = 42
nbits = 16
'{:04X}'.format(val & ((1 << nbits)-1))
Thanks @tm1, for the inspiration!
'{:0{}X}'.format(val & ((1 << nbits)-1), int((nbits+3)/4)) will set the width correct.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}"