1 抽样
最近干非常复杂的问题—阅读4w多线项目源代码,我们头都大了有木有!文件。为了更好的理解代码的含义,我须要一点一点解析二进制文件。在解析到某个位置的时候。有个读浮点数的操作,相应的二进制值为:…CAF249F1…。非常好奇这个值相应的浮点数是多少,所以写代码去求解一下。
这里我没有犯一个错误:原始的文件里是依照从低字节到高字节排序的。所以实际的数应该为:0xF149F2CA。可是还是犯了非常多错误,最初的代码为:
void test1() { unsigned int a=0xf149f2ca; float b=a; printf("a is %u,b is %f ",a,b); }
想法是通过隐式类型强转获得相应的浮点数。但输出结果为:a is 4048155338,b is 4048155392.000000,全然不是想象中的样子。隐式的类型强转仅仅是将一个int型的数变成一个和原值接近的浮点数,而不是将原始的int值依照地址解析成浮点数。后来一想也对。要不这样强转还有什么意义。注意强转的结果并不和原值一样,这是由float类型的精度造成的,后面我们会具体介绍当中的原理。
上述方法不行,我又想到利用字符串进行转换,代码例如以下:
void test2() { char* a="caf249f1"; float b=(float)atof(a); printf("a is %s,b is %f ",a,b); }
想法也非常easy,将连续四个字节利用atof函数解析成浮点数。输出结果为:a is caf249f1,b is 0.000000,再一次不对!
后来细致查看atof的解释才明确,原来atof函数仅仅能解析符合浮点数书写格式的数,比如0.0314或者3.14E-2。假设字符串全然无法被转换为数字,则返回0。
连续两次都没有解决。我怒了。想了一种操作地址的方式,代码例如以下:
void test3() { unsigned int a=0xf149f2ca; float b=*(float*)&a; printf("a is %u,b is %f ",a,b); }
代码的思想是首先获得int型的地址,然后将该地址强转成float型地址。然后再获得float的值,这样应该不会错了,输出的结果为:a is 4048155338,b is -1000000015047466219876688855040.000000。这个结果可能不太直观,可是我知道这就是我想要的结果。b输出了一个非常大的负数,假设将其转换成科学记数法则为-1E30,正好在代码中有个LOG_ZERO的宏定义为该值,正好相应起来了。该浮点数的含义是对某个变量赋初值,初值为log(0)的近似定义。
2 类型强转
通过上面的样例我们能有什么收获呢?在第一次尝试中,我们尽管没有得到正确的结果。可是却发现了一些问题:将一个int型的值转换成float类型之后。值变得和转换之前不一样。精度损失了不少,为什么会这样?要理解造成差异的原因,须要对浮点数的格式很了解。
在计算机中,一个浮点数(真实值)应该用下述形式来表示:
当中,s决定这个数是负数(s=1)还是正数(s=0);M表示尾数,是一个二进制小数,范围是或者;e是阶码。作用是对浮点数加权。权重是2的e次幂。所以在将一个整数表示成浮点数的时候。我们须要先将整数表示成以2为底的科学计数型,然后依照以下的格式填入对应值就可以。
符号 | 阶码 | 尾数 |
假设是单精度(float)类型,上述格式的符号部分为1位。阶码部分为8位,尾数部分为23位;假设是双精度(double)类型。符号部分为1位,阶码部分为11位,尾数部分为52位。
依据阶码的不同。被编码的值能够分为三种情况(最后一种情况有两个变种)。此外。要注意以下的表是IEEE的浮点数表示,而不是原始浮点数的表示。
1. 规格化的
对上述IEEE浮点数进行实际值转换的时候,阶码和尾数都有变化。IEEE标准中的阶码和我们想象中稍有不同,由于它对我们理解中的阶码进行一个偏置操作。
针对规格化的浮点数,阶码字段被解释为以偏置形式表示的有符号整数。也就是说,浮点数真实的阶码值e=E-Bias,当中E就是IEEE给出的阶码值,Bias是一个等于(单精度是127,双精度是1023)的偏置值。尾数和我们想象中也稍有不同。我们在表示一个浮点数的尾数时,总会表示成1.形式,由于第1位总是1。我们能够将其省略,从而获得一个额外的精度位,因而M真实值是1.。但表现出来却是0.。
2. 非规格化的
在这样的情况下,真实值阶码值e=1-Bias,而不是-Bias。
尾数则不包括隐含的1,即M=0.。
非规格化数有两个用途。首先,它提供了一种表示数值0的方法。由于使用规格化数,我们必须总是使>=1,因此我们就不能表示0。当阶码和尾数都为0时就表示0。只是符号位的不同会产生+0.0与-0.0。
其次,非规格化能够表示很接近于0.0的数。
3a.无穷大
3b.NaN
在《深入理解计算机系统》P72有一个非常具体的样例,建议大家细致阅读。
讲了这么多。我们如今应该明确为什么整数强转成浮点数之后会有精度损失。
这是由于float在计算机表示中尾数(float的有效位)仅仅有23位,而int整数有32位,这就导致在将int转换成float的时候,int的低位就会被truncate。最大可能会产生511的误差。
当然并非全部的强转都会导致精度损失,这也和整数值的有效位数相关,详细指的是前置1和后置1之间的部分。
以下给出一个样例:
void test() { int a=0x100010; int b=0x8000008; printf("a is %d, type cast of a is %f, b is %d, type cast of b is %f ",a,(float)a,b,(float)b); }
上述代码的输出为:a is 1048592, type cast of a is 1048592.000000, b is 134217736, type castof b is 134217728.000000。能够看到a在强转的时候没有精度损失。可是b在强转时产生了精度损失。这是由于a的有效位数小于23位,在强转时float的尾数能够容纳;可是b的前置和后置1之间的位数为23位。这样在强转的时候最低位的1就会被truncate,从而结果产生了8的误差。
上述误差的产生是由尾数位数过少造成的,针对float类型的阶码部分,我们能有什么收获呢?利用类型强转将int转成float之后。阶码部分就保存了这个整数的最高位次,因而我们能够用阶码来求整数的前置1位置,我会在后面的博客中具体介绍整数前置1的位置求法。
3 地址强转
地址强转算是一项比較高级的操作。直接将原始地址的类型进行改变。语法也比較复杂,首先通过&符号获得原类型的地址,然后通过(type*)将地址类型进行强转,最好再利用*取值获得新类型的值。这样的强转在大多数情况下都没有意义。可是一种情况例外。即在将float的地址强转成int地址时会有特殊意义。
由前面的介绍我们知道浮点数在计算机中是通过符号位、阶码和尾数三部分来表示的。给定一个数学上的浮点数x。我们能够将其表示成
当中,e是指数(能够小于0)。f是尾数(大于等于0而且小于1)。两边取对数后有
因为f属于[0,1),所以有
当中是一个无穷小量。
我们将x转化成IEEE格式,然后进行int型地址转换有:
当中。表示浮点数的整数表示,E=e+B表示IEEE阶码值,L=表示阶码的起始位置,F=Lf表示尾数的整数表示。上述公式告诉我们一个关系式,和原始的浮点数x之间存在一个log关系。能够用下图来表示(fromfast inverse square root)。
上述公式有什么意义呢?它能够用在开根号的算法中,详细请參考博客高速浮点平方根运算。
版权声明:本文博主原创文章,博客,未经同意不得转载。