如何用环绕或溢出减去两个无符号整数

问题描述 投票:19回答:8

有两个无符号整数(x和y)需要减去。 x总是大于y。但是,x和y都可以环绕;例如,如果它们都是字节,则在0xff之后变为0x00。问题是如果x环绕,而y不环绕。现在x似乎小于y。幸运的是,x不会缠绕两次(只保证一次)。假设字节,x已经包装并且现在是0x2,而y没有并且是0xFE。 x - y的正确答案应该是0x4。

也许,

( x > y) ? (x-y) : (x+0xff-y);

但我认为还有另一种方法,涉及2s补码?在这个嵌入式系统中,x和y是最大的unsigned int类型,因此添加0xff ...是不可能的

编写语句的最佳方法是什么(目标语言是C)?

c overflow int subtraction
8个回答
29
投票

假设有两个无符号整数:

  • 如果你知道一个人应该比另一个人“更大”,那就减去。它可以工作,只要你没有多次缠绕(显然,如果你有,你将无法分辨)。
  • 如果您不知道一个比另一个大,则将结果减去并转换为具有相同宽度的signed int。如果两者之间的差异在signed int的范围内(如果没有,你将无法分辨),它将起作用。

澄清一下:原始海报描述的场景似乎令人困惑,但典型的是单调增加固定宽度计数器,例如硬件计数器计数器或协议中的序列号。计数器(例如8位)0xfc,0xfd,0xfe,0xff,0x00,0x01,0x02,0x03等,你知道你拥有的两个值x和y,x后来出现。如果x == 0x02且y == 0xfe,则计算xy(作为8位结果)将给出4的正确答案,假设减去两个n位值包装模2n - C99保证减去无符号值。 (注意:C标准不保证对减去有符号值的这种行为。)


20
投票

当你从'较大'中减去'较小'时,这里有一个更详细的说明为什么它'正常工作'。

有几件事情要进入...... 1.在硬件中,减法使用加法:在添加之前简单地否定适当的操作数。 2.在二进制补码(几乎所有东西都使用)中,通过反转所有位然后加1来取消整数。

硬件比上面的描述更有效地做到这一点,但这是减法的基本算法(即使值是无符号的)。

因此,让图2 - 250使用8位无符号整数。在二进制中我们有

  0 0 0 0 0 0 1 0  
- 1 1 1 1 1 0 1 0

我们否定被减去的操作数然后添加。回想一下,为了否定我们反转所有的位然后加1.反转第二个操作数的位后我们有

0 0 0 0 0 1 0 1  

然后我们加1后

0 0 0 0 0 1 1 0  

现在我们进行添加......

  0 0 0 0 0 0 1 0   
+ 0 0 0 0 0 1 1 0

= 0 0 0 0 1 0 0 0 = 8, which is the result we wanted from 2 - 250

13
投票

也许我不明白,但有什么不对:

unsigned r = x - y;


3
投票

如上所述,问题令人困惑。你说你正在减去无符号值。如果x总是大于y,正如你所说,那么x - y不可能环绕或溢出。所以你只需要做x - y(如果这就是你需要的)那就是它。


2
投票

这是确定循环缓冲区中的可用空间量或执行滑动窗口流控制的有效方法。使用unsigned ints为headtail - 增加它们并让它们包裹!缓冲区长度必须是2的幂。

free = ((head - tail) & size_mask),其中size_mask是2 ^ n-1的缓冲区或窗口大小。


1
投票

问题应说明如下:

让我们假设一个时钟的两个指针ab的位置(角度)由uint8_t给出。整个循环被分成uint8_t的256个值。如何有效地计算两个指针之间的较小距离?

解决方案是:

uint8_t smaller_distance = abs( (int8_t)( a - b ) );

我怀疑没有什么比这更有效了,否则就会有比abs()更高效的东西。


0
投票

为了回应其他人的回复,如果你只是减去两个并将结果解释为未签名,你就没事了。

除非你有一个明确的反例。

你的qazxsw poi,qazxsw poi的例子不会导致qazxsw poi,它会导致x = 0x2,除非你对未说明的数学有更多限制。


0
投票

只是将已经正确的答案放入代码中:

如果您知道x是较小的值,则以下计算正常:

y= 0x14

如果你不知道哪一个更小:

0x4

在这种情况下,差异必须在0xEE的范围内。

代码实验帮助我更好地理解了解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.