【问题标题】:Bitwise subtraction in PythonPython中的按位减法
【发布时间】:2008-12-14 16:41:28
【问题描述】:

这是my question yesterday的后续行动:

CMS 好心提供了这个在 C 语言中使用位运算符将两个数字相加的示例:

#include<stdio.h>

int add(int x, int y) {
    int a, b;
    do {
        a = x & y;
        b = x ^ y;
        x = a << 1;
        y = b;
    } while (a);
    return b;
}

int main( void ){
    printf( "6 + 3 = %d", add(6,3));
    printf( "6 - 3 = %d", add(6,-3));
    return 0;
}

效果很好,然后我将它移植到 Python,如下所示:

def add(x, y):
    while True:
        a = x & y
        b = x ^ y
        x = a << 1
        y = b
        if a == 0:
            break
    return b

print "6 + 3 = %d" % add(6,3)
print "6 - 3 = %d" % add(6,-3)

它们都适用于加法,C 程序也适用于减法。但是,Python 程序进入一个无限循环进行减法。我正在努力追根溯源,并在此处发布了该程序以供进一步实验:http://codepad.org/pb8IuLnY

谁能告诉我为什么 C 处理这个问题的方式和 CPython 处理这个问题的方式会有所不同?

【问题讨论】:

    标签: python low-level


    【解决方案1】:

    正如我在昨天对 CMS 的回答中指出的那样,左移负数在 C 中是未定义的行为,因此这甚至不能保证在 C 中有效(问题是如何处理有符号位,做您将其像值位一样移动还是不受移动影响?标准委员会无法就行为达成一致,因此未定义)。

    当这发生在 C 中时,它依赖于固定位宽的整数,因此当您进行移位时,最左边的位会被推到末尾(它还需要将符号位视为值位以进行移位)。 C 中的所有整数类型都是固定位的,但 Python 数字可以任意大。在 Python 中左移一个数字只会导致它不断变大:

    >>> 1 << 100
    1267650600228229401496703205376L
    

    你可以试试这样的:

    x = (a << 1) & 0xffffffff
    

    为了将结果限制为 32 位,问题在于 Python 中的左移运算符不会移动有符号数的符号位(这是使此特定解决方案工作所需的一部分)。可能有一种方法可以改变 shift 运算符的行为,但我不知道如何。

    【讨论】:

    • 感谢您的信息。这是否意味着不可能进行按位减法?我在网上阅读的所有内容都建议通过取第二个操作数的二进制补码将其转换为按位加法问题。
    • 我认为您需要更改左移运算符的行为,请参阅我编辑的回复。
    • 左移是在 Python (docs.python.org/reference/expressions.html#shifting-operations) 中根据乘法定义的,所以我认为如果希望它适用于负数,您需要找到另一种方法。
    【解决方案2】:

    移动负数在 python 和 C 之间没有一致的解释。

    【讨论】:

      【解决方案3】:

      如果ij 是两个整数:

      补充:

      printf("%d",(i^j)|((i&j)<<1));
      

      【讨论】:

        【解决方案4】:

        我注意到您假设 python 处理数字的方式与 C 相同。
        这并不完全正确。这意味着 C 的 int 数具有 16 位的固定长度。有关 C 数据类型的详细信息,您可以参考C_data_types on en.wikipedia.org
        另一方面,据说 Python 对 int 数具有几乎无限的长度。
        添加正整数可能会以相同的方式工作。但负整数的减法或加法不应该是简单的映射转换。
        理解这一点的一个简单方法是一个关于负数的小例子: 想象一个 3 位的固定长度整数表示:
        #无符号#

        • 000:0
        • 001:1
        • 010:2
        • 011:3
        • 100:4
        • 101:5
        • 110:6
        • 111:7

        #签名:#

        • 000:0
        • 001:1
        • 010:2
        • 011:3
        • 100:-4
        • 101:-3
        • 110:-2
        • 111:-1

        这很酷,因为您可以看到 1-3=1+(-3), -3 是 101,如果未签名,则为 5。所以1+5=6,6:110:-2。这意味着1-3=-2
        溢出时也会出现问题:

        • -4 + -1 = 3 不是 -5,因为它超出了范围!
        • 3 + 1 = -4 不是 4,因为它超出了范围!

        正如您所见,这适用于固定长度,但在 Python 中却不是这样。

        【讨论】:

          猜你喜欢
          • 2020-10-08
          • 1970-01-01
          • 1970-01-01
          • 2012-04-30
          • 2015-07-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-09
          相关资源
          最近更新 更多