簡體   English   中英

從 double 轉換為 size_t 會產生錯誤的結果?

[英]Cast from double to size_t yields wrong result?

以下代碼有效。 我的問題是,2)不應該導致非常接近 1)的結果嗎? 為什么 2) 鑄造的數量如此之少? 因此,也許值得注意的是 2) 正好是 1) 的一半:

std::cout << "1)  " << std::pow(2, 8 * sizeof(size_t)) << std::endl;
std::cout << "2)  " << static_cast<size_t>(std::pow(2, 8 * sizeof(size_t))) << std::endl; 

output 是:

  1. 18446744073709551616
  2. 9223372036854775808

這是由於規范的那一部分:

7.3.10 浮點整數轉換[conv.fpint]

浮點類型的純右值可以轉換為 integer 類型的純右值。 轉換截斷; 也就是說,小數部分被丟棄。 如果截斷的值不能在目標類型中表示,則行為未定義。

18446744073709551616 (即截斷部分)大於系統上的std::numberic_limit<size_t>::max() ,因此,該強制轉換的行為未定義。

如果我們想計算某個無符號整數數據類型可以表示的不同值的數量,我們可以計算

 std::cout << "1)  " << std::pow(2, 8 * sizeof(size_t)) << std::endl; // yields 18446744073709551616

這將計算 2 的 64 次方並產生 18446744073709551616。由於 sizeof(size_t) 是 8 字節,在 64 位機器上,並且一個字節有 8 位,因此 size_t 數據類型的寬度是 64 位,因此是 2^64。

這並不奇怪,因為通常系統上的 size_t 具有其底層硬件總線系統的寬度,因為我們希望消耗不超過一個時鍾周期來傳遞數組或向量的地址或索引。

上面的數字表示可以用 64 位無符號整數數據類型表示的所有不同整數值的數量,如 size_t 或 unsigned long long,包括 0 作為一種可能性。 並且由於它確實包含 0,因此要表示的最高值正好少一,所以 18446744073709551615。

這個號碼也可以通過

 std::cout << std::numeric_limits<size_t>::max() << std::endl; // yields 18446744073709551615
 std::cout << std::numeric_limits<unsigned long long>::max() << std::endl; // yields the same

現在一個無符號數據類型存儲它的值,比如

   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 is 1 or 2^0
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010 is 2 or 2^1
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000011 is 3 or 2^1+2^0
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000100 is 4 or 2^2
   ...
   11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615
   and if you want to add another 1, you would need a 65th bit on the left which you dont have:
 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 because 
   there are no more bits on the left.

任何高於您希望表示的最高可能值的金額都將歸結為以最大可能值 + 1 為模的金額。(金額 % (max + 1)),正如我們在上面的示例中看到的那樣,這導致為零。

由於這很自然,因此標准定義,如果您將任何有符號或無符號整數數據類型轉換為另一種無符號整數數據類型,則將轉換為最大可能值 + 1 的模數。漂亮。

但是,當我們希望將負積分轉換為無符號積分(例如 -1 到 unsigned long long 示例)時,這條簡單的規則對我們來說有點意外。 你首先有一個 0 值,然后你減去 1。發生的是上面示例的相反序列。 看一看:

  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 and now do -1
  11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615

所以是的,將 -1 轉換為 size_t 會導致 std::numeric_limits<size_t>::max()。 起初非常令人難以置信,但經過一些思考和玩弄之后可以理解。

現在我們的第二行代碼

 std::cout << "2)  " << static_cast<size_t>(std::pow(2, 8 * sizeof(size_t))) << std::endl;

我們會天真地期待 18446744073709551616,當然,結果與第一行相同。

但是,既然我們現在知道模最大 + 1 並且我們現在知道最大的加 1 給出 0,我們也會再次天真地接受 0 作為答案。

為什么天真? 因為 std::pow 返回一個 double 而不是整數數據類型。 double 數據類型也是 64 位的,但在內部它的表示完全不同。

 0XXXXXXX XXXX0000 00000000 00000000 00000000 00000000 00000000 00000000

只有這 11 個 X 位代表 2^n 形式的指數。 這意味着只有這 11 位必須顯示 64,而 double 將表示 2^64 * 1。所以我們的大數的表示在 double 中比在 size_t 中緊湊得多。 在將 2^64 的表示更改為 64 位線之前,是否有人想要對最大加 1 進行模數轉換。

例如,可以在https://docs.microsoft.com/en-us/cpp/build/ieee-floating-point-representation?view=msvc-160中找到有關浮點表示的一些進一步閱讀。

標准規定,如果將浮點值轉換為目標整數數據類型無法表示的整數,則結果為 UB,即未定義的行為。

請參閱 C++17 標准 ISO/IEC14882:7.10 浮點積分轉換 [conv.fpint]

  1. 浮點類型的純右值可以轉換為 integer 類型的純右值。 轉換截斷; 也就是說,小數部分被丟棄。 如果截斷的值不能在目標類型中表示,則行為未定義。 ...

所以 double 可以輕松容納 2^64,這就是為什么第 1 行可以如此輕松地打印出來的原因。 但是在 size_t 中表示太多了,所以結果是 UB。 所以無論我們第 2 行的結果是什么,都是無關緊要的,因為它是 UB。

好的,但是如果任何隨機結果都可以,為什么 UB 結果正好是一半? 首先,結果來自 MSVC。 Clang 或其他編譯器可能會提供任何其他 UB 結果。

但是讓我們看看“一半”的結果,因為它很容易。

   Trying to add 1 to the largest  
   11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615
   would if only integrals would be involved lead to, 
 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
   but thats not possible since the bit does not exist and it is not integral but double datatype and 
   hence UB, so accidentially the result is
   10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 which is 9223372036854775808
   so exactly half of the naively expected or 2^63.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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