很抱歉,由于sf不支持行内公式,因此只能使用行间公式,致使格式有点难看。框架
目前流行都是上层的语言和框架,一般状况下其实咱们并不须要去了解底层实现。但有时候咱们会遇到一些奇怪的错误,不了解底层实现的话就没法想通。
好比下面一个C的例子ui
#include <stdio.h> int main(int argc, char** argv) { int num=8; float* pfnum = # 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
-
因为指数可能为正数,也可能为负数,为了方便表示,引入了一个误差值
$$ 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)$$
产生无穷的通常情形有:
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多是正负零而变得不肯定。因此两害取其轻,零仍是无序问题少一点。
以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 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(错误)。