python 负数 和 任意位数 补码 互转

对于补码,最简单的理解是最高位的权值是负数,而不是取反加一。

最简理解

出自:数字电子技术基础(数电) 清华大学 王红主讲: 02

image

原码和补码其实都是使用最高位来凑出负数,但是方法不同,以8位的编码为例:

  • 负数原码最高位:乘以-1
  • 负数补码最高位:减去128

最简方法

故按照最高位是权值是负数的思路进行转换,代码非常简单:

  • 补码转负数:只要提供的是正经表示负数的补码即可
  • 负数转补码:除输入负数以外,还要指定补码的位数
def bu2fu(a):
    b = bin(a).replace("0b1","0b0")
    return int(b, 2)-pow(2,len(b)-3)

def fu2bu(a,b):
    return pow(2,b)+a

print(hex(fu2bu(-1,32)))
print(hex(fu2bu(-10,64)))
print(bu2fu(0xff))
print(bu2fu(0xfffe))
print(bu2fu(0x80000000))
print(bu2fu(0xfffffffd))

测试:

  python3 bu.py
0xffffffff
0xfffffffffffffff6
-1
-2
-2147483648
-3

完整工具

打印详细信息以及相应位数下的最大最小值的完整脚本如下:

def bu2fu(a):
    b = bin(a).replace("0b1","0b0")
    e = int(b, 2)-pow(2,len(b)-3)
    print("input         : "+hex(a))
    print("bin           : "+b[2:])
    print("signed        : "+str(e))
    print("signed hex    : "+hex(e))
    print("-"*(len(b)+14))
    print("bit           : "+str(len(b)-2))
    print("min signed    : "+str(hex(pow(2,len(b)-3)))+", -"+str(pow(2,len(b)-3)))
    print("max signed    : "+str(hex(pow(2,len(b)-3)-1))+",  "+str(pow(2,len(b)-3)-1))
    print("max unsigned  : "+str(hex(pow(2,len(b)-2)-1))+",  "+str(pow(2,len(b)-2)-1))
    return e

def fu2bu(a,b):
    c = pow(2,b)+a
    print("input         : "+str(a))
    print("hex           : "+hex(c))
    print("bin           : "+bin(c)[2:])
    print("-"*(b+16))
    print("bit           : "+str(b))
    print("min signed    : "+str(hex(pow(2,b-1)))+", -"+str(pow(2,b-1)))
    print("max signed    : "+str(hex(pow(2,b-1)-1))+",  "+str(pow(2,b-1)-1))
    print("max unsigned  : "+str(hex(pow(2,b)-1))+",  "+str(pow(2,b)-1))
    return c

fu2bu(-100,32)
print('\n\n')
bu2fu(0xffffffffffffffff)
  Desktop python3 bu.py 
input         : -100
hex           : 0xffffff9c
bin           : 11111111111111111111111110011100
------------------------------------------------
bit           : 32
min signed    : 0x80000000, -2147483648
max signed    : 0x7fffffff,  2147483647
max unsigned  : 0xffffffff,  4294967295



input         : 0xffffffffffffffff
bin           : 0111111111111111111111111111111111111111111111111111111111111111
signed        : -1
signed hex    : -0x1
--------------------------------------------------------------------------------
bit           : 64
min signed    : 0x8000000000000000, -9223372036854775808
max signed    : 0x7fffffffffffffff,  9223372036854775807
max unsigned  : 0xffffffffffffffff,  18446744073709551615

可以封装成命令行工具如下

bu2fu

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
a = int(sys.argv[1], 0)
b = bin(a)
c = b.replace("0b1","0b0")
e = int(c, 2)-pow(2,len(b)-3)
print("input         : "+hex(a))
print("bin           : "+b[2:])
print("signed        : "+str(e))
print("signed hex    : "+hex(e))
print("-"*(len(b)+14))
print("bit           : "+str(len(b)-2))
print("min signed    : "+str(hex(pow(2,len(b)-3)))+", -"+str(pow(2,len(b)-3)))
print("max signed    : "+str(hex(pow(2,len(b)-3)-1))+",  "+str(pow(2,len(b)-3)-1))
print("max unsigned  : "+str(hex(pow(2,len(b)-2)-1))+",  "+str(pow(2,len(b)-2)-1))
  bu2fu 0xff
input         : 0xff
bin           : 11111111
signed        : -1
signed hex    : -0x1
------------------------
bit           : 8
min signed    : 0x80, -128
max signed    : 0x7f,  127
max unsigned  : 0xff,  255
 bu2fu 0xffffffe0
input         : 0xffffffe0
bin           : 11111111111111111111111111100000
signed        : -32
signed hex    : -0x20
------------------------------------------------
bit           : 32
min signed    : 0x80000000, -2147483648
max signed    : 0x7fffffff,  2147483647
max unsigned  : 0xffffffff,  4294967295

fu2bu

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
a = int(sys.argv[1], 0)
b = int(sys.argv[2], 0)
c = pow(2,b)+a
print("input         : "+str(a))
print("hex           : "+hex(c))
print("bin           : "+bin(c)[2:])
print("-"*(b+16))
print("bit           : "+str(b))
print("min signed    : "+str(hex(pow(2,b-1)))+", -"+str(pow(2,b-1)))
print("max signed    : "+str(hex(pow(2,b-1)-1))+",  "+str(pow(2,b-1)-1))
print("max unsigned  : "+str(hex(pow(2,b)-1))+",  "+str(pow(2,b)-1))
  fu2bu -1 32             
input         : -1
hex           : 0xffffffff
bin           : 11111111111111111111111111111111
------------------------------------------------
bit           : 32
min signed    : 0x80000000, -2147483648
max signed    : 0x7fffffff,  2147483647
max unsigned  : 0xffffffff,  4294967295
  fu2bu -10 64
input         : -10
hex           : 0xfffffffffffffff6
bin           : 1111111111111111111111111111111111111111111111111111111111110110
--------------------------------------------------------------------------------
bit           : 64
min signed    : 0x8000000000000000, -9223372036854775808
max signed    : 0x7fffffffffffffff,  9223372036854775807
max unsigned  : 0xffffffffffffffff,  18446744073709551615

pwntools

后来有一次比赛,xkt问我怎么在python脚本里获得负数的十六进制表示,我说就是补码呗,然后给他看我写的这篇,xkt认为pwntools肯定有这个功能,果然查到:

https://docs.pwntools.com/en/latest/util/packing.html

from pwn import *
pack(-1, 16, endian='little', sign=True)
unpack(b'\xff\xff', 16, endian='little', sign=True)

不过从补码转负数时这里仍然需要给出数值长度。