☀️光天化日学C语言☀️(23)- 整数的存储 | 补码到底有什么用?
一、前言
本文作者是从 2007 年开始学 C语言 的,不久又接触了C++,基本就是 C/C++ 技术栈写了 14 年的样子,不算精通,但也算差强人意。著有《夜深人静写算法》系列,且承诺会持续更新,直到所有算法都学完。主要专攻 高中 OI 、大学 ACM、 职场 LeetCode 的全领域算法。由于文章中采用 C/C++ 的语法,于是就有不少读者朋友反馈语言层面就被劝退了,更何况是算法。
于是,2021 年 06 月 12 日,《光天化日学C语言》 应运而生。这个系列文章主要服务于高中生、大学生以及职场上想入坑C语言的志同道合之人,希望能给祖国引入更多编程方面的人才,并且让自己的青春不留遗憾!
这一章的主要内容是整数的存储。
二、人物简介
- 第一位登场的就是今后会一直教我们C语言的老师 —— 光天。
- 第二位登场的则是今后会和大家一起学习C语言的没什么资质的小白程序猿 —— 化日。
三、整数简介
1、符号位 和 数值位
- 我们知道 整数 分为 有符号整型 和 无符号整型。
- 有符号整型,程序需要区分 符号位 和 数值位。
- 对我们人类来说,很容易分辨;而对计算机而言,就要设计专门的电路,这就增加了硬件的复杂性,从而增加了计算的时间。
所以,如果能够将 符号位 和 数值位 联合起来,让它们共同参与运算,不再加以区分,这样硬件电路就会变得更加简单。
2、整型的加减运算
- 其次,加法 和 减法 的引入,也将问题变得复杂。而由于减去一个数相当于加上这个数的相反数,例如:
1 - 2
等价于1 + (-2)
,1 - (-2)
等价于1 + 2
。
所以,它们可以合并为一种运算,即只保留加法运算。
- 相反数是指 数值位 相同,符号位 不同的两个数,例如,1 和 -1 就是一对相反数。
- 所以,我们需要做的就是设计一种简单的、不用区分符号位和数值位的加法电路,就能同时实现加法和减法运算。首先让我们看几个计算机中的概念。
四、机器数和真值
1、机器数
- 我们知道计算机是内部由 0 和 1 组成的编码,无论是整数还是浮点数,都会涉及到负数,对于机器来说是不知道正负的,而 “正” 和 “负” 正好是两种对立的状态,所以规定用 “0” 表示 “正”,“1” 表示 “负”,这样符号就被数字化了,并且将它放在有效数字的前面,就成了有符号数;
- 把符号 “数字化” 的数称为 机器数;
2、真值
- 而带有 “+” 或者 “-” 的数称为 真值;
- 然而,当符号位和数值部分放在一起后,如何让它一起参与运算呢?那就要涉及到接下来要讲的计算机的各种编码了。
五、计算机编码
1、原码
1)定义
- 这里的原码并不是源码(源代码)的意思,而是机器数中最简单的一种表示形式;为了快速理解,这里只介绍 32位整数;
【定义】 符号位 为 0 代表 正数,符号位 为 1 代表 负数,数值位 为 真值的绝对值。
2)举例
- 1)对于十进制数 37,它的 真值 和 原码 关系如下:
真值:+ 00000000 00000000 00000000 00100101
原码: 00000000 00000000 00000000 00100101
- 2)对于十进制数 -37,它的 真值 和 原码 的关系如下:
真值:- 00000000 00000000 00000000 00100101
原码: 10000000 00000000 00000000 00100101
- 我们发现,对于负数的情况,原码 加上 真值(注意,这里真值为负数)后,二进制数正好等于 , 即 ,表示成公式如下:$$ [x]_原 + x = 2^{31}$$
3)公式
- 因此,我们可以通过移项,得出原码的十进制计算公式如下:
[x]_原 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n-1} - x & (-2^{n-1} < x \le 0) \end{cases} $$ 这里 $x$ 代表真值,而 $n$ 的取值是 $8、16、32、64$,我们通常说的整型```int```都是 32位 的,本文就以 $n = 32$ 的情况进行阐述;
2、反码
[^_^]:
* 为了解决负数加法的问题,引入了反码。
1)定义
【定义】 正数 的 反码 就是它的 原码;负数 的 反码 为 原码 的每一位的 0变1、1变0(即位运算中的按位取反);
2)举例
- 1)对于十进制数 37,它的 真值 和 反码 关系如下:
真值:+ 00000000 00000000 00000000 00100101
反码: 00000000 00000000 00000000 00100101
- 2)对于十进制数 -37,它的 真值 和 反码 的关系如下:
真值:- 00000000 00000000 00000000 00100101
反码: 11111111 11111111 11111111 11011010
- 我们发现,对于负数的情况,反码 减去 真值(注意,这里真值为负数)后,负负得正,转换成二进制位相加正好等于 , 即 ,表示成公式如下:$$ [x]_反 - x = 2^{32}-1$$
3)公式
- 因此,通过移项,我们可以得出反码的十进制计算公式如下:
- 反码有个很难受的点,就是 和 都代表零,就是我们常说的 正零 和 负零。正如公式中看到的,当真值为 0 的时候,有两种情况,这就产生了二义性,而且浪费了一个整数表示形式。
3、补码
[^_^]:
* 为了解决正负零的问题,引入了补码。
1)定义
【定义】 正数 的 补码 就是它的 原码;负数 的 补码 为 它的反码加一;
2)举例
- 1)对于十进制数 37,它的 真值 和 补码 关系如下:
真值:+ 00000000 00000000 00000000 00100101
补码: 00000000 00000000 00000000 00100101
- 2)对于十进制数 -37,它的 真值 和 反码 的关系如下:
真值:- 00000000 00000000 00000000 00100101
补码: 11111111 11111111 11111111 11011011
- 我们发现,对于负数的情况,反码 减去 真值(注意,这里真值为负数)后,负负得正,转换成二进制位相加正好等于 , 即 ,表示成公式如下:$$ [x]_补 - x = 2^{32}$$
3)公式
- 因此,通过移项,我们可以得出补码的十进制计算公式如下:
[x]_补 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n} + x & (-2^{n-1} \le x < 0) \end{cases} $$ 这里 $x$ 代表真值,而 $n$ 的取值是 $8、16、32、64$,我们通常说的整型```int```都是 32位 的,本文就以 $n = 32$ 的情况进行阐述;
对于三种编码方式,总结如下:
1)这三种机器数的最高位均为符号位;
2)当真值为正数时,原码、反码、补码的表示形式相同,符号位用 “0” 表示,数值部分真值相同;
3)当真值为负数时,原码、反码、补码的表示形式不同,但是符号位都用 “1” 表示,数值部分:反码是原码的 “按位取反”,补码是反码加一;
正数
真值:+ 00000000 00000000 00000000 00100101
原码: 00000000 00000000 00000000 00100101
反码: 00000000 00000000 00000000 00100101
补码: 00000000 00000000 00000000 00100101
负数
真值:- 00000000 00000000 00000000 00100101
原码: 10000000 00000000 00000000 00100101
反码: 11111111 11111111 11111111 11011010
补码: 11111111 11111111 11111111 11011011
六、为什么要引入补码
- 最后,我们来讲一下引入补码的真实意图是什么。
1、主要目的
- 计算机的四则运算希望设计的尽量简单。但是引入 符号位 的概念,对于计算机来说还要考虑正负数相加,等于引入了减法,所以希望是计算机底层 只设计一个加法器,就能把加法和减法都做了。
2、原码运算
- 对于原码的加法,两个正数相加的情况如下:
+1 的原码:00000000 00000000 00000000 00000001
+1 的原码:00000000 00000000 00000000 00000001
----------------------------------------------
+2 的原码:00000000 00000000 00000000 00000010
- 好像没有什么问题?于是人们开始探索减法,但是起初设计的人的初衷是希望不用减法,只用加法运算就能够将加法和减法都包含进来,于是,我们尝试用原码的负数表示来做运算;
- 将
1 - 2
表示成1 + (-2)
,然后用原码相加得到:
+1 的原码:00000000 00000000 00000000 00000001
-2 的原码:10000000 00000000 00000000 00000010
----------------------------------------------
-3 的原码:10000000 00000000 00000000 00000011
- 我们发现
1 + (-2) = -3
,计算结果明显是错的,所以为了解决减法问题,引入了反码;
3、反码运算
- 对于正数的加法,两个正数反码相加的情况和原码相加一致,不会有问题。
- 对于正数的减法,转换成一正一负两数相加。
- 将
1 - 2
表示成1 + (-2)
,情况如下:
+1 的反码:00000000 00000000 00000000 00000001
-2 的反码:11111111 11111111 11111111 11111101
----------------------------------------------
-1 的反码:11111111 11111111 11111111 11111110
- 没有什么问题?但是某种情况下,反码会有歧义,当两个相同的数相减时,即
1 - 1
表示成1 + (-1)
,情况 如下:
+1 的反码:00000000 00000000 00000000 00000001
-1 的反码:11111111 11111111 11111111 11111110
---------------------------------------------
-0 的反码:11111111 11111111 11111111 11111111
- 这里出现了一个奇怪的概念,就是 “负零”,反码运算过程中会出现有两个编码表示零这个数值。
- 为了解决正负零的问题引入了补码的概念。
4、补码运算
1)两个正数的补码相加。
- 其和等于 它们的原码相加,已经验证过,不会有问题;
2)一正一负两个数相加,且 答案非零 。
+1 的补码:00000000 00000000 00000000 00000001
-2 的补码:11111111 11111111 11111111 11111110
----------------------------------------------
-1 的补码:11111111 11111111 11111111 11111111
- 结果正确;
3)一正一负两个数相加,且 答案为零。
+1 的补码 00000000 00000000 00000000 00000001
-1 的补码: 11111111 11111111 11111111 11111111
----------------------------------------------
0 的补码:1 00000000 00000000 00000000 00000000
- 两个互为相反数的数相加后,得到的数的补码为 (可以认为是是溢出了),所以那个 1 根本不会被存进计算机中,也就是表现出来的结果就是 零!
- 而且,补码的这个运算,和我们之前提到的定义吻合。
- 综上所述,补码解决了整数加法带来的所有问题。
通过这一章,我们学会了:
1)原码的表示形式;
2)反码的表示形式;
3)补码的表示形式;
- 希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!
课后习题
📢博客主页:https://blog.csdn.net/WhereIsHeroFrom
📢欢迎各位 👍点赞 ⭐收藏 📝评论,如有错误请留言指正,非常感谢!
📢本文由 英雄哪里出来 原创,转载请注明出处,首发于 🙉 CSDN 🙉
作者的专栏:
👉C语言基础专栏《光天化日学C语言》
👉C语言基础配套试题详解《C语言入门100例》
👉算法进阶专栏《夜深人静写算法》
- 点赞
- 收藏
- 关注作者
评论(0)