当前位置: 首页 > backend >正文

大整数乘法实现日志:从查表法到逐位运算

请在此添加图片描述

在我做项目时,遇到一个乘法问题,一算就报错Error。后来咨询大神才知道,这是运算规模超出乘法限度了。

使用基本类型如int或long会导致溢出。浮点数虽能表示大范围数值,但无法精确表示所有整数。

在处理超出基本数据类型范围的整数运算时(例如计算 12345678901234567890 * 98765432109876543210),我们必须自己实现算法。

本文将深入剖析一个基于 C++ 实现的大整数乘法程序,探讨其设计思路、核心算法与可优化之处。

整体架构与思路

大整数乘法通常通过模拟手工竖式计算实现,将每一位相乘并累加到对应位置,处理进位。例如,C语言代码中,将输入字符串转换为整数数组,逆序存储以便从低位开始计算,每一位相乘后累加到结果数组的对应位置,最后处理进位并逆序输出结果。

大整数乘法核心在于模拟手工竖式计算,通过逐位相乘并累加结果,最终处理进位。对于两个n位数,时间复杂度为O(n²)。

该程序的核心思想是模拟人类手算乘法的过程:

  1. 将输入的大数字符串反转,以便从最低位开始计算。
  2. 使用乘数的每一位去乘以被乘数的每一位,同时处理每一位的乘积和进位。
  3. 将每次乘得的中途结果累加起来。
  4. 处理最终结果的进位,并反转回高位在前的字符串表示,同时去除前导零。

程序主要由以下模块组成:

  • 字符串反转 (strrev)
  • 预计算查表 (table_pos, table_addition****)
  • 单比特乘法和进位处理 (bit_multiple, carry****, bit_add****)
  • 大数加法 (add)
  • 大数乘法 (multiple)
  • 输入输出 (Input, main****)

让我们逐模块进行解析。

模块解析与关键代码

1. 字符串反转函数 strrev

void strrev(char * source) {char * des;int len = strlen(source);des = (char*)malloc(sizeof(char) * (len + 1)); // 分配临时空间des[len] = '\0'; // 设置字符串结束符for (int i = 0; i < len; i++) {des[len - i - 1] = source[i]; // 逆序拷贝}strcpy(source, des); // 拷回原字符串free(des); // 释放临时空间
}

  • 功能: 将输入的字符串原地反转。
  • 分析: 此函数为算法做准备。因为手算乘法从最低位开始,而字符串存储数字是高位在前(例如 “123”)。反转后变成低位在前(“321”),极大简化了后续按索引顺序([0] 开始)进行计算的逻辑。
  • 注意: 该实现使用了动态内存分配 (mallocfree),在 C++ 中通常更推荐使用 new[]delete[],或者直接使用 std::stringstd::reverse

2. 核心查表法:预计算矩阵

分治法优化:Karatsuba算法通过分治策略将时间复杂度从O(n²)降至O(n1.59)。其核心是将大数拆分为高位和低位,通过三次递归乘法(AC、BD、(A-B)(D-C))和加减法组合结果,减少乘法次数。例如,将X=A_10(n/2)+B和Y=C_10^(n/2)+D相乘时,传统方法需4次乘法,而Karatsuba仅需3次。

在分治法中,预先计算小规模乘法的结果(如所有可能的m位小数相乘),存储为表格。当处理大数乘法时,直接查表获取结果,避免重复计算。例如,在Karatsuba算法中,若将大数分为m位块,预先计算所有m位小数的乘积,后续递归调用时直接引用,减少计算量。

char table_pos[10][10] = { ... }; // 个位数乘法结果表
char table_addition[10][10] = { ... }; // 个位数乘法进位表
  • 功能: 这两个二维数组是程序的性能优化核心。它们预先计算了所有两个一位数(0-9)相乘的结果。

    • table_pos[a][b]: 存储 a * b 所得结果的个位数字。
    • table_addition[a][b]: 存储 a * b 所得结果的十位(进位)数字。
  • 举例7 * 8 = 56。那么 table_pos[7][8] = '6', table_addition[7][8] = '5'

  • 优势: 避免了在密集循环中进行昂贵的 (a - '0') * (b - '0') % 10(a - '0') * (b - '0') / 10 运算,直接用一次内存访问得到结果,以空间换时间。

3. 单比特运算与进位处理

在模拟竖式计算中,每一位相乘的结果可能超过基数(如十进制中的10),需将余数留在当前位,进位传递到高位。例如,C语言代码中,使用变量存储进位,每次相乘后更新结果数组和进位值,确保每一位数值正确。

void bit_multiple(char &pos, char &addition, const char factor1, const char factor2) {pos = table_pos[factor1 - '0'][factor2 - '0']; // 查表得个位addition = table_addition[factor1 - '0'][factor2 - '0']; // 查表得十位(进位)
}void carry(char pre_addition, char &cur_pos, char &cur_addition) {cur_pos = cur_pos + pre_addition - '0'; // 将进位加到当前位if (cur_pos - '0' > 9) { // 如果当前位又产生新的进位cur_addition++; // 向更高位进位cur_pos -= 10; // 调整当前位}
}

  • bit_multiple: 封装了查表操作,是乘法运算的原子操作。
  • carry关键函数。它处理复杂的进位链。不仅处理乘法本身的进位 (addition),还要处理之前步骤传递下来的进位 (pre_addition)。它确保 cur_pos 始终是一个一位的数字字符。

4. 大数加法 add

void add(char * num1, char * num2) {// ... 逻辑:将 num2 加到 num1 上,结果存储在 num1 中// 1. 遍历两个数字的每一位// 2. 对每一位调用 bit_add (其内部调用 carry) 进行相加和进位处理// 3. 处理两个数字长度不等时剩余的位数和可能的最高位进位
}

  • 功能: 实现两个大数的加法。这是乘法算法中不可或缺的一部分,因为乘法最终体现为部分和的累加。
  • 逻辑: 该函数仔细处理了 num1num2 长度不同的情况,确保了进位能正确地向更高位传播。

5. 核心:大数乘法 multiple

void multiple(char* mul1, char* mul2, char * result) {strrev(mul1); // 反转被乘数strrev(mul2); // 反转乘数char result_basic[MAX_LENGTH] = {0}; // 初始化和(初始为0)char result_add[MAX_LENGTH] = {0}; // 临时存储当前计算的部分积for (int i = 0; i < strlen(mul2); i++) { // 遍历乘数的每一位// ... 内部循环:用乘数的第 i 位 mul2[i] 去乘以被乘数的每一位 mul1[j]// 对于每一对位 (j, i),计算乘积并通过 carry 处理进位// 将计算得到的部分积(已处理完所有进位)存储在 result_add 中,// 其有效位数是 strlen(mul1),并在末尾可能有一位进位。// 将当前部分积 result_add 累加到总结果 result_basic 上add(result_basic, result_add);}strrev(result_basic); // 将总结果反转回高位在前// ... 去除结果的前导零cout << result_basic << endl; // 输出最终结果
}

流程如下

1. **反转**: 将两个操作数反转,转为低位在前模式。
2. **初始化**: `result_basic` 初始化为0,用于累积所有部分和。
3. **双重循环**:- **外层循环**: 遍历乘数 `mul2` 的每一位。- **内层循环**: 对于乘数的当前位 `i`,遍历被乘数 `mul1` 的每一位 `j`,计算 `mul1[j] * mul2[i]`,并与之前的进位相加,得到当前位的值和新进位。这步生成一个**部分积**(`result_add`)。
4. **累加**: 将当前计算出的部分积加到总结果 `result_basic` 上。
5. **后处理**: 将所有结果反转回正常顺序,并去除可能存在的无意义的前导零。

总结与评价

大整数乘法通过模拟竖式计算实现,查表法与分治算法(如Karatsuba)可显著优化性能。进位处理是核心细节,需确保每次累加后正确传递进位。代码优化可从进制转换、并行计算和内存管理入手,同时注意符号与前导零的处理。

1. 优点:

  • 思路清晰: 完美复现了手算乘法的流程,易于理解。
  • 性能优化: 使用查表法替代实时计算,是亮点所在,显著提升了核心运算的速度。
  • 模块化设计: 将进位、加法、乘法等操作封装成函数,结构清晰。

2. 可改进之处:

  • 内存与安全性: 使用原始 char*malloc,存在缓冲区溢出的风险(例如,结果超出 MAX_LENGTH)。现代 C++ 应优先使用 std::stringstd::vector<char>,它们能动态管理内存,更安全。
  • 代码健壮性: 缺乏输入验证。如果输入字符串包含非数字字符,程序会出错。
  • 效率瓶颈: 算法复杂度为 O(n²),这是大数乘法的固有特性。但对于极大数,有更高效的算法(如 Karatsuba 算法、FFT based 算法)。
  • 代码风格: 部分变量命名可更具描述性(如 result_basic 可改为 sum_total),并且存在一些调试用的注释代码 cout
  • 结果返回: 函数 multiple 声称将结果存入 result 参数,但实际实现中直接 coutresult_basic,这是一个逻辑不一致的地方。应改为将处理好的字符串拷贝到 result 中。

3. 深入思考
尽管有可优化之处,这段代码的核心价值在于清晰地展示了大数运算的基本原理,并引入了查表法这一重要优化思想。它为我们理解更复杂、更高效的算法打下了坚实的基础。对于学习者而言,读懂并调试这样的代码,对深入理解计算机如何表示和操作数据大有裨益。

http://www.xdnf.cn/news/20011.html

相关文章:

  • 基于深度掩码的动态模糊处理
  • 《Html泛型魔法学院:用霍格沃茨风格网页教授集合框架》
  • SpringBoot 集成 MyBatis-Plus 的使用指南
  • 学习PaddlePaddle--环境配置-Windows 11 + RTX 4060
  • 优质技术博客分享(第1期)
  • Beautiful.ai:AI辅助PPT工具高效搞定排版,告别熬夜做汇报烦恼
  • maven settings.xml文件的各个模块、含义以及它们之间的联系
  • 阿瓦隆 A1146 Pro 63T:性能与设计详解,探索区块链挖矿新高度
  • 【网工基础】20+常用网络协议介绍
  • 水下管道巡检机器人结构设cad+三维图+设计说明书
  • 2508C++,skia动画
  • 【iOS】对象复制与属性关键字
  • 同步安卓手机的照片到NAS的方案(完美)
  • 人工智能学习:鸢尾花数据获取
  • qwen-code 功能分析报告
  • 软件安装教程(四):在 Windows 上安装与配置 MATLAB(超详细)
  • 【2025企业建站推荐指南】深度解析十大顶尖网站建设公司:从品牌设计到技术落地的全维度解决方案
  • 01_配置版本
  • BERT家族进化史:从BERT到LLaMA,每一次飞跃都源于对“学习”的更深理解
  • 【面试题】生成式搜索能否保证top-1的准确性?
  • MySQL中CASE语法规则的详细解析及扩展示例
  • Spring Cloud Alibaba快速入门01
  • 去中心化投票系统开发教程
  • Java 双亲委派机制解析和破坏双亲委派的方式
  • sealos部署k8s
  • 华为校招实习留学生机试全攻略:真题目录+算法分类+在线OJ+备考策略
  • 如何将两个网段互相打通
  • Java场景题面试合集
  • 「数据获取」中国科技统计年鉴(1991-2024)Excel
  • 江协科技STM32学习笔记补充之004