将十进制整数的内部32位二进制码输出。
输入格式:
输入数据中含有若干整数n,int型取值范围,整数之间空格符间隔或换行符间隔,以^Z或文件结束符结束输入。
输出格式:
对于每个n,输出其对应的32位二进制的机内码与原整数,二进制码每8位空1格,二进制码与原整数之间用“ <->”间隔。每个结果对应一行输出。末尾输出一行。
输入样例:
在这里给出一组输入。例如:
5 12 -12
输出样例:
00000000 00000000 00000000 00000101 <->5
00000000 00000000 00000000 00001100 <->12
11111111 11111111 11111111 11110100 <->-12
那么我们要解决这个问题,首先要了解两个概念:位运算、原码补码反码
位运算基础与应用
在计算机中,所有数据都以二进制形式存储和处理。位运算是一种直接对二进制位进行操作的技术,它允许程序员通过更底层的方式进行高效的数据处理。位运算在底层编程、硬件控制、加密算法、图像处理等领域有广泛的应用,能够显著提升运算效率。常见的位运算符有:
- 与运算 (&):同位均为1时结果才为1,否则为0。
- 或运算 (|):只要有一个位为1,结果就为1。
- 异或运算 (^):当两个位不同时结果为1,相同时结果为0。
- 取反运算 (~):将每一位取反,0变为1,1变为0。
- 左移 (<<):将所有位向左移动指定的位数,右边补0。
- 右移 (>>):将所有位向右移动指定的位数,左边根据符号位补0(对无符号数)或1(对有符号数)。
左移相当于乘以 2
的幂,而右移相当于除以 2
的幂。因此,位运算可以用于高效实现乘法和除法操作。
int a = 5;
int b = a << 1; // 相当于 a * 2,结果为10
int c = a >> 1; // 相当于 a / 2,结果为2
原码反码补码
在计算机中,整数的存储和表示有三种常见的方式:原码、反码 和 补码。它们都是用于表示二进制数的不同方法,特别是用来表示正数和负数。下面分别介绍它们的定义和区别。
原码
原码是最直观的二进制表示方法,直接用一个符号位和数值位来表示正负数:
- 符号位:使用最高位来表示数字的符号。
0
代表正数,1
代表负数。 - 数值位:其余位表示数值本身的二进制形式。
例子:
- 对于
+5
,在 8 位二进制下的原码表示为:00000101
。 - 对于
-5
,在 8 位二进制下的原码表示为:10000101
。
使用原码来进行运算时,会遇到正负数计算复杂的问题。例如,加法和减法在处理符号位时不统一,可能导致进位错误。例如,我们-5+5理应得到的结果是00000000,而按照上面相加,得到的是10001010,这显然不对。
反码
反码是为了简化负数的计算而提出的一种编码方式,正数的反码与原码相同,而负数的反码则是将原码的符号位保持不变,其余位按位取反(即 0
变 1
,1
变 0
)。
例子:
- 对于
+5
,其原码为00000101
,反码与原码相同,还是00000101
。 - 对于
-5
,其原码为10000101
,反码则是11111010
。
二者相加,得到的结果是11111111,转换为原码是10000000,-0,这显然不对使用反码仍然不能完全解决负数运算时的进位问题,尤其是在 0
的表示上出现了两种情况:正 0
表示为 00000000
,负 0
表示为 11111111
,这在计算中可能造成混乱。
补码
补码是目前计算机中广泛使用的数值表示方式,特别是用于表示有符号整数。补码的定义如下:
- 正数的补码与原码相同。
- 负数的补码是其反码加 1。
例子:
- 对于
+5
,其原码为00000101
,补码与原码相同,还是00000101
。 - 对于
-5
,其原码为10000101
,反码是11111010
,补码则是11111011
(反码加 1)。
补码的一个重要特性是,它能将减法转换为加法操作,从而简化计算。例如,在补码下,计算 5 + (-5)
就相当于进行补码加法,结果为 00000000
,不需要单独处理符号位。此外,补码只有一种 0
,即 00000000
,避免了正 0
和负 0
两种表示。
在二进制系统中,每一位的权重是 2 的幂次方,权重从右到左逐渐增加。例如,对于一个 8 位的二进制数,各个位的权重依次为:
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
对于正数,所有位的权重都是正数。例如,对于 5
(即 00000101
)来说,它表示为:
5 = 0×2^7 + 0×2^6 + 0×2^5 + 0×2^4 + 0×2^3 + 1×2^2 + 0×2^1 + 1×2^0
= 4 + 1
= 5
负数的补码是将其原码的反码加 1 得到。对于 n 位的补码数值,最高位的权重为 -2^(n-1),而其余位的权重保持不变。
补码之所以将最高位的权重设为负数,是为了在二进制加法中实现负数的正确运算。这样,计算机在执行加法运算时,能够无缝处理负数与正数的加减。
例子:以 -5
为例
在 8 位补码表示中,-5
的补码为 11111011
。我们来分析每一位的权重:
-5 = 1×(-2^7) + 1×2^6 + 1×2^5 + 1×2^4 + 1×2^3 + 0×2^2 + 1×2^1 + 1×2^0
= -128 + 64 + 32 + 16 + 8 + 0 + 2 + 1
= -128 + 123
= -5
为什么最高位必须为负?
这是为了使负数和正数在二进制加法中能正确计算。例如,当你想计算 5 + (-5)
时,补码表示中只需将 00000101
(+5
)和 11111011
(-5
)相加:
00000101 (+5)
+ 11111011 (-5)
-----------
00000000 (结果为 0)
这种负权重设计使得计算机能够通过同样的二进制加法规则处理正数和负数,而不需要特殊的处理步骤。
解题
回到我们的题目,我们分析解答思路。
首先输入数据中含有若干整数n,int型取值范围,整数之间空格符间隔或换行符间隔,以^Z或文件结束符结束输入。
看到这段话我们便想到使用
while(scanf("%d", &n) != EOF)
进行循环读取输入,直到文件结束符(EOF
)为止。
对于正数,我们可以直接使用二进制转换的基本方法,将十进制数 n
除以 2 逐位取余。注意:这里的除以二其实就是位运算中的右移(>>),当然我是直接/=2,然后将结果存储在 a[]
数组中(长度为32,用来存储32位的二进制码)。数组下标越小表示权重越大的位,因此我们从数组的最后(31位)开始填入二进制位。
if(n >= 0)
{
for(int i = 31; i >= 0; i--)
{
a[i] = n % 2;
n /= 2;
}
}
对于负数,由于负数在计算机中是用补码表示的,因此我们要对负数进行如下处理:
- 先将
n
取绝对值并计算其原码(除去符号位的正数部分)。 - 将符号位(最高位)设置为1,用来表示负数。
- 将剩余的位(31位)取反,得到反码。
- 将反码加1,得到补码(负数的二进制表示)。
转换成计算机能看懂的代码语言便是
else
{
n = -n;
a[0] = 1;
for(int i = 31; i >= 1; i--)
{
a[i] = n % 2;
n /= 2;
}//这一步是获取原码
for(int i = 1; i < 32; i++)
{
if(a[i] == 1)
{
a[i] = 0;
}
else
{
a[i] = 1;
}
}//获取反码。它的作用是对数组 a[] 中的第 1 到第 31 位进行逐位取反操作,即 1 变成 0,0 变成 1。
for (int i = 31; i > 0; i--)
{
if (a[i] == 1)
a[i] = 0;
else
{
a[i] = 1;
break;
}
}//这段代码的作用是将反码加 1,从而得到补码。
}
对于第三个for循环可能会有同学发问,为什么这是加 1 操作?
在二进制中,加 1 操作实际上是寻找二进制数中的最低位 0
,然后将它变成 1
,并且如果有进位,则将前面的 1
变为 0
,依次类推。这段代码模拟了这个过程:
- 遍历数组从右往左(从最低位到最高位):
- 从
a[31]
开始,即从二进制的最低位逐位检查。
- 从
- 遇到
1
时,执行进位操作:- 如果当前位
a[i] == 1
,说明这位在加 1 后会变成0
(因为二进制中的1 + 1 = 10
,需要进位),所以将当前位设为0
,然后继续检查更高的位(继续进位)。
- 如果当前位
- 遇到
0
时,停止进位:- 如果当前位
a[i] == 0
,说明这位可以直接变成1
,同时由于不再需要进位,直接结束操作(break
语句终止循环)。
- 如果当前位
有了处理十进制数到二进制补码的方法,剩下的格式化控制想必大家也是手到擒来了,无非就是设置一个变量当%8==0时,用来控制空格字符,这里就不再过多赘述
最后附上代码截图,有需要的同学自取
运行结果: