无符号整数的“对称差”-假定展期

问题描述

我做了一个简单的函数,叫做symmetricDelta(),它“对称”地计算valueprevIoUs间的差值。我的意思是:考虑一个数字行,例如0ULLONG_MAX,在此处连接数字线的左右两端...要确定“对称”增量,如果value - prevIoUs小于跨度的一半,否则假设变化为负,我们将数字线包裹起来。

在下面的uint64_t中查看其简单版本:

int64_t symmetricDelta(uint64_t value,uint64_t prevIoUs) {
    if (value-prevIoUs < (1ULL << 63)) {
        uint64_t result = value - prevIoUs;
        return result;
    } else {
        uint64_t negativeResult = prevIoUs - value;
        return -1 * negativeResult;
    }
}

用法

    uint64_t value = ULLONG_MAX;
    uint64_t prevIoUs = 0;

    // Result: -1,not UULONG_MAX
    cout << symmetricDelta(value,prevIoUs) << endl;

演示:https://onlinegdb.com/BJ8FFZgrP

其他值示例,为简单起见,假设使用uint8_t版本:

symmetricalDifference(1,0) == 1
symmetricalDifference(0,1) == -1
symmetricalDifference(0,255) == 1
symmetricalDifference(255,0) == -1
symmetricalDifference(227,100) == 127
symmetricalDifference(228,100) == -128

我的问题是:我所说的“对称减法”是否有“正式”名称?感觉好像已经在C ++ STL中实现了,但是我什至不知道要搜索什么...

解决方法

是的。名称为减模2^64。它与您的计算机按照指令执行的操作一样

int64_t symmetricDelta(uint64_t value,uint64_t previous) {
    return (int64_t)(value-previous);
}

在C和C ++中,无符号算术被定义为环绕,从而将可表示数字范围的末端有效地连接成一个圆。这是带符号整数的2补码表示法的基础:您的CPU只需声明数字圆的一半即可解释为负数。这部分是无符号的上部,-1对应于最大可表示的无符号整数。仅仅是因为0在圆圈的后面。

旁注:
这样,CPU可以将完全相同的电路用于有符号和无符号算术。 CPU仅提供一条add指令,无论这些数字应解释为带符号还是无符号都可以使用。加法,减法和乘法都是如此,它们全都作为无符号指令存在。只有除法以有符号和无符号变体形式实现,比较指令/ CPU提供的标志位也是如此。

注释2:
上面的说法并不完全正确,因为现代CPU在其向量单元(AVX等)的一部分中实现了饱和算法。因为饱和算术意味着将结果剪裁到可表示范围的末尾而不是回绕,所以这种剪裁取决于假定数字圆在何处断开。因此,饱和算术指令通常存在于有符号和无符号变体中。

不必要的背景杂乱无章的结束...

因此,当您以无符号表示形式减去两个数字时,结果就是从子代数到减数所要经过的无符号步数。通过将结果重新解释为有符号整数,您将一条长路径(绕圆的一半以上)解释为相反方向上的相应短路径。


有一个陷阱:1 << 63无法代表。它正好位于与零相对的数字圆的另一侧,并且由于设置了其符号位,因此将其解释为-(1 << 63)。如果您尝试取反它,则位模式不会改变一位(就像-0 == 0一样),因此您的计算机会愉快地声明为- -(1 << 63) == -(1 << 63)。这可能对您来说不是问题,但最好知道这一点,因为它可能会咬住您。