浮点数类型
实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。C 语言提供了三种浮点数类型,对应三种不同的浮点数格式:
- float
- double
- long double
当对精度要求不高时 (比如只有一位小数的运算),我们可以使用 float 类型;大多数情况下,我们都会使用 double 类型;在极少数对精度要求非常高的情况下,才会使用 long double。
C 语言标准并没有明确说明 float, double, long double 类型提供的精度到底是多少,不同的计算机可以使用不同的方式存储浮点数。不过,大多数计算机都遵循 IEEE 754 标准 (也就是说 IEEE 754 是事实上的标准)。
下表展示了遵循 IEEE 标准的浮点数的特征:
类型 | 规范化的最小正值 | 最大值 | 精度 |
---|---|---|---|
float | 1.17549 x 10-38 | 3.402 82 x 1038 | 6位数字 |
double | 2.22507 x 10-308 | 1.797 69 x 10308 | 15位数字 |
long double 类型没有显示在此表中,因为它的长度可能随机器的不同而变化,最常见的是 80 位和 128 位。
感兴趣的同学可以自己查阅组成原理相关的书籍,了解下 IEEE-754 标准~
由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。
浮点数的不精确是绝对的!!!
浮点数有精度限制,比如float可以**"基本保证6位有效数字精确"**,那么是不是我只要在6个有效数字内使用float就能保证数据准确呢?
答案当然不是,浮点数的不精确是绝对的,即便是在有效数字内。
这主要是因为某些十进制小数,转换成浮点数用二进制存储,会出现无限循环的情况,此时有限位数的浮点数必然是无法精确表示无限循环小数的!
比如十进制小数
0.1
转换成二进制表示:十进制正小数(0.开头)转换成二进制,先用小数部分乘以2,取结果的整数部分(必然是1或者0):
- 然后小数部分继续乘2
- 直到小数部分为0,或者已经达到了最大的位数
- 最终的结果(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类型。
#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;
}
程序输出:
a = 3.140000
b = 3.140000
a1 = 3200.000000
a2 = 0.100000
a3 = 3.141593
浮点数字面值
浮点数常量有多种书写方式。例如,下面都是 57.0 的有效表示方式:
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;
cppdouble d; scanf("%lf", &d); printf("%lf", d);
读写 long double 类型的值时,需要在 f 前面添加字母 L。
cpplong double ld; scanf("%Lf", &ld); printf("%Lf", ld);
精度范围
在C/C++中,float
和double
的精度并不是固定的,它们取决于具体的编译器、硬件平台和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++程序,演示了float
和double
的精度限制:
#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;
}
程序输出:
f = 1234567.875000
d = 1234567.890000
这个程序将输出float
和double
变量的近似值,并展示它们的精度差异。注意,由于舍入误差,输出的值可能与原始赋值略有不同。