【问题标题】:What is the difference between float and double?浮点数和双精度数有什么区别?
【发布时间】:2011-01-24 02:42:25
【问题描述】:

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

【问题讨论】:

    标签: c++ c floating-point precision ieee-754


    【解决方案1】:

    巨大的差异。

    顾名思义,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),但所有浮点类型遭受舍入误差,因此如果精度非常重要(例如货币处理),您应该使用int 或分数类。


    此外,不要使用+= 对大量浮点数求和,因为错误会迅速累积。如果您使用的是 Python,请使用 fsum。否则,尝试实现Kahan summation algorithm


    [1]:C 和 C++ 标准没有指定 floatdoublelong double 的表示形式。有可能所有三个都实现为 IEEE 双精度。尽管如此,对于大多数架构(gcc、MSVC;x86、x64、ARM)float确实是一个 IEEE 单精度浮点数(binary32),而double em> 一个 IEEE 双精度浮点数 (binary64)。

    【讨论】:

    • 求和的通常建议是在求和之前按大小(从小到大)对浮点数进行排序。
    • 请注意,虽然 C/C++ float 和 double 几乎总是分别是 IEEE 单精度和双精度,但 C/C++ long double 的可变性要大得多,具体取决于您的 CPU、编译器和操作系统。有时它与双精度相同,有时是某些系统特定的扩展格式,有时是 IEEE 四精度。
    • @R..GitHubSTOPHELPINGICE:为什么?你能解释一下吗?
    • @InQusitive:例如考虑一个由值 2^24 和 2^24 重复值 1 组成的数组。按顺序求和产生 2^24。反转产生 2^25。当然,您可以举一些例子(例如,将 1 重复 2^25 次),其中任何顺序最终都会因单个累加器而出现灾难性错误,但最小量级优先是其中最好的。为了做得更好,你需要某种树。
    • @R..GitHubSTOPHELPINGICE:如果数组同时包含正数和负数,求和会更加棘手。
    【解决方案2】:

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

    共有三种浮点类型:floatdoublelong doubledouble 类型提供的精度至少与float 一样高,long double 类型提供的精度至少与double 一样高。 float 类型的值集是double 类型的值集的子集; double 类型的值集是 long double 类型的值集的子集。

    C++ 标准增加了:

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

    我建议您看看优秀的What Every Computer Scientist Should Know About Floating-Point Arithmetic,它深入涵盖了 IEEE 浮点标准。您将了解表示的详细信息,并且您会意识到在幅度和精度之间存在权衡。浮点表示的精度随着幅度的减小而增加,因此-1和1之间的浮点数是精度最高的。

    【讨论】:

      【解决方案3】:

      给定一个二次方程:x2 − 4.0000000 x + 3.9999999 = 0,10 位有效数字的精确根是,r1 = 2.000316228 和 r2 = 1.999683772。

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

      #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,答案也不会改变。)

      【讨论】:

        【解决方案4】:
        • 双精度为 64,单精度 (float) 是 32 位。
        • 双精度数的尾数(实数的整数位)更大。
        • 双精度中的任何不准确之处都会更小。

        【讨论】:

          【解决方案5】:

          我刚刚遇到了一个错误,我花了很长时间才弄清楚,并且可能会给你一个浮点精度的好例子。

          #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 吗?如果你想迭代 100 次,你应该使用 int 而不是使用 double
          • 在这里使用double 不是一个好的解决方案。您使用int 进行计数并进行内部乘法以获得您的浮点值。
          【解决方案6】:

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

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

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

          【讨论】:

            【解决方案7】:

            类型 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,你会省去很多麻烦和类型转换。

            【讨论】:

            【解决方案8】:

            浮点类型共有三种:

            • 浮动
            • 长双

            一个简单的维恩图将解释: 类型的值集

            【讨论】:

              【解决方案9】:

              浮点数的精度低于双精度数。虽然您已经知道,但请阅读 What WE Should Know About Floating-Point Arithmetic 以获得更好的理解。

              【讨论】:

              【解决方案10】:

              使用浮点数时,您不能相信本地测试与在服务器端完成的测试完全相同。本地系统和运行最终测试的环境和编译器可能不同。我之前在一些 TopCoder 比赛中已经多次看到这个问题,尤其是当你尝试比较两个浮点数时。

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  【解决方案12】:

                  如果使用嵌入式处理,最终底层硬件(例如 FPGA 或某些特定处理器/微控制器模型)将在硬件中以最佳方式实现浮点,而双精度将使用软件例程。因此,如果浮点数的精度足以满足需求,那么使用浮点数时程序的执行速度会快几倍然后加倍。如其他答案所述,请注意累积错误。

                  【讨论】:

                    【解决方案13】:

                    从数量上看,正如其他答案所指出的那样,区别在于 double 类型的精度大约是 float 类型的两倍,范围是 float 的三倍(取决于您的计数方式)。

                    但也许更重要的是质的差异。类型 float 具有良好的精度,无论您在做什么,这通常都足够好。另一方面,类型double 具有出色 的精度,无论您在做什么,它几乎总是足够好。

                    结果并不像应有的那样广为人知,您应该几乎总是使用类型double。除非你有一些特别的需要,否则你几乎不应该使用类型float

                    众所周知,在进行浮点运算时,“舍入误差”通常是一个问题。舍入误差可能很微妙,难以追踪,也难以修复。大多数程序员没有时间或专业知识来追踪和修复浮点算法中的数值错误——因为不幸的是,每种不同算法的细节最终都会有所不同。但是类型double 具有足够的精度,因此在大多数情况下,您不必担心。 无论如何你都会得到好的结果。另一方面,使用float 类型时,会出现令人担忧的舍入问题all the time

                    而在floatdouble 类型之间总是不同的是执行速度。在当今的大多数通用处理器上,floatdouble 类型的算术运算所花费的时间或多或少完全相同。一切都是并行完成的,因此您无需为double 类型的更大范围和精度支付速度损失。这就是为什么提出这样的建议是安全的,除非你有一些特别的需要,否则你几乎不应该使用类型float。 (话虽如此,其中一个特殊需求是当您在微控制器上进行嵌入式工作时,或编写针对 GPU 优化的代码时。在这些处理器上,键入 double 可能会慢得多,或者几乎不存在,所以程序员通常会选择 float 类型以提高速度,并以精度为代价。)

                    【讨论】:

                      【解决方案14】:

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

                      【讨论】:

                      • 根本不是这个意思。它实际上意味着 integral 十进制数字的两倍,并且是两倍多。小数位数和精度之间的关系不是线性的:它取决于值:例如0.5 是精确的,但 0.33333333333333333333 不是。
                      猜你喜欢
                      • 2010-10-22
                      • 2023-03-18
                      • 1970-01-01
                      • 2018-02-23
                      • 1970-01-01
                      • 2011-03-20
                      • 2017-08-05
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多