定点数与浮点数整理

374 阅读7分钟

小数 二进制 的 表示与转换

1. 小数用二进制如何表示

首先,给出一个任意实数,整数部分用普通的二进制便可以表示,这里只说小数部分如何表示

例如0.6

文字描述该过程如下:将该数字乘以2,取出整数部分作为二进制表示的第1位;然后再将小数部分乘以2,将得到的整数部分作为二进制表示的第2位;以此类推,直到小数部分为0。 特殊情况:小数部分出现循环,无法停止,则用有限的二进制位无法准确表示一个小数,这也是在编程语言中表示小数会出现误差的原因

下面我们具体计算一下0.6的小数表示过程

0.6 * 2 = 1.2 ——————- 1
0.2 * 2 = 0.4 ——————- 0
0.4 * 2 = 0.8 ——————- 0
0.8 * 2 = 1.6 ——————- 1
0.6 * 2 = 1.2 ——————- 1

我们可以发现在该计算中已经出现了循环,0.6用二进制表示为 0.1001 1001 1001 1001 …… 如果是10.6,那个10.6的完整二进制表示为 1010.100110011001……

##2. 二进制表示的小数如何转换为十进制 其实这个问题很简单,我们再拿0.6的二进制表示举例:0.1001 1001 1001 1001 文字描述:从左到右,v[ i ] × 2^(-i)^,i 为从左到右的index,v[ i ]为该位的值,直接看例子,很直接的

0.10011001....(第n位)
2^-1^2^-2^2^-3^2^-4^2^-5^2^-6^2^-7^2^-8^....2^-n^

0.6 = 1 * 2^-1 + 0 * 2^-2 + 0 * 2^-3 + 1 * 2^-4 + ……

定点数

什么是定点数

数字既包括整数,又包括小数,而小数的精度范围要比整数大得多,所以如果我们想在计算机中,既能表示整数,也能表示小数,关键就在于这个小数点如何表示? 于是人们想出一种方法,即约定计算机中小数点的位置,且这个位置固定不变,小数点前、后的数字,分别用二进制表示,然后组合起来就可以把这个数字在计算机中存储起来,这种表示方式叫做「定点」表示法,用这种方法表示的数字叫做「定点数」。

定点数的数字表示方法

定点数如果要表示整数或小数,分为以下三种情况:

  1. 纯整数:例如整数100,小数点其实在最后一位,所以忽略不写
  2. 纯小数:例如:0.123,小数点固定在最高位
  3. 整数+小数:例如1.24、10.34,小数点在指定某个位置

定点数表示纯整数

对于纯整数100,由于小数点固定在最低位,假定我们以 1 个字节(8 bit)表示,用定点数表示如下(D为十进制缩写,B为二进制缩写):

100(D) = 01100100(B)

定点数表示纯小数

对于纯小数 0.125,由于小数点固定在最高位,同样以 1 个字节(8 bit)表示,用定点数表示如下:

0.125(D) = 0.00100000(B)

定点数表示整数+小数

这种情况下,我们需要先约定小数点的位置。依旧以 1 个字节(8 bit)为例,我们可以约定前 5 位表示整数部分,后 3 位表示小数部分。

  • 对于数字 1.5 用定点数表示就是这样:
1.5(D) = 00001 100(B)
  • 数字 25.125 用定点数表示就是这样:
25.125(D) = 11001 001(B)

这就是用定点数表示一个小数的方式,总结过程如下:

  1. 在有限的 bit 宽度下,先约定小数点的位置
  2. 整数部分和小数部分,分别转换为二进制表示
  3. 两部分二进制组合起来,即是结果

但是定点数存在缺陷,对于一个固定位数,不管如何约定小数点的位置,都会存在以下问题:

  • 数值的表示范围有限(小数点越靠左,整个数值范围越小)
  • 数值的精度范围有限(小数点越靠右,数值精度越低)

总的来说,就是用定点数表示的小数,不仅数值的范围表示有限,而且其精度也很低。要想解决这 2 个问题,所以人们就提出了使用「浮点数」的方式表示数字。虽然定点数表示数字,存在以上说的这些问题,但也只是在表示小数的场景下。如果只是用于表示整数,还是非常方便的。所以,现代计算机中一般使用定点数来表示整数。

浮点数

对于定点数,其中「定点」指的是约定小数点位置固定不变。那浮点数的「浮点」就是指,其小数点的位置是可以是漂浮不定的。

浮点数表示格式:

V = (-1)^S * M * R^E

其中各个变量的含义如下:

  • S:符号位,取值 0 或 1,决定一个数字的符号,0 表示正,1 表示负
  • M:尾数,用小数表示,例如 8.345 * 10^0,8.345 就是尾数
  • R:基数,表示十进制数 R 就是 10,表示二进制数 R 就是 2
  • E:指数,用整数表示,例如前面看到的 10^-1,-1 即是指数

浮点数标准

1985年,IEEE 组织推出了浮点数标准,就是我们经常听到的 IEEE754 浮点数标准,这个标准统一了浮点数的表示形式,并提供了 2 种浮点格式:

  • 单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
  • 双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit

为了使其表示的数字范围、精度最大化,浮点数标准还对指数和尾数进行了规定:

  • 尾数 M 的第一位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字

  • 指数 E 是个无符号整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255。但因为指数可以是负的,所以规定在存入 E 时在它原本的值加上一个中间数 127,这样 E 的取值范围为 -127 ~ 128。表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024。

dafa5bd4c503c8395a4b44790fc5c263.png

除了规定尾数和指数位,还做了以下规定:

  • 指数 E 非全 0 且非全 1:规格化数字,按上面的规则正常计算
  • 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
  • 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
  • 指数 E 全 1,尾数非 0:NaN(Not a Number)
在这里插入图片描述

以25.125 为例,转换为标准的 float 浮点数:

  • 整数部分:25(D) = 11001(B)
  • 小数部分:0.125(D) = 0.001(B)
  • 用二进制科学计数法表示:25.125(D) = 11001.001(B) = 1.1001001 * 2^4(B)

得: S = 0,尾数 M = 1.001001 = 001001(去掉1,隐藏位),指数 E = 4 + 127(中间数) = 135(D) = 10000111(B)。填充到 32 bit 中,如下:

在这里插入图片描述

浮点数精度损失

如果我们现在想用浮点数表示 0.2,它的结果会是多少呢?

0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。

0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(发生循环)
...

所以 0.2(D) = 0.0011 0011…(B)。 因为十进制的 0.2 无法精确转换成二进制小数,而计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。

浮点数的范围和精度

  1. 单精度浮点数 float (float32):
  • 它能表示的最大二进制数为 +1.1.11111…1 * 2^127(小数点后23个1),而二进制 1.11111…1 ≈ 2,所以 float 能表示的最大数为 2^128 ≈ 3.4 * 10^38,即 float 的表示范围为:-3.4 * 10^38 ~ 3.4 * 10 ^38。

  • 精度:float 能表示的最小二进制数为 0.0000…1(小数点后22个0,1个1),用十进制数表示就是 1/2^23。

  1. 双精度浮点数double(float64):
  • double 能表示的最大二进制数为 +1.111…111(小数点后52个1) * 2^1023 ≈ 2^1024 = 1.79 * 10^308,所以 double 能表示范围为:-1.79 * 10^308 ~ +1.79 * 10^308。

  • double 的最小精度为:0.0000…1(51个0,1个1),用十进制表示就是 1/2^52。

占字节数数值范围十进制精度位数
float4-3.4e-383.4e386~7
double8-1.79e-3081.79e30814~15

如果内存不是很紧张或者精度要求不是很低,一般选用double14位的精度*(是有效数字位,不是小数点后的位数)通常够用了。注意,问题来了,数据精度位数达到了14位,但有些浮点运算的结果精度并达不到这么高,可能准确的结果只有10~12位左右,往后的几位呢?自然就是不可预料的数字了,即使是理论上相同的值,由于是经过不同的运算过程得到的,他们在低几位有可能(一般来说都是)*是不同的。这种现象看似没太大的影响,却会对==(判断相等)产生致命的影响。注意,C/C++中浮点数的==需要完全一样才能返回true