为什么 uint64_t 不能正确显示 pow(2, 64) - 1?
Why uint64_t cannot show pow(2, 64) - 1 properly?
我想了解为什么 uint64_t
类型无法正确显示 pow(2,64)-1
。 cplusplus标准是199711L.
我检查了C++98标准下的pow()
函数
double pow (double base , double exponent);
float pow (float base , float exponent);
long double pow (long double base, long double exponent);
double pow (double base , int exponent);
long double pow (long double base, int exponent);
所以我写了下面的片段
double max1 = (pow(2, 64) - 1);
cout << max1 << endl;
uint64_t max2 = (pow(2, 64) - 1);
cout << max2 << endl;
uint64_t max3 = -1;
cout << max3 << endl;
输出是:
max1: 1.84467e+019
max2: 9223372036854775808
max3: 18446744073709551615
浮点数的精度有限。
在您的系统上(通常采用 binary64 IEEE-754 格式)18446744073709551615
不是具有 double
格式表示的数字。确实有表示的最接近的数字恰好是 18446744073709551616
.
将两个大小截然不同的浮点数相减(和相加)通常会产生错误。该错误对于较小的操作数可能很重要。在 18446744073709551616. - 1. -> 18446744073709551616.
的情况下,减法的误差为 1,实际上与较小的操作数的值相同。
当浮点值转换为整数类型,并且该值不能适合整数类型时,程序的行为是未定义的 - 即使整数类型是无符号的。
TL;DR: 不是 uint64_t
类型无法显示 pow(2,64)-1
正确 但相反:double
由于缺少有效数字而无法精确存储 264 - 1位。您只能使用具有 64 位或更高精度的类型(例如许多平台上的 long double
)。尝试 std::pow(2.0L, 64) - 1.0L
(注意 L
后缀)或 powl(2.0L, 64) - 1.0L;
并查看
无论如何,您不应该从一开始就对整数数学使用浮点类型。不仅计算 pow(2, x)
比计算 1ULL << x
慢得多,而且由于 double
的精度有限,它还会导致您看到的问题。使用 uint64_t max2 = -1
代替,如果编译器支持 __int128
,则使用 ((unsigned __int128)1ULL << 64) - 1
pow(2, 64) - 1
是一个double
表达式,不是 int
,因为 pow
没有任何 returns 整数类型的重载。整数 1
将提升到与 pow
的结果相同的排名
但是,由于 IEEE-754 双精度只有 64 位长,您可以永远存储具有 64 个或更多有效位的值,如 264-1
- 64-bit unsigned integers which cannot map onto a double
因此 pow(2, 64) - 1
将四舍五入为 最接近的可表示值 ,即 pow(2, 64)
本身,而 pow(2, 64) - 1 == pow(2, 64)
将得到 1。小于它的最大值是 18446744073709549568 = 264 - 2048。您可以使用 std::nextafter
检查
在某些平台上(特别是 x86,MSVC 除外)long double
does have 64 bits of significand, so you'll get the correct value in that case. The following snippet
double max1 = pow(2, 64) - 1;
std::cout << "pow(2, 64) - 1 = " << std::fixed << max1 << '\n';
std::cout << "Previous representable value: " << std::nextafter(max1, 0) << '\n';
std::cout << (pow(2, 64) - 1 == pow(2, 64)) << '\n';
long double max2 = pow(2.0L, 64) - 1.0L;
std::cout << std::fixed << max2 << '\n';
打印出来
pow(2, 64) - 1 = 18446744073709551616.000000
Previous representable value: 18446744073709549568.000000
1
18446744073709551615.000000
你可以清楚地看到long double
可以按预期存储正确的值
在许多其他平台上 double
可能是 IEEE-754 quadruple-precision or double-double。两者都有超过 64 位的有效位,所以你可以做同样的事情。但是开销当然会更高
我想了解为什么 uint64_t
类型无法正确显示 pow(2,64)-1
。 cplusplus标准是199711L.
我检查了C++98标准下的pow()
函数
double pow (double base , double exponent);
float pow (float base , float exponent);
long double pow (long double base, long double exponent);
double pow (double base , int exponent);
long double pow (long double base, int exponent);
所以我写了下面的片段
double max1 = (pow(2, 64) - 1);
cout << max1 << endl;
uint64_t max2 = (pow(2, 64) - 1);
cout << max2 << endl;
uint64_t max3 = -1;
cout << max3 << endl;
输出是:
max1: 1.84467e+019
max2: 9223372036854775808
max3: 18446744073709551615
浮点数的精度有限。
在您的系统上(通常采用 binary64 IEEE-754 格式)18446744073709551615
不是具有 double
格式表示的数字。确实有表示的最接近的数字恰好是 18446744073709551616
.
将两个大小截然不同的浮点数相减(和相加)通常会产生错误。该错误对于较小的操作数可能很重要。在 18446744073709551616. - 1. -> 18446744073709551616.
的情况下,减法的误差为 1,实际上与较小的操作数的值相同。
当浮点值转换为整数类型,并且该值不能适合整数类型时,程序的行为是未定义的 - 即使整数类型是无符号的。
TL;DR: 不是 uint64_t
类型无法显示 pow(2,64)-1
正确 但相反:double
由于缺少有效数字而无法精确存储 264 - 1位。您只能使用具有 64 位或更高精度的类型(例如许多平台上的 long double
)。尝试 std::pow(2.0L, 64) - 1.0L
(注意 L
后缀)或 powl(2.0L, 64) - 1.0L;
并查看
无论如何,您不应该从一开始就对整数数学使用浮点类型。不仅计算 pow(2, x)
比计算 1ULL << x
慢得多,而且由于 double
的精度有限,它还会导致您看到的问题。使用 uint64_t max2 = -1
代替,如果编译器支持 __int128
((unsigned __int128)1ULL << 64) - 1
pow(2, 64) - 1
是一个double
表达式,不是 int
,因为 pow
没有任何 returns 整数类型的重载。整数 1
将提升到与 pow
但是,由于 IEEE-754 双精度只有 64 位长,您可以永远存储具有 64 个或更多有效位的值,如 264-1
- 64-bit unsigned integers which cannot map onto a double
因此 pow(2, 64) - 1
将四舍五入为 最接近的可表示值 ,即 pow(2, 64)
本身,而 pow(2, 64) - 1 == pow(2, 64)
将得到 1。小于它的最大值是 18446744073709549568 = 264 - 2048。您可以使用 std::nextafter
在某些平台上(特别是 x86,MSVC 除外)long double
does have 64 bits of significand, so you'll get the correct value in that case. The following snippet
double max1 = pow(2, 64) - 1;
std::cout << "pow(2, 64) - 1 = " << std::fixed << max1 << '\n';
std::cout << "Previous representable value: " << std::nextafter(max1, 0) << '\n';
std::cout << (pow(2, 64) - 1 == pow(2, 64)) << '\n';
long double max2 = pow(2.0L, 64) - 1.0L;
std::cout << std::fixed << max2 << '\n';
打印出来
pow(2, 64) - 1 = 18446744073709551616.000000
Previous representable value: 18446744073709549568.000000
1
18446744073709551615.000000
你可以清楚地看到long double
可以按预期存储正确的值
在许多其他平台上 double
可能是 IEEE-754 quadruple-precision or double-double。两者都有超过 64 位的有效位,所以你可以做同样的事情。但是开销当然会更高