Skip to content

浮点数类型

实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。C 语言提供了三种浮点数类型,对应三种不同的浮点数格式:

  • float
  • double
  • long double

当对精度要求不高时 (比如只有一位小数的运算),我们可以使用 float 类型;大多数情况下,我们都会使用 double 类型;在极少数对精度要求非常高的情况下,才会使用 long double。

C 语言标准并没有明确说明 float, double, long double 类型提供的精度到底是多少,不同的计算机可以使用不同的方式存储浮点数。不过,大多数计算机都遵循 IEEE 754 标准 (也就是说 IEEE 754 是事实上的标准)。

下表展示了遵循 IEEE 标准的浮点数的特征:

类型规范化的最小正值最大值精度
float1.17549 x 10-383.402 82 x 10386位数字
double2.22507 x 10-3081.797 69 x 1030815位数字

long double 类型没有显示在此表中,因为它的长度可能随机器的不同而变化,最常见的是 80 位和 128 位。

感兴趣的同学可以自己查阅组成原理相关的书籍,了解下 IEEE-754 标准~

由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。

浮点数的不精确是绝对的!!!

浮点数有精度限制,比如float可以**"基本保证6位有效数字精确"**,那么是不是我只要在6个有效数字内使用float就能保证数据准确呢?

答案当然不是,浮点数的不精确是绝对的,即便是在有效数字内。

这主要是因为某些十进制小数,转换成浮点数用二进制存储,会出现无限循环的情况,此时有限位数的浮点数必然是无法精确表示无限循环小数的!

比如十进制小数0.1转换成二进制表示:

十进制正小数(0.开头)转换成二进制,先用小数部分乘以2,取结果的整数部分(必然是1或者0):

  1. 然后小数部分继续乘2
  2. 直到小数部分为0,或者已经达到了最大的位数
  3. 最终的结果(0.开头)正序排列

0.1 * 2 = 0.2 ---> 取整数部分0

0.2 * 2 = 0.4 ---> 取整数部分0

0.4 * 2 = 0.8 ---> 取整数部分0

0.8 * 2 = 1.6 ---> 取整数部分1

0.6 * 2 = 1.2 ---> 取整数部分1

0.2 * 2 = 0.4 ---> 取整数部分0

0.4 * 2 = 0.8 ---> 取整数部分0

0.8 * 2 = 1.6 ---> 取整数部分1

....

所以十进制小数0.1转换成二进制小数是:0.000110011001100....(1100四位无限循环)

于是我们可以得出结论:

总之:

浮点数执行的浮点数运算,只是在广泛的数字范围上较为精确而快速的近似运算,当你选择使用浮点数进行运算时,那就意味着数据准确对你而言已经不重要了,也不可能了。

所以不要尝试使用浮点数进行精确的小数运算,不过好在C语言也极少在需求精确小数运算的场景中使用。

不以f结尾的常量是double类型,以f结尾的常量(如3.14f)是float类型。

c
#include <stdio.h>
#include <stdlib.h>

int main() {
    //传统⽅式赋值
    float a = 3.14f;//或3.14F
    double b = 3.14;

    printf("a = %f\n", a);
    printf("b = %lf\n", b);

    //科学法赋值
    a = 3.2e3f;//3.2*1000 = 3200,e可以写E
    printf("a1 = %f\n", a);

    a = 100e-3f;//100*0.001 = 0.1
    printf("a2 = %f\n", a);

    a = 3.1415926f;
    printf("a3 = %f\n", a);//结果为3.141593

    return 0;
}

程序输出:

shell
a = 3.140000
b = 3.140000
a1 = 3200.000000
a2 = 0.100000
a3 = 3.141593

浮点数字面值

浮点数常量有多种书写方式。例如,下面都是 57.0 的有效表示方式:

cpp
57.0   57.   57.0e0   57E0   5.7e1   5.7e+1   .57e2   570.e-1

浮点数必须包含小数点或者是指数;字母 E (或 e) 后面的数字表示以 10 为底的指 数。

默认情况下,浮点常量都是 double 类型。如果需要表明以单精度方式存储,可以在 末尾加字母 F 或 f,如 57.0F 。如果以 long double 方式存储,则在后面加 L 或 l, 如 57.0L

读/写浮点数

前面我们已经讲过,可以使用转换说明符 %f 来读写 float 类型的数据。读写 double 和 long double 类型所需的说明符与 float 略有不同。

  • 读写 double 类型的值时,需要在 f 前面添加字母 l;

    cpp
    double d;
    
    scanf("%lf", &d); 
    printf("%lf", d);
  • 读写 long double 类型的值时,需要在 f 前面添加字母 L。

    cpp
    long double ld;
    
    scanf("%Lf", &ld); 
    printf("%Lf", ld);

精度范围

在C/C++中,floatdouble的精度并不是固定的,它们取决于具体的编译器、硬件平台和IEEE 754浮点数标准的实现。然而,我们可以提供一般性的描述来大致了解它们的精度范围。

float

float类型通常使用32位(4字节)来表示。

  • 符号位(Sign):1位,用于表示正负。
  • 指数(Exponent):8位,用于表示2的幂次方。
  • 尾数(Mantissa):23位,用于表示有效数字。

由于float的尾数只有23位,因此它不能精确地表示所有小数。在十进制下,float通常可以提供大约6~7位有效数字的精度。但这并不意味着小数点前或小数点后的位数固定,而是整个数值的有效数字位数。

double

double类型通常使用64位(8字节)来表示。

  • 符号位(Sign):1位,用于表示正负。
  • 指数(Exponent):11位,用于表示2的幂次方。
  • 尾数(Mantissa):52位,用于表示有效数字。

由于double的尾数有52位,因此它可以比float更精确地表示小数。在十进制下,double通常可以提供大约15~17位有效数字的精度。同样,这并不意味着小数点前或小数点后的位数固定,而是整个数值的有效数字位数。

注意事项

  • 精度并不意味着所有的小数都能被精确地表示。例如,0.1在二进制下是一个无限循环小数,因此无论是float还是double都不能完全精确地表示它。
  • 在进行浮点数运算时,由于舍入误差的存在,结果可能会有轻微的精度损失。
  • 如果你需要更高的精度,可以考虑使用专门的数学库或数据类型,如GMP(GNU多精度算术库)或C++中的decimal类型(某些库提供)。
示例

以下是一个简单的C++程序,演示了floatdouble的精度限制:

cpp
#include <stdio.h>

int main() {  
    float f = 1234567.89f;  
    double d = 1234567.89;  
      
    printf("f = %f\n", f);
    printf("d = %lf\n", d);
      
    return 0;  
}

程序输出:

shell
f = 1234567.875000
d = 1234567.890000

这个程序将输出floatdouble变量的近似值,并展示它们的精度差异。注意,由于舍入误差,输出的值可能与原始赋值略有不同。

IEEE754标准

IEEE754标准