【问题标题】:Floating point error in C++ codeC++ 代码中的浮点错误
【发布时间】:2016-10-02 20:20:24
【问题描述】:

我正在尝试解决一个问题,在该问题中我需要找出组成一个由两个成员组成的团队的可能方法的数量。(注意:一个团队最多可以有两个人) 编写此代码后,它可以正常工作,但在某些测试用例中它显示浮点错误广告我无法找出它到底是什么。

输入:第一行:测试用例数 第二行:总人数

谢谢

#include<iostream>
using namespace std;
long C(long n, long r)
{
    long f[n + 1];
    f[0] = 1;
    for (long i = 1; i <= n; i++)
    {
        f[i] = i * f[i - 1];
    }
    return f[n] / f[r] / f[n - r];
}

int main()
{
    long n, r, m,t;
    cin>>t;
    while(t--)
    { 
        cin>>n;
        r=1;
        cout<<C(n, min(r, n - r))+1<<endl;
    }
    return 0;
}

【问题讨论】:

  • 编码风格问题:为简洁起见,请不要 typedef 标准类型。 ll 只会让你的代码更难阅读。
  • 我在这段代码中根本看不到浮点计算。请提供足够的信息,以便我们能够重现您的问题,否则我们无法帮助您。
  • 我确实在在线竞赛门户网站上提交了我的代码,他们为我们提供了测试用例。我不知道在哪种情况下我的代码会失败以及为什么他们不接受我的代码并显示 SIGFPE 错误。
  • 您的代码似乎不完整,因为您从未在main() 中为n 赋值,但您使用了n。在发布您的问题之前,您是否删除了为 n 赋值的代码行?

标签: c++ sigfpe


【解决方案1】:

您没有得到浮点异常。您将获得除以零的异常。因为您的代码试图除以数字 0(这不能在计算机上完成)。

当您调用 C(100, 1) 时,初始化 C 内的 f 数组的主循环呈指数增长。最终,两个值相乘,使得i * f[i-1] 由于溢出而为零。这导致所有后续的 f[i] 值都被初始化为零。然后循环后面的除法就是被零除。

尽管这些论坛上的纯粹主义者会说这是未定义的,但这就是大多数 2 的补码架构上真正发生的情况。或者至少在我的电脑上....

i==21:

f[20] 已经等于2432902008176640000

21 * 2432902008176640000 对于 64 位有符号溢出,通常会变为 -4249290049419214848 所以此时,您的程序存在错误,现在处于未定义行为

i==66

f[65] 等于 0x8000000000000000。所以 66 * f[65] 被计算为零,原因对我来说是有意义的,但应该被理解为未定义的行为。

f[66] 分配为 0,f[i] 的所有后续分配也将变为 0。 C 内部的主循环结束后,f[n-r] 为零。因此,除以零误差。

更新

我回去对您的问题进行了逆向工程。您的 C 函数似乎只是在尝试计算这个表达式:

   N!
 -------------
  R! * (N-R)!

这是“唯一排序组合的数量”

在这种情况下,我们可以将表达式简化为:

         n
      [ ∏ i ]
        n-r
 --------------------
        R!

这不会消除溢出,但会让您的 C 函数能够采用更大的 N 和 R 值来计算组合数而不会出错。

但我们也可以在尝试做一个大的长阶乘表达式之前利用简单的归约

例如,假设我们试图计算 C(15,5)。数学上是:

   15!
 --------
  10! 5!

或者如我们上面所说的:

 1*2*3*4*5*6*7*8*9*10*11*12*13*14*15
 -----------------------------------   
 1*2*3*4*5*6*7*8*9*10  *  1*2*3*4*5

分子和分母的前10个因数相互抵消:

 11*12*13*14*15
 -----------------------------------   
 1*2*3*4*5

但直观地,您可以看到分子中的“12”已经可以被分母 2 和 3 整除。而分子中的 15 可以被分母中的 5 整除。所以可以应用简单的归约:

 11*2*13*14*3
 -----------------------------------   
 1  * 4

最大公约数减少的空间更大,但这是一个很好的开始。

让我们从一个计算列表中所有值的乘积的辅助函数开始。

long long multiply_vector(std::vector<int>& values)
{
    long long result = 1;
    for (long i : values)
    {
        result = result * i;
        if (result < 0)
        {
            std::cout << "ERROR - multiply_range hit overflow" << std::endl;
            return 0;
        }
    }
    return result;
}

在做归约操作后,不要像使用上述函数那样实现C

long long C(int n, int r)
{
    if ((r >= n) || (n < 0) || (r < 0))
    {
        std::cout << "invalid parameters passed to C" << std::endl;
        return 0;
    }

    // compute
    //    n!
    //  -------------
    //   r! *  (n-r)!
    // 
    // assume (r < n)

    // Which maps to

    //      n
    //    [∏ i]
    //    n - r
    // --------------------
    //     R!


    int end = n;
    int start = n - r + 1;

    std::vector<int> numerators;
    std::vector<int> denominators;
    long long numerator = 1;
    long long denominator = 1;

    for (int i = start; i <= end; i++)
    {
        numerators.push_back(i);
    }
    for (int i = 2; i <= r; i++)
    {
        denominators.push_back(i);
    }

    size_t n_length = numerators.size();
    size_t d_length = denominators.size();
    for (size_t n = 0; n < n_length; n++)
    {
        int nval = numerators[n];
        for (size_t d = 0; d < d_length; d++)
        {
            int dval = denominators[d];

            if ((nval % dval) == 0)
            {
                denominators[d] = 1;
                numerators[n] = nval / dval;
            }
        }
    }

    numerator = multiply_vector(numerators);
    denominator = multiply_vector(denominators);
    if ((numerator == 0) || (denominator == 0))
    {
        std::cout << "Giving up.  Can't resolve overflow" << std::endl;
        return 0;
    }

    long long result = numerator / denominator;

    return result;
}

【讨论】:

  • 我怎样才能摆脱它?
  • 我刚刚更新了我的答案,以展示如何计算更大的阶乘组合而不会溢出。
  • 我再次对其进行了更新,使其扩展到甚至更高的 N 和 R 值。
【解决方案2】:

您没有使用浮点数。而且您似乎正在使用可变大小的数组,这是 C 功能,可能是 C++ 扩展,但不是标准的。

无论如何,即使对于相当小的 n 值,您也会得到溢出和未定义的行为。

实际上,溢出将导致数组元素在 n 值不大的情况下变为零。

然后您的代码将除以零并崩溃。

他们也可能有一个像 (1000000000, 999999999) 这样的测试用例,解决起来很简单,但我打赌你的代码会崩溃。

【讨论】:

  • 我怎样才能摆脱它?
【解决方案3】:

您没有指定“浮点错误”的含义-我认为您指的是您正在执行整数除法而不是浮点除法,因此您将始终得到整数而不是浮点数。

int a, b; 
a = 7; 
b = 2; 
std::cout << a / b << std::endl;

这将导致 3,而不是 3.5!如果你想要浮点结果,你应该像这样使用浮点数:

float a, b; 
a = 7; 
b = 2; 
std::cout << a / b << std::end;

因此,您的问题的解决方案就是使用float 而不是long long int

还请注意,您正在使用在 C++ 中无法使用的可变大小数组 - 为什么不改用 std::vector??

【讨论】:

    【解决方案4】:

    数组语法为:

    type name[size]
    

    注意:size 必须是常量而不是变量

    示例 #1:

    int name[10];
    

    示例 #2:

    const int asize = 10;
    int name[asize];
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-03
      • 1970-01-01
      • 2022-01-08
      • 1970-01-01
      • 1970-01-01
      • 2021-11-06
      相关资源
      最近更新 更多