C/C++程式碼優化之求兩個整型的平均值

  • 2020 年 5 月 29 日
  • 筆記

在 C/C++ 中, 直接利用 (x + y) >> 1 來計算 \(\left\lfloor {\left( {x + y} \right)/2} \right\rfloor\) (兩個整數的平均值並向下取整)以及直接利用 (x + y + 1) >> 1 來計算 \(\left\lceil {\left( {x + y} \right)/2} \right\rceil\) (兩個整數的平均值並向上取整)的結果可能有誤, 因為 (x + y) >> 1(x + y + 1) >> 1 中的 x + y 可能會發生數值溢出. 而 \(\left\lfloor {\left( {x + y} \right)/2} \right\rfloor\)\(\left\lceil {\left( {x + y} \right)/2} \right\rceil\) 的結果是不可能數值溢出的, 這就引發我們思考可不可能通過某種方式來規避平均值計算中的數值溢出.

方式一

利用如下公式

\(\begin{align}
\left\lfloor {\left( {x + y} \right)/2} \right\rfloor = \left\lfloor {x/2} \right\rfloor + \left\lfloor {y/2} \right\rfloor + \left\lfloor {\left( {x\bmod 2 + y\bmod 2} \right)/2} \right\rfloor \hfill \\
\left\lceil {\left( {x + y} \right)/2} \right\rceil = \left\lfloor {x/2} \right\rfloor + \left\lfloor {y/2} \right\rfloor + \left\lceil {\left( {x\bmod 2 + y\bmod 2} \right)/2} \right\rceil \hfill \\
\end{align}\)

下面是對上述兩式的證明:
\(\begin{align}
\left\lfloor {\left( {x + y} \right)/2} \right\rfloor &= \left\{ {\begin{array}{*{20}{c}}
{m + n}&{x = 2m,y = 2n} \\
{m + n}&{x = 2m + 1,y = 2n} \\
{m + n}&{x = 2m,y = 2n + 1} \\
{m + n + 1}&{x = 2m + 1,y = 2n + 1}
\end{array}} \right. \\
&= \left\lfloor {x/2} \right\rfloor + \left\lfloor {y/2} \right\rfloor + \left\lfloor {\left( {x\bmod 2 + y\bmod 2} \right)/2} \right\rfloor \\
\end{align}\)

\(\begin{align}
\left\lceil {\left( {x + y} \right)/2} \right\rceil &= \left\{ {\begin{array}{*{20}{c}}
{m + n}&{x = 2m,y = 2n} \\
{m + n + 1}&{x = 2m + 1,y = 2n} \\
{m + n + 1}&{x = 2m,y = 2n + 1} \\
{m + n + 1}&{x = 2m + 1,y = 2n + 1}
\end{array}} \right. \\
&= \left\lfloor {x/2} \right\rfloor + \left\lfloor {y/2} \right\rfloor + \left\lceil {\left( {x\bmod 2 + y\bmod 2} \right)/2} \right\rceil \\
\end{align}\)

其中 \(m,n\) 均為整數.

借用上面的公式可以 \(\left\lfloor {\left( {x + y} \right)/2} \right\rfloor\) 轉化為如下的 C/C++ 程式碼 (據說這段程式碼還被申請了專利):

(x >> 1) + (y >> 1) + (x & y & 1);

可以將 \(\left\lceil {\left( {x + y} \right)/2} \right\rceil\) 轉化為如下的 C/C++ 程式碼:

(x >> 1) + (y >> 1) + ((x | y) & 1);

這兩段程式碼都不會發生數值溢出.

方式二

設 x 和 y 只能取 0 和 1 值, 則:

x y x + y x ^ y x & y x | y 2*(x & y) + (x ^ y) 2*(x | y) – (x ^ y)
0 0 0 0 0 0 0 + 0 = 0 0 – 0 = 0
0 1 1 1 0 1 0 + 1 = 1 10 – 1 = 1
1 0 1 1 0 1 0 + 1 = 1 10 – 1 = 1
1 1 10 0 1 1 10 + 0 = 10 10 – 0 = 10

注意上表中的 10 是二進位下的 10, 即十進位下的 2, & 是邏輯與操作, | 是邏輯或運算, ^ 是邏輯異或操作.

由上表可見 x + y = 2*(x & y) + (x ^ y) = 2*(x | y) - (x ^ y).

無符號整型

對於無符號整型, 設 \(x = \sum\nolimits_{i = 0}^{n – 1} {{u_i}{2^i}}\)\(y = \sum\nolimits_{i = 0}^{n – 1} {{v_i}{2^i}}\), 其中 \(u_i,v_i\in\left\{ 0, 1 \right\}\).

\(\begin{align}
x + y &= \sum\nolimits_{i = 0}^{n – 1} {{u_i}{2^i}} {\text{ + }}\sum\nolimits_{i = 0}^{n – 1} {{v_i}{2^i}} \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} + {v_i}} \right){2^i}} \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {2 \times \left( {{u_i}\& {v_i}} \right) + \left( {{u_i} \wedge {v_i}} \right)} \right){2^i}} \\
&= 2\sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^i}} \\
\end{align}\)

\(\begin{align}
\left\lfloor {\left( {x + y} \right)/2} \right\rfloor &= \left\lfloor {\sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right\rfloor \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} + \sum\nolimits_{i = 1}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} \\
\end{align}\)

上式用 C/C++語言可以表示為:

(x & y) + ((x ^ y) >> 1);

\(\begin{align}
x + y &= \sum\nolimits_{i = 0}^{n – 1} {{u_i}{2^i}} {\text{ + }}\sum\nolimits_{i = 0}^{n – 1} {{v_i}{2^i}} \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} + {v_i}} \right){2^i}} \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {2 \times \left( {{u_i}|{v_i}} \right) – \left( {{u_i} \wedge {v_i}} \right)} \right){2^i}} \\
&= 2\sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} – \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^i}} \\
\end{align}\)

\(\begin{align}
\left\lceil {\left( {x + y} \right)/2} \right\rceil &= \left\lceil {\sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} – \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right\rceil \\
&= \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} – \sum\nolimits_{i = 1}^{n – 1} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} \\
\end{align}\)

上式用 C/C++ 語言可以表示為:

(x | y) - ((x ^ y) >> 1);

有符號整型

對於有符號整型, 設 \(x = – {u_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{u_i}{2^i}}\)\(y = – {v_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{v_i}{2^i}}\), 其中 \(u_i,v_i\in\left\{ 0, 1 \right\}\).

\(\begin{align}
x + y &= – {u_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{u_i}{2^i}} – {v_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{v_i}{2^i}} \\
&= – \left( {{u_{n – 1}} + {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} + {v_i}} \right){2^i}} \\
&= – \left( {2 \times \left( {{u_{n – 1}}\& {v_{n – 1}}} \right) + \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right)} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {2 \times \left( {{u_i}\& {v_i}} \right) + \left( {{u_i} \wedge {v_i}} \right)} \right){2^i}} \\
&= 2\left( { – \left( {{u_{n – 1}}\& {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} } \right) + \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^i}} } \right) \\
\end{align}\)

\(\begin{align}
\left\lfloor {\left( {x + y} \right)/2} \right\rfloor &= \left\lfloor {\left( { – \left( {{u_{n – 1}}\& {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} } \right) + \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 2}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right)} \right\rfloor \\
&= \left( { – \left( {{u_{n – 1}}\& {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}\& {v_i}} \right){2^i}} } \right) + \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 2}} + \sum\nolimits_{i = 1}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right) \\
\end{align}\)

上式用 C/C++ 語言可以表示為:

(x & y) + ((x ^ y) >> 1);

\(\begin{align}
x + y &= – {u_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{u_i}{2^i}} – {v_{n – 1}}{2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {{v_i}{2^i}} \\
&= – \left( {{u_{n – 1}} + {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} + {v_i}} \right){2^i}} \\
&= – \left( {2 \times \left( {{u_{n – 1}}|{v_{n – 1}}} \right) – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right)} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {2 \times \left( {{u_i}|{v_i}} \right) – \left( {{u_i} \wedge {v_i}} \right)} \right){2^i}} \\
&= 2\left( { – \left( {{u_{n – 1}}|{v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} } \right) – \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^i}} } \right) \\
\end{align}\)

\(\begin{align}
\left\lceil {\left( {x + y} \right)/2} \right\rceil &= \left\lceil {\left( { – \left( {{u_{n – 1}}|{v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} } \right) – \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 2}} + \sum\nolimits_{i = 0}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right)} \right\rceil \\
&= \left( { – \left( {{u_{n – 1}}|{v_{n – 1}}} \right){2^{n – 1}} + \sum\nolimits_{i = 0}^{n – 1} {\left( {{u_i}|{v_i}} \right){2^i}} } \right) – \left( { – \left( {{u_{n – 1}} \wedge {v_{n – 1}}} \right){2^{n – 2}} + \sum\nolimits_{i = 1}^{n – 2} {\left( {{u_i} \wedge {v_i}} \right){2^{i – 1}}} } \right) \\
\end{align}\)

上式用 C/C++ 語言可以表示為:

(x | y) - ((x ^ y) >> 1);

綜合

綜合上面的分析, 可見對於有符號整型和無符號整型,

\(\left\lfloor {\left( {x + y} \right)/2} \right\rfloor\) 都可以用 C/C++ 語言表示為:

(x & y) + ((x ^ y) >> 1);

\(\left\lceil {\left( {x + y} \right)/2} \right\rceil\) 都可以用 C/C++ 語言表示為:

(x | y) - ((x ^ y) >> 1);

參考:

版權聲明

版權聲明:自由分享,保持署名-非商業用途-非衍生,知識共享3.0協議。
如果你對本文有疑問或建議,歡迎留言!轉載請保留版權聲明!
如果你覺得本文不錯, 也可以用微信讚賞一下哈.