为什么无符号整数0xFFFFFFFF等于有符号整数-1?

38
在 C 或 C++ 中,size_t(一种无符号的 int 数据类型)可以容纳的最大数字是将 -1 强制转换为该数据类型。例如参见 Invalid Value for size_t
为什么呢?
我的意思是,(针对 32 位整数而言)据我所知,最高有效位在有符号数据类型中表示符号(即使用位 0x80000000 表示负数)。那么,1 是 0x00000001……0x7FFFFFFF 是 int 数据类型可以容纳的最大正数。
因此,据我所知,-1 int 的二进制表示应该是 0x80000001(也许我错了)。为什么/如何将这个二进制值转换为完全不同的东西(0xFFFFFFFF),当将 int 强制转换为 unsigned 时?或者说……如何用 0xFFFFFFFF 来表示二进制 -1?
我毫不怀疑,在 C 中:((unsigned int) -1) == 0xFFFFFFFF 或 ((int) 0xFFFFFFFF) == -1 同真理一样不变,就像 1 + 1 == 2 一样,只是我想知道为什么。

17
在维基百科上阅读关于“二进制补码”的内容;这是在二进制中编码负数的最常见方式。 - Artelius
7
好的,我会尽力进行翻译。以下是您需要翻译的内容:Two's complement is a mathematical operation on binary numbers, and is an example of a radix complement. It is used in computing as a method of signed number representation.Two's complement works by reflecting the binary value about the midpoint of the range of possible values, ignoring the first bit which is used to indicate whether the value is positive or negative. This means that each positive value has a corresponding negative value, and vice versa.Two's complement simplifies arithmetic operations on binary numbers, as addition and subtraction can be performed using the same algorithm regardless of whether the operands are positive or negative. It also allows for efficient implementation of hardware circuits for arithmetic operations.In two's complement representation, the most significant bit indicates the sign of the value, with 0 indicating a positive value and 1 indicating a negative value. The remaining bits represent the magnitude of the value.The range of values that can be represented in two's complement is determined by the number of bits used to represent the value. For example, using 8 bits, the range of values that can be represented is -128 to 127.Overall, two's complement is an important concept in computer science and digital electronics, and is widely used in modern computing systems. - anon
1
你会注意到,就像无符号数字一样,将1加到最大可能的数字上会得到最小可能的数字。 - Drew Dormann
1
负一的二进制表示必须是这样的表示,即在其上加1得到0。这是 0xFFFFFFFF - David Schwartz
6个回答

52
C和C++可以在许多不同的架构和机器类型上运行,因此它们可以具有不同的数字表示方式:二进制补码和一的补码是最常见的。通常情况下,您不应该在程序中依赖于特定的表示方式。
对于无符号整数类型(如size_t),C标准(以及我认为的C++标准)指定了精确的溢出规则。简而言之,如果SIZE_MAX是类型size_t的最大值,则表达式 (size_t) (SIZE_MAX + 1) 保证为0,因此,您可以确定(size_t)-1等于SIZE_MAX。其他无符号类型也是如此。
请注意,即使底层机器不使用二进制补码表示数字,上述内容仍然成立。在这种情况下,编译器必须确保该身份成立。
此外,上述内容意味着您不能依赖于有符号类型的特定表示方式。
编辑:为了回答一些评论:
假设我们有一个代码片段:
int i = -1;
long j = i;

在将值赋给 j 时会进行类型转换。假设 intlong 在内存中占用的字节数不同(大多数 [所有?] 64位系统都是这样),由于它们大小不同,ij 在内存中的位模式将不同。编译器确保 ij 都为 -1

类似地,当我们执行以下操作时:

size_t s = (size_t) -1

这里发生了一种类型转换。 -1 是一个 int 类型的数字,它有其对应的位模式,但在此例中无关紧要,因为当进行强制类型转换后,编译器会按照目标类型(size_t)的规则来翻译该值。即使intsize_t的大小不同,标准也保证存储在上面的s中的值将是size_t类型可以取到的最大值。

如果我们执行以下操作:

long j = LONG_MAX;
int i = j;

如果 LONG_MAX 大于 INT_MAX,则 i 中的值是实现定义的(C89,第3.2.1.2节)。


6
投票支持因为你是第一个指出(size_t)-1是由于C语言对无符号数的算术规则所指定,而不是基于底层表示的。顺便说一下,SIZE_MAX是宏。 - caf
3
标记(Mark):“无符号整数应遵守算术模2 ** n的规律,其中n是该特定大小的整数值表示中位数的位数。”[3.9.1 / 4,C ++ 03] - Roger Pate
1
@ Alok,您的编辑更好地回答了我的问题。简而言之,无论二进制数字的内部表示方式如何,它对于C及其整数算术规则都是无关紧要的。总之,鉴于负整数有不同的硬件表示方式,没有办法在C中以位级别来操作它们以“生成”负数,这正确吗? - royconejo
1
关于“大多数(全部?)32位系统”,我会说“很少有32位系统”。在这些系统上,intlong通常都是32位的。在16位系统(16/32)或某些64位系统(32/64)上会出现不匹配的情况。 - M.M
@MattMcNabb 不错。我可能是想说64位而不是32位。 - Alok Singhal
显示剩余7条评论

32

这被称为二进制补码。为了得到一个负数,需要反转所有的位然后加1。因此,将1转换为-1,需要将它反转为0xFFFFFFFE,再加1得到0xFFFFFFFF。

至于为什么要这样做,维基百科说:

二进制补码系统的优点在于不需要加减电路去检查操作数的符号来确定是加还是减。这个特性使该系统更简单易行并且能够轻松处理高精度算术运算。


6
我曾经使用过一台补码机器。它很奇怪,因为既有正零又有负零。 - Mark Ransom
我从未想过浮点数有负零,但我看到你是正确的:http://en.wikipedia.org/wiki/Signed_zero - Mark Ransom
5
即使您使用的是反码机器或其他奇怪的机器,答案也不会改变。关于详情,请查看我的回答。稍微注意一下,正确的说法是“ones' complement”,而不是“one's complement”。根据Knuth所说:在二进制补码中,一个数是相对于2的幂次方进行取反的;而在一的补码中,一个数是相对于一长串1进行取反的。事实上,还有一种叫做“twos' complement notation”的表示方式,它的基数为3,相对于(2...22)_3进行取反。 - Alok Singhal
@David:恭喜你猜到我第一次学习汇编语言的机器。@Alok:我不知道标准会做出这样的保证,因为我从来没有在任何不使用二进制补码的机器上使用过C或C++。我猜它们非常罕见。 - Mark Ransom
@David:在控制数据上,你可以稍微作弊一下:如果你将零加到一个数字上,那么它会将负零转变为正零,这使得比较变得简单明了。 - Jerry Coffin
显示剩余2条评论

7
关于为什么 (unsigned)-1 会得到最大的无符号值,这与二进制补码只是偶然相关。之所以将-1强制转换成无符号类型后会得到该类型可能的最大值,是因为标准规定无符号类型“遵循模2n算术规则,其中n是该特定大小整数的值表示中的位数。”
对于二进制补码,最大可能的无符号值和-1的表示恰好相同,但即使硬件使用另一种表示(例如1的补码或符号/大小),将-1转换为无符号类型仍必须产生该类型可能的最大值。

3

二補數在做減法時和加法一樣好用 :)

    11111110 (254 或 -2)
   +00000001 (  1)
   ---------
    11111111 (255 或 -1)
11111111 (255 或 -1) +00000001 ( 1) --------- 100000000 ( 0 + 256)

1

这是二进制补码编码。

主要的好处是,无论您使用无符号整数还是有符号整数,您都可以获得相同的编码。如果您从0中减去1,则整数会简单地绕回。因此,比0小1的数是0xFFFFFFFF。


-2

因为int类型的位模式-1在十六进制无符号表示法下是FFFFFFFF, 在二进制无符号表示法下是11111111111111111111111111111111。 但在int类型中,第一位表示它是否为负数。 但在无符号int类型中,第一位只是额外的数字,因为无符号int类型不能为负数。所以额外的位使得无符号int类型能够存储更大的数字。 对于无符号int类型,11111111111111111111111111111111(二进制)或FFFFFFFF(十六进制)是uint可以存储的最大数字。 不建议使用无符号整数,因为如果它们变为负数,则会溢出并变成最大的数字。


3
您刚刚重新陈述了OP的观察,他正在问为什么int的位模式是那样的;以及为什么(unsigned int)-1会给出0xFFFFFFFF(您的答案没有清楚回答)。此外,不建议使用unsigned ints是有争议的建议,它们非常常用。有符号整数和无符号整数都有缺陷。我的观点是,无符号整数比有符号整数的缺陷更少,所以我更喜欢使用它们,除非我知道需要负值。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接