一、计算公式
浮点型变量有两种,即:float和double。float被称作为浮点型,而double被称作是双精度浮点型。double与float的存储原理完全一样,只不过double比float的精度更高而已。
首先来看一下float型内部存储结构:
再看一下double型内部存储结构:
也就是说浮点型变量的内部存储结构为“符号位” + “阶码” + “尾数”。double类型的变量要比float类型的变量精度更高,下面我们就以float类型为例来学习浮点数变量的存储原理。
浮点数的计算公式为:。
例如:有一个float类型的浮点数,在内存中的实际存储值为0xC2F6E979,它的2进制表示为1 10000101 11101101110100101111001根据上面的计算公式来计算:
再来写程序确认一下:
#include <stdio.h> int main(int argc, char **args) { unsigned int i = 0xC2F6E979; float *p = (float *) &i; printf("%.8f\n", *p); return 0; }
运行结果:
-123.45600128
另外还有几个特殊情况需要说明一下:
二、精度
有些书上写的float类型的精度只有6位,而有些书上写的float类型的精度是6 ~ 7位,这是什么原因呢?我们来看一下float尾数所能表示的所有小数:
根据上面的例子可以得出结论:23个bit的尾数可以表示任意小数点后6位的小数,但只能表示有限的几个小数点后7位的小数。所以float类型的变量有效精度只有6 ~ 7位,但准确的说只有6位。而double类型的有效精度为15 ~ 16位,准确的说只有15位。也就是说浮点数是离散的不是所有的小数都可以用float或double来表示,它们所能表示的小数是非常有限的。
三、比较大小
我们所说的浮点数的精度并不是指其值小数点后6位的精度,而是说一个浮点数所能表示的最高位向下6 ~ 7位精度。例如:
#include <stdio.h> int main(int argc, char **args) { float i = 0.123456781; float j = 0.123456782; if (i < j) { printf("i < j\n"); } else if (i > j) { printf("i > j\n"); } else { printf("i == j\n"); } return 0; }
运行结果:
i == j
上面例子可以看见,虽然i > j 但运行结果的确是相等,这是因为当浮点数超出其精度范围时,比较运算也只比较其有效精度。再来看另外一个例子:
#include <stdio.h> int main(int argc, char **args) { float i = 123456781; float j = 123456782; if (i < j) { printf("i < j\n"); } else if (i > j) { printf("i > j\n"); } else { printf("i == j\n"); } return 0; }
运行结果:
i == j
与之前的例子一样,比较结果仍是相等,所以说浮点数的精度并不是指其值小数点后6 ~ 7位的精度,而是说一个浮点数所能表示的最高位向下6位精度。
再来看一个浮点数运算的例子:
#include <stdio.h> int main(int argc, char **args) { float i = 13.19; float j = 13.1; printf("%.7f\n", i - j); printf("%.6f\n", i - j); printf("%.5f\n", i - j); return 0; }
运行结果:
0.0899992 0.089999 0.09000
很奇怪,结果中的前两行不是0.09,而是0.0899992和0.089999。这是因为在float类型表示一个小数时,是采用 形式来表示,也就是说它是由多个很小的小数相加而得的,所以要在其精度范围内四舍五入才能得到正确的结果。
四、赋值与位运算
先来看浮点数与整数之间的赋值问题。我们来写一个程序,让它们相互赋值:
#include <stdio.h> int main(int argc, char **args) { short i = 12; int j = -3456; float k = 123.4; float l = 456.78; //float to short i = k; //int to float l = j; printf("%d\n%f\n", i, l); return 0; }
运行结果:
123 -3456.000000
可见,无论是整型向浮点型赋值还是浮点型向整型赋值,编译器会很“人性化”的将当前数值转化为目标型。
再来看看浮点数的位运算问题:
#include <stdio.h> int main(int argc, char **args) { float i = 1.25; i <<= 1; i >>= 1; i = ~i; i &= 1; i |= 1; return 0; }
gcc在编译上面代码时,会出现下面错误:
make all gcc -m32 -lm -std=c99 main.c -o main main.c: In function ‘main’: main.c:6:4: error: invalid operands to binary << (have ‘float’ and ‘int’) i <<= 1; ^ main.c:7:4: error: invalid operands to binary >> (have ‘float’ and ‘int’) Makefile:2: recipe for target 'all' failed i >>= 1; ^ main.c:8:6: error: wrong type argument to bit-complement i = ~i; ^ main.c:9:4: error: invalid operands to binary & (have ‘float’ and ‘int’) i &= 1; ^ main.c:10:4: error: invalid operands to binary | (have ‘float’ and ‘int’) i |= 1; ^ make: *** [all] Error 1
也就是说浮点数是不能进程位运算的(可以利用指针来做浮点数的位运算)。但是如果使用其它的编译器说不定允许浮点数做位运算,但这并不实际,如果让浮点数做位运算的话将会影响符号位、阶码和尾数,结果是很难预料的。所以浮点数尽量不要做位运算。
Copyright © 2015-2023 问渠网 辽ICP备15013245号