簡體   English   中英

為什么 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不能精確存儲 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) - 1double表達式而不是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM