【问题标题】:Two's Complement in PythonPython中的二进制补码
【发布时间】:2010-12-08 22:26:14
【问题描述】:

python 中是否有内置函数可以将二进制字符串(例如“111111111111”)转换为two's complement integer -1?

【问题讨论】:

  • @CeesTimmerman 虽然这是一种很好的做法,但用户不需要接受答案。非活动的 OP 也不太可能看到您的评论。
  • @mbomb007 没错,但不被接受的答案浪费了人们认为还没有正确答案的时间。
  • @CeesTimmerman 只有当你这么想的时候。改变你的想法。人们的投票代表他们认为正确的答案。接受的答案基本上相当于 OP 的一票。就是这样。一票。
  • @mbomb007 在搜索列表中,问题是否得到正确回答并不明显,除非它们已被标记。
  • 我看到很多错误的接受答案。所以即使那样,也不是很明显。 meta.stackexchange.com/a/26641/285610

标签: python bit-manipulation twos-complement


【解决方案1】:

如果最高位为 1,则二进制补码减去 (1<<bits)。以 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,您能否详细说明该评论?
  • @Likak,答案还可以。另请参阅 Subtraction from 2^N 了解其背后的原因。
  • return val &amp; ((2 ** bits) - 1) 没有这个,你只会得到一个普通python格式的负数。大概在做 2sc 时你会想要这些位。
  • @TechnoSam 我们想要一个普通的 Python 整数。 2 的恭维表示否定(高位设置),它应该是否定的。这就是重点。
  • @Danilo 是的,!= 0 不是必需的,但这是一个很好的做法。该功能甚至可以简化为单行,但不会那么清楚;)
【解决方案2】:

它不是内置的,但如果你想要不寻常的长度数字,那么你可以使用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

【讨论】:

  • 不同位处理工具的比较可以found here
  • @erikb85:那里的答案(包括我的)并没有真正涉及到界面的简单性和灵活性(OP 甚至开始抱怨bitarray 做得比他需要的多……) ,所以这些问题可以很好地相互补充:这个问题展示了像 bitstring 这样的库如何让常见的操作更容易编写,一个展示了它们不会让它们变得更快,而是经常让它们变慢。
【解决方案3】:

从 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 为真的二进制补码函数是:

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

【讨论】:

    【解决方案4】:
    >>> bits_in_word=12
    >>> int('111111111111',2)-(1<<bits_in_word)
    -1
    

    之所以有效,是因为:

    二进制的补码 number 定义为值 通过减去数字获得 从二的大幂 (具体来说,对于 N 位,从 2^N 二进制补码)。两人的 然后数字的补码表现 就像原版的底片一样 大多数算术中的数字,它可以 与正数共存 自然的方式。

    【讨论】:

    • 错了 -> 检查 '000' 得到 -8
    • @quqa123 在应用此方法之前,首先必须检查该值是否为负。如果该值小于等于(1 &lt;&lt; (bits_in_word - 1)) - 1,则为正数,不应采用此方法。
    【解决方案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**bitWidth)-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
    2. 0xFF - int((0x1FE) & 0x100)
    3. 0xFF - int(0x100)
    4. 255 - 256
    5. -1

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

    1. 0x1F - int((0x1F
    2. 0x1F - int((0x3E) & 0x40)
    3. 0x1F - int(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
    2. 0x80 - int((0x100) & 0x100)
    3. 0x80 - int(0x100)
    4. 128 - 256
    5. -128

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

    【讨论】:

      【解决方案6】:

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

      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
      

      【讨论】:

      • 如果您已经将二进制作为字符串处理,为什么不使用这个清晰灵活的函数从它们创建有符号整数?
      • @CeesTimmerman 也许我的意思是“01”-string 不能很好地表示整数(用于算术),因此不应使用直接操作它们的函数。
      【解决方案7】:

      不,没有将two's complement二进制字符串转换为十进制的内置函数。

      执行此操作的简单用户定义函数:

      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
      

      【讨论】:

        【解决方案8】:

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

        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 表示为正数(2 的 n 次方)+x。例如:x=-2, n=4, (2 的 4 次方) + (-2) = 14, bin=1110
        • 既然你在做位操作,你应该使用1 &lt;&lt; wordsize而不是2 ** wordsize;此外,位移比取幂要快得多。
        【解决方案9】:

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

        def twos_complement(j):
           return j-(1<<(j.bit_length()))
        
        In [1]: twos_complement(0b111111111111)                                                                                                                                                             
        Out[1]: -1
        

        【讨论】:

          【解决方案10】:

          由于 erikb85 提高了性能,这里是 travc's answerScott 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
          

          因此,bitstringthe other question 一样,几乎比int 慢一个数量级。但另一方面,很难超越简单性——我将uint 转换为位串,然后再转换为int;你必须努力工作来理解这一点,或者找到任何地方来引入错误。正如 Scott Griffiths 的回答所暗示的那样,该类有更多的灵活性,这可能对同一个应用程序有用。但另一方面,travc 的回答清楚地表明了实际发生的情况——即使是新手也应该​​能够理解从无符号整数到 2s 补码有符号整数的转换意味着什么。

          无论如何,与直接操作位的另一个问题不同,这个问题是关于对固定长度的整数进行算术运算,只是大小奇数的整数。所以我猜你是否需要性能,这可能是因为你有一大堆这些东西,所以你可能希望它被矢量化。改编 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 代码击败它,但您可能不必这样做。

          【讨论】:

            【解决方案11】:

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

            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 位?给定的字符串只是一个例子。
            • 对于这个问题,假设作者询问如何将 12 个二进制数字解释为 12 位二进制补码有符号整数(因为 -1 始终由 N- 中的 N 个 1 位表示位二进制补码表示)。第一位用于符号,其余 (11) 位确定幅度。
            【解决方案12】:

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

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

            【讨论】:

              【解决方案13】:

              我正在使用 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'

              【讨论】:

                【解决方案14】:

                这适用于 3 个字节。 Live code is here

                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
                

                【讨论】:

                  【解决方案15】:

                  好的,我在 PCM wav 文件类型uLaw 压缩算法 中遇到了这个问题。我发现二进制补码有点使某个二进制数为负值as can be seen here。在咨询wikipedia 之后,我认为这是真的。

                  那家伙解释为找到 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 范围内的任何值。因此,不要循环遍历整个 65535short integer 可以让我决定将其拆分为 nibbles(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 个数字是必需的

                  这个操作有点不合逻辑,对于它扫描的每 2 位,如果两者都是 1 或 0,它将为 0,否则为 1。

                   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 来计算数据的数量,对于半字节,我们得到 16,因为我们计算的是 0 的位值。

                  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
                  

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

                  【讨论】:

                    【解决方案16】:

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

                    
                    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            
                    

                    【讨论】:

                      【解决方案17】:

                      仍然是一个非常相关的问题,但在我的场景中没有一个答案有效 - 这令人惊讶。

                      这是一个非常简单的函数,可以根据整数值计算 n 位 2 的补码整数值。

                      此函数特别确保返回的值不会被 python 视为负值,因为它破坏了 2 的补数的本质。

                      2 的补数最初是为了在本身不支持它们的架构上同时处理正值和负值而创建的。这是一种转换,就是使用可用的位来表示和计算正数和负数。

                      因此可以指定位数,默认为 16,并且可以通过将其设置为 0,将其设置为给定值所需的位数。

                          def twos_comp(val, bits=16):
                              """compute the 2's complement of int value """
                              if bits == 0:      # Use as many bits needed for the value.
                                  bits = val.bit_length()
                              return ((val & (2 ** bits) - 1) - (2 ** bits)) * -1
                      

                      测试代码:

                          value = 6752
                          print(f'{value:05d} = 0x{value:04x} = 0b{value:016b}')
                          
                          value = twos_comp(value)
                          print(f'{value:05d} = 0x{value:04x} = 0b{value:016b}')
                          
                          value = twos_comp(value)
                          print(f'{value:05d} = 0x{value:04x} = 0b{value:016b}')
                      

                      测试代码输出:

                       06752 = 0x1a60 = 0b0001101001100000
                       01440 = 0x05a0 = 0b0000010110100000
                       06752 = 0x1a60 = 0b0001101001100000
                      

                      【讨论】:

                        【解决方案18】:

                        使用~^和掩码(掩码决定总位数)

                        # Given negative value, obtain its two's complement form in 16 bits
                        
                        >>> mask = (1 << 16) - 1
                        >>> a = -6
                        >>> bin(~(a ^ mask))
                        '0b1111111111111010'
                        
                        # Given 16-bit signed binary string, return the integer value
                        >>> mask = (1 << 16) - 1
                        >>> b = '1111111111110101'
                        >>> ~(int(b, 2) ^ mask)
                        -11
                        

                        【讨论】:

                          【解决方案19】:

                          这比这一切都容易...

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

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

                          【讨论】:

                          • 这不适用于“1111”。 twoComplement(int('1111', 2), 4) 的结果是 1 使用您的函数时。但正确的结果是-1。另请参阅其他正确的答案。
                          • 我上次检查时,1111 在四位上的 2 补码是 0001。
                          • 我认为您可能会将计算数字的 2 补码与 2 补码域中的数字值(作为负数)混淆。
                          • 别傻了——术语“二进制补码”通常表示位模式的十进制解释——参见。例如en.wikipedia.org/wiki/Two's_complement 中的前 2 个表。此外,OP 专门询问了这一点,并以'111111111111' -&gt; -1 为例。这意味着你不回答这个问题。因此,您的陈述“这比...更容易”不适用。
                          • 啊哈,我猜你是对的。我一定是跳到了线程中间,错过了最初的问题!
                          猜你喜欢
                          • 1970-01-01
                          • 2019-08-04
                          • 2014-03-19
                          • 1970-01-01
                          • 1970-01-01
                          • 2014-12-25
                          • 2012-11-23
                          • 1970-01-01
                          相关资源
                          最近更新 更多