在 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\lceil {\left( {x + y} \right)/2} \right\rceil\) 都可以用 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协议。

如果你对本文有疑问或建议,欢迎留言!转载请保留版权声明!

如果你觉得本文不错, 也可以用微信赞赏一下哈.

C/C++代码优化之求两个整型的平均值的更多相关文章

  1. swap 用指针交换两个整型数值

  2. c++作业:输入两个整数,用函数求两数之和。函数外部声明有什么作用?

    #include <iostream> using namespace std; int main(){ //求两数的和? int a,b,s; cout<<"请你输 ...

  3. Php数据类型之整型详解

    php中支持的数据类型 在php中主要支持8种数据类型.和3中伪类型的一个形式.8种数据类型分为以下三3大类,第一个就是我们的标量类型,标量类型它只能存储单一数据,那第二大类就是我们的复合类型,第三个 ...

  4. java 整型相除得到浮点型

    public class TestFloatOrDouble { public static void main(String[] args) { Point num1 = new Point(84, ...

  5. python 函数求两个数的最大公约数和最小公倍数

    1. 求最小公倍数的算法: 最小公倍数  =  两个整数的乘积 /  最大公约数 所以我们首先要求出两个整数的最大公约数, 求两个数的最大公约数思路如下: 2. 求最大公约数算法: 1. 整数A对整数 ...

  6. JavaScript求两个数字之间所有数字的和

    这是在fcc上的中级算法中的第一题,拉出来的原因并不是因为有什么好说的,而是我刚看时以为是求两个数字的和, 很显然错了.我感觉自己的文字理解能力被严重鄙视了- -.故拉出来折腾折腾. 要求: 给你一个 ...

  7. [LeetCode] Intersection of Two Linked Lists 求两个链表的交点

    Write a program to find the node at which the intersection of two singly linked lists begins. For ex ...

  8. 求两圆相交部分面积(C++)

    已知两圆圆心坐标和半径,求相交部分面积: #include <iostream> using namespace std; #include<cmath> #include&l ...

  9. 面试问题2:给一个5G的大文件,保存的数据为32位的整型,找到所有出现次数超过两次的数字

    问题描述:给一个5G的大文件,保存的数据为32位的整型,找到所有出现次数超过两次的数字 大数据操作: 解决方法一: 依次遍历文件数据, 开始32二进制清0 每次读取一个数,先和二进制位与,如果为0 则 ...

随机推荐

  1. Naigos install pnp4nagios 绘图插件

    原文地址:http://www.cnblogs.com/caoguo/p/5022230.html vim /etc/httpd/conf/httpd.conf <Directory " ...

  2. 【BIM】BIMFACE中创建雾化效果

    背景 在BIM运维场景初始化的时候,一般都是首先将整个运维对象呈现在用户面前,例如一座大厦.一座桥梁.一个园区等等,以便于用户进行总览,总体把握运维对象,如果这个宏大的场景边界过于清晰,与背景融合也不 ...

  3. Programming Languages_04 Deferred Substitution

    Deferred Substitution 在执行出现with时,利用"substitution",每次with的出现,它都绕着整个body置换.这一方式是由F1WAE到env再到 ...

  4. Oracle触发器之替代触发器

    替代触发器 替代视图增删改操作.视图可以认为成逻辑上的一张表,类似于把一个sql语句的执行结果永久的像表存储到数据 库中,视图一般用来做查询. 创建视图的语法: create view 视图名称 as ...

  5. [hdu4763]next数组的应用

    http://acm.hdu.edu.cn/showproblem.php?pid=4763 题目大意:给一个字符串,判断是否可以写成ABACA,B.C表示长度大于等于0的字符串. 方法:ans = ...

  6. [csu/coj 1079]树上路径查询 LCA

    题意:询问树上从u到v的路径是否经过k 思路:把树dfs转化为有根树后,对于u,v的路径而言,设p为u,v的最近公共祖先,u到v的路径必定是可以看成两条路径的组合,u->p,v->p,这样 ...

  7. Java多线程带返回值的Callable接口

    Java多线程带返回值的Callable接口 在面试的时候,有时候是不是会遇到面试会问你,Java中实现多线程的方式有几种?你知道吗?你知道Java中有可以返回值的线程吗?在具体的用法你知道吗?如果两 ...

  8. ql的python学习之路-day9

    前言:本节主要学习装饰器 一.装饰器 定义:本质上是个函数,用来装饰其他函数:(就是为其他函数添加附加功能) 原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 以上两点可以总 ...

  9. 使用QQ同步助手备份同步手机数据

    QQ同步助手官网:https://pim.qq.com/ QQ同步助手,由腾讯精心打造的云端备份工具.能实现手机之间传输文件,并备份文件.照片.视频.联系人.短信.通讯记录.应用程序到云端的换手机必备 ...

  10. 判断数组的方法/判断JS数据类型的四种方法

    参考文: 以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() . instanceof 以及 Array.isArray() h ...