C语言深处

    返回首页    发表留言
本文作者:李德强
          第三节 浮点型变量
 
 

一、计算公式

        浮点型变量有两种,即:float和double。float被称作为浮点型,而double被称作是双精度浮点型。double与float的存储原理完全一样,只不过double比float的精度更高而已。

  • float       浮点型变量               4byte
  • double    双精度浮点型变量     8byte

        首先来看一下float型内部存储结构:


 

        再看一下double型内部存储结构:

 

        也就是说浮点型变量的内部存储结构为“符号位” + “阶码” + “尾数”。double类型的变量要比float类型的变量精度更高,下面我们就以float类型为例来学习浮点数变量的存储原理。

  • 符号位S:1bit,0代表正数,1代表负数。
  • 阶码E:8bit,可以存放0 ~255,为了让浮点数的E能够表示正数和负数,在计算时要将E减去127,这样的话阶码E就可以表示-127 ~ 128。
  •  尾数F:23bit,从22bit开始向低位扩展,其位为1时分别表示当前bit位的浮点值为,也就是0.5、0.25、0.125、0.0625 ……  0.000000119。将所有为1的尾数计算完毕之后再加1,也就是1.xxxx这样的科学计数法形式。

        浮点数的计算公式为:

        例如:有一个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

         另外还有几个特殊情况需要说明一下:

  • 如果阶码E的值为0并且尾数F也为0,则表示浮点数0。此时浮点数受符号位S的影响,可以是+0或-0但它们的数值为相等均为0。比如0x00000000表示+0,0x80000000表示-0。
  • 若阶码E的值0但尾数F不为0时,计算公式为。注意,此时的阶码E为126而不是0 - 127 = -127,尾数是F而不是1 + F。例如0x80000001的浮点数值为
  • 如果阶码E为255并且尾数F不为0时表示非数值,也就是说是非法数,例如0x7F800001。
  • 如果阶码E为255并且尾数F为0时表示无穷大的数,此时浮点数受S位影响,例如0x7F800000表示正无穷大,0xFF800000表示负无穷大。

 

二、精度

        有些书上写的float类型的精度只有6位,而有些书上写的float类型的精度是6 ~ 7位,这是什么原因呢?我们来看一下float尾数所能表示的所有小数:

  • 如果想要表示0.0000001可以使用 来表示。
  • 如果想要表示0.0000002可以使用来表示。
  • 如果想要表示0.0000003可以使用来表示。
  • 如果想要表示0.0000004可以使用 来表示。
  • 如果想要表示0.0000005可以使用 来表示。
  • 如果想要表示0.0000006用什么来表示?使用 太大,而则太小。

        根据上面的例子可以得出结论: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-2018 问渠网 辽ICP备15013245号