算术溢出:对 4 字节值使用运算符“*”,然后将结果转换为 8 字节值
Posted
技术标签:
【中文标题】算术溢出:对 4 字节值使用运算符“*”,然后将结果转换为 8 字节值【英文标题】:Arithmetic overflow: Using operator '*' on a 4 byte value then casting the result to a 8 byte value 【发布时间】:2019-12-12 15:56:03 【问题描述】:我正在尝试编写一个程序来求解系数值绝对值不超过 100 的二次方程,并且保证如果存在任何根,它们都是整数。这是我尝试过的:
#include <cmath>
#include <iostream>
int main()
int a, b, c; // coefficients of quadratic equation
std::cin >> a >> b >> c;
int d = b * b - 4 * a * c; // discriminant
if (d < 0)
std::cout << "No roots";
else if (d == 0)
std::cout << "One root: " << -b / (2 * a);
else
std::cout << "Two roots: " << (-b - std::sqrt(d)) / (2 * a) << " "
<< (-b + std::sqrt(d)) / (2 * a);
return 0;
它工作正常,但是,Visual Studio 2019 显示此警告:
。在调用运算符 '*' 之前将值转换为更宽的类型以避免溢出 (io.2)。
到底为什么会弹出这个警告,它想告诉我什么?我做错了什么,我该如何解决这个问题?
我在 SO 上看到过 this,但我不认为这是一个错误。
【问题讨论】:
我不喜欢“铸造”这个词,因为没有。人们会期望编译器的创建者知道隐式转换和显式转换之间的区别。 这实际上不是一个错误。意思是2 * a
产生一个32 位的值,根据a
的值,它可能会溢出。由于结果被转换为 64 位值(用于除法),因此建议您在乘法之前进行转换。您可以通过 2.0 * a
或 2LL * a
来修复它
“我不相信这是一个错误。” 为什么不呢?如果您对该页面上的问题或答案有批评,您应该在该页面上提出批评,而不是重新提出相同的问题。
@LightnessRaceswithMonica 这不是错误。例如,如果 a
是 INT_MAX,2 * a
会溢出,但 2.0 * a
不会溢出。无论如何,结果都会转换为double
。所以2.0 * a
基本上给出了相同的结果,但溢出的可能性要小得多。
嗯,其实是的,好吧。
【参考方案1】:
这不是错误。这里:
(-b - std::sqrt(d)) / (2 * a)
表达式的结果是double
。但是2 * a
的结果是int
,最终转换为double
。如果a
在转换为double
之前太大,2 * a
可能会溢出。但由于最终结果已经是double
,您也可以将2
或a
转换为double
,并一劳永逸地避免溢出的风险。所以编译器告诉你正确的做法TM是:
(-b - std::sqrt(d)) / (2.0 * a)
它不会溢出((2.0 * a)
的结果是 double
),它可能已经是你想要的了。
【讨论】:
很好的解释!但我想补充一点:编译器并没有告诉你要做正确的事情。它只是指出一个可能的问题并让你决定如何处理它。静态分析器与真假(好或坏代码)无关。我认为如果你把他们看作是一个在问你的同事(不知道你知道的所有细节),他们的工作效果最好:“你真的考虑过这个吗?”在某些情况下,额外的强制转换可以使编译器警告消失(也让你的审阅同事知道你真的考虑过这个声明)。【参考方案2】:这里的根本原因(双关语非常受欢迎)是二次方程不是Diophantine equation。它适用于实数,特别是sqrt(d)
通常不是整数。
在 C++ 中,sqrt(IntegralType)
的返回类型是 double
。因此2*a
也转换为double
,但仅在相乘之后。 Visual Studio 非常合理地指出,最好在乘法之前进行转换。它只是没有注意到你甚至可以从一开始就让a,b,c
all doubles。
【讨论】:
为什么建议在之前进行转换乘法? @khaliyeva:两个整数相乘会溢出;将它们转换为double
肯定不会,并且乘以得到的double
s 也不会溢出。例如。 a==INT_MAX
会导致溢出和未定义行为。【参考方案3】:
解决这个问题的方法是static_cast<long long>
,因为 long long 是 8 个字节,它足够大以在发生溢出时保存信息。当您没有足够的位来保存一个数字时,会发生溢出,因此其中一些会被截断。
这是一个没有警告的例子:
std::cout << "Two roots: " << (-b - std::sqrt(d)) / (2 * static_cast<long long>(a)) << " "
<< (-b + std::sqrt(d)) / (2 * static_cast<long long>(a));
【讨论】:
【参考方案4】:它不再显示 VS2022 中的警告。
显然,微软认为它产生的噪音多于有用的信息。
请参阅我对您在此处链接到的类似问题的回答: Warning C26451: Arithmetic overflow
【讨论】:
以上是关于算术溢出:对 4 字节值使用运算符“*”,然后将结果转换为 8 字节值的主要内容,如果未能解决你的问题,请参考以下文章
如何解决 Visual Studio C++ 问题,“算术溢出:在 4 字节值上使用运算符 '*',然后将结果转换为 8 字节值。”?