在 C/C++ 中, 直接利用 (x + y) >> 1
来计算 (leftlfloor {left( {x + y}
ight)/2}
ight
floor) (两个整数的平均值并向下取整)以及直接利用 (x + y + 1) >> 1
来计算 (leftlceil {left( {x + y}
ight)/2}
ight
ceil) (两个整数的平均值并向上取整)的结果可能有误, 因为 (x + y) >> 1
和 (x + y + 1) >> 1
中的 x + y
可能会发生数值溢出. 而 (leftlfloor {left( {x + y}
ight)/2}
ight
floor) 和 (leftlceil {left( {x + y}
ight)/2}
ight
ceil) 的结果是不可能数值溢出的, 这就引发我们思考可不可能通过某种方式来规避平均值计算中的数值溢出.
注: 本文假设符号数的右移运算符进行的是算术右移, 符号数的编码方式采用的是 two's complement 编码.
方式一
利用如下公式
(egin{align} leftlfloor {left( {x + y} ight)/2} ight floor = leftlfloor {x/2} ight floor + leftlfloor {y/2} ight floor + leftlfloor {left( {xmod 2 + ymod 2} ight)/2} ight floor hfill \ leftlceil {left( {x + y} ight)/2} ight ceil = leftlfloor {x/2} ight floor + leftlfloor {y/2} ight floor + leftlceil {left( {xmod 2 + ymod 2} ight)/2} ight ceil hfill \ end{align})
下面是对上述两式的证明:
(egin{align}
leftlfloor {left( {x + y}
ight)/2}
ight
floor &= left{ {egin{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}}
ight. \
&= leftlfloor {x/2}
ight
floor + leftlfloor {y/2}
ight
floor + leftlfloor {left( {xmod 2 + ymod 2}
ight)/2}
ight
floor \
end{align})
(egin{align} leftlceil {left( {x + y} ight)/2} ight ceil &= left{ {egin{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}} ight. \ &= leftlfloor {x/2} ight floor + leftlfloor {y/2} ight floor + leftlceil {left( {xmod 2 + ymod 2} ight)/2} ight ceil \ end{align})
其中 (m,n) 均为整数.
借用上面的公式可以将 (leftlfloor {left( {x + y} ight)/2} ight floor) 转化为如下的 C/C++ 代码 (据说这段代码还被申请了专利):
(x >> 1) + (y >> 1) + (x & y & 1);
可以将 (leftlceil {left( {x + y} ight)/2} ight ceil) 转化为如下的 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 olimits_{i = 0}^{n - 1} {{u_i}{2^i}}) 和 (y = sum olimits_{i = 0}^{n - 1} {{v_i}{2^i}}), 其中 (u_i,v_iinleft{ 0, 1 ight}).
(egin{align} x + y &= sum olimits_{i = 0}^{n - 1} {{u_i}{2^i}} { ext{ + }}sum olimits_{i = 0}^{n - 1} {{v_i}{2^i}} \ &= sum olimits_{i = 0}^{n - 1} {left( {{u_i} + {v_i}} ight){2^i}} \ &= sum olimits_{i = 0}^{n - 1} {left( {2 imes left( {{u_i}& {v_i}} ight) + left( {{u_i} wedge {v_i}} ight)} ight){2^i}} \ &= 2sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^i}} \ end{align})
(egin{align} leftlfloor {left( {x + y} ight)/2} ight floor &= leftlfloor {sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight floor \ &= sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} + sum olimits_{i = 1}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} \ end{align})
上式用 C/C++ 语言可以表示为:
(x & y) + ((x ^ y) >> 1);
(egin{align} x + y &= sum olimits_{i = 0}^{n - 1} {{u_i}{2^i}} { ext{ + }}sum olimits_{i = 0}^{n - 1} {{v_i}{2^i}} \ &= sum olimits_{i = 0}^{n - 1} {left( {{u_i} + {v_i}} ight){2^i}} \ &= sum olimits_{i = 0}^{n - 1} {left( {2 imes left( {{u_i}|{v_i}} ight) - left( {{u_i} wedge {v_i}} ight)} ight){2^i}} \ &= 2sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} - sum olimits_{i = 0}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^i}} \ end{align})
(egin{align} leftlceil {left( {x + y} ight)/2} ight ceil &= leftlceil {sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} - sum olimits_{i = 0}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight ceil \ &= sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} - sum olimits_{i = 1}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} \ end{align})
上式用 C/C++ 语言可以表示为:
(x | y) - ((x ^ y) >> 1);
有符号整型
对于有符号整型, 设 (x = - {u_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{u_i}{2^i}}) 和 (y = - {v_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{v_i}{2^i}}), 其中 (u_i,v_iinleft{ 0, 1 ight}).
(egin{align} x + y &= - {u_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{u_i}{2^i}} - {v_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{v_i}{2^i}} \ &= - left( {{u_{n - 1}} + {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} + {v_i}} ight){2^i}} \ &= - left( {2 imes left( {{u_{n - 1}}& {v_{n - 1}}} ight) + left( {{u_{n - 1}} wedge {v_{n - 1}}} ight)} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {2 imes left( {{u_i}& {v_i}} ight) + left( {{u_i} wedge {v_i}} ight)} ight){2^i}} \ &= 2left( { - left( {{u_{n - 1}}& {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} } ight) + left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^i}} } ight) \ end{align})
(egin{align} leftlfloor {left( {x + y} ight)/2} ight floor &= leftlfloor {left( { - left( {{u_{n - 1}}& {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} } ight) + left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 2}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight)} ight floor \ &= left( { - left( {{u_{n - 1}}& {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} } ight) + left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 2}} + sum olimits_{i = 1}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight) \ &= left( { - left( {{u_{n - 1}}& {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}& {v_i}} ight){2^i}} } ight) + left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 1}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight) \ end{align})
上式用 C/C++ 语言可以表示为:
(x & y) + ((x ^ y) >> 1);
(egin{align} x + y &= - {u_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{u_i}{2^i}} - {v_{n - 1}}{2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {{v_i}{2^i}} \ &= - left( {{u_{n - 1}} + {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} + {v_i}} ight){2^i}} \ &= - left( {2 imes left( {{u_{n - 1}}|{v_{n - 1}}} ight) - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight)} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {2 imes left( {{u_i}|{v_i}} ight) - left( {{u_i} wedge {v_i}} ight)} ight){2^i}} \ &= 2left( { - left( {{u_{n - 1}}|{v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} } ight) - left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^i}} } ight) \ end{align})
(egin{align} leftlceil {left( {x + y} ight)/2} ight ceil &= leftlceil {left( { - left( {{u_{n - 1}}|{v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} } ight) - left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 2}} + sum olimits_{i = 0}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight)} ight ceil \ &= left( { - left( {{u_{n - 1}}|{v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} } ight) - left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 2}} + sum olimits_{i = 1}^{n - 2} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight) \ &= left( { - left( {{u_{n - 1}}|{v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 0}^{n - 1} {left( {{u_i}|{v_i}} ight){2^i}} } ight) - left( { - left( {{u_{n - 1}} wedge {v_{n - 1}}} ight){2^{n - 1}} + sum olimits_{i = 1}^{n - 1} {left( {{u_i} wedge {v_i}} ight){2^{i - 1}}} } ight) \ end{align})
上式用 C/C++ 语言可以表示为:
(x | y) - ((x ^ y) >> 1);
综合
综合上面的分析, 可见对于有符号整型和无符号整型,
(leftlfloor {left( {x + y} ight)/2} ight floor) 都可以用 C/C++ 语言表示为:
(x & y) + ((x ^ y) >> 1);
(leftlceil {left( {x + y} ight)/2} ight ceil) 都可以用 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协议。
如果你对本文有疑问或建议,欢迎留言!转载请保留版权声明!
如果你觉得本文不错, 也可以用微信赞赏一下哈.