目录
一、浮点数定义与特点
既有整数部分、又有小数部分的实数。浮点数在计算机中用以近似表示任意某个实数。浮点计算是指浮点数参与的运算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入。 常见的浮点数∶3.14159 ,1E10,浮点数家族包括:float、double、long double类型。
二、浮点数的存储与读取
整数是以补码(通过原码求出补码)的形式存储在内存中。那浮点数9.1234567应该怎么存呢?也按补码存吗?如果是的话,整数是直接出原码,但浮点数的小数点该怎么处理?
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
🙋🏻♂️举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
类似于科学表示法。1<=M<2,M写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
存
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754对有效数字M和指数E,还有一些特别规定。对于有线数字M:
由于1<=M<2,M总是可以写成1.xxx*10^n 。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此
存储时不存这个1,只存储后面的xxx部分。
比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂:
首先,规定E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的。所以IEEE 754规定,
存入内存时E的真实值必须再加上一个中间数
。对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^-1的E是-1。所以保存成32位浮点数时,必须保存成-1+127=126:即01111110。
取
然后,指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示:
即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为
1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000
这个时候是:怎么存就怎么取
E全为0
这时,
浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
此时:存储在内存中的E是
加了
中间数
127的指数
。加了127变成的全为0,说明原来的数是个负数,而且是个比较大的负数。-> 指数很小作为分母,浮点数很小。
E全为1
这时,
如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
此时:存储在内存中的E是
加了
中间数
127的指数
。加了127变成的全为1,说明原来的数也是个比较大的整数,-> 指数很大,浮点数大。
三、浮点数存储带来的特性
先解释一下开头的一句话: 浮点数在计算机中用以
近似表示
任意某个实数。
举例1:25.875
存储时,先将整数部分和小数部分化为二进制。结果为11001.111
举例2:25.873
整数部分同理,但是小数部分会一直循环。但是我们知道不管是32位还是64位,小数部分只有那么多空间会有精度限制。对于超出了精度限制的浮点数,计算机会把它们的精度之外的小数部分截断。这个时候只能近似表示25.873。
1. 比较大小
不可将浮点变量用“==”或“!=”与任何数字比较。
由于有些数是近似存储,计算机会把它们的精度之外的小数部分截断。例如: float a=10.222222225,b=10.222222229 ,数学上a和b是不相等的,但在32位计算机中它们是相等的。
整数9和浮点数9.0,在数学意义上相等。但是由于存储机制不同,计算机会认为他们不等。
那应该怎么比较浮点数大小呢?
定义一个精度,用差的绝对值比较,差值在精度范围内就认为是相等的。所以两个浮点数进行比较时,应设法转化成“>=”或“<=”形式。
代码示例:
#include <stdio.h>
#include<math.h>
#define EPS 1e-7 //判断浮点数是否位于0的一个很小的邻域内[-EPS,EPS]内
int main() {
//判断一个浮点数是否等于0
float a;
scanf("%f", &a);
if (fabs(a) <= EPS) { printf("=0"); }
else if (a > EPS) { printf(">0"); }
else { printf("<0"); }
//比较两个浮点数大小
float a, b;
scanf("%f %f", &a, &b);
if (fabs(a - b) <= EPS) { printf("a=b"); }
else if ((a - b) > EPS) { printf("a>b"); }
else { printf("a<b"); }
return 0;
}
2. 浮点数相减
再接着解释开头的一句话:浮点计算是指浮点数参与的运算,这种运算通常伴随着因为
无法精确表示而进行的近似或舍入
。
在计算机中,加减法运算用
补码
实现。
算术运算的常识:两个浮点数如果要进行加减法运算,它们的
阶或者指数必须相等
。浮点数运算比较复杂,涉及到对阶、位数求和、规格化、舍入、溢出判断等步骤。 暂时记住如果两个浮点数比较大,直接相减的话,浮点数损失的精度多。所以通常我们不用浮点数相减判断大小,而是用>或者<比较大小。