Python中的二进制补码


70

python中是否有内置函数将二进制字符串(例如“ 111111111111”)转换为二进制补码整数-1?


@CeesTimmerman尽管这是一种很好的做法,但用户无需接受答案。不活动的OP也不太可能看到您的评论。
mbomb007 '17

@ mbomb007正确,但是无法接受的答案浪费了人们以为尚无正确答案的时间。
Cees Timmerman'2

1
@CeesTimmerman只有您这样认为。改变你的想法。人们的投票代表了他们认为正确的答案。接受的答案基本上相当于OP的一票。就是这样 一票。
mbomb007'2

1
@ mbomb007在搜索列表中,除非正确地标记了问题,否则回答正确的问题并不明显。
Cees Timmerman

2
我看到了很多错误的可接受答案。因此,即使如此,它也不是显而易见的。meta.stackexchange.com/a/26641/285610
mbomb007 '02

Answers:


81

(1<<bits)如果最高位为1 ,则二进制补码会减去。以8位为例,这将得出127至-128的范围。

一个整数的二的补码的函数。

def twos_comp(val, bits):
    """compute the 2's complement of int value val"""
    if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
        val = val - (1 << bits)        # compute negative value
    return val                         # return positive value as is

从二进制字符串开始特别容易...

binary_string = '1111' # or whatever... no '0b' prefix
out = twos_comp(int(binary_string,2), len(binary_string))

对我来说更有用的是从十六进制值开始(在此示例中为32位)...

hex_string = '0xFFFFFFFF' # or whatever... '0x' prefix doesn't matter
out = twos_comp(int(hex_string,16), 32)

@Likak,您能详细说明一下吗?
larsk's

@Likak,答案还可以。另请参见从2 ^ N减去以了解其背后的原因。
maxschlepzig

1
return val & ((2 ** bits) - 1)没有这个,您将仅以常规python格式得到一个负数。大概在执行2sc时,您将需要这些位。
TechnoSam

@TechnoSam我们需要一个普通的python整数。2的补码表示为负(高位置1),它应该为负。这就是重点。
travc

1
@Danilo Yep,!= 0不是必需的,但可以包括在内。该功能甚至可以简化为单一功能,但并不清楚;)
travc

20

它不是内置的,但是如果您想要不寻常的长度数字,则可以使用bitstring模块。

>>> from bitstring import Bits
>>> a = Bits(bin='111111111111')
>>> a.int
-1

可以通过多种方式等效地创建同一个对象,包括

>>> b = Bits(int=-1, length=12)

它的行为就像一串任意长度的位,并使用属性来获得不同的解释:

>>> print a.int, a.uint, a.bin, a.hex, a.oct
-1 4095 111111111111 fff 7777

1
可以在此处找到
erikbwork

2
@ erikb85:那里的答案(包括我的答案)并没有真正进入界面的简单性和灵活性(OP甚至开始抱怨它的bitarray功能超出了他的需求……),因此这些问题相互补充。 :此示例说明了类似的库如何bitstring使通用操作更易于编写,表明它们没有使它们更快,并且常常使它们更慢。
2014年

18

从Python 3.2开始,内置了用于字节操作的函数:https : //docs.python.org/3.4/library/stdtypes.html#int.to_bytes

通过组合to_bytes和from_bytes,您将获得

def twos(val_str, bytes):
    import sys
    val = int(val_str, 2)
    b = val.to_bytes(bytes, byteorder=sys.byteorder, signed=False)                                                          
    return int.from_bytes(b, byteorder=sys.byteorder, signed=True)

检查:

twos('11111111', 1)  # gives -1
twos('01111111', 1)  # gives 127

对于较旧的Python版本,travc的答案很好,但如果要使用整数而不是字符串,它对负值不起作用。对于每个val f(f(val))== val为true的二进制补函数为:

def twos_complement(val, nbits):
    """Compute the 2's complement of int value val"""
    if val < 0:
        val = (1 << nbits) + val
    else:
        if (val & (1 << (nbits - 1))) != 0:
            # If sign bit is set.
            # compute negative value.
            val = val - (1 << nbits)
    return val

11
>>> bits_in_word=12
>>> int('111111111111',2)-(1<<bits_in_word)
-1

之所以有效,是因为:

二进制数的二进制补码定义为通过从2的大幂中减去数字而获得的值(具体而言,对于N位二进制补码,取2 ^ N)。然后,在大多数算术运算中,数字的二进制补码表现为原始数字的负数,并且可以自然方式与正数共存。


5

这将使用按位逻辑高效地为您提供两个补码:

def twos_complement(value, bitWidth):
    if value >= 2**bitWidth:
        # This catches when someone tries to give a value that is out of range
        raise ValueError("Value: {} out of range of {}-bit value.".format(value, bitWidth))
    else:
        return value - int((value << 1) & 2**bitWidth)

这个怎么运作:

首先,我们确保用户传递给我们的值在所提供的位范围内(例如,有人给了我们0xFFFF并指定了8位)。该问题的另一种解决方案是将值与(2 **位宽)-1

为了获得结果,该值向左移动1位。这会将值(符号位)的MSB移到要与进行相加的位置2**bitWidth。当符号位为'0'时,减价位变为0,结果为value - 0。当符号位为“ 1”时,减价变为2**bitWidth,结果为value - 2**bitWidth

示例1:如果参数的值为value = 0xFF(255d,b11111111)和bitWidth = 8

  1. 0xFF-int((0xFF << 1)和2 ** 8)
  2. 0xFF-整数(((0x1FE)&0x100)
  3. 0xFF-整数(0x100)
  4. 255-256
  5. -1

示例2:如果参数的值为value = 0x1F(31d,b11111)和bitWidth = 6

  1. 0x1F-int((0x1F << 1)&2 ** 6)
  2. 0x1F-int(((0x3E)&0x40)
  3. 0x1F-整数(0x00)
  4. 31-0
  5. 31

示例3:值= 0x80,位宽= 7

ValueError: Value: 128 out of range of 7-bit value.

示例4:值= 0x80,bitWitdh = 8

  1. 0x80-int((0x80 << 1)&2 ** 8)
  2. 0x80-整数(((0x100)&0x100)
  3. 0x80-整数(0x100)
  4. 128-256
  5. -128

现在,使用其他人已经发布的内容,将您的位字符串传递给int(bitstring,2)并传递至twos_complement方法的value参数。


3

几个实现(仅是说明,不打算使用):

def to_int(bin):
    x = int(bin, 2)
    if bin[0] == '1': # "sign bit", big-endian
       x -= 2**len(bin)
    return x

def to_int(bin): # from definition
    n = 0
    for i, b in enumerate(reversed(bin)):
        if b == '1':
           if i != (len(bin)-1):
              n += 2**i
           else: # MSB
              n -= 2**i 
    return n

如果您已经将二进制作为字符串处理,为什么不使用这个清晰灵活的函数从它们创建带符号整数?
Cees Timmerman

@CeesTimmerman也许我的意思是“ 01”字符串不能很好地表示整数(用于算术),因此不应使用直接操作它们的函数。
jfs

3

不,没有内置函数将二进制补码二进制字符串转换为小数。

一个简单的用户定义函数可以执行以下操作:

def two2dec(s):
  if s[0] == '1':
    return -1 * (int(''.join('1' if x == '0' else '0' for x in s), 2) + 1)
  else:
    return int(s, 2)

请注意,此函数不将位宽作为参数,而是必须使用一个或多个前导零位指定正输入值。

例子:

In [2]: two2dec('1111')
Out[2]: -1

In [3]: two2dec('111111111111')
Out[3]: -1

In [4]: two2dec('0101')
Out[4]: 5

In [5]: two2dec('10000000')
Out[5]: -128

In [6]: two2dec('11111110')
Out[6]: -2

In [7]: two2dec('01111111')
Out[7]: 127

2

如果有人也需要相反的方向:

def num_to_bin(num, wordsize):
    if num < 0:
        num = 2**wordsize+num
    base = bin(num)[2:]
    padding_size = wordsize - len(base)
    return '0' * padding_size + base

for i in range(7, -9, -1):
    print num_to_bin(i, 4)

应该输出以下内容:0111 0110 0101 0100 0011 0010 0001 0000 1111 1110 1101 1100 1011 1010 1001 1000


n位二进制补码二进制数x表示为正(幂n的2)+ x。例如:x = -2,n = 4,(2
等于

1
由于您正在进行位操作,因此应使用1 << wordsize而不是2 ** wordsize; 此外,移位比取幂快得多。
下午17年

1

由于erikb85提高了性能,因此这是travcScott Griffiths的回答

In [534]: a = [0b111111111111, 0b100000000000, 0b1, 0] * 1000
In [535]: %timeit [twos_comp(x, 12) for x in a]
100 loops, best of 3: 8.8 ms per loop
In [536]: %timeit [bitstring.Bits(uint=x, length=12).int for x in a]
10 loops, best of 3: 55.9 ms per loop

因此,bitstring正如在另一个问题中发现的那样,它比慢了一个数量级int。但另一方面,很难克服简单性-我将a转换uint为位字符串,然后转换为int; 您将不得不努力理解这一点,或者找到任何地方引入错误。正如斯科特·格里菲斯(Scott Griffiths)的答案所暗示的那样,该类具有更大的灵活性,这可能对同一应用程序有用。但是,从第三方面看,travc的答案可以清楚地说明实际发生的情况-即使是新手,也应该能够通过阅读两行代码来理解从无符号int到2s补码有符号int的转换。

无论如何,与另一个问题(直接处理位)不同,该问题全部涉及在固定长度的int上进行算术运算,只是大小不等的整数。因此,我猜测是否需要性能,这可能是因为您有一堆这样的东西,所以您可能希望将其向量化。使travc的答案适应numpy:

def twos_comp_np(vals, bits):
    """compute the 2's compliment of array of int values vals"""
    vals[vals & (1<<(bits-1)) != 0] -= (1<<bits)
    return vals

现在:

In [543]: a = np.array(a)
In [544]: %timeit twos_comp_np(a.copy(), 12)
10000 loops, best of 3: 63.5 µs per loop

您可能可以使用自定义C代码来击败它,但是您不必这样做。


1

您可以使用bit_length()函数将数字转换为它们的二进制补码:

def twos_complement(j):
   return j-(1<<(j.bit_length()))

In [1]: twos_complement(0b111111111111)                                                                                                                                                             
Out[1]: -1

0

我正在使用Python 3.4.0

在Python 3中,数据类型转换存在一些问题。

所以...在这里,我将为那些对十六进制字符串有用的提示(像我一样)。

我将获取一个十六进制数据并对其进行补充:

a = b'acad0109'

compl = int(a,16)-pow(2,32)

result=hex(compl)
print(result)
print(int(result,16))
print(bin(int(result,16)))

结果= -1397948151或-0x5352fef7或'-0b1010011010100101111111011110111'


0

不幸的是,没有内置函数将无符号整数转换为二进制的补码有符号值,但是我们可以定义一个函数来使用按位运算:

def s12(value):
    return -(value & 0b100000000000) | (value & 0b011111111111)

第一个按位与运算用于对负数进行符号扩展(设置了最高有效位),而第二个用于获取其余的11位。这是可行的,因为Python中的整数被视为任意精度的二进制补码值。

然后,您可以将其与int函数结合以将一串二进制数字转换为无符号整数形式,然后将其解释为12位带符号的值。

>>> s12(int('111111111111', 2))
-1
>>> s12(int('011111111111', 2))
2047
>>> s12(int('100000000000', 2))
-2048

该函数的一个不错的特性是它是幂等的,因此,已经签名的值的值不会改变。

>>> s12(-1)
-1

为什么是11位?给定的字符串只是一个示例。
Cees Timmerman

对于这个问题,假定作者正在询问如何将12个二进制数字解释为12位二进制补码有符号整数(因为-1始终由N位二进制补码表示形式中的N个1位表示)。第一位用于符号,其余(11)位用于确定幅度。
dcoles

0

这适用于3个字节。现场代码在这里

def twos_compliment(byte_arr):
   a = byte_arr[0]; b = byte_arr[1]; c = byte_arr[2]
   out = ((a<<16)&0xff0000) | ((b<<8)&0xff00) | (c&0xff)
   neg = (a & (1<<7) != 0)  # first bit of a is the "signed bit." if it's a 1, then the value is negative
   if neg: out -= (1 << 24)
   print(hex(a), hex(b), hex(c), neg, out)
   return out


twos_compliment([0x00, 0x00, 0x01])
>>> 1

twos_compliment([0xff,0xff,0xff])
>>> -1

twos_compliment([0b00010010, 0b11010110, 0b10000111])
>>> 1234567

twos_compliment([0b11101101, 0b00101001, 0b01111001])
>>> -1234567

twos_compliment([0b01110100, 0b11001011, 0b10110001])
>>> 7654321

twos_compliment([0b10001011, 0b00110100, 0b01001111])
>>> -7654321

0

好吧,我有这个问题ULAW压缩算法PCM WAV文件类型。我发现二进制补码有点使某个二进制数负值,如此处所示。在与维基百科协商后,我认为这是对的。

那家伙将其解释为找到least significant bit并翻转所有内容。我必须说,以上所有这些解决方案对我没有太大帮助。当我尝试0x67ff它给我一些结果而不是-26623。现在,如果有人知道least significant bit正在扫描数据列表,但是我不知道,因为PCM中的数据会变化,那么解决方案可能会起作用。所以这是我的答案:

max_data = b'\xff\x67' #maximum value i've got from uLaw data chunk to test

def twos_compliment(short_byte): # 2 bytes 
    short_byte = signedShort(short_byte) # converting binary string to integer from struct.unpack i've just shortened it.
    valid_nibble = min([ x*4 for x in range(4) if (short_byte>>(x*4))&0xf ])
    bit_shift = valid_nibble + min( [ x for x in [1,2,4,8] if ( ( short_byte>>valid_nibble )&0xf )&x ] )
    return (~short_byte)^( 2**bit_shift-1 )

data  = 0x67ff
bit4 = '{0:04b}'.format
bit16 = lambda x: ' '.join( map( bit4, reversed([ x&0xf, (x>>4)&0xf, (x>>8)&0xf, (x>>12)&0xf ]) ) )

# print( bit16(0x67ff) , ' : ', bit16( twos_compliment(  b'\xff\x67' ) ) )
# print( bit16(0x67f0) , ' : ', bit16( twos_compliment(  b'\xf0\x67' ) ) )
# print( bit16(0x6700) , ' : ', bit16( twos_compliment(  b'\x00\x67' ) ) )
# print( bit16(0x6000) , ' : ', bit16( twos_compliment(  b'\x00\x60' ) ) )
print( data, twos_compliment(max_data) )

现在,由于代码不可读,我将带您了解这个想法。

## example data, for testing... in general unknown
data = 0x67ff # 26623 or 0110 0111 1111 1111 

这只是任何十六进制值,我需要进行测试以确保,但是通常它可以是int范围内的任何值。因此,不要遍历整个65535个值,short integer可以让我决定按半字节(4位)进行拆分。如果您以前从未使用bitwise operators过,可以这样做。

nibble_mask = 0xf # 1111
valid_nibble = []

for x in range(4): #0,1,2,3 aka places of bit value
    # for individual bits you could go 1<<x as you will see later

    # x*4 is because we are shifting bit places , so 0xFA>>4 = 0xF
    #     so 0x67ff>>0*4 = 0x67ff
    #     so 0x67ff>>1*4 = 0x67f
    #     so 0x67ff>>2*4 = 0x67
    #     so 0x67ff>>3*4 = 0x6
    # and nibble mask just makes it confided to 1 nibble so 0xFA&0xF=0xA
    if (data>>(x*4))&nibble_mask: valid_nibble.append(x*4) # to avoid multiplying it with 4 later 

因此,我们正在搜索,least significant bit因此这里min(valid_nibble )就足够了。在这里,我们得到了第一个活动的(设置了位)半字节的地方。现在我们只需要找到所需的半字节中的第一个置位位。

bit_shift = min(valid_nibble)
for x in range(4): 
    # in my example above [1,2,4,8] i did this to spare python calculating 
    ver_data = data>>min(bit_shift ) # shifting from 0xFABA to lets say 0xFA
    ver_data &= nibble_mask # from 0xFA to 0xA 
    if ver_data&(1<<x): 
        bit_shift += (1<<x)
        break

现在在这里,我需要弄清楚一些东西,因为看到它们~^会使那些不习惯的人感到困惑:

XOR^:2号是necesery

此操作有点不合逻辑,对于其他所有1,它每扫描2个位(如果两者均为1或0,它将为0)进行扫描。

 0b10110
^0b11100
--------- 
 0b01010   

再举一个例子:

 0b10110
^0b11111
---------
 0b01001

1's complement~-不需要任何其他数字

此操作翻转数字中的每一位。它与我们所追求的非常相似,但是并没有留下最低的要求

0b10110  
~  
0b01001

正如我们在这里看到的,1的补码与XOR完全置位位数相同。


现在,我们已经了解对方,我们会得到two's complement通过还原所有咬伤至少显著位一个补

data = ~data # one's complement of data 

不幸的是,这会翻转数字中的所有位,因此我们只需要找到一种方法即可翻转想要的数字。我们可以做到这一点,bit_shift因为这是我们需要保留的位置。因此,当计算数据数量时,可以容纳一些位数,因此可以做到这一点,2**n对于半字节,由于我们要计算的位数为0,因此得到16。

2**4 = 16 # in binary 1 0000 

但是,我们需要字节之后的字节,1因此我们可以使用该字节将值减1,然后得到。

2**4 -1 = 15 # in binary 0 1111 

因此,让我们看一下具体示例中的逻辑:

 0b110110
 lsb = 2 # binary 10 

~0b110110
----------
 0b001001 # here is that 01 we don't like  

 0b001001
^0b000011 # 2**2 = 4 ; 4-1 = 3 in binary 0b11 
--------- 
 0b001010

希望这对您或遇到此问题的新手有所帮助,并研究了他们的解决方案。请记住,我编写的这段代码是科学怪人代码,这就是我为什么必须解释它的原因。如果有人想使我的代码漂亮,可以做得更漂亮,请成为我的客人。


0

这是一个将十六进制字符串中的每个值转换为二进制补码版本的版本。


In [5159]: twoscomplement('f0079debdd9abe0fdb8adca9dbc89a807b707f')                                                                                                 
Out[5159]: '10097325337652013586346735487680959091'


def twoscomplement(hm): 
   twoscomplement='' 
   for x in range(0,len(hm)): 
       value = int(hm[x],16) 
       if value % 2 == 1: 
         twoscomplement+=hex(value ^ 14)[2:] 
       else: 
         twoscomplement+=hex(((value-1)^15)&0xf)[2:] 
   return twoscomplement            

0

您可以将整数转换为字节,然后用于struct.unpack转换:

from struct import unpack

x = unpack("b", 0b11111111.to_bytes(length=1, byteorder="little"))
print(x)  # (-1,)

-1

比这更容易...

对于N位上的X:Comp =(-X)&(2 ** N-1)

def twoComplement(number, nBits):
    return (-number) & (2**nBits - 1)

这不适用于“ 1111”。结果twoComplement(int('1111', 2), 4)1使用函数时的结果。但是正确的结果是-1。另请参阅其他答案可以解决此问题。
maxschlepzig

上次我检查时,四个数字的1111的2补码是0001。–
Francois

我认为您可能会将2的补数与2的补数域中数字的值(作为负数)相混淆。
弗朗索瓦·

别傻了-术语“二进制补码”通常表示位模式的十进制解释-cf。例如en.wikipedia.org/wiki/Two's_complement中的前2个表格。另外,OP对此进行了具体询问,并给出了示例'111111111111' -> -1。那意味着您不回答问题。因此,您的陈述“比这一切容易得多……”不适用。
maxschlepzig

啊哈哈,我想你是对的。我一定跳到线程中间,错过了原来的问题!
Francois
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.