contributed by < Holycung
>
2020q3 Homework5 (quiz5) 題目
考慮到以下浮點數除法程式: (fdiv.c)
假設 divop()
的第二個參數必為大於 0 的整數,而且不超過 int 型態能表達的數值上界。請補完程式碼。
回答這題要先了解一下在 IEEE 754 下浮點數的定義。
這題的觀念是把原本的浮點數除法都替換成除以 2 進行約分,因為整數除以二可以用往右 shift 一位來加速,浮點數除以二只需要把 exponent 的部分減一就好。那就可以把狀況分成下面兩種,假設 ,已知 Y 為大於零的整數,第一種狀況就是 為偶數,那我們可以直接對 都向右 shift 一位進行約分,也就是 。第二種狀況就是 為奇數,這就是我們所要探討的。
為奇數了話我們就沒有辦法直接除二,那這邊的做法就是先將 ,把原本的 變成 ,這樣 就是偶數就可以用第一種狀況的方法,不過我們知道 跟原本的值會不同,且 ,所以我們會需要補差值回去,那個差值就是 ,稍微化減一下如下。
所以這邊我們得到了差值 ,可以發現這邊 跟我們前面第二種狀況是一樣的,接下來我們就可以來看程式。
第 5 行這邊是判斷如果,除數 slot
是 1 或是被除數 orig
是 0 就直接回傳被除數 orig
。
第 7 行 od
是判斷除數是否為奇數,只要拿最後一個位元跟 1 做 bitwise and 就可以得到。
第 8 行這邊就是我們剛剛上面推論的方法,把浮點數的除法全部換成除以二約分遞迴下去,divop
第一個參數是 orig / D1
,就是對被除數除以二的動作,所以 D1
= 2
這邊是浮點數除二所以能用 shift。
第二個參數會先判斷除數 slots
是否為奇數,如果是偶數就直接進行 shift,如果是奇數就加一後在進行 shift,所以 D2
= 1
。最後把遞迴的結果存回到 result
變數,所以 result
= 。
第 9 10 行最後就是要判斷除數是否為奇數,如果是奇數了話我們需要把差值補回去,經過剛剛的推導差值 ,也就是 。
TODO
延續 從 的運算談浮點數,假設 float 為 32-bit 寬度,考慮以下符合 IEEE 754 規範的平方根程式,請補上對應數值,使其得以運作:
請補完程式碼,使上述程式碼得以運作。
再回答這題前可以先看過浮點數運算和定點數操作,熟悉一下 IEEE 754 對浮點數的規範,再來直接看到程式碼。
在 C 當中的 union 用法可以參考 Union type wiki。
a union is a value that may have any of several representations or formats within the same position in memory;
這邊有用到 你所不知道的C語言:技巧篇 提到的 do { ... } while(0) 巨集
是為了避免 dangling else 問題,也可以參考這篇 macro do{}while(0) 更詳細的解釋。
這兩個 macro INSERT_WORDS
、EXTRACT_WORDS
,是運用 union 來轉換 float 跟 int,參考 RinHizakura 的例子如下:
如果用 uint32_t
的角度來解釋,就是
如果從 float
(IEEE 754) 的角度來解釋,則是
第 34 行先把浮點數 x 轉成整數 ix0
。
第 37 行這邊是先處理小魚等於 0 的數字,第 38 行是判斷是否為正零或負零(除了 sign bit 其他都是 0),如果是 0 的話就回傳 0。
第 40 行是判斷是否小於 0,對小於零的數取平方根的話要回傳 NaN,第 41 行是透過 0.0 / 0.0 產生 NaN,這邊可以從 IEEE 754-2019 規格書當中 6.2.1 先了解到 NaN 的編碼格式,然後又分程 quiet NaN 和 signal NaN,再看到 7.2 有明確規範當浮點數 0.0 / 0.0 的時候會產生 quiet NaN。
6.2.1 NaN encodings in binary interchange formats
All binary NaN bit strings have the sign bit S set to 0 or 1 and all the bits of the biased exponent field E set to 1 (see 3.4). A quiet NaN bit string should be encoded with the first bit (d1) of the trailing significand field T being 1. A signaling NaN bit string should be encoded with the first bit of the trailing significand field being 0.
7.2 Invalid operation
For operations producing results in floating-point format, the default result of an operation that signals the invalid operation exception shall be a quiet NaN that should provide some diagnostic information.
e) division: division(0, 0) or division(∞, ∞)
第 44 行是判斷 +INF 和 NaN 如果是,ix0 & 0x7f800000 == 0x7f800000
就可以判斷 Exponent 是否等於 255,也就是 INF、NaN 的狀況。
第 48 行把 ix0 向右 shift 23 個得到 m 就是 exponent 的值,如果 m == 0
代表是 denormalized number,這邊就需要先把他變成 normalized number 的形式一起處理,所以這邊的想法是把 ix0 往左 shift 也就是把 fraction 的部分往左移動,直到第 24 個位元是 1,然後記錄下移動了幾次 i
,每移動一次就相當指數 m 要減一,所以最後要把 m -= i - 1
,就變成了 normalized 的形式。
因為剛剛處理完 denormalized,這邊就可以直接用 normalized 的形式處理,也就是 ,所以第 55 行就把 m 減掉 127。
第 56 行是把 ix0
只留下後面 fraction 23 bits 的部分,把 24 位元變成 1,因為前面已經把 exponent 的部分保留下來到 m,然後也把 denormalized 的部分變成 normalized 的形式,所以第 24 位元本來就是 1,因此這邊就只留下後面 24 位元也就是 1.F。
第 57 行是判斷 m 是否為偶數,因為對 取平方根了話就會是 ,那如果 m 不是偶數了話,就把 ix0
乘以二,這樣就可以把 m 除以二。
接著為計算 square root 的部分,這邊的方法可以參考 How to calculate a square root without a calculator,其方法的證明可以參考 Why the square root algorithm works,應用到二進位 Methods of computing square roots 也是一樣的,我們則可以利用這個方法把平方根換成較快的 bit shift operations,接下來進行推導,推導這部分是參考 RusselCK。
接下來我們可以回到程式碼。
第 63 行,把 ix0
往左 shift 一位。這樣 ix0
的寬度可能是 25 或 26 個位元,所以 QQ0
= 0x01000000
,第 25 個位元為一,等於把小數點點在第 24 25 個位元之間,從第 25 個 bit 開始,這樣代表我們會精確到小數點後面第 24 位,那在 IEEE 754 的規範中 fraction 部分有 23 位,我們就可以把最後一位做 rounding,精確到小數點後 23 位。小數點點在這邊也可以確保我們前面的假設 是正確的。
第 79 行這邊是判斷 rounding,首先要先判斷系統的 rounding mode,這邊透過 1 加減一個很小的數 tiny
,讓系統進行 rounding 來判斷其 rounding mode。
one - tiny
如果小於 1,代表是 rounding down,那不管最低位是 0 或 1 都會被捨棄掉,所以不需要額外動作。one + tiny
如果大於 1,代表是 rounding up,那 23 bit 就直接直接進位,所以這邊是 q += 2
。one + tiny
如果等於 1,代表是 round to nearest,此時就要判斷最低位是 0 或 1,如果是 1 就要進位,所以是 q += (q & 1)
。最後要把 exponent 跟 fraction 的部分合併回來,所以第 90 行先把 q 往右 shift 1 位,不過要注意 q 現在還剩下 24 位,因為剛剛整理得到的是 所以我們這邊的 QQ1
= 0x3f000000
,讓中間 exponent 最低 7 位 bit 都補上一,因為 IEEE 754 normalized 的格式是 ,也就是在這邊 exponent 先加上 127。第 91 行,在把 m 往左 shift 23 個,加回去 ix0
。
最後在透過 INSERT_WORDS
轉回浮點數回傳。
練習 LeetCode 69. Sqrt(x),提交不需要 FPU 指令的實作。
從 的運算談浮點數 了解到開根號可以透過二分逼近法或是十分逼近法來找到,再研讀 以牛頓法求整數開二次方根的近似值 (第 19 頁),決定使用牛頓法來實作。
透過多次逼近可以取得更精準的值,今天我們要找到平方根,所以 n=2:
接下來看到程式。
那最開始的值要從多少開始逼近呢,這邊可以從 INT_MAX 最大值開始逼近,但是我們可以透過算幾不等式,找到比 大但又不會太大的值開始,轉換成程式碼就是 (x + 1) >> 1
,不過這邊要小心因為會對 x 做加一的動作,所以如果 x == INT_MAX
要避免 overflow 就要先把他減一。
最後 while 迴圈的終止條件則是找到第一個 的 。
另外還有找到一個快速整數平方根算法,有時間了話再來研究QQ
LeetCode 829. Consecutive Numbers Sum 給定一個正整數 N,問 N 能寫成多少種連續正整數之和,例如 9 可寫成 4+5,或者 2+3+4。由於要寫成連續正整數之和,則可用等差數列來思考,且差值為 1,這個等差數列不必從 1 開始。
參考實作如下:
請補完程式碼,使上述程式碼得以運作。
假設從 x 開始,且個數共有 k 個,則可寫出該等差數列為:
經過化減後:
因此我們就可以透過 來判斷這個 k 長度的連續整數是否是一組解。
第 5 行先初始化回傳值為 1,因為任何大於 0 的整數都必定會有自己的一個解。
第 7 行的迴圈就是從 k=2 的長度開始找起,第 8 行就是 ,所以 ZZZ
= x += (1 - k)
,第 9 行就是判斷能不能整除,可以的話就代表可以找到一個解 ret++
。
TODO