【问题标题】:e^x 没有 math.h
【发布时间】:2022-01-19 17:43:50
【问题描述】:

我正在尝试在不使用 math.h 的情况下查找 ex。当 x 大于或小于 ~±20 时,我的代码给出了错误的答案。我试图将所有双精度类型更改为长双精度类型,但它在输入时产生了一些垃圾。

我的代码是:

#include <stdio.h>

double fabs1(double x) {
    if(x >= 0){
        return x;
    } else {
        return x*(-1);
    }
}

double powerex(double x) {
    double a = 1.0, e = a;
    for (int n = 1; fabs1(a) > 0.001; ++n) {
        a = a * x / n;
        e += a;
    }
    return e;
}

int main(){
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int n;
    scanf("%d", &n);
    for(int i = 0; i<n; i++) {
        double number;
        scanf("%lf", &number);
        double e = powerex(number);
        printf("%0.15g\n", e);
    }
    return 0;
}

输入:

8
0.0
1.0
-1.0
2.0
-2.0
100.0
-100.0
0.189376476361643

我的输出:

1
2.71825396825397
0.367857142857143
7.38899470899471
0.135379188712522
2.68811714181613e+043
-2.91375564689153e+025
1.20849374134639

右输出:

1
2.71828182845905
0.367879441171442
7.38905609893065
0.135335283236613
2.68811714181614e+43
3.72007597602084e-44
1.20849583696666

你可以看到我对 e-100 的回答是绝对不正确的。为什么我的代码会输出这个?我可以做些什么来改进这个算法?

【问题讨论】:

  • 修改代码以在循环中打印na 的值,然后针对-100 的情况运行它,看看这些值是什么样的。看看他们有多大。 double 格式的分辨率是多少?当数字变得那么大时,总和的表示精度如何?您预计会有多大的误差?
  • 使用调试器运行,或在计算循环中添加中间打印,看看a 的值何时开始偏离预期。
  • 会不会有int溢出?
  • GCC 具有内置函数 __builtin_expf__builtin_expd,您可以调用 as documented here,但我想您的问题实际上更多是关于自己进行计算,而不是避免使用特定的头文件。无论如何,这些函数似乎只是在底层调用 C 库,而不是做更直接的事情。
  • 这里for (int n = 1; fabs1(a) &gt; 0.001; ++n) {,你强加的近似值一般不优于0.001

标签: c math numerical-methods


【解决方案1】:

x 为负数时,每个术语的符号交替出现。这意味着当使用正功率时,每个连续的总和的值会发生广泛的变化,而不是逐渐增加。这意味着连续项的精度损失对结果有很大影响。

要处理这个问题,请在开头检查x 的符号。如果是负数,切换x的符号进行计算,当你到达循环结束时,将结果反转。

此外,您可以使用以下违反直觉的条件来减少迭代次数:

e != e + a

从表面上看,这似乎总是正确的。但是,当a 的值超出e 值的精度时,条件变为假,在这种情况下,将a 添加到e 不会改变e 的值。

double powerex(double x) {
    double a = 1.0, e = a;
    int invert = x<0;
    x = fabs1(x);
    for (int n = 1; e != e + a ; ++n) {
        a = a * x / n;
        e += a;
    }
    return invert ? 1/e : e;
}

我们可以通过使用 0 而不是 a 初始化 e 并在循环底部而不是顶部计算下一项来进一步优化以删除一个循环迭代:

double powerex(double x) {
    double a = 1.0, e = 0;
    int invert = x<0;
    x = fabs1(x);
    for (int n = 1; e != e + a ; ++n) {
        e += a;
        a = a * x / n;
    }
    return invert ? 1/e : e;
}

【讨论】:

    【解决方案2】:

    对于大于 1 左右的 x 值,您可以考虑单独处理整数部分并通过平方计算 e 的幂。 (例如 e^9 = ((e²)²)².e 需要 4 次乘法)

    的确,泰勒展开式的总称,x^n/n!仅在 n>x 之后才开始减少(每次乘以 x/k),因此求和至少需要 x 项。另一方面,e^n 最多可以计算 2lg(n) 次,这样更高效、更准确。

    所以我会建议

    • 取 x 的小数部分并使用 Taylor,
    • 当整数部分为正时,乘以 e 的次方,
    • 当整数部分为零时,您就完成了,
    • 当整数部分为负数时,除以 e 的幂。

    您甚至可以通过考虑四分之一来节省更多时间:在最坏的情况下 (x=1),Taylor 需要 18 个术语才能使最后一个可以忽略不计。如果您考虑从 x 中减去 1/4 的直接下倍数(并乘以预先计算的 e 的幂进行补偿),则项数将降至 12。

    例如e^0.8 = e^(3/4+0.05) = 2.1170000166126747 。 e^0.05

    【讨论】:

    • 如果添加常量,考虑使用ln2,然后分解x=m*ln2+uabs(u)&lt;=0.5*ln2&lt;0.35,这样结果就是2^m*exp(u)。否则,想法将是计算 y=exp(x/2^n) 并将 exp(x) 恢复为 n y 的平方。
    • @LutzLehmann:由于截断错误,exp(x/2^n) 的平方不起作用。
    • 但是您建议从e 的平方计算大部分结果。
    • @LutzLehmann:是的,这是安全的,不是像 1.000000001 这样的数字的平方,如果我是对的。
    • 这个想法当然是让x/2^n在某个中等范围内,例如0.5 to 1,这样幂级数或多项式近似就可以相当快地工作,并且平方不会对精度产生太大影响.这种方法确实用在bc多精度命令行工具的数学库中,可以说是通过提高工作精度来解决平方“技巧”的精度损失。
    猜你喜欢
    • 2010-10-12
    • 2011-10-16
    • 1970-01-01
    • 2017-09-01
    • 1970-01-01
    • 2016-01-16
    • 1970-01-01
    • 2016-04-18
    • 1970-01-01
    相关资源
    最近更新 更多