Visual Studio

时间:2016-12-08 15:36:10

标签: c++ long-long

我们知道-2 * 4 ^ 31 + 1 = -9.223.372.036.854.775.807,这是你可以存储的最长值,如下所述:What range of values can integer types store in C++。 所以我有这个操作:

#include <iostream>

unsigned long long pow(unsigned a, unsigned b) { 
    unsigned long long p = 1; 
    for (unsigned i = 0; i < b; i++) 
        p *= a; 
    return p; 
}

int main()
{
    long long nr =  -pow(4, 31) + 5 -pow(4,31);
    std::cout << nr << std::endl;
}

为什么显示-9.223.372.036.854.775.808而不是-9.223.372.036.854.775.803?我正在使用Visual Studio 2015。

5 个答案:

答案 0 :(得分:14)

这是一个非常讨厌的小问题,有三个(!)原因。

首先,存在浮点运算是近似的问题。如果编译器选择返回float或double的pow函数,那么4 ** 31是如此之大,以至于5小于1ULP(精度最低的单位),因此添加它将无效(换句话说,4.0 * * 31 + 5 == 4.0 ** 31)。乘以-2可以毫无损失地完成,并且结果可以存储在long long中而不会丢失作为错误的答案:-9.223.372.036.854.775.808。

其次,标准标题可能包含其他标准标题,但不是必需的。显然,Visual Studio的<iostream>版本包括<math.h>(在全局命名空间中声明pow),但Code :: Blocks'版本不包括。{/ p>

第三,未选择OP的pow函数,因为他传递的参数431都是int类型,并且声明的函数有参数类型为unsigned。从C ++ 11开始,std::pow有很多重载(或函数模板)。这些都返回floatdouble(除非其中一个参数的类型为long double - 这里不适用)。

因此std::pow的重载将更好地匹配...具有双返回值,并且我们得到浮点舍入。

故事的道德:不要编写与标准库函数同名的函数,除非你真的知道你在做什么!

答案 1 :(得分:3)

Visual Studio定义了pow(double, int),只需要转换一个参数,而pow(unsigned, unsigned)需要转换两个参数,除非您使用pow(4U, 31U)。 C ++中的重载分辨率基于输入 - 而不是结果类型。

答案 2 :(得分:2)

可以通过numeric_limits获得最低的长期长值。很长一段时间,它是:

auto lowest_ll = std::numeric_limits<long long>::lowest();

导致:

  

-9223372036854775808

被调用的pow()函数不是您的,因此观察到的结果。更改功能的名称。

答案 3 :(得分:2)

对-9.223.372.036.854.775.808结果唯一可能的解释是使用标准库中的pow函数返回double值。在这种情况下,5将低于双精度计算的精度,结果将精确为-2 63 并转换为long long将给出0x8000000000000000-9.223.372.036.854.775.808

如果您使用函数返回unsigned long long,则会收到一条警告,提示您将unary减号应用于无符号类型并仍然获得ULL。因此整个操作应该以unsigned long long的形式执行,并且应该在没有溢出0x8000000000000005的情况下作为无符号值。当您将其转换为有符号值时,结果是未定义的,但我知道的所有编译器只使用带有相同表示形式的有符号整数-9.223.372.036.854.775.803

但只要使用:

就可以很简单地将计算设置为长签名而不会发出任何警告
long long nr =  -1 * pow(4, 31) + 5 - pow(4,31);

另外,你既没有未定义的强制转换也没有溢出,因此如果无符号long long至少为64位,则每个标准都可以完美地定义结果。

答案 4 :(得分:1)

您对pow的第一次调用是使用C标准库的函数,该函数在浮点上运行。尝试为pow函数指定一个唯一名称:

unsigned long long my_pow(unsigned a, unsigned b) {
    unsigned long long p = 1;
    for (unsigned i = 0; i < b; i++)
        p *= a;
    return p;
}

int main()
{
    long long nr = -my_pow(4, 31) + 5 - my_pow(4, 31);
    std::cout << nr << std::endl;
}

此代码报告错误:“一元减运算符应用于无符号类型,结果仍未签名”。所以,基本上,你的原始代码称为浮点函数,否定了该值,对它应用了一些整数运算,因为它没有足够的精度来给出你想要的答案(在19位数的预测!)。要获得您正在寻找的答案,请将签名更改为:

long long my_pow(unsigned a, unsigned b);

这在MSVC ++ 2013中对我有用。正如其他答案所述,你得到了浮点pow,因为你的函数需要unsigned,并且接收有符号整数常量。将U添加到整数会调用您的pow版本。