您遇到环绕行为。
无符号类型是循环的(另一方面,有符号类型可能是循环的,也可能不是循环的,但它是您不应该依赖的未定义行为)。也就是说,比最小可能值小一是最大可能值。您可以使用以下代码段自己演示这一点:
int main()
{
unsigned int x = 5;
for (int i = 0; i < 10; ++i) cout << x-- << endl;
return 0;
}
您会注意到,在达到零后,x 的值会跳到 2^32-1,即最大可表示值。减去进一步的行为符合预期。
当您从无符号 0 中减去 1 时,位模式会以下列方式发生变化:
0000 0000 0000 0000 0000 0000 0000 0000 // before (0)
1111 1111 1111 1111 1111 1111 1111 1111 // after (2^32 - 1)
对于无符号数,负数被视为从零减去的正数。所以(unsigned int) -10
会相等((unsigned int) 0) - ((unsigned int) 10)
。
我喜欢将其视为无符号整数,它是高精度任意值的最低 32 位。像这样:
v imaginary high order bit
1 0000 0000 0000 0000 0000 0000 0000 0000 // before (2^32)
0 1111 1111 1111 1111 1111 1111 1111 1111 // after (2^32 - 1)
在这些溢出情况下,unsigned int 的行为与从 256 中减去 1 时 unsigned int 的低 8 位的行为完全相同。像这样查看 unsigned char(1 字节)更有意义,因为如果转换为 0 和 256 值是相等的unsigned char
,因为有限的精度会丢弃额外的位。
0 0000 0000 0000 0000 0000 0001 0000 0000 // before (256)
0 0000 0000 0000 0000 0000 0000 1111 1111 // before (255)
正如其他人指出的那样,这称为模运算。使用更高的精度值来帮助可视化环绕工作时所做的转换,因为您屏蔽了高阶位。它是什么并不重要,所以它可以是任何东西,它只是被丢弃。整数是超过模 2^32 的值,因此 2^32 的任何倍数在整数空间中都为零。这就是为什么我可以假装最后还有一点点。
模运算有自己的专用运算符,以防您需要在程序中为 2^32 以外的数字计算它们,如以下语句所示:
int forty_mod_twelve = 40 % 12;
// value is 4: 4 + n * 12 == 40 for some whole number n
对 2 的幂(如 2^32)的模运算直接简化为屏蔽高阶位,如果你取一个 64 位整数并以 2^32 为模计算,则该值将与转换后的值完全相同到一个无符号整数。
01011010 01011100 10000001 00001101 11111111 11111111 11111111 11111111 // before
00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111 // after
程序员喜欢使用这个属性来加速程序,因为很容易砍掉一些位数,但是执行模运算要困难得多(它和除法一样难)。
那有意义吗?