双重溢出?

时间:2017-01-05 18:25:02

标签: c++ overflow

我一直想知道double达到最大值时会发生什么,所以我决定编写这段代码:

#include <stdint.h>
#include <iostream>
#define UINT64_SIZE 18446744073709551615
int main() {
    std::uint64_t i = UINT64_SIZE;
    double d1 = ((double)(i+1)) / UINT64_SIZE; 
    double d2 = (((double)(i)) / UINT64_SIZE)*16;
    double d3 = ((double)(i * 16)) / UINT64_SIZE;

    std::cout << d1 << " " << d2 << " " << d3; 
}

我期待这样的事情:

0 16 0 

但这是我的输出:

0 16 1 

这里发生了什么?为什么d3d1的值不同?

编辑:

我决定将我的代码更改为此以查看结果:

#include <stdint.h>
#include <iostream>
#define UINT64_SIZE 18446744073709551615
int main() {
    std::uint64_t i = UINT64_SIZE;
    double d1 = ((double)(i+1.0)) / UINT64_SIZE; //what? 
    double d2 = (((double)(i)) / UINT64_SIZE)*16;
    double d3 = ((double)(i * 16.0)) / UINT64_SIZE;

    std::cout << d1 << " " << d2 << " " << d3; 
}

我现在得到的结果是:

1 16 16 

但是,d1d3不应该是相同的值吗?

2 个答案:

答案 0 :(得分:4)

double通过失去精度而溢出,而不是从0开始(因为它与无符号整数一起使用)

<强> D1

所以,当你将1.0加到非常大的值(18446744073709551615)时,你在double中没有得到0,但是像18446744073709551610(注意最后10而不是15)或18446744073709551620(注意最后20)而不是15),所以 - 不太重要的数字是四舍五入的。

现在,您将两个几乎相同的值分开,结果将是0.9(9)9或1.0(0)1,只要double无法保持这么小的值 - 再次失去精度和舍入到1.0。

<强> D3

几乎相同的是,当你有多个巨大值16时 - 你得到圆润的结果(不太重要的数字被抛弃),通过潜水 - 你得到的几乎&#34;几乎&#34; 16,舍入到16。

答案 1 :(得分:2)

这是精度损失的情况。请考虑以下事项。

#include <stdint.h>
#include <iostream>

#define UINT64_SIZE 18446744073709551615

int main() {
    std::uint64_t i = UINT64_SIZE;

    auto a = i;
    auto b = i * 16;
    auto c = (double)b;
    auto d = (uint64_t)c;

    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    std::cout << d << std::endl;

    return 0;
}

在我的系统上输出如下。

18446744073709551615
18446744073709551600
1.8446744073709552e+19
9223372036854775808
在这种情况下,

double根本没有足够的精确度。

编辑:还有一个舍入问题。当您使用UINT64_SIZE预先形成除法时,会将分母提升为双倍,并且您将得到介于0.0和1.0之间的小数值。小数不会四舍五入。实际值非常接近1.0,并在推到std::cout时向上舍入。

在你的问题中,你会问“如果双倍达到它的最大值,会发生什么”。请注意,在您提供的示例中,double没有接近它的最大值。只有它的精度超过了。当超过double的精度时,将丢弃多余的精度。