我正在尝试用long long
数据类型计算大整数,但是当它变得足够大(2^55
)时,算术行为是不可预测的。我在Microsoft Visual Studio 2017工作。
在第一种情况下,我从初始化中m
的long long
变量中减去2
。这适用于所有n
,直到我尝试54
,然后m
根本不会被2
减去。
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
using namespace std;
#define LL long long
int main()
{
LL n;
cin >> n;
LL m = pow(2, n + 1) - 2;
cout << m;
return 0;
}
但是,使用此代码m
确实会被减去2
并且按我预期工作。
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
using namespace std;
#define LL long long
int main()
{
LL n;
cin >> n;
LL m = pow(2, n + 1);
m -= 2;
cout << m;
return 0;
}
我希望两个代码是等效的,为什么不是这样?
LL m = pow(2, n + 1) - 2;
是pow(2, n + 1)
不是long long
. 它具有类型double
(参考 cpp首选项),并且由于该值非常大,因此从中减去2
不会更改其值。 这意味着m
将没有正确的值。 正如您已经发现的,您需要先分配结果,然后再进行减法。 另一种选择是编写您自己的pow
,当给定整数类型时,该将返回整数类型,以便您可以同时进行幂和减法的提高。
我希望两个代码是等效的,为什么不是这样?
你的期望是错误的。您的第二个代码等效于以下内容:
auto m = static_cast<LL>( pow(2, n + 1) ) - 2;
由于算术运算符的转换规则以及在这种情况下std::pow()
返回double
的事实:
对于二元运算符(移位除外),如果提升的操作数具有不同的类型,则会应用额外的隐式转换集,称为通常的算术转换,目的是生成公共类型(也可通过 std::common_type 类型特征访问)。如果在进行任何整型升级之前,一个操作数是枚举类型,另一个操作数是浮点类型或其他枚举类型,则不推荐使用此行为。(自C++20起)
如果任一操作数具有作用域枚举类型,则不执行转换:另一个操作数和返回类型必须具有相同的类型
否则,如果其中一个操作数为长双精度,则另一个操作数将转换为长双精度
否则,如果其中一个操作数为双精度,则另一个操作数将转换为双精度
否则,如果其中一个操作数是浮点数,则另一个操作数将转换为浮点
数。
(强调是我的)您的原始表达式将导致double
-double
而不是long long int
-long long int
就像您在第二种情况下所做的那样,因此有所不同。
pow
函数返回一个类型为double
的值,该值只有 53 位精度。 虽然即使n
大于 53,返回值也适合double
,但减去 2 会导致需要超过 53 位精度的double
类型的值,因此减法的结果将舍入到最接近的可表示值。
分解减法工作的原因是,从pow
返回的double
值被分配给long long
,然后你从long long
中减去一个int
。
由于您不处理浮点数,并且仅将 2 提高到一个幂,因此您可以将调用替换为简单的左移pow
:
LL m = (1LL << (n + 1)) - 2;
这会将所有中间值保留在类型long long
中。