MATALB大数的加减乘除幂运算

by HPC_ZY

最近需要做大数计算,且要求结果精度与原始数据一致(不能使用科学计数等近似解)。所以总结分享一些“超大正整数的基本运算方法”,如加、减、乘、除、幂等

一、基本原理

uint32(unsigned int)型可表示的最大数为 ( 2 32 − 1 ) (2^{32}-1) (2321),uint64(unsigned long long)型可表示的最大数为 ( 2 64 − 1 ) (2^{64}-1) (2641),超过这样的数就没法直接计算了。
但是我们知道加减乘除的本质还是按位计算,所以我们可以将大数拆分为多个小位数。
根据这个原理最合适的就是使用字符串的形式,这样就将上限从最大数据类型变成了计算机最大内存
下面我们就来研究字符串格式的加减乘除,首先分享MATLAB中的实现方法,然后改写成C。

注:本文仅研究正整数,暂不涉及负数与小数

二、MATLAB简单实现

1. 加(+)

任意大数相加,主要原理:
1)按位相加
2)满十进一

%% 大数加(输入为字符串格式)
function out = OLNadd(a,b)

% 转数字矩阵(为了逻辑方便,我们将数字反序)
mata = a(end:-1:1)-'0';
matb = b(end:-1:1)-'0';

% 计算位数
la = length(a);
lb = length(b);

% 初始化
if la>=lb
    digitStart = lb;
    matout = [mata,0];
    mattmp = matb;
else
    digitStart = la;
    matout = [matb,0];
    mattmp = mata;
end

% 循环求解
for k = digitStart:-1:1
    % 位求和,满十进一
    tmp = matout(k)+mattmp(k);
    if tmp<10
        matout(k) = tmp;
    else
        matout(k) = tmp-10;
        matout(k+1) = matout(k+1)+1;
    end
end

% 转字符
out = char(matout(end:-1:1)+'0');
if out(1) == '0'
    out(1) = [];
end

end

2. 减(-)

任意大数相减,主要原理:
1)比较大小,以大减小
2)符号由大小关系决定
3)按位相减
4)不够向前借位

%% 大数减(输入为字符串格式)
function out = OLNsub(a,b)

% 转数字矩阵(为了逻辑方便,我们将数字反序)
mata = a(end:-1:1)-'0';
matb = b(end:-1:1)-'0';

% 计算位数
la = length(a);
lb = length(b);

% 初始化
isEqual = 0;
if la>lb % 默认用大的减小的(正负号由大小关系决定)
    digitEnd = lb;
    matout = [mata,0];
    mattmp = matb;
    flag = 1; % 结果为正
elseif la<lb
    digitEnd = la;
    matout = [matb,0];
    mattmp = mata;
    flag = 0;   % 结果为负
else
    % 对比第一个不相等的位数,谁大谁小
    idx = find(a~=b,1);
    if ~isempty(idx)
        if a(idx)>b(idx)
            digitEnd = lb;
            matout = [mata,0];
            mattmp = matb;
            flag = 1; % 结果为正
        else
            digitEnd = la;
            matout = [matb,0];
            mattmp = mata;
            flag = 0;   % 结果为负
        end
    else % 如果为空,说明两个数完全一致
        isEqual = 1;
    end
end

if ~isEqual 
    % 循环求解
    for k = 1:digitEnd
        % 位求差,不足借一
        if matout(k)>=mattmp(k)
            matout(k) = matout(k)-mattmp(k);
        else
            matout(k) = 10+matout(k)-mattmp(k);
            borrow = 1;
            while 1 % 不够减时往前借,直到借到为止。
                if matout(k+borrow)>=1 % 够借ok
                    matout(k+borrow) = matout(k+borrow)-1;
                    break
                else % 不够,再往前
                    matout(k+borrow) = 10+matout(k+borrow)-1;
                    borrow = borrow+1;
                end
            end
        end
    end
    % 转字符
    out = char(matout(end:-1:1)+'0');
    while out(1)=='0'      
            out(1) = [];
    end
    if ~flag
        out = ['-',out];
    end
else
    out = '0';
end

end

3. 乘(*)

任意大数相乘,主要原理:
1)按位求积
2)结果累加

%% 大数乘(输入为字符串格式)
function out = OLNmult(a,b)

% 转数字矩阵(为了逻辑方便,我们将数字反序)
mata = a(end:-1:1)-'0';
matb = b(end:-1:1)-'0';

% 计算位数
la = length(a);
lb = length(b);

% 初始化
matout = zeros(1,la+lb);

% 循环求解
for i = 1:la
    if mata(i)==0
        continue
    end
    for j = 1:lb
        if matb(j)==0
            continue
        end       
        % 位求积,分高低位
        valtmp = mata(i)*matb(j);
        valup = floor(valtmp/10);
        vallow = valtmp-valup*10;
        % 低位求和
        idx = i+j-1;
        val = matout(idx)+vallow;
        if val<10
            matout(idx) = val;
        else
            matout(idx) = val-10;
            matout(idx+1) = matout(idx+1)+1;
        end
        % 求高位
        idx = i+j;
        val = matout(idx)+valup;
        if val<10
            matout(idx) = val;
        else
            matout(idx) = val-10;
            matout(idx+1) = matout(idx+1)+1;
        end
    end
end

% 转字符
out = char(matout(end:-1:1)+'0');
while out(1)=='0'
    out(1) = [];
end

if ~flag
    out = ['-',out];
end

end

4. 除(/)

被除数任意大,除数 < 99999999 <99999999 <99999999
(原因是在除法中,被除数符合分配律,除数不符合。对于超过类型允许的除数,我不知道怎么处理。所以采取的思路是将被除数拆分为类型允许的大小,即 < 2 32 − 1 = 4294967295 <2^{32}-1 = 4294967295 <2321=4294967295(十位数),拆分的被除数最大只能为 999999999 999999999 999999999(九位数),进而得到除数最多为 99999999 99999999 99999999(八位数),主要原理:
1)按位求商,不足移位(即用最高位/除数,不够除则用最高两位,还不够用最高三位,以此类推)
2)高位余数,加至低位

%% 大数除(输入为字符串格式)
function [out,remainder] = OLNdiv(a,b)

% 转数字矩阵(为了逻辑方便,我们将数字反序)
mata = a(end:-1:1)-'0';
matb = b(end:-1:1)-'0';

% 计算位数
la = length(a);
lb = length(b);

% 转数字(按四位分组)
numa = mata(end:-1:1);
numb = 0;
for k = 1:lb
    numb = numb+matb(k)*10^(k-1);
end

% 分组除保存结果
res = zeros(1,la); %
remainder = 0; % 每次计算的余数
for i = 1:la
    divisor = numa(i)+remainder*10; % 被除数 = 上一组的余数*10000+这一组
    remainder = rem(divisor,numb);
    res(i) = (divisor-remainder)/numb; 
end

% 转字符
remainder = num2str(remainder);
out = char(res+'0');
while out(1)=='0'
    out(1) = [];
end
if ~flag
    out = ['-',out];
end

end

5. 幂(^)

底数指数任意大。主要原理:
1)循环大数乘

%% 大数幂(输入为字符串格式)
function out = OLNpow(a,b)

% 转数字矩阵(为了逻辑方便,我们将数字反序)
matb = b(end:-1:1)-'0';

% 计算位数
lb = length(matb);

% 初始化
n = -1;
for k = 1:lb
n = n+matb(k)*10^(k-1);
end
% 循环乘(直接利用大数乘法,偷下懒)
tmp = a;
for k = 1:n 
   tmp = OLNmult(tmp,a);
end
out = tmp;

end

三、测试

  1. 准确性测试
    首先测试各个算法结果是否正确,我们用int64能支持的数字进行,如下
%% 加
ca = '68924355';
cb = '411533';

cc = OLNadd(ca,cb); % 字符计算
nc = str2double(ca)+str2double(cb); % 数字计算

disp('加:')
disp([ca,' + ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')


%%% 基本测试
ca = '985355';
cb = '411533';

cc = OLNsub(ca,cb); % 字符计算
nc = str2double(ca)-str2double(cb); % 数字计算

disp('减-基本测试:');
disp([ca,' - ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')

% 小减大测试
ca = '85355';
cb = '411533';

cc = OLNsub(ca,cb); % 字符计算
nc = str2double(ca)-str2double(cb); % 数字计算

disp('减-小减大测试:')
disp([ca,' - ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')

% 借位测试
ca = '100000';
cb = '1';

cc = OLNsub(ca,cb); % 字符计算
nc = str2double(ca)-str2double(cb); % 数字计算

disp('减-借位测试:')
disp([ca,' - ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')


%% 乘
ca = '996655';
cb = '872233';

cc = OLNmult(ca,cb); % 字符计算
nc = str2double(ca)*str2double(cb); % 数字计算

disp('乘:')
disp([ca,' * ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')


%%% 整除测试
ca = '869315380615';
cb = '872233';

[cc,cr] = OLNdiv(ca,cb); % 字符计算
nr = rem(str2double(ca),str2double(cb)); % 余数计算
nc = (str2double(ca)-nr)/str2double(cb); % 数字计算

disp('除-整除测试:')
disp([ca,' / ', cb,' = ', cc,' 余 ',cr])
disp(['标准答案 = ',num2str(nc),' 余 ',num2str(nr)])
disp(' ')

% 非整除测试
ca = '88772211996655';
cb = '872233';

[cc,cr] = OLNdiv(ca,cb); % 字符计算
nr = rem(str2double(ca),str2double(cb)); % 余数计算
nc = (str2double(ca)-nr)/str2double(cb); % 数字计算

disp('除-非整除测试:')
disp([ca,' / ', cb,' = ', cc,' 余 ',cr])
disp(['标准答案 = ',num2str(nc),' 余 ',num2str(nr)])
disp(' ')

%% 幂
ca = '16';
cb = '11';

cc = OLNpow(ca,cb); % 字符计算
nc = str2double(ca)^str2double(cb); % 数字计算

disp('幂:')
disp([ca,' ^ ', cb,' = ',cc])
disp(['标准答案 = ',num2str(nc)])
disp(' ')

结果如下,完全一致,接下来可以放心测试大数

在这里插入图片描述


  1. 大数测试
    使用超大数字进行测试
%% 加
ca = '22368936111988924355';
cb = '118897773322411533';

cc = OLNadd(ca,cb); % 字符计算

disp('加:')
disp([ca,' + ', cb]),disp([' = ',cc])
disp(' ')


%% 减
ca = '22368936111988924355';
cb = '118897773322411533';

cc = OLNsub(ca,cb); % 字符计算

disp('减:');
disp([ca,' - ', cb]),disp([' = ',cc])
disp(' ')


%% 乘
ca = '789123996655';
cb = '432987872233';

cc = OLNmult(ca,cb); % 字符计算

disp('乘:')
disp([ca,' * ', cb]),disp([' = ',cc])
disp(' ')


%% 除
ca = '12345678988772211996655';
cb = '32872233';

[cc,cr] = OLNdiv(ca,cb); % 字符计算

disp('除')
disp([ca,' / ', cb]),disp([' = ',cc,' 余 ',cr])
disp(' ')


%% 幂
ca = '541';
cb = '25';

cc = OLNpow(ca,cb); % 字符计算

disp('幂:')
disp([ca,' ^ ', cb]),disp([' = ',cc])
disp(' ')

结果如下
在这里插入图片描述


四、关于优化

  1. 为了理解方便所以函数写法有累赘的地方,自己使用时可合理优化。
  2. 有大佬提出,加减乘除可以在二进制计算中完成。
  3. 关于幂运算中,指数太大计算时间会爆表,可以改写为快速幂方法。
  4. 我没有加入判断语句,即使输入的不是数字还是会计算,可以加上。
  5. 可以对输入加上符号,这样就能进行负数计算。
  6. 关于小数计算,加减乘很简单,只要判断小数点位置即可。除法则需要在此基础上加上计算精度(小数后保留几位)。关于幂,底数为小数类似乘法,但指数为小数就要配合开方。

五、其他

1、该方法的C实现可看另一篇文章《C/C++大数的加减乘除幂运算》。撰写中……
2、函数与测试代码文中已全部公开,如果嫌懒得复制且已开通会员,可以在这里下载demo

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1 2/3维图像分割工具箱 2 PSORT粒子群优化工具箱 3 matlab计量工具箱Lesage 4 MatCont7p1 5 matlab模糊逻辑工具箱函数 6 医学图像处理工具箱 7 人工蜂群工具箱 8 MPT3安装包 9 drEEM toolbox 10 DOMFluor Toolbox v1.7 11 Matlab数学建模工具箱 12 马尔可夫决策过程(MDP)工具箱MDPtoolbox 13 国立SVM工具箱 14 模式识别与机器学习工具箱 15 ttsbox1.1语音合成工具箱 16 分数阶傅里叶变换的程序FRFT 17 魔方模拟器与规划求解 18 隐马尔可夫模型工具箱 HMM 19 图理论工具箱GrTheory 20 自由曲线拟合工具箱ezyfit 21 分形维数计算工具箱FracLab 2.2 22 For-Each 23 PlotPub 24 Sheffield大学最新遗传算法工具箱 25 Camera Calibration 像机标定工具箱 26 Qhull(二维三维三角分解、泰森图)凸包工具箱 2019版 27 jplv7 28 MatlabFns 29 张量工具箱Tensor Toolbox 30 海洋要素计算工具箱seawater 31 地图工具箱m_map 32 othercolor配色工具包 33 Matlab数学建模工具箱 34 元胞自动机 35 量子波函数演示工具箱 36 图像局域特征匹配工具箱 37 图像分割graphcut工具箱 38 NSGA-II工具箱 39 chinamap中国地图数据工具箱(大陆地区) 40 2D GaussFit高斯拟合工具箱 41 dijkstra最小成本路径算法 42 多维数据快速矩阵乘法 43 约束粒子群优化算法 44 脑MRI肿瘤的检测与分类 45 Matlab数值分析算法程序 46 matlab车牌识别完整程序 47 机器人工具箱robot-10.3.1 48 cvx凸优化处理工具箱 49 hctsa时间序列分析工具箱 50 神经科学工具箱Psychtoolbox-3-PTB 51 地震数据处理工具CREWES1990版 52 经济最优化工具箱CompEcon 53 基于约束的重构分析工具箱Cobratoolbox 54 Schwarz-Christoffel Toolbox 55 Gibbs-SeaWater (GSW)海洋学工具箱 56 光声仿真工具箱K-Wave-toolbox-1.2.1 57 语音处理工具箱Sap-Voicebox 58 贝叶斯网工具箱Bayes Net Toolbox(BNT) 59 计算机视觉工具箱VFfeat-0.9.21 60 全向相机校准工具箱OCamCalib_v3.0 61 心理物理学数据分析工具箱Palamedes1_10_3 62 生理学研究工具箱EEGLAB 63 磁共振成像处理工具箱CONN 18b 64 matlab 复杂网络工具箱 65 聚类分析工具箱FuzzyClusteringToolbox 66 遗传规划matlab工具箱 67 粒子群优化工具箱 68 数字图像处理工具箱DIPUM Toolbax V1.1.3 69 遗传算法工具箱 70 鱼群算法工具箱OptimizedAFSAr 71 蚁群算法工具箱 72 matlab优化工具箱 73 数据包络分析工具箱 74 图像分割质量评估工具包 75 相关向量机工具箱 76 音频处理工具箱 77 nurbs工具箱 78 Nurbs-surface工具箱 79 grabit数据提取工具箱 80 量子信息工具箱QLib 81 DYNAMO工具箱 82 NEDC循环的整车油耗量 83 PlotHub工具箱 84 MvCAT_Ver02.01 85 Regularization Tools Version 4.1 86 MatrixVB 4.5(含注册) 87 空间几何工具箱 matGeom-1.2.2 88 大数计算工具箱 VariablePrecisionIntegers 89 晶体织构分析工具包 mtex-5.7.0 90 Minimal Paths 2工具箱 91 Matlab数学建模工具箱
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值