关于C++:float和double的区别是什么?

What is the difference between float and double?

我读过双精度和单精度的区别。然而,在大多数情况下,floatdouble似乎是可互换的,即使用一个或另一个似乎不会影响结果。是真的吗?浮球和双打何时可以互换?他们之间有什么区别?


巨大的差异。

顾名思义,double的精度是float的2倍。一般来说,double有15个十进制的精度数字,而float有7个。

以下是计算位数的方法:

double has 52 mantissa bits + 1 hidden bit: log(253)÷log(10) = 15.95 digits

float has 23 mantissa bits + 1 hidden bit: log(224)÷log(10) = 7.22 digits

当重复计算时,这种精度损失可能导致更大的截断误差累积,例如:

1
2
3
4
5
6
float a = 1.f / 81;
float b = 0;
for (int i = 0; i < 729; ++ i)
    b += a;
printf("%.7g
"
, b); // prints 9.000023

虽然

1
2
3
4
5
6
double a = 1.0 / 81;
double b = 0;
for (int i = 0; i < 729; ++ i)
    b += a;
printf("%.15g
"
, b); // prints 8.99999999999996

另外,float的最大值约为3e38,double约为1.7e308,因此使用float可以比double更容易达到"无穷大"(即特殊的浮点数),例如计算60的阶乘。

在测试期间,可能有一些测试用例包含这些巨大的数字,如果使用float,可能会导致程序失败。

当然,有时甚至double也不够精确,因此有时我们有long double1(上面的例子在Mac上给出了9.00000000000000066),但所有浮点类型都有舍入错误,因此,如果精度非常重要(例如,货币处理),则应使用int或分数类。

此外,不要使用+=来求和大量的浮点数,因为错误会快速累积。如果您使用的是python,请使用fsum。否则,尝试实现Kahan求和算法。

〔1〕:C和C++标准没有指定EDCOX1、1、EDCOX1、0和EDCOX1×9的表示。这三个都有可能实现为IEEE双精度。然而,对于大多数架构(gcc、msvc、x86、x64、arm),float实际上是一个IEEE单精度浮点数(binary32),double是一个IEEE双精度浮点数(binary64)。


以下是标准C99(ISO-IEC 9899,2.2.5×10)或C++ 2003(ISO-IEC 1488—2003—3.1.9×8)标准:

There are three floating point types: float, double, and long double. The type double provides at least as much precision as float, and the type long double provides at least as much precision as double. The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double.

C++标准增加了:

The value representation of floating-point types is implementation-defined.

我建议大家深入研究一下每一位计算机科学家应该知道的关于浮点运算的优秀知识,它涵盖了IEEE浮点标准。您将了解表示细节,并且您将认识到大小和精度之间存在权衡。浮点数表示的精度随着大小的减小而增加,因此-1和1之间的浮点数是精度最高的浮点数。


给定一个二次方程:x2减去4.0000000 x+3.99999999=0,10个有效数字的确切根是,r12.000316228和r21.999683772。

使用floatdouble我们可以编写一个测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <math.h>

void dbl_solve(double a, double b, double c)
{
    double d = b*b - 4.0*a*c;
    double sd = sqrt(d);
    double r1 = (-b + sd) / (2.0*a);
    double r2 = (-b - sd) / (2.0*a);
    printf("%.5f\t%.5f
"
, r1, r2);
}

void flt_solve(float a, float b, float c)
{
    float d = b*b - 4.0f*a*c;
    float sd = sqrtf(d);
    float r1 = (-b + sd) / (2.0f*a);
    float r2 = (-b - sd) / (2.0f*a);
    printf("%.5f\t%.5f
"
, r1, r2);
}  

int main(void)
{
    float fa = 1.0f;
    float fb = -4.0000000f;
    float fc = 3.9999999f;
    double da = 1.0;
    double db = -4.0000000;
    double dc = 3.9999999;
    flt_solve(fa, fb, fc);
    dbl_solve(da, db, dc);
    return 0;
}

运行程序会给我:

1
2
2.00000 2.00000
2.00032 1.99968

请注意,这些数字并不大,但仍然可以使用float获得取消效果。

(事实上,上述方法不是使用单精度或双精度浮点数求解二次方程的最佳方法,但即使使用更稳定的方法,答案仍然保持不变。)


  • 双精度是64,单精度是(float)是32位。
  • 双精度数有一个更大的尾数(实数的整数位)。
  • 任何不准确的地方都会减少。

浮点数计算中涉及的数字大小不是最相关的。正在执行的计算是相关的。

实际上,如果您正在执行一个计算,结果是一个无理数或循环小数,那么当该数被压缩到您使用的有限大小的数据结构中时,就会出现舍入错误。因为double的大小是float的两倍,所以舍入误差会小很多。

测试可能特别使用会导致此类错误的数字,因此测试您是否在代码中使用了适当的类型。


类型float,32位长,精度为7位。虽然它可以存储范围非常大或非常小的值(+/-3.4*10^38或*10^-38),但它只有7个有效数字。

类型double,64位长,具有更大的范围(*10^+/-308)和15位精度。

类型long double名义上是80位,但是给定的编译器/OS对可能会将其存储为12-16字节,以便于对齐。长双精度数的指数非常大,应该有19位数的精度。微软以其无穷的智慧,将长双字节限制为8字节,与普通双字节相同。

一般来说,只需在需要浮点值/变量时使用类型double。默认情况下,表达式中使用的文本浮点值将被视为双精度浮点值,并且大多数返回浮点值的数学函数都返回双精度浮点值。如果你只使用double,你会省去很多头痛和打字。


浮点数的精度比双精度小。尽管你已经知道了,但是为了更好地理解浮点运算,请阅读我们应该知道的内容。


我刚刚遇到了一个错误,我花了很长时间才弄明白,它可能会给您一个关于浮点精度的好例子。

1
2
3
4
5
6
7
8
#include <iostream>
#include <iomanip>

int main(){
  for(float t=0;t<1;t+=0.01){
     std::cout << std::fixed << std::setprecision(6) << t << std::endl;
  }
}

输出是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
0.000000
0.010000
0.020000
0.030000
0.040000
0.050000
0.060000
0.070000
0.080000
0.090000
0.100000
0.110000
0.120000
0.130000
0.140000
0.150000
0.160000
0.170000
0.180000
0.190000
0.200000
0.210000
0.220000
0.230000
0.240000
0.250000
0.260000
0.270000
0.280000
0.290000
0.300000
0.310000
0.320000
0.330000
0.340000
0.350000
0.360000
0.370000
0.380000
0.390000
0.400000
0.410000
0.420000
0.430000
0.440000
0.450000
0.460000
0.470000
0.480000
0.490000
0.500000
0.510000
0.520000
0.530000
0.540000
0.550000
0.560000
0.570000
0.580000
0.590000
0.600000
0.610000
0.620000
0.630000
0.640000
0.650000
0.660000
0.670000
0.680000
0.690000
0.700000
0.710000
0.720000
0.730000
0.740000
0.750000
0.760000
0.770000
0.780000
0.790000
0.800000
0.810000
0.820000
0.830000
0.839999
0.849999
0.859999
0.869999
0.879999
0.889999
0.899999
0.909999
0.919999
0.929999
0.939999
0.949999
0.959999
0.969999
0.979999
0.989999
0.999999

正如您在0.83之后看到的,精度显著下降。

但是,如果我把t设置为双精度,这样的问题就不会发生。

我花了五个小时才意识到这个小错误,它毁了我的程序。


当使用浮点数时,您不能相信您的本地测试将与在服务器端完成的测试完全相同。在本地系统和运行最终测试的地方,环境和编译器可能是不同的。我以前在一些Topcoder竞赛中见过很多次这个问题,特别是当你试图比较两个浮点数时。


内置比较操作的不同之处在于,当您将两个数字与浮点进行比较时,数据类型(即float或double)的差异可能会导致不同的结果。


float和double的区别在于double比float变量具有更高的精度值。当您将变量声明为float时,它只允许在输入小数点后6位小数。也就是说,对于一个浮点变量float f= 2.3333333;//7位小数点后接。是可以存储的最大值

即使您存储的值在小数点后超过六位,它也不会存储整个数字,而是只存储到小数点后的前六位。此外,如果尝试在该变量的小数点后存储超过7位,则会出现错误。在这种情况下,您需要如下初始化它-float f= 2.3333334443f;//则编译器将其解释为2.3333334。

如果是双精度,它将在小数点后最多存储15位数字。例:double d=1.222222345675423;//小数点后15位


与EDOCX1(整数)不同,float有小数点,double也有小数点。但两者的区别在于,double的细节是float的两倍,这意味着它可以有小数点后两倍的数字。