声明:本篇博文中的图片均来源于CMU 15-213的PPT
一、IEEE浮点数标准(IEEE Floating Point Standard)
type(bits) | sign bit(s) | exponent(E) | fractional value / significand(M) |
single(32) | 1 | 8 | 23 |
double(64) | 1 | 11 | 52 |
Extended Presicion(80)(Intel only) | 1 | 15 | 63/64 |
对于f取值的五种分类
M / frac value | E | 什么情况下会产生 | |
Normalized Value | M = 1.xxxxx , 其中xxxxx被frac value表示 | 000...01 <= E <= 111...10 | 正常情况下多为这种数 |
Denormed Value | M = 0.xxxxx, 其中xxxxx被frac value表示 | E = 000...00 | 当数非常接近0时 |
+0 / -0 | frac value = 000...00 | E = 000...00 | 当数非常非常接近0,以至于Denormed Value也表示不了的时候 |
+∞ / -∞ | frac value = 000...00 | E = 111...11 | 除以0,或者溢出时 |
nan (not a number) | frac value ≠ 0 | E = 111...11 | 0/0,或者0*∞时 |
如果把浮点数看做带符号的整数,那么它们的大小关系可以用下图表示出来:(float->int, double->long long)
事实上,这正体现了这种浮点数表示法的优点,它可以像整型一样轻易地比较大小
为了更加深入地理解浮点数的取值分布,可以考虑一个低位浮点数,这里取8位浮点数,其中exp(指数)占4位,frac(数值)占3位,我们得到下表
就着这张表理一下浮点数表示的一些细节
- 对于normalized value,相当于就是二进制版的“科学计数法”。因此我们就知道了为什么M的首位要自动补1了,因为如果首位是0,说明E的值有问题,该调整E使得M的首位变成1,这样的话,无论如何M的首位都会是1。既然如此,又何必浪费1bit去表示1呢?况且,原本23位的M现在能表示24位的数了,看到8的倍数就觉得好。
- 无符号的4位的E可以表示2^4-1=15种可能,除去1111和0000还有13种,由于E可正可负,因此我们选取一个偏置,在读到E时自动减去偏置。这里偏置的选择是2^3-1=7,E原本的取值是0001~1110,即1~14,减去偏置后为-6~7。和带符号整型的表示略有区别,带符号整型的“偏置”不会-1,所以这里的E正值范围大于负值,带符号整型的负值范围大于正值
- normalized value没能物尽其用,因此引入denormed value。denormed value和最小的normalized value之间的过渡平滑。对于denormed value,E=1-bias,为什么是1呢?因为对于normalized value,Exp最小是1,为了平滑过渡要做这样的处理。换句话说,denormed value的Exp(000...00)已经没有实际意义,仅仅是一个标识。
- 对于normalized value之间的间距与normalized value和denormed value之间的间距,如下图所示
二、浮点数运算
基本理念:先做无穷精度的计算,再近似舍入
1. 舍入
舍入分四种,分别是向0舍入,向上(+∞)舍入,向下(-∞)舍入,以及向偶数舍入
浮点数的舍入采取最后一种,具体来说,类比“四舍五入”,如果不是.50000就正常舍入,如果是就向最近的偶数舍入,并不是说舍入结果一定是偶数
举例:
2. 乘法
3. 加法
4. Mathematical Properties of Addition
- 交换律√
- 结合律x
- 0是加法单位元√
5. Mathematical Properties of Multiplication
- 交换律√
- 结合律x
- 1是乘法单位元√
- 分配率x
- 例如
- cout << 1e200*1e200-1e200*1e200 << endl; // nan
- cout << 1e200*(1e200-1e200) << endl; // 0
三、浮点数与整数的相互转化(Casting)
- float -> int 截取整数部分,不进行舍入
- int->double(32位->52位)没有问题
- int->float (32位->23位)需要舍入,思路还是先弄成无限精度,再舍入