计算机基础

浮点数

2.4.1 二进制小数

十进制小数: 12.34 = 1*10^1 + 2*10^0 + 3*10^(-1) + 4*10^(-2)

二进制小数:101.11 =1*2^2 + 0*2^1 + 1*2^0 + 1*2^(-1) + 1*2^(-2) = 4+0+1+0.5+0.25 = 5.75(十进制)
二进制小数:1011.1 = 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 + 1*2^(-1)    = 8+0+2+1+0.5 = 11.5(十进制)

很容易看出,二进制小数点左移一位相当于这个数被2除,小数点向右移一位相当于将该数乘2.

假定我们仅考虑有限长度的编码,那么十进制表示法不能准确地表达三分之一和七分之五这样的数, 同理,小数的二进制表示法只能表示哪些能够被写成x*2^y的数。其他的值只能够被近似的表示。 增加二进制表示的长度可以提高表示的精确度:

二进制表示十进制
0.00/20.0
0.011/40.25
0.00113/160.1875
0.00110113/640.203125

2.4.2 IEEE浮点数表示

前一节谈到的定点表示法不能有效地表示非常大的数,如 52^100是用101后面跟100个0组成的位模式来表示。 相反,我们希望通过给定x和y的值来表示形如x2^y的数。

IEEE浮点标准用 V = (-1)^s * M * 2^E 的形式来表示一个数:

  • s(sign):符号,决定这个数是正数(s = 0)还是负数(s = 1)
  • 尾数(significand):M 是一个二进制小数,它的范围是[1,2)或者[0,1)
  • 阶码(exponent):E的作用是对浮点数的加权,这个权重是2的E次幂(可能是负数) 将浮点数的位表示划分位三个字段,分别对这些值进行编码:
  • 一个单独的符号位s直接编码符号2。
  • k位的阶码字段exp=e(k-1)…e1 e0 编码阶码E
  • n位小数字段 frac = f(n-1)…f1 f0 编码尾数M,但是编码出来的值也依赖于阶码字段的是否等于0

下图给出了将三个字段装进一个字中两种最常见的格式,在单精度浮点格式(c语言中的float),s、exp和 frac字段分别装进1位、k=8位和n=32位,得到一个32位的表示。 在双精度浮点格式(C语言中的double)中,s、exp和frac字段分别是1位、k=11位和n=52位,得到一个 64位的表示。

单精度:

31-30-----------23-22-------------------------------0
s|  exp          |               frac
-----------------------------------------------------

双精度:

63-62----------------52-51--------------------------32
s|      exp          |               frac(51:32)
-----------------------------------------------------
31----------------52-51-----------------------------0
            frac(31:0)
-----------------------------------------------------

给定了位表示,根据exp的值,被编码的值可以分成三种不同的情况(最后一种有两个变种):

1.规格化的:
s| 0< ? <255 |                f             |
2.非格化的:
s| 0000 0000 |                f             |
3a.无穷大:
s| 1111 1111 | 000 0000 0000 0000 0000 0000 |
3b.NaN:
s| 1111 1111 |           ?>0                |

单精度浮点数值的分类(阶码的值决定了这个数是规格化的、非规格化、或特殊值)
  • 情况1:规格化的值,这是最普遍的情况,当exp不全为0也不全为1时,阶码字段被解释为 以偏置形式表示的有符号整数。也就是说阶码的值是E = e - Bias,其中e是无符号数, 其位表示位e(k-1)…e1 e0, 而Bias是一个等于2^(k-1) -1(单精度是127,双精度是1023)的偏置值。 由此产生指数的取值范围,对于单精度是-126 ~ 127,而对于双精度是-1022 ~ 1023。 (1-127 = -126,254 -127=127). 对于小数字段frac的解释位描述小数值f,其中f的范围是[0,1),其二进制表示0.f(n-1)…f1 f0,也就是二进制 小数点在最高有效位左边。尾数定义为 M = 1 + f。有时这种方式也叫作隐含的以1开头的表示,因为我们可以把M 看做一个二进制表达式1.f(n-1)f(n-2)….f0的数字。既然我们总是能够调整阶码E,使得尾数M在范围[1,2)之中(假设没有溢出), 那么这种表示方法是一种轻松获得一个额外精度的技巧。由于第一位总是1,因此我们就不需要显示地表示它。(注意:不能表示数值0)

  • 情况2:非规格化的值, 当阶码域全是0时,所表示的数就是非规格化形式。这种情况下,阶码值是E = 1 - Bias(单精度: -126), 而尾数的值M = f,也就是小数字段的值,不包含隐含的开头 的1. 非规格化数有两个用途,首先,它们提供了一种表示数值0的方法,因为使用规格化数,我们必须总是使M>=1,因此不能表示0. 实际上+0.0的浮点表示的位模式全为0,当符号位为1,其他位是0,得到-0.0。根据IEEE的浮点格式,认为+0.0和-0.0在某些方面是不同的, 而在其他方面是相同的。 非规格化数的另一个功能是表示哪些非常接近0.0的数。

  • 情况3:特殊值,当阶码全为1时,小数域全为0时,得到的数表示无穷,s=0,表示正无穷,s=1表示负无穷。 当,小数域为非0时,结果值被成为“NaN“,表示“不是一个数”(Not a Number)。 一些运算的结果不能是是实数或者无穷,就会返回NaN, 如计算:√(-1)或者 无穷减无穷。在某些应用中,表示未初始化的数据时,它们也很有用处。

2.4.3 数字示例

2.4.4 舍入

因为表示方法限制了浮点数的范围和精度,浮点运算只能近视地表示实数运算。因此,对于值x,我们一般想用一种系统的方法,能够 找到“最接近”的匹配值x’,它可以用期望的浮点形式表示出来,这就是舍入运算的任务。

四种舍入方式: 方式|1.40|1.60|1.50|2.50|-1.50 —|—|—|—|—|— 向偶数舍入(向最近的值舍入)|1|2|2|2|-2 向零舍入|1|1|1|2|-1 向下舍入|1|1|1|2|-2 向上舍入|2|2|2|3|-1

2.4.5 浮点运算

浮点加法不具有结合性。

2.4.6 C语言中的浮点数

float, double.

较新的C语言版本包括ISO C99,包含第三种浮点类型long double.对于许多机器和编译器来说,这种类型等价于double类型。 不过对于inte兼容机来说,GCC用80位 “拓展精度”格式来实现这种类型,提供了比标准64位 格式大得多的取值范围和精度。

当int, float, double格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(假设int是32位):

  • int->float: 数字不会溢出,但是可能被舍入。
  • int,float->double: 因为double有更大的范围,也有哦更高的精度,所以能够保留精确的数值。
  • double->float: 因为范围要小一些,所以值可能是正无穷或负无穷,另外,由于精度较小,它还可能被舍入。
  • float,double->int: 值将会向零舍入。

关于作者

程序员,软件工程师,java, golang, rust, c, python,vue, Springboot, mybatis, mysql,elasticsearch, docker, maven, gcc, linux, ubuntu, centos, axum,llm, paddlepaddle, onlyoffice,minio,银河麒麟,中科方德,rpm