浮点型和双精度型有什么区别?

528

我已经了解了双精度和单精度之间的差异。然而,在大多数情况下,floatdouble 似乎可以互换,也就是说,使用其中一个似乎不会影响结果。这是真的吗?何时可以用浮点数和双精度互换?它们之间有什么区别?

14个回答

629
巨大的差别。
正如名称所示,doublefloat具有两倍的精度[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

此外,float 的最大值约为 3e38,而 double 约为 1.7e308,因此在处理一些简单的问题时,如计算阶乘 60,使用 float 比 double 更容易遇到"无穷大"(即特殊的浮点数)。 在测试期间,可能会有一些测试用例包含这些巨大的数字,如果使用 float,这可能导致程序失败。
当然,有时候,即使是double也不够准确,因此我们有时会使用long double[1](上面的例子在Mac上得到9.000000000000000066),但所有浮点类型都会受到舍入误差的影响,因此如果精度非常重要(例如处理货币),应该使用int或分数类。
此外,不要使用+=来对大量浮点数进行求和,因为误差会很快积累。如果您正在使用Python,请使用fsum。否则,请尝试实现Kahan求和算法

[1]: C和C++标准没有规定float, doublelong double的表示方式。所有三个数据类型在实现上可能都使用IEEE双精度浮点数。然而,对于大多数架构(gcc、MSVC;x86、x64、ARM),float确实是IEEE单精度浮点数(binary32),而double则是IEEE双精度浮点数(binary64)。


19
在对一组浮点数求和之前,通常的建议是按照大小顺序(从小到大)排列它们,然后再进行求和。 - R.. GitHub STOP HELPING ICE
2
请注意,虽然C/C++中的float和double几乎总是分别对应IEEE单精度和双精度,但C/C++中的long double则因CPU、编译器和操作系统而异。有时它与double相同,有时它是某些特定于系统的扩展格式,有时它是IEEE四倍精度。 - plugwash
@R..GitHubSTOPHELPINGICE:为什么?你能解释一下吗? - InQusitive
2
考虑一个由值2^24和2^24个值为1的重复组成的数组。按顺序求和得到2^24。反转后得到2^25。当然,你可以举出例子(例如,将其变为2^25个1的重复),其中任何顺序都会因单个累加器而彻底失败,但最小幅度优先是这种情况下最好的选择。要做得更好,你需要某种类型的树。 - R.. GitHub STOP HELPING ICE
3
如果数组包含正数和负数,求和会更加棘手。 - chqrlie
double作为一个名称甚至不暗示浮点数,更不用说精度了。这个术语绝对不是自解释的,但却像癌症一样无处不在地传播开来。我总是很难记住它是双精度、双值范围还是双位大小。 - undefined

62

以下是标准C99(ISO-IEC 9899 6.2.5 §10)或C++2003(ISO-IEC 14882-2003 3.1.9 §8)的规定:

有三种浮点类型:floatdoublelong double。类型double提供至少与float相同的精度,而类型long double提供至少与double相同的精度。类型float的值集是类型double的值集的子集;类型double的值集是类型long double的值集的子集。

C++标准补充道:

浮点类型的值表示是实现定义的。

我建议查看优秀的计算机科学家应该了解的浮点运算的一切,深入了解IEEE浮点标准。你会了解到表示细节,并意识到精度和量级之间存在权衡。浮点表示的精度随着数量级的降低而增加,因此在-1和1之间的浮点数具有最高的精度。


32
给定一个二次方程:x² - 4.0000000x + 3.9999999 = 0,精确到10位有效数字的根为r₁ = 2.000316228和r₂ = 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

请注意,这些数字并不大,但是使用浮点数仍然会产生抵消效应。
(实际上,以上方法既不是使用单精度浮点数,也不是使用双精度浮点数解决二次方程的最佳方式,但即使使用更稳定的方法,答案仍然不变。)

19
  • 一个双精度浮点数(double)为64位,单精度浮点数(float)为32位。
  • 双精度浮点数(double)拥有更大的尾数(即实数的整数位)。
  • 在双精度浮点数(double)中任何不精确性将会更小。

14

我刚遇到一个错误,花了很长时间才找出来,这可能会给你提供一个浮点精度的好例子。

#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设置为双精度浮点数,则不会出现这样的问题。

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


5
确定一下:你的问题解决方案应该是使用 int 类型吗?如果你想要迭代 100 次,使用 int 类型进行计数比使用 double 类型更好。 - BlueTrin
10
在这里使用double不是一个好的解决方案。你应该使用int进行计数,并进行内部乘法以获取浮点值。 - Richard

13

有三种浮点数类型:

  • float
  • double
  • long double

一个简单的维恩图将说明这些类型的值集:

enter image description here


12

浮点数计算中涉及的数字大小并不是最重要的因素,而是计算本身才是关键。

实际上,如果你进行一次计算,其结果为无理数或循环小数,那么当这个数被压缩到所使用的有限数据结构中时,就会出现舍入误差。双精度(double)比单精度(float)大两倍,因此舍入误差会小得多。

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


10

浮点型数据类型分为三种: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类型,就可以避免许多头痛和类型转换问题。


实际上,对于浮点数[它在7到8之间,确切地说是7.225](http://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32)。 - Peter Mortensen

9

例如,所有AVR双精度浮点数都是浮点数(四字节)。 - Peter Mortensen

3

在使用浮点数时,您不能保证本地测试与服务器端执行的测试完全相同。您本地系统的环境和编译器可能与最终测试运行的环境不同。我曾经在一些TopCoder比赛中多次遇到这个问题,特别是当您尝试比较两个浮点数时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接