我已经阅读了双精度和单精度之间的区别。但是,在大多数情况下,float
和 double
似乎可以互换,即使用其中一个似乎不会影响结果。真的是这样吗?浮点数和双精度数何时可以互换?它们之间有什么区别?
巨大的差异。
顾名思义,double
的精度是 float
[1] 的 2 倍。通常,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
此外,float 的最大值约为 3e38
,但 double 约为 1.7e308
,因此对于简单的事情,使用 float
可以比 double
更容易地达到“无穷大”(即特殊的浮点数) ,例如计算 60 的阶乘。
在测试过程中,可能有一些测试用例包含这些巨大的数字,如果使用浮点数,可能会导致程序失败。
当然,有时,即使 double
也不够准确,因此我们有时会有 long double
[1](上面的示例在 Mac 上给出 9.000000000000000066),但所有浮点类型都会受到 < em>四舍五入误差,因此如果精度非常重要(例如货币处理),您应该使用 int
或分数类。
此外,不要使用 +=
对大量浮点数求和,因为错误会迅速累积。如果您使用的是 Python,请使用 fsum
。否则,请尝试实施 Kahan summation algorithm。
[1]:C 和 C++ 标准没有指定 float
、double
和 long double
的表示。有可能所有三个都实现为 IEEE 双精度。尽管如此,对于大多数架构(gcc、MSVC;x86、x64、ARM)float
is 确实是 IEEE 单精度浮点数(binary32),而 double
is em> 一个 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 浮点标准的优秀 What Every Computer Scientist Should Know About Floating-Point Arithmetic。您将了解表示的详细信息,并且您会意识到在幅度和精度之间存在权衡。浮点表示的精度随着幅度的减小而增加,因此介于 -1 和 1 之间的浮点数是精度最高的。
给定一个二次方程:x2 - 4.0000000 x + 3.9999999 = 0,10 位有效数字的精确根为 r1 = 2.000316228 和 r2 = 1.999683772。
使用 float
和 double
,我们可以编写一个测试程序:
#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
请注意,数字并不大,但您仍然可以使用 float
获得取消效果。
(事实上,上述方法不是使用单精度或双精度浮点数求解二次方程的最佳方法,但即使使用 more stable method,答案也不会改变。)
双精度是 64,单精度(浮点)是 32 位。
双精度数的尾数更大(实数的整数位)。
双精度中的任何不准确性都会更小。
我刚刚遇到了一个错误,我花了很长时间才弄清楚,并且可能会给你一个浮点精度的好例子。
#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,则不会发生此类问题。
我花了五个小时才意识到这个小错误,它毁了我的程序。
double
不是一个好的解决方案。您使用 int
进行计数并进行内部乘法以获得浮点值。
浮点计算中涉及的数字大小并不是最相关的事情。正在执行的计算是相关的。
从本质上讲,如果您正在执行计算并且结果是无理数或循环小数,那么当将该数字压缩到您正在使用的有限大小的数据结构中时会出现舍入错误。由于 double 是 float 大小的两倍,因此舍入误差会小很多。
测试可能会专门使用会导致此类错误的数字,因此测试您在代码中使用了适当的类型。
类型 float,32 位长,精度为 7 位。虽然它可能存储范围非常大或非常小的值(+/- 3.4 * 10^38 或 * 10^-38),但它只有 7 个有效数字。
双精度类型,64 位长,具有更大的范围 (*10^+/-308) 和 15 位精度。
类型 long double 名义上是 80 位,尽管给定的编译器/操作系统配对可能会将其存储为 12-16 字节以用于对齐目的。 long double 的指数非常大,应该有 19 位的精度。微软以其无限的智慧将 long double 限制为 8 个字节,与普通 double 相同。
一般来说,当您需要浮点值/变量时,只需使用 double 类型即可。默认情况下,表达式中使用的文字浮点值将被视为双精度值,并且大多数返回浮点值的数学函数都返回双精度值。如果你只使用 double,你会省去很多麻烦和类型转换。
共有三种浮点类型:
漂浮
双倍的
长双
一个简单的维恩图将解释: 类型的值集
https://i.stack.imgur.com/R5LXW.jpg
浮点数的精度低于双精度数。尽管您已经知道,但请阅读 What WE Should Know About Floating-Point Arithmetic 以获得更好的理解。
使用浮点数时,您不能相信您的本地测试与在服务器端完成的测试完全相同。本地系统和运行最终测试的环境和编译器可能不同。我之前在一些 TopCoder 比赛中已经多次看到这个问题,尤其是当你尝试比较两个浮点数时。
内置比较操作的不同在于,当您将 2 个数字与浮点数进行比较时,数据类型(即浮点数或双精度数)的差异可能会导致不同的结果。
如果使用嵌入式处理,最终底层硬件(例如 FPGA 或某些特定处理器/微控制器模型)将在硬件中以最佳方式实现浮点,而双精度将使用软件例程。因此,如果浮点数的精度足以满足需求,则程序的执行速度会比浮点数快几倍,然后加倍。如其他答案所述,请注意累积错误。
从数量上讲,正如其他答案所指出的那样,不同之处在于 double
类型的精度大约是 float
类型的两倍,范围是 float
的三倍(取决于您的计数方式)。
但也许更重要的是质的差异。类型 float
具有良好的精度,无论您在做什么,它通常都足够好。另一方面,类型 double
具有优秀的精度,无论您在做什么,它几乎总是足够好。
结果并不像应有的那样广为人知,您应该几乎总是使用类型 double
。除非您有一些特别的需要,否则您几乎不应该使用类型 float
。
众所周知,在进行浮点运算时,“舍入误差”通常是一个问题。舍入误差可能很微妙,难以追踪,也难以修复。大多数程序员没有时间或专业知识来追踪和修复浮点算法中的数值错误——因为不幸的是,每种不同算法的细节最终都会有所不同。但是类型 double
具有足够的精度,因此在大多数情况下,您不必担心。无论如何你都会得到好的结果。另一方面,对于类型 float
,会出现看起来令人担忧的舍入问题 all the time。
类型 float
和 double
之间不始终不同的是执行速度。在当今的大多数通用处理器上,类型 float
和 double
的算术运算所花费的时间或多或少完全相同。一切都是并行完成的,因此您无需为类型 double
的更大范围和精度支付速度损失。这就是为什么提出建议是安全的,除非您有一些特别的需要,否则您几乎不应该使用类型 float
。 (话虽如此,其中一个特殊需求是当您在微控制器上进行嵌入式工作,或编写针对 GPU 优化的代码时。在这些处理器上,类型 double
可能会显着变慢,或者几乎不存在,因此程序员通常会选择类型 float
以提高速度,并以精度为代价。)
与 int
(整数)不同,float
有小数点,double
也有。但两者的区别在于,double
的详细程度是 float
的两倍,这意味着它可以有双倍的小数点后数字。