在 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\) 的結果是不可能數值溢出的, 這就引發我們思考可不可能通過某種方式來規避平均值計算中的數值溢出.
注: 本文假設符號數的右移運算符進行的是算術右移, 符號數的編碼方式采用的是 two's complement 編碼.
方式一
利用如下公式
\(\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) \\ &= \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 = 1}^{n - 1} {\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) \\ &= \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 = 1}^{n - 1} {\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);
參考:
- https://stackoverflow.com/questions/3816446/how-can-i-safely-average-two-unsigned-ints-in-c
- https://stackoverflow.com/questions/24920503/what-is-the-right-way-to-find-the-average-of-two-values
版權聲明
版權聲明:自由分享,保持署名-非商業用途-非衍生,知識共享3.0協議。
如果你對本文有疑問或建議,歡迎留言!轉載請保留版權聲明!