计算机是如何存储数字的

前言

1
2
int a=12;
int b=1.234;

问:a 和 b 在计算机中到底是如何存储的?

答:转为二进制?

问:转成二进制就直接存储了?

答:…

问:小数 b 如何转成二进制的?

答:…

我们日常更多的时候都在使用十进制的数,要知道我们的祖先可厉害了,应该很长一段时间都在使用 16 进制。譬如一个成语叫“半斤八两”,解释:半斤、八两轻重相等,比喻彼此不相上下。等等!半斤和八两怎么相等,对的,宋代一斤就是等于十六两,当然半斤等于八两。

都知道计算机使用的是二进制,只用 0 和 1 就能表示数字,但是计算机到底是如何解决数字存储的问题?下面分别讨论整数和小数的存储:

如何存储整数

假设我们现在有一个 8 位的操作系统,最高位是符号位,1 表示负数,0 表示正数,所以能表示的最大的整数区间为 11111111 ~ 01111111 => -127 ~ 127

但是在存储表达的时候会遇到一个问题,10000000 和 00000000 两个 0 的问题,+0 和 -0 应该也是相等的,这给电路设计上带来了很多麻烦和多余的计算规则。

十进制转成二进制

简单点的记忆就是:除 2 取余再倒序。

  • 12 / 2 = 6..0
  • 6 / 2 = 3..0
  • 3 / 2 = 1..1
  • 1 / 2 = 0..1

0011 倒序 即为 1100,转成八位即在前面补0,则结果为 0000_1100。

0000_1100 是 java8 中的写法,我觉得看起来比较舒服且前后连贯,_ 是为了看起来比较清楚人为添加的,后面沿用这种写法,0000_1100 = 00001100= 0000 1100
都是相等。

一个天才的设计师

看了很多的文档没有找到解决这个问题的作者是谁?不管是谁反正提出了补码这个概念,真的是有效的解决了这个问题,不仅解决了 0 这个问题还带来了一个更大的优点,后面补充。

正数的原码、反码、补码

正数原码 = 反码 = 补码

  • 12 的原码是 0000_1100
  • 12 的反码是 0000_1100
  • 12 的补码是 0000_1100

这里并没有废话,尽是科学。

负数的原码、反码、补码

反码 = 原码符号位不变其他位取反

补码 = 反码 + 1

  • -12 的原码是 1000_1100
  • -12 的反码是 1111_0011
  • -12 的补码是 1111_0100

0 的存储

+0

  • 原码:0000_0000
  • 反码:0000_0000
  • 补码:0000_0000

-0

  • 原码:1000_0000
  • 反码:1111_1111
  • 补码:0000_0000

这里应该把前面的问题都解决了吧,补码不仅解决了 +0 和 -0 的问题,还神奇的把符号位都去掉了,计算机在运行的过程中从而可以更简单的进行运算。

减法运算

为了效率,计算机底层计算的时候是没有减法运算,减法运算都转成加法运算。

12 - 12 => 12 + (-12) => 0000_1100 + 1111_0100 => 0000_0000

  • 0000_1100 + 1111_0100 = 0 + 0 = 0
  • 0000_1100 + 1111_0100 = 0 + 0 = 0
  • 0000_1100 + 1111_0100 = 1 + 1 = 0 进 1
  • 0000_1100 + 1111_0100 = 1+ 0 + 进1= 0 进 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 进1= 0 进 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 进1= 0 进 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 进1= 0 进 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 进1= 0 进 1

因为只能存储 8 位,最后一个进 1 爆掉就剩下 0000_0000 了。

这里都是以 8 位计算机进行举例,现在的 32 位或者 64 位计算机都是同理的。

如何存储浮点数

相信很多人小数转成二进制都是不知道如何运算的,更别谈存储了,下面我就娓娓道来。

文章刚开始编辑的时候将浮点数称为小数,由于发现这样不专业,其实小数不一定都是浮点数,在那个没有标准各自为政的早计算机时代其实还是有定点数,感兴趣可以看参考文档。

浮点数转成二进制

1.234 在计算机中转成二进制是按照是分整数部分和小数部分进行的,整数部分上面以前讲到,这里说下小数部分 0.234 为例:

简单点的记忆就是:乘 2 取整再顺序。

  • 0.234 * 2 = 0.468 => 整数部分为 0 => 取 0
  • 0.468 * 2 = 0.936 => 整数部分为 0 => 取 0
  • 0.936 * 2 = 1.872 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.872 * 2 = 1.744 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.744 * 2 = 1.488 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.488 * 2 = 0.976 => 整数部分为 0 => 取 0
  • 0.976 * 2 = 1.952 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.952 * 2 = 1.904 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.904 * 2 = 1.808 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.808 * 2 = 1.616 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.616 * 2 = 1.232 => 整数部分为 1 => 取 1 (并将整数部分抹去)
  • 0.232 * 2 = 0.464 => 整数部分为 0 => 取 0

下面就不写下去了结果是 0.001110111110…,写的越长精度越高。

可见浮点数存储必将是一个头疼的问题,浮点数底层的逻辑肯定也是比整数更为复杂。

IEEE754 标准

电气电子工程师学会(英语:Institute of Electrical and Electronics Engineers)简称为 IEEE,IEEE754是专门规定浮点数该如何存储的一个标准,规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。

任意一个浮点数都可以表示为:

$$ V = (-1)^s \times M \times 2^E $$

1
2
3
(-1)^s 表示符号位,当 s=0,V 为正数;当 s=1,V 为负数。
M 表示有效数字,大于等于 1,小于 2
2^E 表示指数位。

例子:V = 0.234(十进制) = 0.001110111110(二进制) = 1.110111110 * 2^-3 ,则 s=0 ,M= 1.110111110,E=-3。

IEEE754 规定:

IEEE 754浮点数的三个域

  • 对于 32 位的浮点数,最高的 1 位是符号位 s,接着的 8 位是指数 E,剩下的 23 位为有效数字 M。

  • 对于 64 位的浮点数,最高的 1 位是符号位 s,接着的 11 位是指数 E,剩下的 52 位为有效数字 M。

IEEE754 还有一些特殊的规定:

针对 M

由于 1<= M <=2 ,所以 M 始终为 1.xxxx 形式,xxxx 表示小数,那些对于计算机底层严苛的设计师这时候又要将 1 这一位舍掉,只保留 xxxx 部分,这样的好处是可以多储存一位有效数字。

针对 E

由于 E 是一个 8 位的无符号存储,只能表示 0 ~ 255,现实中的指数还存在负数,所以规定:E必须再加上一个中间数,对于 8 位的 E,这个中间数是 127;对于 11 位的 E,这个中间数是 1023,这样就可以将指数的表达范围扩大到 -127 ~ +128-1023 ~ +1024

实际中的取值范围是 -126~+127,-127 和 128 被用作特殊值处理,双精度同理。

阮一峰的博客中写的是 减去一个中间数 应该是有误的。

举个例子,如果 E=17,则 17+127 =144 ,实际的存储指为 144。

针对 E 还有一些特殊值的情况

  1. 如果指数 E 是 0 并且尾数的小数部分是 0,这个数是 ±0(和符号位相关)。
  2. 如果指数 E 是 1 并且尾数的小数部分是0,这个数是±∞(同样和符号位相关)
  3. 如果指数 E 是 1 并且尾数的小数部分非0,这个数表示为不是一个数(NaN)。

计算

V = 0.234(十进制) = 0.00111011111001110110110010(二进制) = 1.11011111001110110110010 * 2^-3 ,则 s=0 ,M= 1.110111110,E=-3。

根据 IEEE754 标准转化 :

以 单精度为例

s 不变
E=-3+127=124(十进制) = 0111_1100(二进制)

M=110_1111_1001_1101_1011_0010

结果将他们连接 0_0111_1100_110_1111_1001_1101_1011_0010(s_E_M)

可以在线校验结果

小结

这篇文章详细介绍了计算机如何存储整数以及浮点数,个人也是从朦胧状态到理解透彻,参考了众多大佬的文章,在此表示感谢。有问题的朋友可以通过邮箱联系到我 jake.zou.me@gmail.com

计算机之美妙不可言啊!

参考文档

坚持原创技术分享,您的支持将鼓励我继续创作!