有人能够用二进制解释C#中的溢出吗?

7
我目前正在阅读一本关于C#编程的书,其中简要介绍了Overflow和Underflow,并且作者大致讲述了当您超出特定类型的允许范围时会发生什么。
例如:
short a = 30000;
short b = 30000;
short sum = (short)(a + b); // Explicitly cast back into short
Console.WriteLine(sum); // This would output the value -5536

短整型只有从-32768到32767的范围,在书中作者给出的解释是:“对于整数类型(byte、short、int和long),最高位(溢出的位)会被丢弃。这特别奇怪,因为计算机会将其解释为环绕。这就是为什么我们在示例中得到了负值的原因。如果你使用某个特定类型的最大值(例如short.MaxValue)并加上一,你可以轻松地看到这种情况发生。”(《C#玩家指南第二版》,第9章,第58页)
这样,你会得到最小值(-32768)。
我不太理解这个问题,当作者谈论“计算机将其解释为环绕”时,我感到困惑。
我试图理解这个问题,短整型使用2个字节(16位)
所以数字32767=0111111111111111 如果我把二进制字符串+1,我就会得到 32768=1000000000000000(不能用短整型表示,因为最大值是32767),所以编译器会给出-32768。为什么会变成负数?
我理解使用二进制补码表示负数的概念,但是否有人能更正我的想法或详细说明一下?我不完全理解为什么我们只使用15位的16位来表示正值,而使用最高位来表示负值。

1
你忘记考虑符号位了。 - Ken White
我不明白 :(,你能解释一下吗? - Shabubble
1
没什么好解释的。这是惯例。MSB 是符号位。你还有什么其他的选择呢? - Luc Morin
因此,负数的二进制数字始终将具有 1 作为 MSB(按照惯例),而无符号二进制数字必须将 0 作为 MSB。我不知道以 MSB 为符号位的惯例。我觉得混淆是因为我使用了“纯”二进制表示法,我实际上并不理解二补数约定。 - Shabubble
每个告诉你高位是符号位的人都只是增加了你的困惑。这与符号位无关。 - Eric Lippert
显示剩余4条评论
1个回答

18

忽略那些告诉你最高位是符号位的人,这是错误的想法。

正确的想法是:

  • 我们有65536种可能的位模式。
  • 因此,我们可以表示65536个可能的数字。
  • 我们必须有一个映射将每个位模式赋予含义

对于无符号短整型,我们将位模式分配如下:

0000000000000000 --> 0
0000000000000001 --> 1
...
0111111111111111 --> 32767
1000000000000000 --> 32768
1000000000000001 --> 32769
...
1111111111111111 --> 65535

对于 带符号 的短整型,我们使用以下约定:

0000000000000000 --> 0
0000000000000001 --> 1
...
0111111111111111 --> 32767
1000000000000000 --> -32768
1000000000000001 --> -32767
...
1111111111111111 --> -1

就是这么简单。

为什么我们使用这种约定?

有三个原因:

(1) 前32K个值在有符号或无符号情况下都相同,非常方便。

(2) 在两种约定中,“所有零位”表示为零。

(3) 因为加法在两种约定中完全相同!

我们已经

0000000000000000 --> 0

我们想要加1。使用二进制规则加1,得到:

0000000000000001 --> 1
无论short是有符号还是无符号,这都适用。
我们有一个无符号的short:
1000000000000000 --> 32768
我们希望添加一个。我们使用二进制规则这样做,然后我们得到正确的答案:
1000000000000001 --> 32769

对于有符号的 short 类型同理。我们有

1000000000000000 --> -32768

我们希望增加一个。我们使用二进制规则来实现,得到:

1000000000000001 --> -32767
同样地,您可以验证通过将1111111111111111添加到任何二进制数中,都会得到"少一",因此减去一与加上一一样有效。然后,您可以继续展示,在有符号和无符号算术中,加法和减法总是起着相同的作用,这意味着处理器不需要知道编译器认为值是有符号还是无符号的
这就是为什么我们使用二进制补码:因为无论您是进行有符号还是无符号算术,底层数学都是完全相同的。
请注意,我在其中根本没有提到"符号位"。高位对于负数的设置只是一个很好的附加优势。我们想要的真正属性是只需构建一次执行数学运算的硬件。
二进制补码只是一种约定,根据保存该比特模式的变量关联的类型,为其分配两种可能含义中的一种。作为产业界,我们选择了这种约定,因为使用这种约定制造高性能硬件很便宜。
我们可以选择许多其他约定。例如,我们可以说对于有符号数,我们使用以下映射:
0000000000000000 --> -32768
0000000000000001 --> -32767
...
0111111111111111 --> -1
1000000000000000 --> 0
1000000000000001 --> 1
...
1111111111111111 --> 32767

请注意,这与以前完全相同,除了顶部的位!

在这种解释下,“加法仍然按照我们的期望工作”。但是这个映射没有这样一种属性:short和ushort之间的前32K个值相同,并且所有的零位表示为零。因此,我们不使用这个约定,因为它“不太方便”。在这个约定中,从short转换为ushort需要进行加法运算。将short设置为零需要将字节设置为非零值。我们可以使用有序的有符号数字约定,只是因为这样做很麻烦,所以不这样做。


很好的解释,现在更容易理解了 :) 我有点难以理解“前32K个值无论是有符号还是无符号都是相同的”这句话。 - Shabubble
等一下,我想我知道 1000000000000000 --> 32768 和 1000000000000000 --> -32768 都有相同的位模式,我之前没有注意到。 - Shabubble
@Shabubble:有符号短整型中123的位模式是什么?无符号短整型中123的位模式又是什么?它们是相同的。实际上,一直到32767,它们都是相同的。 - Eric Lippert
@Shabubble:1000000000000000 是第一个根据是 short 还是 ushort 而不同的数。 - Eric Lippert
我现在明白了,对于有符号和无符号的情况下,123 ----> 01111011。我的解释关于为什么有符号位的范围会减半是否正确?例如,假设您有二进制的65535,反转位,加一(二进制补码),您将不再拥有2个字节,而是2个字节和1个比特17位。这是因为为了保持在2个字节的范围内,您只能使用大约32k范围内的数字或(32768)进行二进制补码。 - Shabubble

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