C/C++代码优化之求两个整型的平均值
在 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++代码优化之求两个整型的平均值的更多相关文章
- swap 用指针交换两个整型数值
- c++作业:输入两个整数,用函数求两数之和。函数外部声明有什么作用?
#include <iostream> using namespace std; int main(){ //求两数的和? int a,b,s; cout<<"请你输 ...
- Php数据类型之整型详解
php中支持的数据类型 在php中主要支持8种数据类型.和3中伪类型的一个形式.8种数据类型分为以下三3大类,第一个就是我们的标量类型,标量类型它只能存储单一数据,那第二大类就是我们的复合类型,第三个 ...
- java 整型相除得到浮点型
public class TestFloatOrDouble { public static void main(String[] args) { Point num1 = new Point(84, ...
- python 函数求两个数的最大公约数和最小公倍数
1. 求最小公倍数的算法: 最小公倍数 = 两个整数的乘积 / 最大公约数 所以我们首先要求出两个整数的最大公约数, 求两个数的最大公约数思路如下: 2. 求最大公约数算法: 1. 整数A对整数 ...
- JavaScript求两个数字之间所有数字的和
这是在fcc上的中级算法中的第一题,拉出来的原因并不是因为有什么好说的,而是我刚看时以为是求两个数字的和, 很显然错了.我感觉自己的文字理解能力被严重鄙视了- -.故拉出来折腾折腾. 要求: 给你一个 ...
- [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 ...
- 求两圆相交部分面积(C++)
已知两圆圆心坐标和半径,求相交部分面积: #include <iostream> using namespace std; #include<cmath> #include&l ...
- 面试问题2:给一个5G的大文件,保存的数据为32位的整型,找到所有出现次数超过两次的数字
问题描述:给一个5G的大文件,保存的数据为32位的整型,找到所有出现次数超过两次的数字 大数据操作: 解决方法一: 依次遍历文件数据, 开始32二进制清0 每次读取一个数,先和二进制位与,如果为0 则 ...
随机推荐
- MySQL必知必会1-20章读书笔记
MySQL备忘 目录 目录 使用MySQL 检索数据 排序检索数据 过滤数据 数据过滤 用通配符进行过滤 用正则表达式进行搜索 创建计算字段 使用数据处理函数 数值处理函数 汇总数据 分组数据 使用子 ...
- 谷歌浏览器的F12用处及问题筛查笔记
在前端测试功能的时候,经常有些莫名其妙的错误,这个时候开发会说打开F12看一下吧,所以感觉这个开发者功能很有用,研究一下,做如下记录: Elements:左栏以DOM树形式查看网页源代码(HTML), ...
- IP 基础知识全家桶,45 张图一套带走
前言 前段时间,有读者希望我写一篇关于 IP 分类地址.子网划分等的文章,他反馈常常混淆,摸不着头脑. 那么,说来就来!而且要盘就盘全一点,顺便挑战下小林的图解功力,所以就来个 IP 基础知识全家桶. ...
- 重新认识 Spring IOC
spring IOC 剖析 再品IOC与DI IOC(Inversion of Control) 控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器来帮忙实现. ...
- EChart将timeline图标设置为加号
一 要实现的效果 二 官方文档 看一下官方文档上,替换symbol的描述: timeline.symbol string [ default: 'emptyCircle' ] timeline标记的图 ...
- Linux文件操作命令并举例说明其作用
ls ,常用于查看当前文件下有工作中需要的文件 cd, 常用于进行切换文件的位置 vim,常用于编辑软件系统相关的配置文件 ps –ef|grep jdk,常用语显示跟jdk有关的进程 |:表示 ...
- [java作业]Fan、求直线交点、Triangle2D、选课
public class Fan { public static void main(String[] args) { Fan fan1 = new Fan(), fan2 = new Fan(); ...
- Mybatis学习笔记汇总(包括源码和jar包)
博客整理 Mybatis学习笔记(一)--对原生jdbc中问题的总结 Mybatis学习笔记(二)--Mybatis框架 Mybatis学习笔记(三)--入门程序 MyBatis学习笔记(四)--入门 ...
- JS的IIFE
1. 定义 IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数. 首先我们要了解一般情况下什 ...
- LUNIX命令集
Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 UNIX 的多用户.多任务.支持多线程和多 CPU 的操作系统. Linux 能运行主要的 UNIX 工具软件 ...