【问题标题】:overflow of cummulative sum累积和溢出
【发布时间】:2018-08-06 18:15:33
【问题描述】:

假设我要加 1+11+111....加 n 次。 很明显,从n的某个值来看,可能存在累积和的溢出。

假设我使用以下非常简单的函数来计算上面的总和:

int calcSum(int num)
{
    int sum = 0, sequnt = 1, i;

    for (i = 0; i < num; i++)
    {
        sum += sequnt;
        sequnt = (sequnt * 10) + 1;
    }

    return sum;
}

我想为该函数添加溢出检查。

我试图在这里获得一些帮助How to check if a number overflows an 'int'

但我不得不承认它让我感到困惑,而且我仍然发现在我的任务中实现它有些困难。

任何帮助都将不胜感激。

【问题讨论】:

  • if(INT_MAX - sequnt &gt;= sum) 这样做是安全的sum += sequnt;
  • INT_MAX 应该是你的朋友
  • @WeatherVane 并非所有事件都如此。假设对于 16 位系统,调用 calcSum(111111) 将在函数调用时溢出到 -19961。 num 的值为 -19961,并且由于整数提升 INT_MAX - -19961 将始终大于 sum。
  • @Kenneth 111111 不是 16 位值。
  • @WeatherVane 是一个完全有效的 16 位长整数类型。

标签: c overflow


【解决方案1】:

由于 2 个加法或乘法中的任何一个都大致是溢出候选者,因此请为每个调用一个安全的 overflow checker。使用&lt;limits.h&gt; 中的常量来指导范围检查。

#include <limits.h>
int is_undefined_add(int a, int b) {
  return (a < 0) ? (b < INT_MIN - a) : (b > INT_MAX - a);
}

int is_undefined_mult(int a, int b) {
  if (a > 0) {
    if (b > 0) {
      return a > INT_MAX / b;       // a positive, b positive
    }
    return b < INT_MIN / a;         // a positive, b not positive
  }
  if (b > 0) {
    return a < INT_MIN / b;         // a not positive, b positive
  }
  return a != 0 && b < INT_MAX / a; // a not positive, b not positive
}

int calcSum(int num) {
  int sum = 0, sequnt = 1, i;

  for (i = 0; i < num; i++) {
    if (is_undefined_add(sum, sequnt) Handle_Overflow();
    sum += sequnt;

    if (is_undefined_mult(sequnt, 10) Handle_Overflow();
    sequnt *= 10;

    if (is_undefined_add(sequnt, 1) Handle_Overflow();
    sequnt++;
  }

  return sum;
}

is_undefined_add()is_undefined_mult()int a, int b 的所有组合都有效。

【讨论】:

    【解决方案2】:

    只需使用来自limits.hINT_MAX

    int calcSum(int num)
    {
        int sum = 0, sequnt = 1, i;
    
        for (i = 0; i < num; i++)
        {
            if (INT_MAX - sequnt < sum) exit(1); // overflow
            sum += sequnt;
            if (INT_MAX/10 <= sequnt) exit(1); // overflow on the two next sentences.
            sequnt *= 10;
            sequnt++;
        }
    
        return sum;
    }
    

    exit(1) 只是为了使示例简短。您可以添加任何您喜欢的错误处理。

    【讨论】:

    • 我不明白需要检查 INT_MAX/10INT_MAX - 1。你能解释一下需要检查那个而不是两个操作数的符号吗?你知道如果操作数有相同的符号溢出是不可能的吗?你想回答什么问题,因为你的代码和解释都没有回答任何问题。
    • @LuisColorado 引用:“你知道如果操作数有相同的符号溢出是不可能的吗?”你在说什么?这种说法是完全错误的。
    • 对不起,我写错了,并试图说如果两个操作数有不同的符号,那么添加溢出是不可能的......看我的回答,那个与INT_MAX/10INT_MAX - 1 无关。
    • @LuisColorado 我认为你完全误解了这一点。 a) OP 没有使用任何负值,因此无需讨论不同的符号。 b)乘法也可能溢出,因此必须进行检查。所有操作都经过检查。不仅仅是添加到sum
    • 代码也显示了这些错误,但问题标题关于整数溢出累积总和。无论如何,它没有谈论乘法。我同意你的看法。
    【解决方案3】:

    如果您确定您使用的是二进制补码有符号整数,那么您可以检查以下内容:两个正数相加必须给出正结果,两个负数相加必须给出负结果。只有一个总和不可能得到两个溢出,所以如果你添加正数,当你的结果第一次为负时就会出现溢出。

    无论哪种情况,如果您希望您的代码可移植,那么建议您执行类似的操作:

    #include <limits.h>
    ...
        if ((a > 0 && b > 0 && MAX_INT - a > b) ||
            (a < 0 && b < 0 && MIN_INT - a < b)) {
            /* OVERFLOW OF a + b WILL OCCUR */
            ...
        }
    

    如果操作数的符号不同,则a + b的绝对值不可能大于ab,因此不可能发生溢出。

    对于unsigneds,你有一个类似的方法(虽然你保存了一半的测试,因为操作数只能是正数)但是这次第一种方法总是有效的(因为标准说无符号加法被认为是一个求和模块@ 987654326@ 其中n 是以位为单位的字长),在这种情况下,您可以求和,然后检查结果是否小于任何操作数(如果两者都是正数,则总和必须大于或等于任何一个操作数):

    unsigned a, b;
    
    ...
    unsigned sum = a + b;
    if (sum < a || sum < b) {
        /* OVERFLOW ON a + b HAS HAPPENED */
        ...
    }
    

    您还必须检查整数乘法溢出。如果ab 相乘,那么a*b 会溢出。但是这一次问题更进一步,因为溢出可能不止一次发生,而且你不能后验。在这种情况下,您可以使用等号或不同号溢出,因为您正在添加 ab(并且 b 与自身具有相同的符号)如果两个符号相等,则乘积将为正,并且溢出将发生

    if (MAX_INT/b < a) { /* overflow */ }
    

    如果符号不同,乘积应该是负数,然后

    if (MIN_INT/b < a) { /* overflow */ }
    

    如果其中一个数为0,则乘法不会溢出,结果为0。

    【讨论】:

    • "如果您添加正数,当您的结果第一次为负时会出现溢出" 在某些平台上可能是这样,但从标准的角度来看这是错误的。当溢出发生时,从标准的角度来看,行为是不确定的,使用结果是没有意义的
    • 对不起,我很抱歉,我可以保证我会在某个地方读到。回复已编辑。
    猜你喜欢
    • 1970-01-01
    • 2019-02-15
    • 1970-01-01
    • 2013-12-25
    • 2014-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多