为什么我的算术 long long int 会这样?

Why is my arithmetic with a long long int behaving this way?

我正在尝试使用 long long 数据类型计算大整数,但是当它变得足够大时 (2^55),算术行为是不可预测的。我在 Microsoft Visual Studio 2017.

工作

在第一种情况下,我在初始化时从 long long 变量 m 中减去 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(参考cppreference),因为这个值太大了,减去2不会改变它的值。这意味着 m 不会有正确的值。正如您已经发现的那样,您需要先分配结果,然后再进行减法。另一种选择是编写自己的 pow,当给定一个整数类型时,它将 return 一个整数类型,这样您就可以同时进行乘方和减法。

I expect both codes to be equivalent, why is this not the case?

你的期望是错误的。您的第二个代码将等效于此:

auto m = static_cast<LL>( pow(2, n + 1) ) - 2;

由于 arithmetic operators and the fact that std::pow() returns double 的转换规则,在这种情况下:

For the binary operators (except shifts), if the promoted operands have different types, additional set of implicit conversions is applied, known as usual arithmetic conversions with the goal to produce the common type (also accessible via the std::common_type type trait). If, prior to any integral promotion, one operand is of enumeration type and the other operand is of a floating-point type or a different enumeration type, this behavior is deprecated. (since C++20)

If either operand has scoped enumeration type, no conversion is performed: the other operand and the return type must have the same type

Otherwise, if either operand is long double, the other operand is converted to long double

Otherwise, if either operand is double, the other operand is converted to double

Otherwise, if either operand is float, the other operand is converted to float

...

(重点是我的)你的原始表达式会导致 double - double 而不是 long long int - long long int 就像你在第二种情况下所做的那样,因此有所不同.

pow 函数 returns 一个 double 类型的值,它只有 53 位精度。虽然即使 n 大于 53,返回值也将适合 double,减去 2 会导致需要超过 53 位精度的类型 double 的值,因此结果减法四舍五入到最接近的可表示值。

分解减法的原因是因为从 pow 返回的 double 值被分配给 long long,然后从 int 中减去 int =18=].

由于您不是在处理浮点数,而只是计算 2 的幂,因此您可以用简单的左移替换对 pow 的调用:

LL m = (1LL << (n + 1)) - 2;

这会将所有中间值保留为 long long 类型。