[英]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
不能精確存儲 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
精度有限,它還會導致您看到的問題。 使用uint64_t max2 = -1
代替,或者((unsigned __int128)1ULL << 64) - 1
如果編譯器支持__int128
pow(2, 64) - 1
是double
表達式,而不是int
,因為pow
沒有任何返回整數類型的重載。 整數1
將被提升到與pow
的結果相同的等級
但是,由於 IEEE-754 雙精度只有 64 位長,因此您永遠無法存儲具有 64 位或更多有效位的值,例如 2 64 -1
因此pow(2, 64) - 1
將四舍五入到最接近的可表示值,即pow(2, 64)
本身,而pow(2, 64) - 1 == pow(2, 64)
將導致 1。小於它的最大值是 18446744073709549568 = 2 64 - 2048。您可以使用std::nextafter
檢查
在某些平台(特別是 x86,MSVC 除外)上, long double
確實有64 位的 significand ,因此在這種情況下您將獲得正確的值。 以下片段
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 四倍精度或double-double 。 兩者都有超過 64 位的有效數,所以你可以做同樣的事情。 但當然開銷會更高
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.