我已经了解了双精度和单精度之间的差异。然而,在大多数情况下,float
和 double
似乎可以互换,也就是说,使用其中一个似乎不会影响结果。这是真的吗?何时可以用浮点数和双精度互换?它们之间有什么区别?
我已经了解了双精度和单精度之间的差异。然而,在大多数情况下,float
和 double
似乎可以互换,也就是说,使用其中一个似乎不会影响结果。这是真的吗?何时可以用浮点数和双精度互换?它们之间有什么区别?
double
比float
具有两倍的精度[1]。一般而言,double
具有15位小数精度,而float
只有7位。这种精度损失可能会导致在重复计算时积累更大的截断误差,例如。
double
具有52位有效数字+1隐藏位:log(253)÷log(10) = 15.95位
float
具有23位有效数字+1隐藏位:log(224)÷log(10) = 7.22位
float a = 1.f / 81;
float b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.7g\n", b); // prints 9.000023
当
double a = 1.0 / 81;
double b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.15g\n", b); // prints 8.99999999999996
double
也不够准确,因此我们有时会使用long double
[1](上面的例子在Mac上得到9.000000000000000066),但所有浮点类型都会受到舍入误差的影响,因此如果精度非常重要(例如处理货币),应该使用int
或分数类。
+=
来对大量浮点数进行求和,因为误差会很快积累。如果您正在使用Python,请使用fsum
。否则,请尝试实现Kahan求和算法。
[1]: C和C++标准没有规定float
, double
和long double
的表示方式。所有三个数据类型在实现上可能都使用IEEE双精度浮点数。然而,对于大多数架构(gcc、MSVC;x86、x64、ARM),float
确实是IEEE单精度浮点数(binary32),而double
则是IEEE双精度浮点数(binary64)。
以下是标准C99(ISO-IEC 9899 6.2.5 §10)或C++2003(ISO-IEC 14882-2003 3.1.9 §8)的规定:
有三种浮点类型:
float
、double
和long double
。类型double
提供至少与float
相同的精度,而类型long double
提供至少与double
相同的精度。类型float
的值集是类型double
的值集的子集;类型double
的值集是类型long double
的值集的子集。
C++标准补充道:
浮点类型的值表示是实现定义的。
我建议查看优秀的计算机科学家应该了解的浮点运算的一切,深入了解IEEE浮点标准。你会了解到表示细节,并意识到精度和量级之间存在权衡。浮点表示的精度随着数量级的降低而增加,因此在-1和1之间的浮点数具有最高的精度。
#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\n", 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\n", 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;
}
2.00000 2.00000
2.00032 1.99968
我刚遇到一个错误,花了很长时间才找出来,这可能会给你提供一个浮点精度的好例子。
#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;
}
}
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
设置为双精度浮点数,则不会出现这样的问题。
我花了五个小时才意识到这个小错误,它毁了我的程序。
double
不是一个好的解决方案。你应该使用int
进行计数,并进行内部乘法以获取浮点值。 - Richard浮点数计算中涉及的数字大小并不是最重要的因素,而是计算本身才是关键。
实际上,如果你进行一次计算,其结果为无理数或循环小数,那么当这个数被压缩到所使用的有限数据结构中时,就会出现舍入误差。双精度(double)比单精度(float)大两倍,因此舍入误差会小得多。
测试可能会特别使用会导致此类错误的数字,从而测试你是否在代码中使用了适当的类型。
浮点型数据类型分为三种:float、double和long double。
其中,float类型占用32位内存,精度为7位有效数字。虽然它可以储存非常大或非常小的范围内的值(+/- 3.4 * 10^38 或 * 10^-38),但只有7位有效数字。
而double类型则占用64位内存,范围更广 (*10^+/-308),精度为15位有效数字。
long double类型在理论上占用80位内存,但由于编译器/操作系统配对的原因,可能会将其存储为12-16字节以对齐。long double类型的指数极大,精度可达19位有效数字。不过,微软限制了long double类型的大小为8字节,与普通double类型相同。
一般来说,在需要使用浮点数值/变量时,建议使用double类型。默认情况下,在表达式中使用的字面浮点数值将被视为double类型,并且返回浮点数值的大多数数学函数也会返回double类型。如果您只使用double类型,就可以避免许多头痛和类型转换问题。
在使用浮点数时,您不能保证本地测试与服务器端执行的测试完全相同。您本地系统的环境和编译器可能与最终测试运行的环境不同。我曾经在一些TopCoder比赛中多次遇到这个问题,特别是当您尝试比较两个浮点数时。
double
作为一个名称甚至不暗示浮点数,更不用说精度了。这个术语绝对不是自解释的,但却像癌症一样无处不在地传播开来。我总是很难记住它是双精度、双值范围还是双位大小。 - undefined