【C语言】计算机是如何存储整数和小数的?


前言

一条数轴上的数可以分为整数和小数,整数包括正整数、负整数和0,小数也包括正小数和负小数。

人类借助"+“、”-“、”."、"0~9"等符号以及约定俗成的摆放顺序来表示和存储不同的数字。

但计算机只认识0和1,要想在计算机中表示和存储不同的数字,就需要借助一套完整的存储规则。

对于不同类型的数字,存储规则也是不一样的。


一、正整数和0

正整数和0在计算机中的表示方法最为简单,就是转化的二进制数本身。

如3在计算机中就表示为0000_0011。

二、负整数

先说答案,负整数在计算机中采用补码表示。

转化过程:
先取得负整数绝对值的二进制形式(注意符号位为1),得到原码,按位取反(符号位不用管)得到反码,再加1即可得到补码。

举一个例子:
-7的原码为1000_0111,反码为1111_1000,补码为1111_1001。

问:为什么负数要以这样的形式存储在计算机中呢?

答:为了方便进行数值运算。如要实现-7+3 = -4,

在计算机中是这样进行的:

1111_1001 + 0000_0011 = 1111_1100

而-4的原码为1000_0100,反码为1111_1011,补码为1111_1100,这样就跟上面的计算结果相吻合了。

而如果不用补码,假设用1000_0111表示-7,其中左边第一位为符号位,那进行如下计算:

1000_0111 + 0000_0011 = 1000_1010,结果并不能表示-4。

这里涉及补码向十进制的转换,反过来就行:

-4的补码为1111_1100,先减一,为1111_1011,按位取反为1000_0100,即-4的原码。

三、小数

先说答案,现代计算机采用浮点数的方式保存小数。

但早期的计算机使用定点数的方式保存小数。

1、定点数

(1)什么是定点数

注意:定点数不是指一类数字,而是指一种小数在计算机中的存储方法。

定点数就是指小数点位置不动的小数存储方式。

定点数存储法其实就是将小数直接转化为二进制存储在计算机中。这跟正整数和0的存储方法是一样的,其实正整数和0也是按照定点数的方式存储的,只是此时小数部分全为0省略了而已。

如用8bit空间存储定点数,约定前4位为整数部分,后4位为小数部分。很显然这样小数点的位置就固定在了第3位和第4位之间。
在这里插入图片描述

这样做的好处就在于能一眼看出所要表示的数字。

比如这里就能一眼看出所要表达的小数为1011.0111

转化为十进制就是:
在这里插入图片描述

现在我们来分析定点数表示法的精度和范围。

(2)表示精度

首先精度在这里就是表示数学上的准确数值与计算机中实际存储的数值的误差。

为了说明这个误差有时候是不可避免的,我举一个例子:

数学上的π是一个无限不循环小数,要存储它理论上需要一块无限大的空间,但这是不可能的,计算机的存储空间是有限的,因此只能存储π的近似值,由此误差就产生了。

以上面的8bit空间为例,其中4位表示小数,因此这种情况下精度是1/16,即0.0625。

这里结合之前写的博文【科普】精度和分辨率的区别与联系

可以知道,由于没有系统误差,因此这里的精度等价于分辨率。

这样就好理解了,4位小数的分辨率就是1/16。

(3)表示范围

如上所述的8bit空间可以存储的最大数字为1111_1111=8+4+2+1+1/2+1/4+1/8+1/16=15.9375

最小可以表示的正数为0000_0001=1/16=0.0625。

(4)优缺点

优点:

——简单易懂:定点数表示法相对于其他复杂的表示方法(如浮点数)来说更加简单和直观,易于理解和实现。

——固定精度:定点数具有固定的位数来表示整数和小数部分,因此精度是固定的。这在某些应用中可能是有益的,可以保持一致的精度。

——运算速度快:相比于浮点数运算,定点数运算通常更加高效,因为定点数的运算只涉及整数运算,无需处理浮点数的特殊规则。

缺点:

——固定精度:虽然固定精度是定点数的一个优点,但在某些情况下也可能成为缺点。当处理非常大或非常小的数值时,固定的精度可能不足以保持所需的准确性,导致舍入误差。

——难以表示极小或极大的数值:定点数通常不适用于表示极小或极大的数值范围。对于超出表示范围的数值,定点数无法准确表示,因此需要使用更高精度的表示方法。

这里可以举一个例子:

普朗克长度约等于1.6x10^-35米,即0.00000000000000000000000000000000016m,用定点数表示为(也就是转化为二进制):

0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011010100101011010010110110111111110000111101000001111。

大约需要20多个字节才能存储得下。

地球的质量5.965×10²⁴kg,即59650000000000000000000000千克,用定点数表示为(也就是转化为二进制):

11000101010111011000001111101010111100011100110010101101011101010000000000000000000000

需要十几个字节才能存储得下。

注意:这里用了一个整数的例子,既在说明极大的数字用定点数方式存储的缺点,也在说明整数也可以用定点数表示,只是此时全部位都用来表示整数。

可以发现在表示极大或极小的数字时,定点法非常消耗存储空间。

为了解决上述问题,更复杂的浮点数诞生了。

2、浮点数

(1)什么是浮点数

跟定点数一样,浮点数也不是指一类数字,它指的是一种小数在计算机中的存储方法。

浮点数就是指小数点位置会变动的小数存储方式。

浮点数表示法跟科学计数法很相似:
在这里插入图片描述
Float:要表示的小数
s:符号位,1表示负数,0表示正数
M:尾数
E:指数

(2)将小数转为浮点数

举两个例子:

–将15.625转化为浮点数
首先将其转化为二进制1111.101(这其实就是定点数表示法)
用浮点形式表示为:
在这里插入图片描述

–将0.01875转化为浮点数
首先将其转化为二进为0.0011
用浮点形式表示为:
在这里插入图片描述

可以看到小数点位置发生了变动,因此称为浮点数,浮即浮动,点即小数点

(3)浮点数在计算机中的存储格式

要存储一个浮点数,需要存储三个东西:符号、指数和尾数。

这里C语言提供了两种浮点数封装格式:float和double
在这里插入图片描述

其中符号位为0就表示正数,符号位为1就表示负数。

通过上面的例子可以发现尾数取值范围为 [1,2),也就是说尾数的整数部分一定为1,这样就没必要浪费空间存储整数部分了。对于1.111101只要存储后面的111101就行。因此计算机真正存储的只是尾数的后半部分。

指数部分可能为正数也可能为负数,如果再添加一个符号位按照补码的方式存储未免过于繁琐,这里聪明的科学家设计了一个巧妙的方法。

(4)浮点数中的指数部分如何存入计算机

先说答案,以float为例,在存入指数前,先加上127,这样即使指数为负数,加上127后也会变为正数。这样就不用再用补码表示负数了。

因此,读取指数时也要先减去127。

举2个例子:

15.625浮点形式为:
在这里插入图片描述
符号位为0,尾数为1.111101,只存储后面的111101,指数部分为3,加上127后为130,其二进制为1000_0010。

因此15.625在计算机中的最终存储格式为0_10000010_11110100000000000000000。

0.01875浮点形式为:
在这里插入图片描述
符号位为0,尾数为1.1,只存储后面的1,指数部分为-3,加上127后为124,其二进制为0111_1100。

因此0.01875在计算机中的最终存储格式为0_01111100_10000000000000000000000。

:为什么要加上127?
:这个偏移值是按照下式计算的:
在这里插入图片描述

如float的指数占用8bit,那么其偏移值就是
在这里插入图片描述

而double的指数占用11bit,其偏移值为
在这里插入图片描述

到此,我们可以发现,计算机中存储的尾数并不是真正的尾数;存储的指数也并不是真正的指数。

(5)表示精度

这里以0.35举例对比一下两种表示法的精度

用32bit来存储0.35,规定1位为整数部分,其余为小数部分,用定点法表示为0.0101100110011001100110011001100,再转回来就是0.34999999962747097,依然存在误差 0.00000000037252903。

将0.35首先转化为浮点数形式:1.01100110011001100110011001100*2^(-2),如果是float格式(同样是32bit),则尾数部分只能截取01100110011001100110011,指数部分为-2+127=125,即0111_1101
因此0.35的float格式为0_0111_1101_01100110011001100110011

再将其转化为十进制为1.01100110011001100110011*2^(-2),即0.3499999940395355,误差为0.0000000059604645,比用定点法表示的误差略大。

因此可以得出结论

同样的存储空间,浮点数相比定点数的表示精度更低。

这也就是浮点数能表示更大范围数字所付出的代价。

也就是说使用相同存储空间,定点数精度较高,但范围小;浮点数范围大,但精度较低。凡是皆有代价,不是吗?

(6)表示范围

在float的内存分布中,指数部分占据8位,可以表示0~255,由于进行了偏移,因此可以表示负数,能表示的指数范围为-127 ~ 128。

真的吗?

float实际只能存入的指数范围为-126 ~ 127。因为指数部分全为1或全为0用于表示其他特殊情况了。

在这里插入图片描述

1)规格化的值
一般情况下,即指数在(0,255)范围内时,转化规则为:

真实的尾数=1.存储的尾数
真实的指数=指数部分-偏移值

因此规格化的值能表示的范围为:
在这里插入图片描述

其中1.11…11中一共有24个1。

2)非规格化的值
当指数位全为0时,转化规则发生了变化:

真实的尾数=0.存储的尾数
真实的指数=1-偏移值

当尾数的所有二进制位都为 0 时,整个浮点数的值就为 0:
如果符号 sign 为 0,则表示 +0;
如果符号 sign 为 1,则表示 -0。

因此非规格化的值能表示的范围为:
在这里插入图片描述

这里有必要解释一下

偏移值为127,因此真实的指数为1-127=-126

指数部分是不变的,变的只有尾数部分,真实的尾数范围为0~0.11…11

因此非规格化的值能表示的范围为:
在这里插入图片描述
非规格化的值能表示的最接近0的值为:

在这里插入图片描述

在这里插入图片描述

3)无穷大
当指数全为1,且尾数部分为0,则表示无穷大。

s=0表示正无穷;s=1表示负无穷

4)NaN
当指数全为1,且尾数部分不为0,则表示是一个无效的数字NaN。

从上面的讨论可以看出,float的整体表示范围为:
0~+∞

其中规格化的值的表示范围为:
在这里插入图片描述
即:
在这里插入图片描述

非规格化的值表示范围为:
在这里插入图片描述
其中1.11…11*2^(-127)略小于 2 ^ (-126)

当符号位为1时,float表示负数,此时float表示的数字范围为
-∞~ 0

同样,double能表示的数字范围为:
在这里插入图片描述

(7)优缺点

优点:

——大范围:浮点数表示法可以表示非常大和非常小的数值范围,例如可以表示非常大的天文数字和非常小的微观粒子。
——硬件支持:现代计算机硬件对浮点数运算进行了优化,可以高效地执行浮点数计算。

缺点:
——精度损失:相同存储空间,定点数精度更高
——舍入误差:由于尾数位数的限制,进行浮点数计算时可能会发生舍入误差,特别是在连续的浮点数运算中,舍入误差可能逐渐积累导致结果偏离预期。
——复杂度和成本:浮点数计算的硬件实现相对复杂,同时存储和处理浮点数所需的内存和计算资源也更多,相比于定点数来说更加昂贵。

同样举上面的例子:

普朗克长度约等于1.6x10^-35米,用二进制表示为:

1.010101000100100001001001001100101101001011100111001*2^(-116)

由于float尾数最多只能存23位,因此尾数存储01010100010010000100100,指数为-116,加上127为11,即0000_1011,因此普朗克长度的float存储形式为:

0_0000_1011_01010100010010000100100

我们只用了32bit就存储了如此小的一个数,相比用定点数存储方式空间消耗量缩小了5倍以上。

但细心的读者也发现了,由于位数限制我在存储尾数的时候做了舍入,由此也造成了舍入误差。

其实除了部分以5结尾的小数外(如0.5,0.125),大部分小数都无法精确存储,0.35都不能精确存储。

四、答疑

1、定点数表示法已经被淘汰了吗?

定点数表示法在某些嵌入式系统、数字信号处理、特定硬件平台和低功耗设备等领域仍有应用,没有完全被浮点数表示法取代。

2、为什么说整数的存储其实也是用定点数存储格式?

对于short、int、 long这样的整数,我们可以将它们视为没有小数部分的定点数,即小数部分为零,小数点在末尾。这样,整数的存储可以看作是定点数存储格式的特例。

这样数字在计算机中的存储格式可以这样划分:

整数采用定点数格式存储;小数采用浮点数格式存储。

也正是因为浮点数和小数紧紧绑定在一起,因此可以视浮点数和小数这两个概念是等价的,可以不用区分。

但严格上来说,浮点数是小数在计算机中的存储格式。

3、硬件FPU单元

硬件浮点单元(Floating-Point Unit,FPU)是一种在计算机处理器中集成的硬件组件,专门用于执行浮点数运算。FPU提供了硬件级别的浮点数运算指令集和计算单元,能够高效地执行浮点数的加减乘除、平方根、取整等运算。

高级芯片中集成有FPU,如Intel的Core系列和AMD的Ryzen系列、智能手机SOC、GPU芯片、FPGA芯片、高端型号的STM32微控制器(如STM32F4和STM32H7系列)。

对于不带硬件FPU的STM32芯片,仍然可以进行浮点数运算,但是需要使用软件库来模拟浮点数运算,这被称为软件浮点数库。软件浮点数库通过软件算法实现浮点数运算,但相比硬件浮点单元,其性能较低。


参考资料

1、https://fishc.com.cn/thread-67214-1-1.html
2、http://c.biancheng.net/view/vip_1764.html
3、二进制小数转十进制https://jisuan5.com/binary-to-decimal/