先前对于浮点数的理解,只是停留在不能精确的表示一个数上,对于浮点数的位组成,以及二进制和十进制之间的转换并没过深入了解过。
因为 Swift 是一门类型安全的语言,不允许不同类型之间的数值进行运算,整数溢出的时候也无法编译通过。对于 C,如果对类型不敏感的话,可能就会引入一些「看不见」的 Bug。
下面来看一下浮点数的构成。
IEEE 浮点数的定义标准用 V = (-1)^s × M × 2^(E) 的形式来表示一个数。
- 符号(sign)s 决定这数是负数(s=1)还是正数(s=0),对于数值 0 的符号解释做特殊处理。
- 尾数(significand)M 是一个二进制小数,它的范围是 1~2-ε,或者 01-ε。1ε 代表小数位全为 1 ,非小数为 0 的二进制数。
- 阶码 (exponent)E 的作用是对浮点数加权,这个权重是 2 的 E 次幂(可能是负数)。
浮点数在计算机中的表示形式分为 符号、尾数、阶码 三部分,编码形式:
- 一个单独的符号位 s 直接编码。
- k 位的阶码字段 exp = e(k-1)...e(1)e(0) 编码为阶码 E。
- n 位小数字段 frac = f(n-1)...f(1)f(0) 编码为尾数 M。
单精度的浮点数的符号位、阶码位、尾数位字段分别为 1 位, 8 位和 23 位。双精度的浮点数分别为 1 位、11 位和 52 位。
IEEE 定义的浮点数标准和计算机的位级表示方式相互转换,需要分三种情况讨论:
情况 1:规范化的值
当 exp 即不全为 0 也不全为 1 时,属于规范化的值。阶码的值 E = e - Bias,其中 e 是二进制阶码的无符号数,Bias 表示二进制阶码能表示的最大的有符号数。例如:某个单精度浮点数的阶码为 00000101 ,则它的 e = 5,Bias = 127。
小数字段 frac 被解释为描述小数值 f,其中 0 ≤ f < 1。尾数的定义为 M = 1 + f,默认以隐式的 1 开头。把所有的 frac 字段都用来表示小数部分,因为既然尾数的个位总是为 1,就直接省略,这样可以提高一位精度。例如某个单精度的浮点数的小数字段为 101010101010101010101(23位),那么 M = 1.101010101010101010101。
判断小数位是否包含隐式 1 开头,看阶码是否不全为 0 而不全为 1 即可。
情况 2: 非规范化的值
当阶码全为 0 时,所表示的数是非规范化形式。这种情况下,阶码值 E = 1 - Bias,而尾数的值 M = f,直接就是小数字段的值,不包含隐式的开头 1。例如某个单精度的浮点数的小数字段为 101010101010101010101(23位),那么 M = 0.101010101010101010101。
情况 3: 特殊值
在阶码全为 1 的时候,当小数位全为 0 时,得到的值表示无穷。当 s=0 时是 +∞,当 s=1 时是 -∞。如果我们把两个非常大的数相乘,或者除以 0 时,无穷能表示溢出的结果。
当小数域为非零时,结果表示为 NaN,即 “不是一个数(Not a Number)” 的缩写。一些运算的结果不能使无穷或实数,就会返回 NaN 的值,比如 ${\sqrt{-1}}$。
练习
如果希望加深印象,可自行尝试将某个浮点数在二进制与十进制之间转换。我写了一个将浮点数转换为二进制位表示的形式,点击查看。
思考:表达式 print(0.1 + 0.2 == 0.3) 的输出结果是?