我很难理解如何处理C中的位,关于2的补码。
这是家庭作业的一部分,但我不是在寻找代码答案,而是要了解用两个补码表示的内容。
我的任务是将一个数字限制为一定数量的比特(n),并确定给定数字是否可以用n个比特中的两个补码表示。
根据示例,5不能表示为3位整数,而-4 CAN表示为3位整数。
为什么会这样?
编辑:我对我的思维过程进行了详细的解释,但意识到我已经完全离开,所以决定省略它。
我最初的推理是看是否允许5和-5,4和-4以3位表示是否有意义。但这并没有意义,因为它并没有真正回答这个问题。
我理解5和-4是如何表示为2的补码。例如,4位:
5:0101
-4:1100
第二次编辑:
为澄清,决定加上我原来的推理:
5是0101,-5是1011.
我可以看到当限制为3位时,5不能用2的补码表示,因为没有第4位,我们不能指示-5是负数。我们需要在1011中额外增加1个。如果我们最多只能有3个比特,那么我们就有011,并且没有办法将-5与3区分开来,后者是4中的0011位,和3位的011。
这种推理是否正确?
4是0100,-4是1100.
在这里,我很困惑。我不明白为什么-4可以用3位表示为2的补码整数。
4表示为0100,100表示为3位。 -4是,如果我们从4(100)开始,我们翻转100(011),并加1(100),我们再次留下100(3位)。在4位中,我认为这表示为1100。
我的困惑是,我们不需要额外的1,1100,以区分-4和4,即0100?如果我们只有3位,我们如何区分100和100?
答案 0 :(得分:4)
为了理解这一点,有必要记住,尽管它在现代硬件上无处不在,但它们不是自然法则,而是人类构造。
假设我们要将整数打包成n位。设n = 3以简化和简洁。很明显,我们可以选择任何2 3 = 8个整数,只要我们保持一致,但是在算术方面,有些选择会让生活更轻松。
无符号整数是直接的。有一个自然的映射:
Encoding Value
000 0
001 1
010 2
011 3
100 4
101 5
110 6
111 7
这只是值的二进制编码,填充为零。零的所有位都为零,这很方便。加法和减法只是工作,模2 n 。
有符号整数比较棘手。在普遍采用二进制补码之前,还使用了至少两个其他表示:带符号量(SM)和 1'补码(1C)。
Encoding SM 1C
000 0 0
001 1 1
010 2 2
011 3 3
100 -0 -3
101 -1 -2
110 -2 -1
111 -3 -0
两种编码SM和1C使用n位来存储从 - (2 n-1 -1)到2 n-1 -1的有符号整数。两种编码都以与以前相同的方式存储正整数。好:两者在正反方向上走的是相同的距离。好:两者都容易进行负面测试(除零之外,通常需要特殊处理)。好:两者都使数字变得容易(SM:翻转符号位; 1C:翻转所有位)。坏:两者都包括一个不必要的“负零”,这使得测试平等变得复杂,以及算术。
这使我们得到了两个补语(2C)。
Encoding 2C
000 0
001 1
010 2
011 3
100 ???
101 -3
110 -2
111 -1
这里我们像以前一样从正整数开始,通过从零开始“向后计数”直到我们在中间相遇来得到负整数。好:这使得算术非常自然,就像在未签名的情况下一样容易。坏:否定比SM或1C更难。
但是我们怎么处理???的行呢?我们已经拥有1C和SM中的所有数字。我们可以选择4,或-4,或者可能是“未定义”。 2C中的惯例是我们选择负值。这给出了-4至+3,或更通常-2 n-1 至2 n-1 -1的范围。这个不对称的范围很尴尬(我们有-4但不是+4,结果是-4是它自己的负数![1]),但保留了所有负数都设置了最高位的属性,以及确保每个编码都有一个与之关联的值。
所以(最后!)你的问题。为什么-4可以代表 在2的补码作为3位整数? 因为那是最不好的选择。
进一步阅读:维基百科上的signed number representations。
[1]这是一个测试程序,用于演示这是多么混乱。这里int
是32位宽。
[~]% cat 2c.c
#include <stdio.h>
#include <limits.h>
int main(void) {
int i = INT_MAX;
int j = INT_MIN;
printf (" i = % d (hex %x)\n", i, i);
printf (" j = % d (hex %x)\n", j, j);
printf (" -i = % d (hex %x)\n", -i, -i);
printf (" -j = % d (hex %x)\n", -j, -j);
printf ("i*-1 = % d (hex %x)\n", i*-1, i*-1);
printf ("j*-1 = % d (hex %x)\n", j*-1, j*-1);
printf ("i/-1 = % d (hex %x)\n", i/-1, i/-1);
printf ("j/-1 = % d (hex %x)\n", j/-1, j/-1);
return 0;
}
请注意,根据C标准,否定最负整数(INT_MIN
)是未定义的行为,因此使用三种否定方法中的一种杀死程序是完全合理的:
[~]% clang -Wall 2c.c -o 2c && ./2c
i = 2147483647 (hex 7fffffff)
j = -2147483648 (hex 80000000)
-i = -2147483647 (hex 80000001)
-j = -2147483648 (hex 80000000)
i*-1 = -2147483647 (hex 80000001)
j*-1 = -2147483648 (hex 80000000)
i/-1 = -2147483647 (hex 80000001)
zsh: floating point exception ./2c
这是security vulnerabilities的来源(遗憾的是,大多数真实的人都没有获得有趣的演练)。