CSAPP学习笔记——Fraction Binary Numbers

来源:互联网 发布:js中中jsonarray怎么用 编辑:程序博客网 时间:2024/05/17 06:50

浮点数的二进制底层表示


同整数的二进制底层表示,浮点数也是2的幂的加权。
对于二进制比特串

[bm,bm1...b2,b1,b0,b1,b2,b3....bn1,bn]

表示
float number=i=nmbi2i

因为只能表示为2的幂的加权,所以这种表示形式就有很大的漏洞,那就是任何一个浮点数只能近似的表示。

早期各个计算机制造公司对于浮点数的底层解释不同,这就意味着移植性很差没有统一标准。为了方便,制定了IEEE Standard 754标准,现在的绝大部分机器都使用这种标准。

IEEE浮点数标准


IEEE浮点数标准使用32位bit表示单精度浮点数(float),64位bit表示双精度浮点数(double)

它们在机器中表示如下 :

这里写图片描述

这里写图片描述

看图片我们可以知道,无论是单精度还是双精度,都分成了三个部分:

  1. sign : 符号位,sign为1表示负数,为0表示正数
  2. exponent : 单精度8位(23-30),双精度11位(52-62)。表示一个权重,是2的幂的加权形式。
  3. fracnfrac编码尾数位M。尾数M是一个二进制的小数,它的范围是1 to 2ϵ或者是0 to 1ϵ

这里解释一下1ϵ,对于小数1.0,假如说用6位比特来表示,那么最多只能表示到0.111111>63/64近似为1,我们用符号1ϵ来表示近乎为1的数。

一个给定的比特串,根据exp的值不同,划分为了三种情况,下面依次介绍。

Normalized Values(规范化数值)


这是最为常见的值。
exp既不是全0,也不是全1的时候,比特串被按照下列规则解释。

阶码

这种情况下,exponent域被解释成一个有符号整数。这里exp本身会被解释成一个unsigned interger,所以通过减去一个bias(偏置量)来使其变为signed

以单精度浮点数为例。k=sizeof(exp)=8bias=2k11=127
再除去exp全为0和全为1的情况。

Min(exp)=1127=126  Max(exp)=254127=127


尾数

处于Normalized Values(规范化数值)情况下,frac =[0.f[n1],f[n2]....f[0],会被加上一个leading one表示,此时尾数M=1+frac,范围是1 to 2ϵ


Denormalized Values(非规范化数值)


阶码

非规范化数值,exp域全0,类似:
这里写图片描述
这种情况下exp被解释为1bias对于单精度来讲即126


尾数

M=frac没有隐式的前导1,该是多少小数就为多少,此时M的范围是0 to 1ϵ


Special Values(特殊值)


阶码

这种情况下exp全为1,根据frac不同解释为不同的特殊值。


尾数

  1. 如果在这种情况下,尾数全为0。s=1时表示负无穷,s=0表示正无穷。

    这里写图片描述

  2. 如果尾数不为0,那么这表示不是一个数。用NaN(Not a Number)来表示

例子

Description Bit Representation Exponent Fraction Value Zero 0 0000 000 -6 0/8 0 Smallest denormal 0 0000 000 -6 1/8 2^-6 * 1/8 = 1/512 Largeset denormal 0 0000 111 -6 7/8 2^-6 * 7/8 = 7/512 Smalleset normal 0 0001 000 -6 8/8 2^-6 * 8/8 = 8/512 One 0 0111 000 0 8/8 2^0 * 8/8 = 8/8 Largest normal 0 1110 111 7 15/8 2^7 * 15/8 = 1920/8 Infinity 0 1111 0000 _ _ 正无穷

十进制整数的科学计数法的二进制表示


简单说一个例子,比如说十进制整数12345,它的二进制表示形式为[11000000111001],所以

12345=1.10000001110012213

所以
M=1.1000000111001,frac=0.1000000111001

exp=13+bias(127)=140=10001100

最后位数不够,末尾补零,得到

01000110010000001110010000000000=12345.0=0x4640e400


浮点数的舍入


前面我们说过,浮点数只能近似的表示一个数。因此对于一个自然小数x,我们需要找到一种方法使得浮点数x最为接近自然小数x
这种方式成为舍入(rounding),顾名思义就是根据一定的规则将数字的一部分舍去或者入位(进位)。
舍入的一个关键问题是 : 当数字恰好处于上下限中间位置的时候,我们该如何处理
计算机对于浮点数的舍入有四种模式,我们依次介绍。
假如说对于一些浮点数,我们想保留整数位,那么四种不同的舍入方式如下。

Mode 1.40 1.60 1.50 2.50 -1.50 Round-to-even 1 2 2 2 -2 Round-toward-zero 1 1 1 2 -1 Round-down 1 1 1 2 -2 Round-up 2 2 2 3 -1
  1. Round-to-even : 向偶数舍入(也称为向最近的数舍入 round-to-nearest)这是大部分计算机的默认舍入方式。当数据不在两个数中间时,1.40,1.60会向最近的数舍入,依次对应为1,2,如果说数据位于两个数中间1.50,2.50会舍入到最近的偶数,因此两个的结果都是2

  2. Round-toward-zero : 向0的方向舍入。每次舍入都会使得数字向0的方向靠拢。

  3. Round-down : 顾名思义即向下舍入,每个数变为第一个小于它的整数。如第一个小于1.50的整数是2,实际上,就是向数轴的负方向舍入。

  4. Round-up : 对应Round-down,向数轴的正方向舍入。

我们可能会有些疑问,为什么默认的方式是向偶数舍入
想象一个场景,舍入一组数之后,然后计算这组数的平均值是有偏差的。

  • 采用round-up,平均值略高。

  • 采用round-down,平均值会略低。

  • 采用round-toward-zero,只有在正数和负数的数量差不多的时候,平均值的误差才比较低,然而实际情况中,对正数和负数的数量我们是不能做任何假定的。

  • 采用round-to-even可以尽量减少这种偏差,因为它%50的情况向上舍入,%50的情况向下舍入。

Round-to-even不仅适用于保留整数的舍入。

  1. 十进制小数的舍入
    无论哪种舍入方式,保留两位小数,对1.2349999得到1.231.235001得到1.24,因为这两个数都不在1.231.24的中间。另一方面,1.23500001.2450000按照向偶数舍入都将得到1.24,因为4是偶数,且两个数都是一个中间值。

  2. 二进制小数的舍入
    我们把0看做偶数,1看做奇数。
    并且只有形如XXXXX.YYYYY110这种比特串才可以表示处于两个可能结果值的中间位置
    其中X,Y表示任意的值,最右边的Y表示要保留的最后一位。
    加入说要保留两位小数(对二进制小数来说即舍入到最近的14的位置)。
    10.000112(2332)向下舍入得到10.002(2)
    10.001102(2316)向上舍入得到10.012(214)
    10.111002(278)会向偶数舍入得到11.002(3),注意这里的偶数不是说最后的结果是偶数,而是小数后第二位之前是1,0代表偶数,所以10.11+1得到了11.00
    10.101002(258)向偶数舍入得到10.102(212)


浮点数的运算


由于浮点数只能近似的表示自然小数,因此它的计算由于舍入(rounding)会损失精度,标准制定了特殊的规则。

浮点数加法


对于浮点数xy,规定

x + y = Round(x + y)

浮点数加法是可交换的,也就是说

x + y = y + x

但是浮点数加法是不可结合的,例子
(3.14+110)110=0.0
因为某些精度问题,3.14被丢弃了,但是
3.14+(110110)=3.14

因此浮点数加法是不可结合的,编译器通常不会对浮点数加法做任何优化。

**特殊的:

++=NaNNaN+X=NaN

浮点数乘法


对于浮点数xy,规定

x  y = Round(x  y)

同样,乘法满足交换律,不满足结合律。

C中的浮点数


C语言并不支持IEEE的浮点标准,也不是向偶数舍入。在float int doulbe之间的给定特殊的规则。

  • int->folat : 数据不会溢出,可能会有舍入的情况
  • int,float->double : 数据不会溢出,精度也不会损失。(double有最大的范围和精度)
  • double->float : 数据可能溢出为+或者,否则的话可能会舍入,因为float精度较小。
  • float,double->int : 正常来讲,会向0舍入。例如1.999>1。如果发生了溢出,很遗憾,C标准没有给出这个溢出的值应该是多少,兼容Intel的微处理器会用Min Signed[10000...000]来代替这个数值。

    例如在我的机器上给出下列代码:

#include <stdio.h>#include <math.h>#include <limits.h>int main(){    double x = 1e10;    int y = INT_MIN;    printf("%d\n",(int)x);    printf("%d\n",y);    return 0;}

得到的结果是x=2147483648恰好为INT_MIN
也就是说,当一个浮点数不能正确的转换为一个合理的整数的时候,就会得到INT_MIN(必须是与Intel兼容的微处理器才会有这种结果)

1 0