双溢出?
Double 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
这是怎么回事?为什么 d3
和 d1
的值不同?
编辑:
我决定将我的代码更改为这样以查看结果:
#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
但是,d1
和 d3
不应该仍然是相同的值吗?
这是一个精度损失的案例。考虑以下因素。
#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
的精度时,超出的精度将被丢弃。
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 - 您将得到四舍五入的结果(不太重要的数字被丢弃),通过潜水 - 您将得到 "almost" 16,这是四舍五入的到 16.
我一直想知道如果 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
这是怎么回事?为什么 d3
和 d1
的值不同?
编辑:
我决定将我的代码更改为这样以查看结果:
#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
但是,d1
和 d3
不应该仍然是相同的值吗?
这是一个精度损失的案例。考虑以下因素。
#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
的精度时,超出的精度将被丢弃。
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 - 您将得到四舍五入的结果(不太重要的数字被丢弃),通过潜水 - 您将得到 "almost" 16,这是四舍五入的到 16.