加减乘除简单吗?不,一点都不,利用位运算实现加减乘除(代码中不含+ - * /)

news2024/11/13 15:17:56

请添加图片描述

文章目录

  • 🚀前言
  • 🚀异或运算以及与运算
  • 🚀加法的实现
  • 🚀减法的实现
  • 🚀乘法的实现
  • 🚀除法的实现

🚀前言

这也是阿辉开的新专栏,知识将会很零散不成体系,不过绝对干货满满,今天这一篇利用位运算实现加减乘除费了阿辉九牛二虎之力,干的很自备饮水😆不多bb,进入今天的学习吧!!!


以下int均为有符号int,所求的加减乘除也是int类型的整型数
严谨 😏

🚀异或运算以及与运算

在写加减乘除之前,先给铁子们介绍一下异或运算以及与运算的其他理解
异或运算:也叫无进位相加
这怎么理解呢?
铁子们都知道,异或运算,是二进制位相异为1,相同为0

其实异或运算也可以解释为无进位相加。这是什么意思呢?就是对应的二进制位相加,如果产生进位就将进位舍去。什么是进位呢?对应的二进制位相加为2时就向前进一位,就像我们小时候学的加法一样。而二进制进位后,本位就剩下一个0。而在异或运算中,进位被舍去,所以当两个位都为1时,异或的结果为0;当两个位都为0时,异或的结果也为0;只有一个位为1,另一个位为0时,异或的结果才为1

给铁子们上图解释:
请添加图片描述
与运算:得到对应二进制位相加的进位信息右移一位后的结果
怎么理解呢?
铁子们都知道,与运算,是两二进制位都为1为1,一个为0就为0

实际上,与运算是得到对应二进制位相加的进位信息右移一位后的结果,怎么理解呢?二进制位相加时产生的进位保留,没有进位舍去直接置0,然后将得到的结果右移一位。

给铁子们上图解释:

请添加图片描述

🚀加法的实现

有了上述关于异或运算和与运算的新的理解,基于此,我们可以用他们拼凑出加法,怎么拼呢?铁子们接着看👇

根据前面的讲解我们知道,两个数异或运算得到的是两个数相加去掉进位信息后的结果,而两个数与运算得到的是两个数相加保留进位信息右移一位后的结果,铁子们,睁大眼骚操作来了😏,异或运算是去掉进位信息的结果,与运算结果再左移一位(进位信息右移了嘛,再左移回去)是保留进位信息的结果,那两个数异或运算的结果加上两个数与运算再左移一位的结果不就是两个数相加的结果嘛

铁子们一定会说这不还得在加一遍,有毛用,别急还有更骚的😏😏,两个数的异或运算的结果和与运算左移一位的结果,只要与运算左移一位的结果不为0也就是进位信息不为0,得到的俩个数重复上述操作,直到进位信息为0,异或运算的结果不就是两数相加的结果嘛。铁子们,骚不骚?觉得骚的,记得在评论区给阿辉扣个666 😘

肯定有老铁会说难道进位信息一定会计算到0吗?好问题哈哈哈哈 😜,一定会计算到0,为什么呢?阿辉用图说明,鼠标写的铁子们凑合看一下🙏
请添加图片描述

异或运算就是把二进制数相加得到如上述不加上蓝色进位信息的结果,然后和进位信息继续异或,直到不在产生进位信息得到相加的结果

如果还有铁子不懂,可以私聊阿辉加个微信,阿辉必给你搞明白,上面进位信息为啥一定会算到0是阿辉自己想的,自认还有点骚,哈哈哈 😁!
下面就是代码实现了,别看讲了这一堆,其实代码很好写

int Add(int x, int y)//传入需要需要相加的数
{
	while (y)//y就是进位信息,为0时跳出循环
	{
		int a = x ^ y;//利用临时变量保证下一步计算进位信息x不变
		y = (x & y) << 1;//进位信息
		x = a;//把去掉进位信息的结果赋给a,重复上述操作
	}
	return x;
}

代码就这么短,骚不骚?哈哈哈,这篇博客写的爽,铁子们后面的内容更骚,嘿嘿嘿!!!

🚀减法的实现

减法就没什么技术含量了,因为比如5-4还可以写成5+(-4),套上前面写的加法然后,给减数改成相反数即可
相反数怎么整?让阿辉装一手,嘿嘿

原反补码,阿辉就不讲了,默认大家都会
按位取反操作符~,一个数取反后,符号位会改变,这么一整,该数的正负相反了,符号位以外的数也取反了,如果再加个1,大家不看符号位这不就是负数原码得到补码的过程,哟,这不拿捏了-a = ~a + 1
代码不就好写了:

int Sub(int x, int y)//传入被减数与减数
{
	return Add(x, ~y + 1);//减数取相反数后与被减数相加
}

🚀乘法的实现

乘法也是稀松平常,没什么难理解的地方,怎么实现呢?铁子们别急,阿辉整个例子你们就懂了👊
请添加图片描述
铁子们这里阿辉,先给出代码再解释:

int Mul(int x, int y)//传进来两数
{
	int ret = 0;//作返回值
	for (int i = 0; i <= 30; i = Add(i, 1))//符号位不算,固定循环31次
	{
		//遍历y的除符号位的31位,判断是否为1
		if (y >> i & 1 == 1)//对应二进制位为1,进入if
		{
		//因为二进制位只有0和1,0乘任何数还是0,所以不用管
		//当为1时,1乘任何数还是它本身,所以加上x
			ret = Add(ret, x);
		}
		//遍历一次x左移一位,因为再循环,y向后一位找1
		//这个1代表2的i次方,相当于把这个2的i次方乘在了x上
		//左移一位相当于乘2,右移一位相当于除2
		x <<= 1;
	}
	return ret;
}

铁子们注意,上述实现的乘法可以计算负数,为什么呢?这与原反补码有关,阿辉水平有限解释不出来,铁子们感兴趣可以研究一下,找到记得和阿辉分享一波,嘿嘿
铁子们,没懂的话可以自己想想试试,如果实在不懂可以私信我好吧,下面的除法才是重头戏特别麻烦 💀

🚀除法的实现

除法比较麻烦,这里阿辉实现的除法是整数除法,上图给大家引导:
请添加图片描述
关于将除法抽象成代码,这是一个相当复杂的过程。首先,让我们回顾一下如何进行除法运算。在我们最常见的十进制系统中,以被除数728÷8为例,我们是如何得出十位上的9的呢?除法运算实际上是在找到一个最大的数,使得将除数8乘以这个数接近被除数728,然后再用728减去这个数720(8×90),然后继续这个过程,要么除尽要么除不尽。不过在整数除法中,如果除不尽就舍去余数。实际上,二进制数的除法运算也是类似的过程。与十进制不同的是,二进制位只有1,因此只需要将除数101后面加上0来逼近被除数101001011(在二进制中,将除数101左移一位就相当于除数后面加上一个0),然后用101001011减去101×1000000(101左移6位),然后重复这个操作直到除尽

但是我们在写代码时不能通过除数左移去逼近,因为这样会有风险
请添加图片描述
红色方块的位置代表符号位。我们给除数b左移,左移一位比a小,继续左移直到比a大才知道上一次左移逼近被除数a。但是对于我们给的这个例子b怎么左移也不可能比a大,因为当b右移11位时,符号位变成1了,b直接变成负数了。
这里我们通过被除数右移代替除数左移来逼近除数达到一样的效果,因为被除数右移多少位逼近除数也就是除数左移多少位逼近被除数,这样就不会有改变符号位的风险
请添加图片描述
我们让除数a从右移14位开始,然后右移位数依次递减,当a右移10位时第一次大于b也就是b左移10位逼近a,然后a减去b左移10位后的数得到的结果重复上述操作第一次找到的就是最高位的1就像我们算十进制除法一样,我们首先找到千位随后就是百位、十位只不过二进制只有0和1很多位都是0

铁子们,下面我会根据代码讲,尽我所能讲明白好吧!
首先,我们实现的除法并不能处理负数,要先把负数处理成正数
这里我们实现一个函数oppo()求相反数

int oppo(int x)//相反数
{
	return Add(~x, 1);//上面减法的时候解释过了
}

下面是关于除法的具体实现(不包括系统最小值)

int dived(int x, int y)//不含系统最小数的除法
{
	//负数改正数,正数则不变
	int a = x < 0 ? oppo(x) : x;//小于0就取相反数
	int b = y < 0 ? oppo(y) : y;
	int ret = 0;//作返回值
	//除了符号位,遍历31次
	for (int i = 30; i >= 0; i = Sub(i, 1))
	{
		//i从30开始依次递减
		//当a第一次右移i位大于b时,说明最高位的数字找到了
		//进入if把值加到返回值ret上
		if (a >> i >= b)
		{
			ret |= 1 << i;//ret初始值时0,把1或上去
			a = Sub(a, Mul(b,ret));//同时更新a的值,a=a-b×ret
		}
	}
	//只有x和y不同时为负数或正数时要取相反数
	//x,y同时大于或小于0,相除就是正数嘛
	//异或值相等为0嘛就是假
	//这个异或骚不骚,代替了下面这么挫的代码
	//if((x > 0 && y < 0) || (x < 0 && y > 0))
	if (x > 0 ^ y > 0)
		return oppo(ret);//取相反数
	return ret;
}

包含系统最小值,int类型的取值为-2^30^ ~ 0 ~ 2^30^-1,因为0是包含在正数里的,所以系统最小值的绝对值大于系统最大值,所以我们面临一个问题,系统最小值没有它的相反数,所以求系统最小值我们要单独拎出来求
五种情况(x是被除数,y是除数):

  • x,y都是系统最小,直接返回1
  • y是系统最小,x不是,直接返回0(整数除法)
  • x是系统最小,y是-1,那结果就是系统最小的绝对值,值域没这个数,直接返回-1
  • x,y都不是系统最小,咱们上面实现的那个代码就是
  • x是系统最小,y不是,这个情况就是我们下面要解决的

x是系统最小,y不是,这里我们无法给它取相反数,我们给x拆成两部分,a=(x+1)得到的就不是系统最小了,用b = (a÷y),可以用我们上面的dived()这个函数来求,然后在用c = x - (b × y),接着a = c ÷ y ,最后a+b就是x÷y的结果
请添加图片描述
就是上面这张图这个原理,没啥难的,不一定要加1,2、3、4……都可以

INT_MIN被宏定义成int类型的最小值,使用需要引<limits.h>头文件

int Div(int x, int y)
{
	if (x == INT_MIN && y == INT_MIN)
		return 1;
	else if (x == INT_MIN && y == -1)
		return -1;
	else if (y == INT_MIN)
		return 0;
	//这就是上面解释的代码
	else if (x == INT_MIN)
	{
		int a = Add(x, 1);
		int b = dived(a, y);
		a = dived(Sub(x, Mul(b, y)), y);
		return Add(a, b);
	}
	else
		return dived(x,y);//不含系统最小值的除法
}

coding有三点要注意:

  • 负数要先转化成正数
  • 被除数左移会有风险
  • 系统最小无法取相反数

加减乘除的完整代码,他们并不是孤立的,互相调用

#include<stdio.h>
#include<limits.h>
int Add(int x, int y)
{
	while (y)
	{
		int a = x ^ y;
		y = (x & y) << 1;
		x = a;
	}
	return x;
}

int Sub(int x, int y)
{
	return Add(x, Add(~y, 1));
}
int Mul(int x, int y)
{
	int ret = 0;
	for (int i = 0; i <= 30; i = Add(i, 1))
	{
		if (y >> i & 1 == 1)
		{
			ret = Add(ret, x);
		}
		x <<= 1;
	}
	return ret;
}

int oppo(int x)
{
	return Add(~x, 1);
}

int dived(int x, int y)
{
	int a = x < 0 ? oppo(x) : x;
	int b = y < 0 ? oppo(y) : y;
	int ret = 0;
	for (int i = 30; i >= 0; i = Sub(i, 1))
	{
		if (a >> i >= b)
		{
			ret |= 1 << i;
			a = Sub(a, Mul(b,ret));
		}
	}
	if (x > 0 ^ y > 0)
		return oppo(ret);
	return ret;
}

int Div(int x, int y)
{
	if (x == INT_MIN && y == INT_MIN)
		return 1;
	else if (x == INT_MIN && y == -1)
		return -1;
	else if (y == INT_MIN)
		return 0;
	else if (x == INT_MIN)
	{
		int a = Add(x, 1);
		int b = dived(a, y);
		a = dived(Sub(x, Mul(b, y)), y);
		if (x > 0 ^ y > 0)
			return oppo(Add(a, b));
		return Add(a, b);
	}
	else
		return dived(x,y);
}

好的,到这里阿辉就讲完了,说实话不容易,着力于构思怎么把铁子们讲懂,应该没有比我更细节的了,写完这篇满满成就感,铁子们觉得讲得不错的话给阿辉评论区扣个666,哈哈哈!!!!
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1304166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

螺旋方阵-2d

Description 一个 n 行 n 列的螺旋方阵按如下方法生成&#xff1a;从方阵的左上角&#xff08;第 1 行第 1 列&#xff09;出发&#xff0c;初始时向右移动&#xff1b;如果前方是未曾经过的格子&#xff0c;则继续前进&#xff1b;否则&#xff0c;右转。重复上述操作直至经过…

【离散数学】——期末刷题题库( 二元关系)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

CAVLC(基于上下文自适应的可变长编码)

以1个4 x 4的图像块为例,来说明CAVLC的编码及解码过程。 编码 ZIG-ZAG排列 ZIG-ZAG排列后的序列为: 计算非零系数数目和拖尾系数数目 TotalCoeffs:非零系数数目。变换系数level中所有不为0的level的数目,本例中为5。 TrailingOnes:拖尾系数的数目。指的是矩阵重排列后…

系统韧性研究(7)| 韧性系统的16大指导原则

不良事件和条件可能会中断系统&#xff0c;导致系统无法提供必要的功能和服务。正如我在本系列的前几篇文章中所概述的那样&#xff0c;韧性是大多数系统的一个基本质量属性&#xff0c;因为它们提供了关键的能力和服务&#xff0c;尽管存在着不可避免的困难&#xff0c;但这些…

部署Nextcloud详细步骤及优化方法

一、安装PHP8.0以上 我这里使用PHP8.0.30 [rootlocalhost ~]# php -v PHP 8.0.30 (cli) (built: Aug 3 2023 17:13:08) ( NTS gcc x86_64 ) Copyright (c) The PHP Group Zend Engine v4.0.30, Copyright (c) Zend Technologies [rootlocalhost ~]# 安装方法参考 二、安装MY…

台式扫描电镜中的扫描速度和扫描模式如何选择?

台式扫描电镜&#xff08;SEM&#xff09;是一种利用电子束扫描样品表面&#xff0c;通过检测样品反射或发射的次级电子、背散射电子、X 射线等信号&#xff0c;来获取样品的形貌、结构、组成和分布等信息的仪器。台式扫描电镜具有体积小、操作简单、样品制备方便、分辨率高、成…

2023年团体程序设计天梯赛——总决赛题

F-L1-1 最好的文档 有一位软件工程师说过一句很有道理的话&#xff1a;“Good code is its own best documentation.”&#xff08;好代码本身就是最好的文档&#xff09;。本题就请你直接在屏幕上输出这句话。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在一…

禾匠榜店商城系统 RCE漏洞复现

0x01 产品简介 禾匠榜店商城系统是浙江禾匠信息科技有限公司的一套基于PHP和MySQL的商城系统。 0x02 漏洞概述 禾匠榜店商城系统的api/testOrderSubmit模块下的preview方法存在命令执行漏洞,攻击者可以向服务器写入木马文件,直接获取服务器权限 0x03 漏洞概述 FOFA:bod…

GZ029 智能电子产品设计与开发赛题第6套

2023年全国职业院校技能大赛高职组 “GZ029智能电子产品设计与开发”赛项赛卷六 题目&#xff1a;模拟工业传送带物品检测系统的设计与开发 1 竞赛任务 在智能电视机上播放工业传送带传输物品视频&#xff0c;模拟工业传送带物品检测系统&#xff08;以下简称物品检测系统&…

Vue路由跳转重定向动态路由VueCli

Vue路由跳转&重定向&动态路由&VueCli 一、声明式导航-导航链接 1.需求 实现导航高亮效果 如果使用a标签进行跳转的话&#xff0c;需要给当前跳转的导航加样式&#xff0c;同时要移除上一个a标签的样式&#xff0c;太麻烦&#xff01;&#xff01;&#xff01; …

web Speech Synthesis 文字语音播报,Audio 播放base64提示音

SpeechSynthesisUtterance基本介绍 SpeechSynthesisUtterance是HTML5中新增的API,用于将指定文字合成为对应的语音.也包含一些配置项,指定如何去阅读(语言,音量,音调)等 SpeechSynthesisUtterance基本属性 SpeechSynthesisUtterance.lang 获取并设置话语的语言SpeechSynthesisU…

FPGA高端项目:UltraScale GTH + SDI 视频解码,SDI转DP输出,提供2套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 GT 高速接口解决方案我目前已有的SDI编解码方案 3、详细设计方案设计框图3G-SDI摄像头LMH0384均衡EQUltraScale GTH 的SDI模式应用UltraScale GTH 基本结构参考时钟的选择和分配UltraScale GTH 发送和接收处理流程UltraScale…

vscode报错cnpm : 无法加载文件 C:\Program Files\nodejs\cnpm.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅

报错 打开powershell 执行 Start-Process powershell -Verb runAs set-ExecutionPolicy RemoteSigned 选择 Y 完成

Zookeeper(服务注册中心)安装以及启动服务

概述 ZooKeeper是一个分布式的开源协调服务&#xff0c;用于管理和协调大规模分布式系统中的各种任务。它提供了一个简单的分层命名空间&#xff0c;以及对数据的强一致性&#xff08;ACID特性&#xff09;和高可用性的支持。 ZooKeeper提供了一个类似文件系统的层次结构&…

微服务学习:Nacos微服务架构中的服务注册、服务发现和动态配置Nacos下载

Nacos的主要用途包括&#xff1a; 服务注册与发现&#xff1a;Nacos提供了服务注册和发现的功能&#xff0c;服务提供者可以将自己的服务注册到Nacos服务器上&#xff0c;服务消费者则可以通过Nacos来发现可用的服务实例&#xff0c;从而实现服务调用。 动态配置管理&#xff…

PR分屏模板|Premiere动态多画面多屏特效视频模板剪辑素材

这一个很棒的分屏效果PR幻灯片模板视频素材&#xff01;为您的视频制作多屏幕动画&#xff01; 非常易于定制、更改颜色。 仅支持Premier Pro 2024及最新版本。 高清分辨率&#xff1a;19201080/30fps。 持续时间–00:37。 21媒体占位符&#xff08;照片或视频&#xff09;。 包…

MySQL数据库,函数与分组

单行函数&#xff1a; 操作数据对象 接受参数返回一个结果 只对一行进行变换 每行返回一个结果 可以嵌套 参数也可以是一列或一个值 数值函数 基本函数&#xff1a; 注&#xff1a;ROUND(x,y)函数的y是负数时&#xff0c;即往高位进行四舍五入&#xff0c;如-3就是按百位…

每天五分钟计算机视觉:VGG网络相对于AlexNet网络有哪些不同?

本文重点 在前面的课程中&#xff0c;我们已经学习了VGG网络模型&#xff0c;也学习了AlexNet网络模型&#xff0c;AlexNet模型先于VGG网络模型产生&#xff0c;所以VGG在一定程度上要优于AlexNet模型&#xff0c;二者来看一下&#xff0c;二者究竟有什么不同&#xff1f; 深度…

解决微信小程序中 ‘nbsp;‘ 空格不生效的问题

在微信小程序开发中&#xff0c;我们经常会使用 来表示一个空格。这是因为在 HTML 中&#xff0c;空格会被解析为一个普通字符&#xff0c;而不会产生实际的空白间距。而 是一种特殊的字符实体&#xff0c;它被解析为一个不可见的空格&#xff0c;可以在页面上产生真正的空…

AI数字人在tiktok平台开播教程!

AI数字人作为一种虚拟形象的代表&#xff0c;正在逐渐走进人们的生活。在小红书这样的短视频平台上&#xff0c;AI数字人也有机会开展直播活动&#xff0c;展示自己的个性魅力&#xff0c;也可以进行直播带货&#xff01; 数字人在淘宝购物平台开播原理 &#xff08;1&#xff…