整数溢出和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
之前,参数被转换为 double
。 double
结果由赋值运算符转换为左侧类型。
这有两个问题:
- double 不能保证像
long long
一样具有 63 位的尾数(不包括符号)。常见的 IEEE754 实现都有这个问题。 (与本次具体计算无关)
- 所有浮点运算都可能包含舍入误差,因此结果可能与实际结果有偏差。这就是为什么你必须使用第一个版本。
作为一般规则,如果需要精确的结果,则永远不要使用浮点运算。混合浮点数和整数应该非常谨慎。
当我尝试这个乘法编译器时给出了一个整数溢出错误
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
之前,参数被转换为 double
。 double
结果由赋值运算符转换为左侧类型。
这有两个问题:
- double 不能保证像
long long
一样具有 63 位的尾数(不包括符号)。常见的 IEEE754 实现都有这个问题。 (与本次具体计算无关) - 所有浮点运算都可能包含舍入误差,因此结果可能与实际结果有偏差。这就是为什么你必须使用第一个版本。
作为一般规则,如果需要精确的结果,则永远不要使用浮点运算。混合浮点数和整数应该非常谨慎。