JNL*_*iu5 12 python integer bit-manipulation
>>> x = -4
>>> print("{} {:b}".format(x, x))
-4 -100
>>> mask = 0xFFFFFFFF
>>> print("{} {:b}".format(x & mask, x & mask))
4294967292 11111111111111111111111111111100
>>>
>>> x = 0b11111111111111111111111111111100
>>> print("{} {:b}".format(x, x))
4294967292 11111111111111111111111111111100
>>> print("{} {:b}".format(~(x ^ mask), ~(x ^ mask)))
-4 -100
Run Code Online (Sandbox Code Playgroud)
我无法弄清楚Python如何表示负整数,以及因此位操作如何工作.我的理解是Python试图模拟两个补码,但是有任意数量的位.因此,通常使用32位掩码强制Python在位操作之前在整数上设置标准大小.
正如您在我的示例中所看到的,-4 & 0xFFFFFFFF产生一个大的正数.为什么Python似乎将其读作无符号整数,而不是2的补码负数?之后,该操作~(x ^ mask)应该产生与大正值相同的二进制补码位模式-4.导致转换为signed int的原因是什么?
谢谢!
jfe*_*ard 10
TLDR;CPython 整数类型将符号存储在结构的特定字段中。执行按位运算时,CPython 用负数的补码替换负数,有时(!) 执行相反的操作(即用负数替换二进制的补码)。
整数的内部表示是一个PyLongObject结构体,它包含一个PyVarObject结构体。(当 CPython 创建一个新PyLong对象时,它会为结构分配内存并为数字分配一个尾随空间。)这里重要的PyLong是大小:嵌入结构的ob_size字段PyVarObject包含整数的大小(以数字为单位)(数字是 15 或 30 位数字)。如果整数为负,则此大小为减去位数。
(参考:https://github.com/python/cpython/blob/master/Include/object.h和https://github.com/python/cpython/blob/master/Include/longobject.h)
如您所见,内部 CPython 的整数表示与通常的二进制表示相去甚远。然而,CPython 必须为各种目的提供按位运算。我们来看看代码中的注释:
static PyObject *
long_bitwise(PyLongObject *a,
char op, /* '&', '|', '^' */
PyLongObject *b)
{
/* Bitwise operations for negative numbers operate as though
on a two's complement representation. So convert arguments
from sign-magnitude to two's complement, and convert the
result back to sign-magnitude at the end. */
/* If a is negative, replace it by its two's complement. */
/* Same for b. */
/* Complement result if negative. */
}
Run Code Online (Sandbox Code Playgroud)
为了处理按位运算中的负整数,CPython 使用二进制补码(实际上,这是一个逐位的二进制补码,但我不详细介绍)。但请注意“符号规则”(名字是我的):结果的符号是应用于数字符号的按位运算符。更准确地说,如果nega <op> negb == 1, ( negx=1为负,0为正) ,则结果为负。简化代码:
switch (op) {
case '^': negz = nega ^ negb; break;
case '&': negz = nega & negb; break;
case '|': negz = nega | negb; break;
default: ...
}
Run Code Online (Sandbox Code Playgroud)
另一方面,即使在二进制表示中,格式化程序也不执行二进制补码:[format_long_internal](https://github.com/python/cpython/blob/master/Python/formatter_unicode.c#L839)调用[long_format_binary](https://github.com/python/cpython/blob/master/Objects/longobject.c#L1934)并删除两个前导字符,但保留符号。看代码:
/* Is a sign character present in the output? If so, remember it
and skip it */
if (PyUnicode_READ_CHAR(tmp, inumeric_chars) == '-') {
sign_char = '-';
++prefix;
++leading_chars_to_skip;
}
Run Code Online (Sandbox Code Playgroud)
该long_format_binary函数不执行任何二进制补码:只输出以 2 为基数的数字,前面有符号。
if (negative) \
*--p = '-'; \
Run Code Online (Sandbox Code Playgroud)
我将遵循您的 REPL 序列:
>>> x = -4
>>> print("{} {:b}".format(x, x))
-4 -100
Run Code Online (Sandbox Code Playgroud)
考虑到格式中没有二进制补码,而是一个符号,这并不奇怪。
>>> mask = 0xFFFFFFFF
>>> print("{} {:b}".format(x & mask, x & mask))
4294967292 11111111111111111111111111111100
Run Code Online (Sandbox Code Playgroud)
数字-4是负数。因此,它在逻辑与之前被其二进制补码替换,逐个数字。您预计结果会变成负数,但请记住“签名规则”:
>>> nega=1; negb=0
>>> nega & negb
0
Run Code Online (Sandbox Code Playgroud)
因此: 1. 结果没有负号;2. 结果不补为二。您的结果符合“签名规则”,即使此规则看起来不太直观。
现在,最后一部分:
>>> x = 0b11111111111111111111111111111100
>>> print("{} {:b}".format(x, x))
4294967292 11111111111111111111111111111100
>>> print("{} {:b}".format(~(x ^ mask), ~(x ^ mask)))
-4 -100
Run Code Online (Sandbox Code Playgroud)
同样,-4是负数,因此用它的补码代替0b11111111111111111111111111111100,然后与 异或0b11111111111111111111111111111111。结果是0b11(3)。你取一元补码,这又是0b11111111111111111111111111111100一次,但这次符号是负的:
>>> nega=1; negb=0
>>> nega ^ negb
1
Run Code Online (Sandbox Code Playgroud)
因此,正如您预期的那样,结果被补充并获得负号。
结论:我想没有完美的解决方案来拥有任意的长符号数并提供按位运算,但是文档并不是很详细地说明了所做的选择。