使用 float 而不是 double 时出现奇怪的输出

Strange output when using float instead of double

当我使用 float 而不是 double

时出现奇怪的输出
#include <stdio.h>
void main()
{
    double p,p1,cost,cost1=30;
    for (p = 0.1; p < 10;p=p+0.1)
    {
        cost = 30-6*p+p*p;
        if (cost<cost1)
        {
            cost1=cost;
            p1=p;
        }
        else
        {
            break;
        }
        printf("%lf\t%lf\n",p,cost);
    }
    printf("%lf\t%lf\n",p1,cost1);
}

p = 3 时给出预期的输出;

但是当我使用 float 时,输出有点奇怪。

#include <stdio.h>
void main()
{
    float p,p1,cost,cost1=40;
    for (p = 0.1; p < 10;p=p+0.1)
    {
        cost = 30-6*p+p*p;
        if (cost<cost1)
        {
            cost1=cost;
            p1=p;
        }
        else
        {
            break;
        }
        printf("%f\t%f\n",p,cost);
    }
    printf("%f\t%f\n",p1,cost1);
}

为什么第二种情况下p的增量在2.7之后变得奇怪了?

发生这种情况是因为 floatdouble 数据类型以 2 为基数存储数字。大多数以 10 为基数的数字无法准确存储。使用 floats 时舍入误差加起来更快。在内存有限的嵌入式应用程序之外,出于这个原因,使用 doubles 通常更好,或者至少更容易。

要查看 double 类型的情况,请考虑以下代码的输出:

#include <stdio.h>
int main(void)
{
    double d = 0.0;
    for (int i = 0; i < 100000000; i++)
        d += 0.1;
    printf("%f\n", d);
    return 0;
}

在我的电脑上,它输出 9999999.981129。所以经过 1 亿次迭代后,舍入误差使结果相差 0.018871。

有关浮点数据类型如何工作的更多信息,请阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic. Or, as akira mentioned in a comment, see the Floating-Point Guide

您的程序可以与 float 一起正常工作。您不需要 double 来计算 100 个值的 table 到几个有效数字。您 可以 使用 double,如果您这样做,即使您在交叉用途中使用二进制浮点二进制,它也有机会工作。大多数 C 编译器用于 double 的 IEEE 754 双精度格式非常精确,使得许多浮点误用不明显(但不是全部)。

简单的十进制值在二进制中可能不简单

结果是简单的十进制值可能无法用二进制精确表示。

0.1 就是这种情况:它在二进制中并不简单,它既不完全表示为 double 也不完全表示为 float,但 double 表示的位数更多结果,更接近预期值 1/10。

浮点运算通常不精确

floatdouble 等格式的二进制浮点运算必须产生预期格式的结果。这导致每次计算操作时都必须从结果中删除一些数字。当以高级方式使用二进制浮点数时,程序员有时会知道结果将有足够的数字来表示格式中的所有数字(换句话说,有时浮点运算可以是精确的和高级的程序员可以预测并利用发生这种情况的条件)。但是在这里,您要添加 0.1,这并不简单并且(以二进制形式)使用所有可用数字,因此大多数时候,此添加并不准确。

如何仅使用 float

打印一小部分 table 值

for (p = 0.1; p < 10;p=p+0.1) 中,p 的值是 float,将在每次迭代时四舍五入。每次迭代都将根据已经舍入的前一次迭代进行计算,因此舍入误差会累积并使最终结果偏离预期的数学值。

以下是对您所写内容的改进列表,按正确性倒序排列:

for (i = 1, p = 0.1f; i < 100; i++, p = i * 0.1f)

在上面的版本中,0.1f 不完全是 1/10,但是 p 的计算只涉及一次乘法和一次舍入,而不是最多 100。该版本给出了更精确的近似值i/10。

for (i = 1, p = 0.1f; i < 100; i++, p = i * 0.1)

在上面略有不同的版本中,i 乘以 double0.1,更接近于 1/10。结果总是最接近 float 到 i/10,但是这个解决方案有点作弊,因为它使用了 double 乘法。我说过只有 float!

存在解决方案
for (i = 1, p = 0.1f; i < 100; i++, p = i / 10.0f)

在最后一个解决方案中,p 计算为 i 的除法,精确表示为 float,因为它是一个小整数,由 10.0f,出于同样的原因,这也是准确的。唯一的计算近似值是单个操作,并且参数正是我们想要的,所以这是最好的解决方案。它为 1 到 99 之间的所有 i 值生成最接近 float 到 i/10 的值。