C语言中,整数和实数在内存中的存放形式是怎么样的?它们相同吗?
2条回答 默认 最新
关注 一、整数的存储 - 补码
1、补码
[^_^]:
* 为了解决正负零的问题,引入了补码。1)定义
【定义】 正数 的 补码 就是它的 原码;负数 的 补码 为 它的反码加一;
2)举例
- 1)对于十进制数 37,它的 真值 和 补码 关系如下:
真值:+ 00000000 00000000 00000000 00100101 补码: 00000000 00000000 00000000 00100101
- 2)对于十进制数 -37,它的 真值 和 反码 的关系如下:
真值:- 00000000 00000000 00000000 00100101 补码: 11111111 11111111 11111111 11011011
- 我们发现,对于负数的情况,反码 减去 真值(注意,这里真值为负数)后,负负得正,转换成二进制位相加正好等于 $1(\underbrace{0...0}_{32})_2$, 即 $2^{32}$,表示成公式如下:$$ [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$ 的情况进行阐述;4、编码总结
对于三种编码方式,总结如下:
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、原码运算
- 对于原码的加法,两个正数相加的情况如下:
```c
+1 的原码:00000000 00000000 00000000 00000001
+1 的原码:00000000 00000000 00000000 00000001
+2 的原码:00000000 00000000 00000000 00000010
* 好像没有什么问题?于是人们开始探索减法,但是起初设计的人的初衷是希望不用减法,只用加法运算就能够将加法和减法都包含进来,于是,我们尝试用原码的负数表示来做运算; * 将 ```1 - 2```表示成```1 + (-2)```,然后用原码相加得到: ```c +1 的原码:00000000 00000000 00000000 00000001 -2 的原码:10000000 00000000 00000000 00000010 ---------------------------------------------- -3 的原码:10000000 00000000 00000000 00000011
- 我们发现
1 + (-2) = -3
,计算结果明显是错的,所以为了解决减法问题,引入了反码;3、反码运算
- 对于正数的加法,两个正数反码相加的情况和原码相加一致,不会有问题。
- 对于正数的减法,转换成一正一负两数相加。
- 将
1 - 2
表示成1 + (-2)
,情况如下:
```c
+1 的反码:00000000 00000000 00000000 00000001
-2 的反码:11111111 11111111 11111111 11111101
-1 的反码:11111111 11111111 11111111 11111110
* 没有什么问题?但是某种情况下,反码会有歧义,当两个相同的数相减时,即```1 - 1```表示成```1 + (-1)```,情况 如下: ```c +1 的反码:00000000 00000000 00000000 00000001 -1 的反码:11111111 11111111 11111111 11111110 --------------------------------------------- -0 的反码:11111111 11111111 11111111 11111111
- 这里出现了一个奇怪的概念,就是 "负零",反码运算过程中会出现有两个编码表示零这个数值。
- 为了解决正负零的问题引入了补码的概念。
4、补码运算
1)两个正数的补码相加。
- 其和等于 它们的原码相加,已经验证过,不会有问题;
2)一正一负两个数相加,且 答案非零 。
```c
+1 的补码:00000000 00000000 00000000 00000001
-2 的补码:11111111 11111111 11111111 11111110
-1 的补码:11111111 11111111 11111111 11111111
* 结果正确; > 3)一正一负两个数相加,且 **答案为零**。 ```c +1 的补码 00000000 00000000 00000000 00000001 -1 的补码: 11111111 11111111 11111111 11111111 ---------------------------------------------- 0 的补码:1 00000000 00000000 00000000 00000000
- 两个互为相反数的数相加后,得到的数的补码为 $2^n$(可以认为是是溢出了),所以那个 1 根本不会被存进计算机中,也就是表现出来的结果就是 零!
- 而且,补码的这个运算,和我们之前提到的定义吻合。
- 综上所述,补码解决了整数加法带来的所有问题。
二、浮点数的存储
1、科学计数法
- C语言中,浮点数在内存中是以科学计数法进行存储的,科学计数法是一种指数表示,数学中常见的科学计数法是基于十进制的,例如 $5.2 × 10^{11}$;计算机中的科学计数法可以基于其它进制,例如 $1.11 × 2^7$ 就是基于二进制的,它等价于 $(11100000)_2$。
- 科学计数法的一般形式如下:
- $$value = (-1)^{sign} \times fraction \times base^{exponent}$$
$value$:代表要表示的浮点数;
$sign$:代表 $value$ 的正负号,它的取值只能是 0 或 1:取值为 0 是正数,取值为 1 是负数;
$base$:代表基数,或者说进制,它的取值大于等于 2;
$fraction$:代表尾数,或者说精度,是 $base$ 进制的小数,并且 $1 \le fraction \lt base$,这意味着,小数点前面只能有一位数字;
$exponent$:代表指数,是一个整数,可正可负,并且为了直观一般采用 十进制 表示。
1)十进制的科学计数法
- 以 $14.375$ 这个小数为例,根据初中学过的知识,想要把它转换成科学计数法,只要移动小数点的位置。如果小数点左移一位,则指数 $exponent$ 加一;如果小数点右移一位,则指数 $exponent$ 减一;
- 所以它在十进制下的科学计数法,根据上述公式,计算结果为:
- $$(14.375)_{10} = 1.4375 \times 10^1$$
- 其中 $value = 14.375$、$sign = 0$、$base = 10$、$fraction = 1.4375$、$exponent = 1$;
- 这是我们数学中最常见的科学计数法。
2)二进制的科学计数法
- 同样以 $14.375$ 这个小数为例,我们将它转换成二进制,按照两部分进行转换:整数部分和小数部分。
- 整数部分:整数部分等于 14,不断除 2 取余数,转换成 2 的幂的求和如下:
- $$(14)_{10} = 1 \times 2^3 + 1 \times 2^2 + 1 \times 2^1 + 0 \times 2^0$$
- 所以 14 的二进制表示为 $(1110)_2$。
- 小数部分:小数部分等于 0.375,不断乘 2 取整数部分的值,转换成 2 的幂的求和如下:
- $$(0.375)_{10} = 0 \times 2^{-1} + 1 \times 2^{-2} +1 \times 2^{-3}$$
- 所以 0.375 的二进制表示为 $(0.011)_2$
- 将 整数部分 和 小数部分 相加,得到的就是它的二进制表示:
- $$(1110.011)_2$$
- 同样,我们参考十进制科学计数法的表示方式,通过移动小数点的位置,将它表示成二进制的科学计数法,对于这个数,我们需要将它的小数点左移三位。得到:
- $$(1110.011)_2 = (1.110011)_2 \times 2^3$$
- 其中 $value = 14.375$、$sign = 0$、$base = 2$、$fraction = (1.110011)_2$、$exponent = 3$;
- 我们发现,为了表示成科学计数法,小数点的位置发生了浮动,这就是浮点数的由来。
2、浮点数存储概述
- 计算机中的浮点数表示都是采用二进制的。上面的科学计数法公式中,除了 $base$ 确定是 2 以外,符号位 $sign$、尾数位 $fraction$、指数位 $exponent$ 都是未知数,都需要在内存中体现出来。还是以 $14.375$ 为例,我们来看下它的几个关键数值的存储。
1)符号的存储
- 符号位的存储类似存储整型一样,单独分配出一个比特位来,用 0 表示正数,1 表示负数。对于 $14.375$,符号位的值是 0。
2)尾数的存储
- 根据科学计数法的定义,尾数部分的取值范围为 $$1 \le fraction \lt 2$$
- 这代表尾数的整数部分一定为 1,是一个恒定的值,这样就无需在内存中提现出来,可以将其直接截掉,只要把小数点后面的二进制数字放入内存中即可,这个设计可真是省(扣)啊。
- 对于 $(1.110011)_2$,就是把
110011
放入内存。我们将内存中存储的尾数命名为 $f$,真正的尾数命名为 $fraction$,则么它们之间的关系为: $$fraction = 1.f$$ - 这时候,我们就可以发现,如果 $base$ 采用其它进制,那么尾数的整数部分就不是固定的,它有多种取值的可能,以十进制为例,尾数的整数部分可能是 $1 \to 9$ 之间的任何一个值,如此一来,尾数的整数部分就无法省略,必须在内存中表示出来。但是将 $base$ 设置为 2,就可以节省掉一个比特位的内存,这也是采用二进制的优势。
3)指数的存储
- 指数是一个整数,并且有正负之分,不但需要存储它的值,还得能区分出正负号来。所以存储时需要考虑到这些。
- 那么它是参照补码的形式来存储的吗?
- 答案是否。
- 指数的存储方式遵循如下步骤:
- 1)由于
float
和double
分配给指数位的比特位不同,所以需要分情况讨论; - 2)假设分配给指数的位数为 $n$ 个比特位,那么它能够表示的指数的个数就是 $2^n$;
- 3)考虑到指数有正负之分,并且我们希望正负指数的个数尽量平均,所以取一半,$2^{n-1}$ 表示负数,$2^{n-1}$ 表示正数。
- 4)但是,我们发现还有一个 0,需要表示,所以负数的表示范围将就一点,就少了一个数;
- 5)于是,如果原本的指数位 $x$,实际存储到内存的值就是:$$x + 2^{n-1} - 1$$
- 接下来,我们拿具体
float
和double
的实际位数来举例说明实际内存中的存储方式。
3、浮点数存储内存结构
- 浮点数的内存分布主要分成了三部分:符号位、指数位、尾数位。浮点数的类型确定后,每一部分的位数就是固定的。浮点数的类型,是指它是
float
还是double
。 - 对于
float
类型,内存分布如下:
- 对于
double
类型,内存分布如下:
- 1)符号位:只有两种取值:0 或 1,直接放入内存中;
- 2)指数位:将指数本身的值加上 $2^{n-1}-1$ 转换成 二进制,放入内存中;
- 3)尾数位:将小数部分放入内存中;
浮点数类型 指数位数 指数范围 尾数位数 尾数范围 float
$8$ $[-2^7+1,2^7]$ $23$ $[(0)2, (\underbrace{1...1}{23})_2]$ double
$11$ $[-2^{10}+1,2^{10}]$ $52$ $[(0)2, (\underbrace{1...1}{52})_2]$ 4、内存结构验证举例
- 以上文求得的 $14.375$ 为例,我们将它转换成二进制,表示成科学计数法,如下:
- $$(1110.011)_2 = (1.110011)_2 \times 2^3$$
- 其中 值 $value = 14.375$、符号位 $sign = 0$、基数 $base = 2$、尾数 $fraction = (1.110011)_2$、指数 $exponent = 3$;
1)float 的内存验证
- 为了方便阅读,我采用了颜色来表示数字,橙色代表符号位,蓝色代表指数位,红色代表尾数,绿色代表尾数补齐位;并且 八位一分隔,增强可视化。
- 符号位的内存:0
- 指数的内存(加上127后等于130,再转二进制):10000010
- 尾数的内存(不足23位补零):1100110 00000000 00000000
- 按顺序组织到一起后得到:01000001 01100110 00000000 00000000
#include <stdio.h> int main() { int value = 0b01000001011001100000000000000000; // (1) printf("%f\n", *(float *)(&value) ); // (2) return 0; }
运算结果如下:
$(1)$ 第一步,就是把上面那串二进制的 01串 直接拷贝下来,然后在前面加上0b
前缀,代表了 $value$ 这个四字节的内存结构就是这样的;
$(2)$ 第二步,分三个小步骤:
$(2.a)$&value
代表取value
这个值的地址;
$(2.b)$(float *)&value
代表将这个地址转换成float
类型;
$(2.c)$*(float *)&value
代表将这个地址里的值按照float
类型解析得到一个float
数;- 运行结果为:
```c
14.375000
* (有关取地址和指针相关的内容,由于前面章节还没有涉及,如果读者看不懂,也没有关系,后面在讲解指针时会详细讲解这块内容,敬请期待)。 ### 2)double 的内存验证 * 为了方便阅读,我采用了颜色来表示数字,<font color=orange>橙色</font>代表符号位,<font color=blue>蓝色</font>代表指数位,<font color=red>红色</font>代表尾数,<font color=green>绿色</font>代表尾数补齐位;并且 八位一分隔,增强可视化。 * 符号位的内存:<font color=orange>0</font> * 指数的内存(加上1023后等于1026,再转二进制):<font color=blue>100 00000010</font> * 尾数的内存(不足52位补零):<font color=red>1100 11</font><font color=green>000000 00000000 00000000 00000000 00000000 00000000</font> * 按顺序组织到一起后得到:<font color=orange>0</font><font color=blue>1000000 0010</font><font color=red>1100 11</font><font color=green>000000 00000000 00000000 00000000 00000000 00000000</font> ```c #include <stdio.h> int main() { long long value = 0b0100000000101100110000000000000000000000000000000000000000000000; // (1) printf("%lf\n", *(double *)(&value) ); // (2) return 0; }
运算结果如下:
$(1)$ 第一步,就是把上面那串二进制的 01串 直接拷贝下来,然后在前面加上0b
前缀,代表了 $value$ 这个八字节的内存结构就是这样的;
$(2)$ 第二步,分三个小步骤:
$(2.a)$&value
代表取value
这个值的地址;
$(2.b)$(double *)&value
代表将这个地址转换成double
类型;
$(2.c)$*(double *)&value
代表将这个地址里的值按照double
类型解析得到一个double
数;- 没错,运行结果也是:
```c
14.375000
```
- 这块内容,如果你看的有点懵,没有关系,等我们学了指针的内容以后,再来回顾这块内容,你就会如茅塞一样顿开了!
- 你学废了吗?🤣
☀️光天化日学C语言☀️(23)- 整数的存储 | 补码到底有什么用?_英雄哪里出来-CSDN博客 补码到底有什么用? https://blog.csdn.net/WhereIsHeroFrom/article/details/118666729☀️光天化日学C语言☀️(24)- 浮点数的存储 | 天才般的设计,反正我这么认为_英雄哪里出来-CSDN博客 浮点数的存储,天才般的设计 https://blog.csdn.net/WhereIsHeroFrom/article/details/118690590本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 2无用- 1)对于十进制数 37,它的 真值 和 补码 关系如下:
悬赏问题
- ¥15 如何在scanpy上做差异基因和通路富集?
- ¥20 关于#硬件工程#的问题,请各位专家解答!
- ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
- ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
- ¥30 截图中的mathematics程序转换成matlab
- ¥15 动力学代码报错,维度不匹配
- ¥15 Power query添加列问题
- ¥50 Kubernetes&Fission&Eleasticsearch
- ¥15 報錯:Person is not mapped,如何解決?
- ¥15 c++头文件不能识别CDialog