为什么UINT64_T不能正确显示POW(2,64)-1

Why uint64_t cannot show pow(2, 64) - 1 properly?

本文关键字:POW 显示 UINT64 不能 为什么      更新时间:2023-10-16

我试图了解为什么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)-1strong> double无法精确存储2 64 -1 由于缺乏显着范围。您只能使用具有64位精度或更多位的类型(例如许多平台上的long double)。尝试std::pow(2.0L, 64) - 1.0L(注意L后缀)或powl(2.0L, 64) - 1.0L;,请参见

无论如何,您从一开始就不应将浮点类型用于整数数学。计算pow(2, x)的不仅要比1ULL << x慢得多,而且由于double的精确度有限,还会导致您看到的问题。如果编译器支持__int128

,请改用uint64_t max2 = -1((unsigned __int128)1ULL << 64) - 1

pow(2, 64) - 1double表达式不是 int,因为pow没有任何返回积分类型的过载。整数1将被提升为与pow

结果相同的等级

但是,由于IEEE-754双重精度仅为64位长,因此您可以从不P>

  • 64位未签名的整数无法映射到双重
  • 所有整数值是否完美地表示为双打?

因此,pow(2, 64) - 1将被四舍五入到pow(2, 64)本身的最接近代表值,并且pow(2, 64) - 1 == pow(2, 64)将导致1.最大的值。SUP>-2048。您可以使用std::nextafter

检查

在某些平台上(尤其是x86,除了在MSVC上) long double确实具有64位显着的,因此在这种情况下您将获得正确的值。以下片段

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四倍体或双双。两者都有超过64位的显着性,因此您可以做同样的事情。但是,当然,开销将更高