【问题标题】:For loop with unsigned int带有 unsigned int 的 For 循环
【发布时间】:2015-11-23 20:51:22
【问题描述】:

我的代码有一个逻辑问题,可能是溢出引起的,但我自己无法解决,所以如果有人能帮助我,我将不胜感激。

在下面的代码中,我实现了函数 taylor_log(),它可以计算泰勒多项式的“n”次迭代。在 void 函数中,我正在寻找迭代次数(*limit),与来自的 log 函数相比,它足以计算具有所需精度的对数。

问题是有时 UINT_MAX 的迭代次数不足以获得所需的精度,此时我想让用户知道所需的迭代次数高于 UINT_MAX。但是我的代码不起作用,例如 x = 1e+280, eps = 623。它只是计数,计数,从不给出结果。

TaylorPolynomial

double taylor_log(double x, unsigned int n){

    double f_sum = 1.0;
    double sum = 0.0;

    for (unsigned int i = 1; i <= n; i++)
    {
        f_sum *= (x - 1) / x;
        sum += f_sum / i;
    }

    return sum;
}


void guessIt(double x, double eps, unsigned int *limit){

    *limit = 10;
    double real_log = log(x);
    double t_log = taylor_log(x, *limit);
    while(myabs(real_log - t_log) > eps)
    {
        if (*limit == UINT_MAX)
        {
            *limit = 0;
            break;
        }
        if (*limit >= UINT_MAX/2)
        {
            *limit = UINT_MAX;
            t_log = taylor_log(x, *limit);
        }
        else
        {
            *limit = (*limit) *2;
            t_log = taylor_log(x, *limit);
        }
    }
}

编辑:好的,谢谢你们到目前为止的反应。我已将代码更改为:

if (*limit == UINT_MAX-1)
            {
                *limit = 0;
                break;
            }
if (*limit >= UINT_MAX/2)
            {
                *limit = UINT_MAX-1;
                t_log = taylor_log(x, *limit);
            }

但它仍然无法正常工作,我已将 printf 设置为 taylor_log() 函数的开头,以查看“n”的值及其 (..., 671088640, 1342177280, 2684354560, 5, 4, 3 , 2, 2, 1, 2013265920, ...)。没看懂。。

【问题讨论】:

  • 这是在C 还是C++ 中? (看起来像C)。至于你的问题——为什么不在进入循环之前做一些边界检查?您可以对可接受的输入进行严格限制,并确保不会发生此问题。如果您想支持任意大的输入,请考虑使用libgmp 或其他支持此类计算的数学库。
  • 选择一种语言。 CC++,没有C/C++这样的东西
  • "我已经实现了函数 taylor_log()" --> 这样做不正确,所以添加更多术语不会解决问题。
  • 我已经在我实现的公式中包含了图像,我认为它是正确的。
  • 我同意您可能已经根据log() 函数的图像进行了编码。我建议的下面的代码使用不同的系列。我需要看看你的系列是有效的还是不同的。

标签: c algorithm loops unsigned


【解决方案1】:

以下代码将限制分配给 UINT_MAX

if (*limit >= UINT_MAX/2)
{
    *limit = UINT_MAX;
    t_log = taylor_log(x, *limit);
}

你的 for 循环是这样定义的:

for (unsigned int i = 1; i <= n; i++)

i 将始终小于或等于UINT_MAX,因为永远不会有大于UINT_MAXi 值。因为那是i 的最大价值。所以肯定有溢出,你的循环退出条件永远不会满足。 i 翻转为零,并且该过程无限重复。

您应该将循环条件更改为 i &lt; n 或将限制更改为 UINT_MAX - 1

【讨论】:

  • 我认为,除此之外,您的myabs(real_log - t_log) &gt; eps 条件没有得到满足,导致无限循环。
  • 我的错误。 OP 的公式确实在有限的范围内起作用 (0.5
【解决方案2】:

[编辑]

OP 编码正确,但必须确保有限范围 (0.5 &lt; x &lt; 2.0 ?)

以下是自行决定何时停止的代码版本。 x near 0.5 and 2.0 附近的迭代次数很高。所需的迭代次数达到数百万。这种替代编码远低于。

double taylor_logA(double x) {
  double f_sum = 1.0;
  double sum = 0.0;

  for (unsigned int i = 1; ; i++) {
    f_sum *= (x - 1) / x;
    double sum_before = sum;
    sum += f_sum / i;
    if (sum_before == sum) {
      printf("%d\n", i);
      break;
    }
  }

  return sum;
}

错误替代系列的实现:Ref

示例替代方案 - 收敛速度更快。

double taylor_log2(double x, unsigned int n) {
  double f_sum = 1.0;
  double sum = 0.0;

  for (unsigned int i = 1; i <= n; i++) {
    f_sum *= (x - 1) / 1;  // / 1 (or remove)
    if (i & 1) sum += f_sum / i;
    else sum -= f_sum / i;  // subtract even terms
  }
  return sum;
}

合理数量的术语将根据需要收敛。

或者,继续直到术语太小(可能是 50 左右)

double taylor_log3(double x) {
  double f_sum = 1.0;
  double sum = 0.0;
  for (unsigned int i = 1; ; i++) {
    double sum_before = sum;
    f_sum *= x - 1;
    if (i & 1) sum += f_sum / i;
    else sum -= f_sum / i;
    if (sum_before == sum) {
      printf("%d\n", i);
      break;
    }
  }
  return sum;
}

可能的其他改进。例如see More efficient series

【讨论】:

    【解决方案3】:

    首先,使用std::numeric_limits&lt;unsigned int&gt;::max() 将使您的代码比c-ish 更c++-ish。其次,您可以使用整数类型unsigned long longstd::numeric_limits&lt;unsigned long long&gt;::max() 作为限制,这与整数类型的限制非常相似。如果你想要更高的限制,你可以使用long double。浮点数还允许您将无穷大与std::numeric_limits&lt;double&gt;::infinity() 一起使用,注意无穷大与doublefloatlong double 一起使用。

    如果这两种类型都没有为您提供所需的精度,请查看boost::multiprecision

    【讨论】:

      【解决方案4】:

      首先,对数函数的泰勒级数仅在值 0 eps 精度。

      其次,您确定它会永远循环,而不是在 非常 很长时间后点击*limit &gt;= UINT_MAX/2

      【讨论】:

        【解决方案5】:

        OP 正在使用该系列远远超出其可用范围 0.5 x &lt; 2.0 的调用,例如 taylor_log(1e280, n)

        即使在该范围内,x 值接近 0.5 and 2.0 的极限也会非常缓慢地收敛,需要数百万次以上的迭代。不会产生精确的log()。最好使用大约 1.0 的 2x 范围。

        创建一个包装函数以调用其甜蜜范围sqrt(2)/2 &lt; x &lt; sqrt(2) 中的原始函数。收敛,最坏的情况,大约 40 次迭代。

        #define SQRT_0_5  0.70710678118654752440084436210485
        #define LN2       0.69314718055994530941723212145818
        
        // Valid over the range (0...DBL_MAX]
        double taylor_logB(double x, unsigned int n) {
          int expo;
          double signif = frexp(x, &expo);
          if (signif < SQRT_0_5) {
            signif *= 2;
            expo--;
          }
          double y = taylor_log(signif,n);
          y += expo*LN2;
          return y;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-21
          • 1970-01-01
          • 2013-10-08
          • 2017-09-27
          • 1970-01-01
          • 2011-07-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多