1. 程式人生 > >程式碼優化之整型除以2的指數並四捨五入

程式碼優化之整型除以2的指數並四捨五入

# 引子 前幾天QQ群裡一位好友提出來一個問題: "整型(有正有負)除以2的指數結果四捨五入, 應該如何優化呢", 當時做了答, 發表在這裡, 希望對大家有用. # 符號約記 下取整函式的定義: $\left\lfloor x \right\rfloor = \max \left\{ {z \in \mathbb{Z}:z \leqslant x} \right\}$ 上取整函式的定義: $\left\lceil x \right\rceil = \min \left\{ {z \in \mathbb{Z}:z \geqslant x} \right\}$ 四捨五入取整函式的定義: $\left[\kern-0.15em\left[ x \right]\kern-0.15em\right] = \left\{ {\begin{array}{*{20}{c}} {\left\lfloor {x + 0.5} \right\rfloor }&{x > 0} \\ {\left\lceil {x - 0.5} \right\rceil }&{x \leqslant 0} \end{array}} \right.$ # 正文 問題: 整型(有正有負)除以2的指數結果四捨五入, 應該如何優化呢? 問題轉化為: 將 $\left[\kern-0.15em\left[ {\frac{m}{2^k}} \right]\kern-0.15em\right]$ 轉化為向下取整 (以便能夠使用移位運算) 的形式,其中 $m$ 為整數, $k$ 為非負整數. 答: 因為 $\left[\kern-0.15em\left[ {\frac{m}{n}} \right]\kern-0.15em\right] = \left\{ {\begin{array}{*{20}{c}} {\left\lfloor {\frac{m}{n} + \frac{1}{2}} \right\rfloor = \left\lfloor {\frac{1}{n}\left( {m + \left\lfloor {\frac{n}{2}} \right\rfloor } \right)} \right\rfloor }&{mn \geqslant 0} \\ {\left\lceil {\frac{m}{n} - \frac{1}{2}} \right\rceil = \left\lceil {\frac{1}{n}\left( {m - \left\lfloor {\frac{n}{2}} \right\rfloor } \right)} \right\rceil }&{mn < 0} \end{array}} \right.$, 其中 $m, n$ 均為整數, $n \neq 0$. 特別地, 設 $n = 2^k$ ( $k$ 為非負整數) 時, 當 $k=0$ 時, $\left[\kern-0.15em\left[ m \right]\kern-0.15em\right] = m$; 當 $k>0$ 時, $\left[\kern-0.15em\left[ {\frac{m}{{{2^k}}}} \right]\kern-0.15em\right] = \left\{ {\begin{array}{*{20}{c}} {\left\lfloor {\frac{m}{{{2^k}}} + \frac{1}{2}} \right\rfloor = \left\lfloor {\frac{1}{{{2^k}}}\left( {m + \left\lfloor {\frac{{{2^k}}}{2}} \right\rfloor } \right)} \right\rfloor = \left\lfloor {\frac{{m + {2^{k - 1}}}}{{{2^k}}}} \right\rfloor }&{m \geqslant 0} \\ {\left\lceil {\frac{m}{{{2^k}}} - \frac{1}{2}} \right\rceil = \left\lceil {\frac{1}{{{2^k}}}\left( {m - \left\lfloor {\frac{{{2^k}}}{2}} \right\rfloor } \right)} \right\rceil = \left\lceil {\frac{{m - {2^{k - 1}}}}{{{2^k}}}} \right\rceil }&{m < 0} \end{array}} \right.$. 又因為 $\left\lceil {\frac{m}{n}} \right\rceil = \left\{ {\begin{array}{*{20}{c}} {\left\lfloor {\frac{{m + n - 1}}{n}} \right\rfloor = \left\lfloor {\frac{{m - 1}}{n}} \right\rfloor + 1}&{n > 0} \\ {\left\lfloor {\frac{{m + n + 1}}{n}} \right\rfloor = \left\lfloor {\frac{{m + 1}}{n}} \right\rfloor + 1}&{n < 0} \end{array}} \right.$, 所以 $\left\lceil {\frac{{m - {2^{k - 1}}}}{{{2^k}}}} \right\rceil = \left\lfloor {\frac{{m - {2^{k - 1}} + {2^k} - 1}}{{{2^k}}}} \right\rfloor = \left\lfloor {\frac{{m + {2^{k - 1}} - 1}}{{{2^k}}}} \right\rfloor$. 綜上, $\left[\kern-0.15em\left[ {\frac{m}{{{2^k}}}} \right]\kern-0.15em\right] = \left\{ {\begin{array}{*{20}{c}} m&{k = 0} \\ {\left\lfloor {\frac{{m + {2^{k - 1}}}}{{{2^k}}}} \right\rfloor }&{k > 0,m \geqslant 0} \\ {\left\lfloor {\frac{{m + {2^{k - 1}} - 1}}{{{2^k}}}} \right\rfloor }&{k > 0,m < 0} \end{array}} \right.$. 上式可以很方便地轉寫為只包含加減和位運算的C/C++程式碼 (注意該函式是有適用條件, 見程式碼註釋). ```C const int INT_BITS = 32; int div_exp2(int x, unsigned char k) { assert( (k >= 0) && (k < INT_BITS)); if (k == 0) return x; int tail = x >> (INT_BITS - 1); int exp2_k_1 = 1 << (k - 1); // 當 x >=0 時, tail += exp2_k_1 相當於 x = 1 << (k - 1) // 當 x < 0 時, tail += exp2_k_1 相當於 x = (1 << (k - 1)) - 1 // 這時 k > 0, 所以 tail >= 0 恆成立. tail += exp2_k_1; // 注意 x + tail 不能有溢位. 當 x <= 0 時, x + tail 顯然不會溢位; // 當 x > 0 時, 則有可能發生溢位, 需要對 x 和 k 施加更嚴格的取值範圍限制 (待續). return (x + tail) >> k; } ``` # 測試程式碼 ```C #