首页 > Note > 谁动了我的精度 — 浮点数运算的问题

谁动了我的精度 — 浮点数运算的问题

2017年3月23日 发表评论 阅读评论


1 引子

同事写的程序出现了点问题。调试发现,错误出现在一个 if 语句上:

这个 if 表达式被判定为 false, 程序没有按照预订执行下去。0.2 + 0.4 = 0.6,这还会有错吗?同事表示其小学数学还是很过关的。 是的,在人类的认知里,这毫无疑问是正确的,但是在计算机的认知里,就不一定了。这需要我们了解浮点数这种数据类型。

2 浮点数

2.1 用二进制表示小数

首先我们来想一下如何来表示一个十进制整数 d:

$$ d_m d_{m-1} ... d_1 d_0 d_{-1} d_{-2} ... d_{-1}, d\in [0,9] $$

这个表示方法描述的数值 d 的定义如下:

$$ d = \sum_{i=-1}^m{10^i \times d_i} $$

同样,引申到小数,小数点左边的数字是 10 的非负幂,得到整数部分;小数点右面的数字是 10 的负幂,得到小数部分。

例如: \(12.34_{10}\) 所表示的字为: \( 1 \times 10^{1} + 2 \times 10^{0} + 3 \times 10^{-1}
+ 4 \times 10^{-2} =12 \frac{34}{100} \)

类似地我们考虑一个二进制数 b 的表示:

$$ b_{m} b_{m-1} ... b_{1} b_{0} b_{-1} ... b_{-n} ,b\in[0,1] $$

它的定义如下:
$$ b = \sum_{i=-n}^{m}2^i \times d_i $$

如 \(10.11_2\) 表示数字: \(1 \times 2^1 + 1 \times 2^{0} + 1 \times 2^{-1} + 1 \times 2^{-2} = 2 \frac{3}{4} \)

假定在二进制中,我们的编码长度有限,我们就不能,或者不能精确的表示某一些数字。假定我们的编码长度只有8位,那么我们可以准确表达的数有 28 = 256 个。如果用来表示无符号整数,范围为 0~255,如果表示有符号整数,范围为 -128~127.如果用来表示小数呢?由前面我们可以知道,小数的二进制表示法只可以表示能写成 \(b \times 2^{i}\) 的娄数,如

0.12 = 1 x (1/2) = 0.510,

0.112 = 1 x (1/2) + 1 x (1/4) = 0.7510

其它的值都只能被近似的表示。我们在十进制中可以轻松表达的数,如 0.20, 0.30 都无法在有限的二进制中精确的表达。就如我们在十进制中无法精确地表达某些数,如 \( \frac{1}{3},\frac{5}{7} \) 一样, .对于这些数,增加编码的长度只能提高数的精度。

如果一个十进制小数可以使用二进制精确表达,那么它的最后一个数字一定是 5 。正在阅读这篇文章的你,可以试着证明一下。 (这是一个必要不充分语句)

2.2 IEEE 浮点数

2.2.1 IEEE 标准

IEEE 浮点标准使用公式:

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

来表示一个浮点数。其中:

  • s 符号位,决定该数是正(s=0)还是负(s=1).
  • M 尾数,是一个二进制小数,取值获取为 1 ~ \( 2-\epsilon\)
  • E 阶码,作用是对浮点数加权,权重是2的E次幂

在C语言中,对于一个浮点占用32位内存(双精度占用64位),这块内存被划分为三个区:

  • 最高位为单独的符号区, s = 1 位
  • 接下来 k = 8 位为阶码编码区。值用 \(e\) 表示
  • 余下来 n = 23 位为尾数编码区。值用 \(f\) 表示

根据阶码编码区的值,将浮点数的表示分为三种情况:规格化、非规格化与特殊值

2.2.2 规格化浮点数

这是最常见的一种情况,阶码区里即不全是0,也不全是1. 即 \(e \not= 0\) 且 \(e \not= 255\) 在这种情况下,阶码字段被解释为 偏置 的有符号整数。阶码的值为: $$ E = e - Bias , Bias = 2^{(k-1)}-1 $$ 那么这里的 Bias 为 28-1-1 = 127, \(e\) 的取值范围为 1 ~ 254,那么 E 的取值范围为 -126 ~ 127 。

而对于尾数部分,将其解释为小数值 \( f, 0 \leq f \lt 1\) .而尾数定义为: $$ M = 1 + f $$

2.2.3 非规格化浮点数

当阶码全为 0 是,即 \(e=0\) 时,表表示的数值即为非规格化形式的。在这种情况下,码的值: $$ E= 1-Bias $$ 尾数的值为: $$ M=f $$ 因为使用规格化数时,我们必须总令 \(M \geq 1\),所以我们无法表示 0 值。那么在非规格化形式中,当符号位为0,阶码值为0,尾数区为0时,我们得到 +0.0,而当符号位为1时,我们得到 -0.0

2.2.4 特殊值

当阶码区全为1,即 \(e=255\)时,表示特殊值。

当尾数区全为0时,得到的值表示无穷:当 s=0时,表示 \(+\infty\),当s=1 时,表示\(-\infty\)

当尾数区不全为0时,得到的值表示 'Nan' (Not a Number).

2.2.5 示例

为了便于描述,我们假设某种语言的float数据类型占用6位内存。最高位表示符号号,次高4位为阶码区,最后3位为尾数区。那么有 \(k=4,Bias=2^{k-1}-1=7\).我们来看一下:

描述 位表示 指数 小数 值\((-1)^{s}\times M\times 2^E\)
0 0 0000 000 \(e=0, E=-6\) \( f=0,M=f=0\) \(  M \times 2^{E} = 0\)
非规格化数 0 0000 001 \(e=0,E=-6\) \(f=\frac{1}{2^3},M=f=\frac{1}{8}\) \( M\times 2^{-6}=\frac{1}{512}=0.001953\)
非规格化数 0 0000 111 \(f=\frac{7}{2^3},M=f=\frac{7}{8}\) \( M \times 2^{-6}=\frac{7}{512}=0.013672\)
规格化数 0 0001 000 \(e=1,E=-6\) \(f=0,M=1+f=1\) \( 1\times 2^{-6}=\frac{1}{64}=0.015625\)
规格化数 0 1110 111 \(e=14,E=7\) \(f=\frac{7}{8},M=1+f=\frac{15}{8}\) \(\frac{15}{8} \times 2^7=240.0\)
无穷 0 1111 000 -- -- \(\infty\)

 

2.2.6 舍入

既然某些十进制小数无法在二进制中精确地表示,那么当我们向计算机中输入一个浮点数,如 0.2 以后,计算机应该怎么存储它呢. IEEE给出的解决方案是,存储它的近似值。IEEE 有一系列舍入运算的标准,如向0舍入、向偶数舍入、向上舍入、向下舍入,以取到最接近要表达的浮点数的数值。具体的硬件或软件厂商在实现这一标准各有不同。

 

 

  1. 2017年3月31日16:40 | #1

    学习使人进步,到此拜读!

  1. 本文目前尚无任何 trackbacks 和 pingbacks.