整数溢出和pow()与乘法的区别

Integer Overflow and the difference between pow() and multiplication

当我尝试这个乘法编译器时给出了一个整数溢出错误

int main(){
    long long int x;
    x = 55201 * 55201;
    printf("%lld", x);
    return 0;
}

但是当我用 pow() 函数做同样的操作时,我没有得到任何错误。

int main(){
    long long int x;
    x = pow(55201, 2);
    printf("%lld", x);
    return 0;
}

为什么会这样?我必须使用第一个代码。

这里(Linux 64bits, gcc 5.2.1),55201是一个大小为4的整型字面量,表达式55201 * 55201好像是存储在一个大小为整数的4 在分配给您之前 long long int.

一个选项是在相乘之前将因子存储在另一个变量中,以增加范围。

int main(){
  long long int x, factor;
  factor = 55201;
  x = factor * factor;
  printf("%lld", x);
  return 0;
}

您需要像这样更改您的代码

int main(){
    long long int x;
    x = 55201LL * 55201LL;  // <--- notice the LL
    printf("%lld", x);
    return 0;
}

使乘法完成long long

当您使用 pow 函数时,您没有发现任何问题,因为 pow 使用浮点数进行计算。

下面代码中默认将55201作为整数进行相乘,相乘后的结果也是整数。在 code optimization phase 期间将计算乘法,但随后它似乎溢出了整数限制...这就是编译器生成警告的原因,即 integer overflow

int main(){
    long long int x;
    x = 55201 * 55201;
    printf("%lld", x);
    return 0;
}

pow 的声明如下:

double pow(double x, double y);

但在第二种情况下,函数 pow 将每个参数都作为 double,所以现在“55201”和“2”将隐式转换为 double,现在计算发生在双精度所以计算结果不会超过双精度类型的限制...因此 the compiler will not generate any overflow message in this case.

要建立相同的结果,但使用方法 1 可以按以下方式完成:

long long int result, number;
number = 55201;
result = number * number;
// Print result as..
printf("%lld\n", result);

就是这样..对理解有帮助吗...

问题是使用最大类型的操作数执行操作,至少 int (C standard, 6.3.1.8)。赋值只是 C 中的另一个表达式,= 左侧的类型与右侧操作无关。

在您的平台上,两个常量都适合 int,因此表达式 55201 * 55201 的计算结果为 int。问题是结果不适合 int,因此会产生溢出。

有符号整数溢出是未定义的行为。这意味着一切都可能发生。幸运的是,您的编译器足够聪明,可以检测到这一点并警告您,而不是让计算机跳出 window。简而言之:避免它!

解决方案是使用可以容纳完整结果的类型执行操作。简短的计算得出该乘积需要 32 位来表示 。因此 unsigned long 就足够了。如果你想要一个带符号的整数,你需要另一位符号,即 33 位。现在这种类型很少见,所以你必须使用至少有 64 位的 long long。 (不要试图使用 long,即使它在您的平台上有 64 位;这会使您的代码 实现定义 ,因此不可移植且没有任何好处。)

为此,您需要至少 个操作数之一才能具有结果类型的类型:

x = 55201LL * 55201;   // first factor is a long long constant

如果涉及变量,请使用强制转换:

long f1 = 55201;      // an int is not guaranteed to hold this value!
x = (long long)f1 * 55201;

注意这里的常量没有使用 L 后缀。它们将自动提升为可以表示值的最小类型(至少int)。


另一个表达式 x = pow(55201, 2) 使用 floating point function。因此,在调用 pow 之前,参数被转换为 doubledouble 结果由赋值运算符转换为左侧类型。

这有两个问题:

  1. double 不能保证像 long long 一样具有 63 位的尾数(不包括符号)。常见的 IEEE754 实现都有这个问题。 (与本次具体计算无关)
  2. 所有浮点运算都可能包含舍入误差,因此结果可能与实际结果有偏差。这就是为什么你必须使用第一个版本。

作为一般规则,如果需要精确的结果,则永远不要使用浮点运算。混合浮点数和整数应该非常谨慎。