【问题标题】:Floating point issue in C when solving a math expression求解数学表达式时 C 中的浮点问题
【发布时间】:2021-09-04 10:13:23
【问题描述】:

我有一个用 C 语言解决数学函数的问题。

double task1_double() {
    double a = 1000;
    double b = 0.0001;
    double result = (pow((a + b), 2) - (pow(a, 2) + 2 * a * b))/(pow(b, 2));
    return result; }

float task1_float() {
    float a = 1000;
    float b = 0.0001f;
    float result = (powf((a + b), 2) - (powf(a, 2) + 2 * a * b))/(powf(b, 2));
    return result; }

当我使用双精度数据类型时,它返回 1.001172,但使用浮点数据类型时,结果是 6250000.000000。

有人可以解释为什么会这样吗?提前致谢

【问题讨论】:

  • 将复合表达式拆分为更简单的表达式,使用临时双精度来保存中间结果。打印出所有内容,从而缩小生成错误的范围。这称为“调试”。您还可以使用实际的调试器逐行跟踪执行,检查每个阶段的值。
  • 我发现了一个问题,float b = 0.0001 节省了 9.99999975e-05,尽管节省了 0.0001。我可以修复它吗?
  • 对所有内容都使用双精度。这将减轻但不会消除任何尝试(但失败)使用非无限 RAM 覆盖无限范围值的浮点表示的固有问题。
  • 以防万一这是一个实际问题:公式在数学上简化为 1。

标签: c floating-point


【解决方案1】:

你的分子是准确的

1000000.20000001 - 1000000.2 = 0.00000001

但是当你减去两个几乎相等的大数时,结果中的相对舍入误差可能会爆炸。这就是你所看到的。这是因为float 数字具有大约 7 个十进制数字的精度,而 double 数字具有大约 16 个十进制数字。

让我们一步一步来:

                           exact            float        double
x = pow((a + b), 2)        1000000.20000001 1000000.25   1000000.200000009965
y = pow(a, 2) + 2 * a * b  1000000.2        1000000.1875 1000000.199999999953
x - y                      0.00000001       0.0625       0.000000010011717677

对于两个数字相对于它们的大小的如此小的差异,您通常会得到一个等于 0.0 的float 结果。但在这种情况下,恰好 1000000.20000001 和 1000000.2 位于舍入边界的任一侧,导致前者向上舍入,后者向下舍入。所以他们的差异是六个数量级。

【讨论】:

  • 非常感谢您的帮助,您的解释很完美
【解决方案2】:

通过输出中间结果可以发现是float的精度损失造成的


#include <stdio.h>
#include <math.h>

double task1_double() {
    double a = 1000;
    double b = 0.0001;
    double s1, s2, s3;
    s1 = pow((a + b), 2);
    s2 = pow(a, 2) + 2 * a * b;
    s3 = pow(b, 2);
    double result = (pow((a + b), 2) - (pow(a, 2) + 2 * a * b)) / (pow(b, 2));

    printf("_double : %lf - %lf = %.15lf\n", s1, s2, s1 - s2);
    printf("_double : (%lf - %lf) / %.10lf = %lf\n", s1, s2, s3, result);

    return result;
}

float task1_float() {
    float a = 1000;
    float b = 0.0001f;
    float s1, s2, s3;
    s1 = powf((a + b), 2);
    s2 = powf(a, 2) + 2 * a * b;
    s3 = powf(b, 2);
    float result = (powf((a + b), 2) - (powf(a, 2) + 2 * a * b)) / (powf(b, 2));

    printf("_float : %lf - %lf = %.15lf\n", s1, s2, s1 - s2);
    printf("_float : (%lf - %lf) / %.10lf = %lf\n", s1, s2, s3, result);

    return result;
}

int main()
{
    printf("%.10lf\n%.10lf\n", task1_double(), task1_float());
    return 0;
}

输出:

_float : 1000000.250000 - 1000000.187500 = 0.062500000000000
_float : (1000000.250000 - 1000000.187500) / 0.0000000100 = 6250000.500000
_double : 1000000.200000 - 1000000.200000 = 0.000000010011718
_double : (1000000.200000 - 1000000.200000) / 0.0000000100 = 1.001172
1.0011717677
6250000.5000000000

可以看出: 浮点数运算后会出现轻微的误差,但是除以b的小值会导致误差放大很多倍

【讨论】:

  • 不,导致问题的不是除法,而是减法。看我的回答。
  • @TonyK:减法的结果是相对误差增加了。除法的结果是绝对误差增加。
  • @EricPostpischil:但是导致问题的是减法,不是吗?该部门表现良好,没有四舍五入的问题。它无法解释 OP 令人惊讶的结果。
  • @TonyK:我不会这么说。两个值相近的数字相减永远不会增加任何错误。其输出的相对误差大于其输入的相对误差并不是它的错。错误的实际来源更早,在源文本0.0001ffloat 以及+pow 操作的舍入中。 - 完美无误地完成了自己的工作,而其他人都搞砸了,为什么还要承担责任?
  • @EricPostpischil:我不知所措(这种情况并不经常发生)。你读过我的回答吗?你不同意吗?
猜你喜欢
  • 1970-01-01
  • 2013-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多