IEEE754浮点表示法详解

很抱歉,由于sf不支持行内公式,因此只能使用行间公式,致使格式有点难看。框架

引言

目前流行都是上层的语言和框架,一般状况下其实咱们并不须要去了解底层实现。但有时候咱们会遇到一些奇怪的错误,不了解底层实现的话就没法想通。
好比下面一个C的例子ui

#include <stdio.h>
int main(int argc, char** argv)
{
    int num=8;
    float* pfnum = &num;
    printf("num = %d\n", num);
    printf("*pfnum = %f\n", *pfnum);
    *pfnum = 8.0;
    printf("num = %d\n", num);
    printf("*pfnum = %f\n", *pfnum);
    return 0;
}

输出结果为spa

num = 8
*pfnum = 0.000000
num = 1090519040
*pfnum = 8.000000

另一个有趣的应用是计算2的74次方,很明显64位系统上只能表示到2的64次方
但下面的例子能够获得3d

#include <stdio.h>
#include <math.h>

int main(int argc, char** argv)
{
    printf(" num = %f\n", pow(2, 74));
    return 0;
}

输出是code

num = 18889465931478580854784.000000

要理解以上问题,那咱们就须要对浮点在底层的实现有必定了解orm

总述

IEEE754是IEEE二进制浮点算术标准。这个标准定义了表示浮点数的常规值与非规格化值(denormal number),一些特殊值(infinity)和非数值(NaN), 以及这些数值的浮点运算。另外它还规定了运算结果的近似原则和例外情况(包括例外发生的时机和处理方式).
虽然IEEE754只定义了单精度(32位),双精度(64位),扩展单精度(43位以上),与扩展单精度(79位以上)。但实现上它的定义法能够扩展到任意精度。因此下面的公式尽可能针对任意精度。blog

格式

做为对比,咱们先列出实数表示法ip

msb ... lsb
n-1 ........................ 0

下面是浮点表示法it

Sign Exponent Fraction
(e+f) (e+f-1)......f (f-1)................0

从上面表格能够看出浮点由三部分组成:io

  • (e+f)位 : 表示浮点的符号位
  • (e+f-1)......f 位 : 表示浮点的指数域
  • (f-1)......0 位 : 表示浮点的尾数域

-

分类

clipboard.png

常规最大值和最小值

因为指数可能为正数,也可能为负数,为了方便表示,引入了一个误差值

$$ Ebias = 2^{e-1}-1 $$

假设把咱们要表示的浮点值改写成这样的二进制格式

$$ (-1)^{S}\times1.M_{0}M_{1}M_{2}...M_{22}\times2^{N} $$

那么表示成浮点数就是

Sign Exponent Fraction
S N+Ebias $$M_{0}M_{1}M_{2}...M_{22}$$

N+Ebias一共有e位,那么它们的取值范围是0 - (2^{e}-1))
其中Ebias = 2^{e-1}-1
N的取值范围是(-2^{e-1}+1) - (2^{e-1})
-2^{e-1}+1 和 2^{e-1} 被保留,用来表示一些特殊值和非数值。

因此作为常规值, N的取值范围是(-2^{e-1}+2) - (2^{e-1}-1)

MinNorm MaxNorm
$$(-1)^{S} \times 2^{-2^{e-1}+1} \times 1.0$$ $$(-1)^{S} \times (2-2^{-f}) \times 2^{2^{e-1}-1} $$

上面公式可能有点绕,不过若是代入具体数值就比较好理解了。 咱们以单精度为例

Sign 8-Bit Based Exponent 23-Bit Normalized Fraction
[31] [30:23] [22:0]

取w=8; t=23;
因此Ebais = 2^{8-1}-1 = 127
N的取值范围是 (-2^{8-1}+1) - (2^{8-1}), 即-127 -- 128
作为常规值, N的取值范围是-126 -- 127
最大值和最小值为

MinNorm MaxNorm
$$(-1)^{S} \times 2^{-126} \times 1.0$$ $$(-1)^{S} \times 2^{127} \times (2-2^{-23}) $$
S 00000001 00000000000000000000000 S 11111110 11111111111111111111111

非规格化数

对于单精度值,有两个数:
$$1.001 \times 2^{-125}$$
$$1.01 \times 2^{-125}$$
它们的差值是
$$0.001 \times 2^{-125} = 1.0 \times 2^{-128}$$
-128超过了咱们常规值容许的最小值-126.
若是近似为0, 那么下面的公式就会出问题

if(x != y) { z = 1/(x-y)}

为了解决上面的问题,引入了非规格化浮点数。
IEEE754规定当指数N是-2^{e-1}+1(此时E=0)时,尾数没必要是规范化的, 也就是尾数域再也不隐藏1.
把上面的差表示成$$0.01 \times 2^{-126}$$, 它的二进制浮点数为0_00000000_01000000000000000000000.
注意 非规格化数没有隐藏位,或者是能够看隐藏位是0
它能够表示成
$$\pm(f) \times 2^{0-Ebias}$$
f的取值范围是(0,2)
IEEE754标准中对它的值定义为:
$$v=(-1)^s \times 2^{emin} \times (0+2^{-t} \times M)$$

无穷大

产生无穷的通常情形有:

  • 自身运算, 如负无穷+2.0获得负无穷
  • 被0除, 例如1/0获得正无穷
  • 上溢, 即计算结果超出类型范围,经过舍入获得无穷

NaN

QNaN(Quiet NaN): 参与运算不触发异常
SNaN(signal NaN): 参与运算触发异常
IEEE引入NaN的目的是给compiler等系统一个约定的值未初始化的数据,或者在计算出问题时能够返回一个值来提示计算出问题了。

正零和负零

零有正负之分,很是容易让人困惑。这主要是基于数值分析后的权衡结果。
IEEE规定+0 == -0.
好比,若是零无符号, 则等式1/(1/x) == x 在x = 正负无穷 时再也不成立。
缘由是 1除负无穷都等于0, 1除以0等于正无穷,与x不相等。要解决这个问题的一个方法是无穷也无符号,但正无穷和负无穷显然分布在轴的两侧,能够表示上溢和下溢发生在哪一侧,因此不能不要。
固然零有符号也形成了一些问题,好比当x=y时, 1/x=1/y在x和y分别为+0和-0时,再也不成立。解决这个问题的方法是规定零是有序的,即+0不等于-0, 但若是这样的话,即便if(x==0)这样简单的判断也会因为x多是正负零而变得不肯定。因此两害取其轻,零仍是无序问题少一点。


计算异常

clipboard.png


和十进制间的转换

十进制浮点数到二进制浮点数的转换

  1. 十进制到二进制: 整数部分用2来除,小数部分用2来乘
  2. 规格化二进制数: 改变小数点,使小数点前只有第一位有效数字
  3. 计算指数的移码:原来的指数加上2^{e-1}-1
  4. 把符号位,指数的移码,尾数合在一块儿 (尾数不够补0)

以100.25变例

  • 第一步
100/2 = 50 ... 0
50/2  = 25 ... 0
25/2  = 12 ... 1
12/2  = 6  ... 0
6/2   = 3  ... 0
3/2   = 1  ... 1
1/2   = 0  ... 1      // 获得0,  中止

0.25 * 2 = 0.5 ... 0
0.5  * 2 = 1.0 ... 1     // 获得1.0, 中止

$$(100.25)_{10} = (1100100.01)_{2}$$

  • 第二步

$$1100100.01 = (-1)^0 \times 1.10010001 \times 2^{6}$$

  • 第三步

$$(6+127)_{10} = (133)_{10} = (85)_{16}$$

  • 第四步

拼接结果以下

符号 指数 尾数
0 1000_0101 1001_0001_0000_0000_0000_000

二进制浮点数到十进制浮点数的转换

过程同十进制到二进制相逆

  1. 分割符号位, 指数移码, 尾数
  2. 将移码送去偏移2^{e-1}-1, 获得真正的指数
  3. 写成规格化的二进制浮点数
  4. 写成非规格化的二进制浮点数形式
  5. 把二进制数转换成十进制数

以1 10001000 10011111100000000000000为例

符号 指数 尾数
1 1000_1000 1001_1111_1000_0000_0000_000

$$(10001000)_{2} - (127)_{10} = (136)_{10} - (127)_{10} = (9)_{10}$$

$$(-1 \times 2^{9} \times 1.100111111)_{2} = (-1 \times 1100111111.1)_{2} = (-831.5)_{10}$$

精度取舍规则

简明口诀:「4舍6入5看右,5后有数进上去,尾数为0向左看,左数奇进偶舍弃」。为了不四舍五入规则形成的结果偏高,偏差偏大的现象出现,通常采用四舍六入五留双规则。  当尾数小于或等于4时,直接将尾数舍去例如将下列数字所有修约到两位小数,结果为:10.2731——10.2718.5049——18.5016.4005——16.4027.1829——27.18当尾数大于或等于6时将尾数舍去向前一位进位例如将下列数字所有修约到两位小数,结果为:16.7777——16.7810.29701——10.3021.0191——21.02(三)当尾数为5,而尾数后面的数字均为0时,应看尾数“5”的前一位:若前一位数字此时为奇数,就应向前进一位;若前一位数字此时为偶数,则应将尾数舍去。数字“0”在此时应被视为偶数。例如将下列数字所有修约到两位小数,结果为:12.6450——12.6418.2750——18.2812.7350——12.7421.845000——21.84(四)当尾数为5,而尾数“5”的后面还有任何不是0的数字时,不管前一位在此时为奇数仍是偶数,也不管“5”后面不为0的数字在哪一位上,都应向前进一位。例如将下列数字所有修约到两位小数,结果为:12.73507——12.7421.84502——21.8512.64501——12.6518.27509——18.2838.305000001——38.31按照四舍六入五留双规则进行数字修约时,也应像四舍五入规则那样,一次性修约到指定的位数,不能够进行数次修约,不然获得的结果也有多是错误的。例如将数字10.2749945001修约到两位小数时,应一步到位:10.2749945001——10.27(正确)。若是按照四舍六入五留双规则分步修约将获得错误结果:10.2749945001——10.274995——10.275——10.28(错误)。

相关文章
相关标签/搜索