【问题标题】:long long value in Visual StudioVisual Studio 中的 long long 值
【发布时间】:2016-12-08 15:36:10
【问题描述】:

我们知道 -2*4^31 + 1 = -9.223.372.036.854.775.807,可以存储在 long long 中的最低值,如这里所说: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。

【问题讨论】:

标签: c++ long-long


【解决方案1】:

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

首先存在浮点运算是近似的问题。如果编译器选择一个返回浮点数或双精度的pow 函数,那么 4**31 太大以至于 5 小于 1ULP(最小精度单位),因此添加它不会执行任何操作(换句话说,4.0**31 +5 == 4.0**31)。乘以 -2 可以不丢失,结果可以存储在 long long 中而不丢失作为错误答案:-9.223.372.036.854.775.808。

其次,标准标头可能包含其他标准标头,但不是必须的。显然,Visual Studio 的 &lt;iostream&gt; 版本包括 &lt;math.h&gt;(在全局命名空间中声明 pow),但 Code::Blocks 的版本没有。

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

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

故事的寓意:不要编写与标准库函数同名的函数,除非您真的知道自己在做什么!

【讨论】:

  • “Visual Studio 包含”应为“Visual Studio 附带的&lt;iostream&gt; 版本包含”
【解决方案2】:

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

【讨论】:

  • 另外,包括可以(但不是必须)声明pow的标准版本。我怀疑问题在于它在 VS 上,而不是在 Code:Blocks 上。
【解决方案3】:

可以通过numeric_limits获取最低long long值。很长很长是:

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

导致:

-9223372036854775808

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

【讨论】:

  • 请看问题下的cmets。他还尝试了自制的 unsigned long long 函数。
【解决方案4】:

-9.223.372.036.854.775.808 结果的唯一可能解释是使用标准库中的pow 函数返回一个双精度值。在这种情况下,5 将低于双精度计算的精度,结果将恰好为 -263 并转换为 long long 将得到 0x8000000000000000-9.223.372.036.854.775.808

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

但是,只需使用以下命令就可以很简单地使计算成为有符号的 long long 且没有任何警告:

long long nr =  -1 * pow(4, 31) + 5 - pow(4,31);

此外,这里既没有未定义的强制转换也没有溢出,因此如果 unsigned long long 至少为 64 位,则结果完全按照标准定义。

【讨论】:

    【解决方案5】:

    您对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 版本。

    【讨论】:

    • “一元减号运算符应用于无符号类型,结果仍然无符号”不是错误。
    • 接受 MSVC。 :) 我没有设置任何特殊的编译标志。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多