算术溢出:对 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 * a2LL * 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,您也可以将2a 转换为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 肯定不会,并且乘以得到的doubles 也不会溢出。例如。 a==INT_MAX 会导致溢出和未定义行为【参考方案3】:

解决这个问题的方法是static_cast&lt;long long&gt;,因为 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 字节值。”?

C ++中的算术溢出添加2个DWORD

警告 C26451:算术溢出(减去两个整数时)

整数算术溢出问题的分析

Jvm(44),指令集----运算指令

javascript算术溢出