【计算器】四则运算的算法实现

news2024/11/17 1:40:39

先实现整数部分

加减乘比较简单, 可以参考: 高精度算法全套(加,减,乘,除,全网最详细). 除法我参考的是: 大数加减乘除运算总结

四则运算相关的OJ题目

四则远算

关于除法

基本上参考的是: 大数加减乘除运算总结
最简单的办法就是使用减法, 但是这样就会导致效率极其低下, 所以我们应该把这个过程加速

一个可用的代码

  1. 这份代码包含了整数的四则运算
  2. 可以用这个题目进行验证: https://tioj.ck.tp.edu.tw/problems/1006
  3. 运行效率如下

在这里插入图片描述

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

int compare(std::string x, std::string y) {
	if (x.size() < y.size()) {
		return -1;
	}
	if (x.size() > y.size()) {
		return 1;
	}
	return x.compare(y);
}

std::string add(std::string x, std::string y) {
	/**
	 * 对齐数字
	 * x = 123 , y = 4567
	 * x = 0123 , y = 4567
	 */
	if (x.size() != y.size()) {
		if (x.size() > y.size()) {
			int len = x.size() - y.size();
			for (int i = 0; i < len; i++) {
				y.insert(0, "0");
			}
		} else {
			int len = y.size() - x.size();
			for (int i = 0; i < len; i++) {
				x.insert(0, "0");
			}
		}
	}

	/**
	 * 对齐后的字符串转换为倒置的 int 数组
	 * x = 0123 , y = 4567
	 * x = 3210 , y = 7654
	 */
	char *sx = const_cast<char *>(x.c_str());
	char *sy = const_cast<char *>(y.c_str());
	const int len = x.size();
	auto a = std::make_unique<int[]>(len);
	auto b = std::make_unique<int[]>(len);

	for (int i = 0; i < len; i++) {
		a[len - 1 - i] = sx[i] - '0';
	}

	for (int i = 0; i < len; i++) {
		b[len - 1 - i] = sy[i] - '0';
	}

	/** 计算 */
	const int size = len + 1;
	auto c = std::make_unique<int[]>(size);

	for (int i = 0; i < len; i++) {
		c[i] += a[i] + b[i];
		// 处理进位
		if (c[i] >= 10) {
			c[i + 1] += c[i] / 10;
			c[i] = c[i] % 10;
		}
	}

	/** 翻转计算结果 */
	std::string str;
	int i = size - 1;
	// 删除前导0
	if (0 == c[i]) {
		i--;
	}
	// 拼接计算结果
	while (i >= 0) {
		str.push_back(c[i] + '0');
		i--;
	}

	return str;
}

std::string substract(std::string x, std::string y) {
	// 结果是否为负数
	bool isNegativeNumber = false;

	/**
	 * 对齐数字
	 * x = 123 , y = 4567
	 * x = 0123 , y = 4567
	 */
	if (x.size() != y.size()) {
		if (x.size() > y.size()) {
			int len = x.size() - y.size();
			for (int i = 0; i < len; i++) {
				y.insert(0, "0");
			}
		} else {
			int len = y.size() - x.size();
			for (int i = 0; i < len; i++) {
				x.insert(0, "0");
			}
		}
	}

	/**
	 * 数字排序
	 * s1 为较大数字
	 * s2 为较小数字
	 */
	std::string s1 = x;
	std::string s2 = y;
	if (s1.compare(s2) < 0) {
		s1 = y;
		s2 = x;
		isNegativeNumber = true;
	}

	/**
	 * 对齐后的字符串转换为倒置的 int 数组
	 * x = 0123 , y = 4567
	 * x = 3210 , y = 7654
	 */
	char *sx = const_cast<char *>(s1.c_str());
	char *sy = const_cast<char *>(s2.c_str());
	const int len = s1.size();
	auto a = std::make_unique<int[]>(len);
	auto b = std::make_unique<int[]>(len);

	for (int i = 0; i < len; i++) {
		a[len - 1 - i] = sx[i] - '0';
	}

	for (int i = 0; i < len; i++) {
		b[len - 1 - i] = sy[i] - '0';
	}

	/** 计算 */
	const int size = len;
	auto c = std::make_unique<int[]>(size);
	for (int i = 0; i < len; i++) {
		// 借位处理
		if (a[i] < b[i]) {
			a[i + 1]--;
			a[i] += 10;
		}
		c[i] = a[i] - b[i];
	}

	/** 翻转计算结果 */
	std::string str;
	int i = size - 1;
	// 删除前导0
	while (0 == c[i] && i >= 1) {
		i--;
	}
	// 添加负号
	if (isNegativeNumber) {
		str.push_back('-');
	}
	// 拼接计算结果
	while (i >= 0) {
		str.push_back(c[i] + '0');
		i--;
	}

	return str;
}

std::string multiply(std::string x, std::string y) {
	/**
	 * 字符串转换为倒置的 int 数组
	 * x = 123 , y = 4567
	 * x = 321 , y = 7654
	 */
	char *sx = const_cast<char *>(x.c_str());
	char *sy = const_cast<char *>(y.c_str());
	const int la = x.size() + 1;
	const int lb = y.size() + 1;
	auto a = std::make_unique<int[]>(la);
	auto b = std::make_unique<int[]>(lb);

	for (int i = 1; i < la; i++) {
		a[la - i] = sx[i - 1] - '0';
	}

	for (int i = 1; i < lb; i++) {
		b[lb - i] = sy[i - 1] - '0';
	}

	/** 计算 */
	const int lc = x.size() + y.size() + 1;
	auto c = std::make_unique<int[]>(lc);
	for (int i = 1; i < la; i++) {
		for (int j = 1; j < lb; j++) {
			c[i + j - 1] += a[i] * b[j];
			c[i + j] += c[i + j - 1] / 10;
			c[i + j - 1] %= 10;
		}
	}

	/** 翻转计算结果 */
	std::string str;
	int i = lc - 1;
	while (0 == c[i] && i > 1) {
		i--;
	}
	// 拼接计算结果
	while (i >= 1) {
		str.push_back(c[i] + '0');
		i--;
	}
	return str;
}

std::string divide(std::string x, std::string y) {
	// 被除数为 0 或者被除数小于除数, 均返回 0
	if (0 == x.compare("0") || compare(x, y) < 0) {
		return "0";
	}
	// 相等直接返回 1
	if (0 == compare(x, y)) {
		return "1";
	}

	// 除数为 1 , 直接返回被除数
	if (0 == y.compare("1")) {
		return x;
	}

	std::string str = "0";
	std::string remainder;
	// 被除数只比除数多一位, 则使用减法
	if (x.size() - y.size() < 2) {
		// 余数
		remainder = x;
		while (compare(remainder, y) >= 0) {
			remainder = substract(remainder, y);
			str = add(str, "1");
		}
	} else {
		// 被除数比除数多两位或以上, 先用加法再用减法
		// 余数
		str = "0";
		std::string times = "1";
		std::string num = y;
		std::string t = x;
		while (t.size() - y.size() >= 2) {
			while (compare(num, t) <= 0) {
				remainder = num;
				num = add(num, num);
				if (compare(num, t) > 0) {
					num = y;
					str = add(str, times);
					times = "1";
					t = substract(t, remainder);
					break;
				}
				times = multiply(times, "2");
			}
		}
		remainder = t;
		if (compare(remainder, y) >= 0) {
			while (compare(remainder, y) >= 0) {
				remainder = substract(remainder, y);
				str = add(str, "1");
			}
		}
	}

//	std::cout << str << std::endl;
//	std::cout << remainder << std::endl;

	return str;
}

int main() {
	std::string s1, s2;
	std::cin >> s1;
	std::cin >> s2;
	std::cout << divide(s1, s2) << std::endl;
	return 0;
}

算法实现解释

按照 大数加减乘除运算总结 所讲解得思路, 我们可以用扩大除数或者二分倍数的方法. 扩大除数的方法没想出来怎么实现, 二分倍数的倒是想出来了.我们以 11111/9为例进行讲解.
使用python命令行计算结果

Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 11111/9
1234.5555555555557
>>>

思路如下:

  1. 如果被除数的长度-除数的长度 >= 2, 那么使用加法模拟, 计算过程就是除数不停的乘以2, 商也就是不停的乘以2, 直到加法计算结果大于等于被除数
  2. 如果上一步的余数的长度 - 除数长度 >= 2, 重复步骤 1, 否则进入步骤3
  3. 如果余数长度 - 除数长度 >= 0 进入步骤4 , 否则进入步骤5
  4. 使用减法进行计算
  5. 累加每一轮的商

现在我们新建一个文本文档, 复制如下代码并观察

# 被除数    除数  商         余数
  11111  /  9   = 1234  ...  5
  
9+9=18 # 第 1 次累加
1*2=2 # 此时商为 2

18+18=36 # 第 2 次累加
2*2=4 # 此时商为 4

36+36=72 # 第 3 次累加
4*2=8 # 此时商为 8

72+72=144
8*2=16

144+144=288
16*2=32

288+288=576
32*2=64

576+576=1152
64*2=128

1152+1152=2304
128*2=256

2304+2304=4608
256*2=512

4608+4608=9216 # 第 10 次累加
512*2=1024 # 此时商为 1024

9216+9216=18432 # 此时累加已经超过了被除数 11111 , 这一轮计算结束

如果此时结束计算, 那么商为1024, 余数为 11111-9216=1895.

然后我们计算第二轮:1895/9

9+9=18 # 第 1 次累加
1*2=2 # 此时商为 2

18+18=36 # 第 2 次累加
2*2=4 # 此时商为 4

36+36=72 # 第 3 次累加
4*2=8 # 此时商为 8

72+72=144
8*2=16

144+144=288
16*2=32

288+288=576
32*2=64

576+576=1152
64*2=128

1152+1152=2304 # 此时商为 128

如果此时结束计算, 那么商为128, 余数为 1895-1152=743.

然后我们计算第三轮:743/9

9+9=18 # 第 1 次累加
1*2=2 # 此时商为 2

18+18=36 # 第 2 次累加
2*2=4 # 此时商为 4

36+36=72 # 第 3 次累加
4*2=8 # 此时商为 8

72+72=144
8*2=16

144+144=288
16*2=32

288+288=576
32*2=64

576+576=1152 # 此时商为 64

如果此时结束计算, 那么商为64, 余数为 743-576=167.

然后我们计算第四轮:167/9

9+9=18 # 第 1 次累加
1*2=2 # 此时商为 2

18+18=36 # 第 2 次累加
2*2=4 # 此时商为 4

36+36=72 # 第 3 次累加
4*2=8 # 此时商为 8

72+72=144
8*2=16

144+144=288 # 此时商为 16

如果此时结束计算, 那么商为16, 余数为 167-144=23.

至此, 我们一共使用了 10+7+6+4=27次加法, 10+7+6+4=27次乘法. 现在我们得到的计算结果为:
商: 1024+128+64+16=1232
余数为: 23
然后我们使用减法计算

23-9=14 # 此时商为 1232+1=1233
14-9=5 # 此时余数小于除数, 计算结束, 商为 1233+1=1234, 余数为 5

最终计算结果与我们的期望一致

上面使用加法计算商的部分, 其实可以不用乘法而用加法. 这样的话, 我们共用了 27+27+2=56次加法, 而单纯的用减法, 那就要1234+1=1235次, 两种方法的运行效率不可同日而语.

模仿 java 的 BigDecimal 设计 API

现在我们可以实现真正的大数计算了

参考资料

高精度算法全套(加,减,乘,除,全网最详细)
大数加减乘除运算总结
Java BigDecimal详解

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

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

相关文章

数据结构:队列

队列 队列(Queue)是一个数据集合&#xff0c;仅允许在列表的一端进行插入&#xff0c;另一端进行删除。 进行插入的一端称为队尾(rear)&#xff0c;插入动作称为进队或入队 进行删除的一端称为队头(front)&#xff0c;删除动作称为出队 队列的性质:先进先出(First-in, First-o…

C++【二叉树进阶(二叉搜索树)】

文章目录前言1、二叉搜索树1-1、 二叉搜索树概念2、二叉搜索树操作2-1、树和节点的基本框架2-2、二叉搜索树的查找2-3、中序遍历2-4、二叉搜索树的插入2-5、二叉搜索树的删除3、二叉搜索树的模拟实现3-1、循环版本3-2、递归版本4、二叉搜索树的应用4-1、K模型4-2、KV模型4-3、K…

Linux 中断实验

目录 一、Linux 中断简介 上半部与下半部 二、添加设备树 三、编写驱动 1、定义宏 2、编写一个key结构体 3、imx6uirq设备添加成员 ​编辑4、按键中断处理函数 5、按键初始化 6、在驱动入口添加初始化 7、 驱动出口函数 代码如下 四、利用定时器进行消抖处理 1、添…

Spring Security in Action 第十四章 实现资源服务器端

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

ArcGIS中的附件功能

从ArcGIS10起,空间数据库增加了"附件"的功能,可灵活管理与要素相关的附加信息,可以是图像、PDF、文本文档或任意其他文件类型。例如,如果用某个要素表示建筑物,则可以使用附件来添加多张从不同角度拍摄的建筑物照片。 启动附件功能 要想使用附件功能,要素类必…

Docker 中遇到的问题

1&#xff1a;docker-tomcat 篇 第一天启动主机和虚拟机都可以正常访问&#xff0c;晚上睡觉的时候就挂起关机睡觉了&#xff0c;但到了第二天主机访问不了了&#xff0c;ping 也能ping 通&#xff0c;后来停掉容器&#xff0c;重启了虚拟机就好了&#xff0c;就很离谱。 这是成…

Web3CN|Damus刷频背后,大众在期待什么样的去中心化社交?

刚过去的一周&#xff0c;许多人的朋友圈包括Twitter、Faceboo在内都在被一串公钥字母刷屏&#xff0c;其重要起因就是 Twitter 前首席执行官 Jack Dorsey 发推称&#xff0c;&#xff08;2月1日&#xff09;基于去中心化社交协议 Nostr 的社交产品 Damus 和 Amethyst 已分别在…

互联网舆情监测系统的设计研究,TOOM舆情监测系统研究框架?

舆情监测研究分析是指通过对社会公众对某个事件、话题、品牌、政策等的态度和情绪进行收集、处理、分析和评估&#xff0c;帮助政府、企业、媒体等利益相关者及时掌握公众的反应&#xff0c;做好应对危机和制定舆情管理策略的工作&#xff0c;互联网舆情监测系统的设计研究&…

全志V853芯片 如何在Tina V85x平台切换sensor?

目的 V85x某方案目前默认Sensor是GC2053。实际使用时若需要用到GC4663&#xff08;比如wdr功能&#xff09;和SC530AI&#xff08;支持500W&#xff09;&#xff0c;可按如下步骤完成切换。 步骤 下面以GC4663为例&#xff0c;SC530AI按相应方式适配。 Step1 检查Sensor驱动…

Spring Security in Action 第十七章 全局方法安全:预过滤和后过滤

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

Rust学习入门--【8】复合类型

复合类型&#xff08;compound type&#xff09; 可以将多个不同类型的值组合为一个类型。 Rust中提供了两种内置的复合数据类型&#xff1a;元组&#xff08;tuple&#xff09;和数组&#xff08;array&#xff09;。 元组类型 元组是一个具有 固定长度 的数据集合 —— 无…

按键输入驱动

目录 一、硬件原理 二、添加设备树 1、创建pinctrl 2、创建节点 3、检查 编译复制 三、修改工程模板​编辑 四、驱动编写 1、添加keyio函数 2、添加调用 3、驱动出口函数添加释放 4、添加原子操作 5、添加两个宏定义 6、初始化原始变量 7、打开操作 8、读操作 总体代…

自启动管理 - Win10

自启动管理 - Win10前言关闭开机自启方案1&#xff1a;在软件中设置方案2&#xff1a;在任务管理器设置方案3&#xff08;不推荐&#xff09;&#xff1a;通过注册表管理方案4&#xff1a;通过第三方工具管理工具1&#xff1a;360安全卫士工具2&#xff1a;Autoruns工具3&#…

性能测试概述

目录 一.什么是性能测试 1.生活中软件存在的性能问题 2.性能测试的概念 3.功能测试和性能测试的区别 4.什么样的软件表现是性能好的表现&#xff0c;什么样的软件是性能不好的表现 二.一个项目为什么要进行性能测试 三.性能测试常见术语以及衡量指标 1.专业术语&#x…

Docker的数据卷管理与容器互联

目录 一、Docker数据管理介绍 二、数据卷 1、数据卷概念 三、数据卷容器 1、数据卷容器的概念 2、数据卷容器示例 四、容器互联 1、容器互联概念 2、容器互联示例 一、Docker数据管理介绍 用户在使用Docker的过程中&#xff0c;往往需要能查看容器内应用产生的数据&…

基于transformer和图卷积网络的人体运动预测时空网络

效果演示&#xff1a; python行为识别行为骨骼框架检测动作识别动作检测行为动作分类近年来&#xff0c;人体运动预测已成为计算机视觉领域的一个活跃研究课题。然而&#xff0c;由于人体运动的复杂性和随机性&#xff0c;它仍然是一个具有挑战性的问题。在以前的工作中&#x…

[golang] 实现 jwt 方式登录

1 Jwt 和 Session 登录方案介绍 JSON Web Token&#xff08;缩写 JWT&#xff09;是目前流行的跨域认证解决方案。 原理是生存的凭证包含标题 header&#xff0c;有效负载 payload 和签名组成。用户信息payload中&#xff0c;后端接收时只验证凭证是否有效&#xff0c;有效就…

【Spark分布式内存计算框架——Spark Core】11. Spark 内核调度(下)

8.5 Spark 基本概念 Spark Application运行时&#xff0c;涵盖很多概念&#xff0c;主要如下表格&#xff1a; 官方文档&#xff1a;http://spark.apache.org/docs/2.4.5/cluster-overview.html#glossary Application&#xff1a;指的是用户编写的Spark应用程序/代码&#x…

leetcode练习二:排序

文章目录排序一、排序算法1.1 冒泡排序1.1.1 算法步骤1.1.2 算法分析1.1.3 代码实现&#xff1a;1.1.4 冒泡排序优化1.2 选择排序1.2.1 算法步骤1.2.2 算法分析1.2.3 代码实现1.3 插入排序1.3.1 算法步骤1.3.2 算法分析1.3.3 代码实现1.4 希尔排序1.4.1 算法步骤1.4.2 算法分析…