程式碼優化之整型除以2的指數並四捨五入
阿新 • • 發佈:2020-05-24
# 引子
前幾天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
#