10

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

4

5 回答 5

15

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

首先存在浮点运算是近似的问题。如果编译器选择pow返回 float 或 double 的函数,则 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 的版本没有。

第三,pow没有选择 OP 的函数,因为他传递了参数431,它们都是 type int,并且声明的函数具有 type 的参数unsigned。从 C++11 开始,有很多std::pow. 这些都返回floator double(除非其中一个参数是类型long double- 此处不适用)。

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

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

于 2016-12-08T17:10:07.183 回答
3

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

于 2016-12-08T16:37:20.263 回答
2

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

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

但是,只需使用以下命令,就可以简单地使计算为 long long 而没有任何警告:

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

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

于 2016-12-08T17:26:02.743 回答
2

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

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

这导致:

-9223372036854775808

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

于 2016-12-08T15:54:11.327 回答
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.

于 2016-12-08T17:00:24.367 回答