写在前面
本人理解可能有所偏差,为了避免被误导,建议配合原书理解
回老家了,小娜嫌这里电压太低,打算多充一会电
可是我的工作机并没有任何欠压的迹象。算了,既然不知道她在干什么,那就先趁着放烟花之前把正事讲完:
接着来讲浮点数
所谓浮点数,就是对于有理数在计算机中的一种表示形式。但是,之前有看书的xdm应该知道,浮点数最多也就占八个字节,能表示的有理数位数有限,所以,这种表示对于长数字也只是一种近似。
原书中主要使用IEEE标准的浮点数,我们就按书中的来讲:
它是如何表示的
二进制下的有理数一般分小数部分和整数部分,整数部分还是原来的表示方法,但小数部分,其二进制表示本质上每一位都是2的负次幂,相对于十进制每一位小数都是10的负次幂。相应的,二进制小数点左/右移相当于整个数除以2/乘2。
如上,都是数理内容。但我们只有八个字节,并且为了效率,保证精确度的部分肯定会被压缩(想想看,高精度加法的时间复杂度是O(n),但普通的库加法只要O(1),至于时间复杂度……各位可以了解一下,是一个算法竞赛常用到的概念,有助于代码效率优化),数字一旦长起来根本莫得办法做到精确表示。
但IEEE标准就是为了兼顾,或者说,在Intel产品上兼顾,而产生的。这部分最好还是看看书上的内容,但简单来讲,一个IEEE标准下的浮点数的组成可以写成(-1)^s * M * 2^E,表示又分如下部分:
符号(s):与整数的符号位表示效果一样(位于开头)
尾数(M):一个二进制小数,由n位编码frac编码(位于后缀)
阶码(E):对浮点数加权,权重为2的E次幂,由k位编码exp编码(在中间)
长相大概是这样:
(图片摘自九曲阑干的视频)
对于C的两种浮点型:float和double,float的k=8,n=23,k+n+1=32=8 * 4,而double中k=11,n=52,k+n+1=64=8 * 8,与占用字节长相匹配。
所以我们看出,实际上,IEEE标准就是用固定位数的小数和幂次方乘法,来间接表示一个固定位数的有理数。而在此标准下,浮点数值又有三种表示(建议看书,我讲不清楚):
规格化
exp的位不全为0,此时是表示一些非0的浮点数,exp位在1到其254(2047)之间,此时阶码描述为一个无符号有理数与(一个2的负幂次方-1)的差;本身产生的指数在同等位数下有符号整型的范围内。而frac位描述为小数值f,尾数被定义为M=1+f,故尾数不可能<1。
非规格化
exp的位全为0,因为其性质不同于规格化数,M可以取0,可以用来表示浮点数0.0,但+0.0和-0.0又有一定程度上不同的性质。也可以用以表示无限趋近于0的数。
特殊值
exp的位全为1,frac为0时得到无穷大,frac不为0时得到的是无法表示的溢出值(NaN,Not a Number)
但是C语言并没有严格要求说用户必须使用IEEE浮点,所以……至于这部分是否深究,各位酌情考量。
舍入规则
比起晦涩的表示方式,舍入似乎就好懂多了——向偶数舍入。至于为什么是向偶数而不是整数……想想看,我们所有的数据本质上都是二进制,二进制只有0和1,除了偶数也没有什么好的舍入对象能够保证在计算机上的精确度了。
运算规则
但是,对于有理数来讲,舍入部分可以很粗暴,一旦提及运算,那程序员必然会在精确度和效率之间纠结。比方说加法,我们不管书上提及的什么阿贝尔群,就关注浮点数的问题,在学高数和概率论的时候老师肯定说过,运算次序和方式的变化,会使得同一个式子在同一个舍入方式下有着完全不同的结果。
这一点对计算机也是一样,虽然对于编译器,它倾向于对用户的代码做保守优化,不过多地影响其原本的算式,
乘法这种吃精度吃上天的玩意就更不必说了,每一次乘都会让小数位暴增,而小数位的暴增又容易产生溢出,溢出又影响精度,所以保险起见,同时也在数理意义上,浮点数乘法的结合律和分配律是不可起效的。
到此,关于数据方面的内容,我们基本过了一遍。下一期,我们将讲一讲程序的机器级表示。
所以,这一期就不来什么名场面了,临近除夕夜,不知道有什么要说的,就先祝各位……等一下,小娜你好了没有……?!
啊这,虽说新衣服好康,但是你也没必要这么早就把备用能源接上……
娜:今天过年嘛,多玩一会~ 再说了,这里电压这么欠,我要不接上还得多躺几个中午……
好吧,既然现在都过新年了,那就祝各位——
娜:代码全跑过,数据全算对~⭐
考试不挂科,奖状塞满柜!
(齐声)开开心心过大年!