问题描述
我做了一个简单的函数,叫做symmetricDelta()
,它“对称”地计算value
和prevIoUs
之间的差值。我的意思是:考虑一个数字行,例如0
到ULLONG_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)
。这可能对您来说不是问题,但最好知道这一点,因为它可能会咬住您。