ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 3007|回复: 13

[原创] 巨大数运算(加、减、乘、除、取余数、开平方、开立方)

[复制链接]

TA的精华主题

TA的得分主题

发表于 2022-2-14 00:17 | 显示全部楼层 |阅读模式
本帖最后由 aoe1981 于 2022-2-16 16:06 编辑

我至少是受下面3个帖子的影响,对巨大数的运算产生了好奇,满腹疑问。
按时间顺序:
1.2011年8月
自定义函数,位数超长数字的加减乘除计算。
https://club.excelhome.net/thread-748124-1-1.html
(出处: ExcelHome技术论坛)


2.2013年11月
[开_145-2]计算根号2,比精度比速度[已总结]
https://club.excelhome.net/thread-1075113-1-1.html
(出处: ExcelHome技术论坛)


3.2014年4月
二个巨大数的三則運算
https://club.excelhome.net/thread-1108590-1-1.html
(出处: ExcelHome技术论坛)


百度搜索了一阵子,找到一个网站,有“大数计算器”(TKCB),样子如图:

巨大数计算器.JPG

用着不错,也很羡慕。我测试了一下竞赛帖中的开根号2,开1000位时很快,但测试开10000位时,耗时约在1000秒。而竞赛帖中大神jsxjd的优秀代码开根号2到10000位,在我现在的电脑上只需要0.18秒,让人咋舌。不过,TKCB开发的“大数计算器”是可以针对巨大数开方的,这也是很了不起的。
我愈发好奇。开始搜各种视频教程,研读竞赛帖,学习CSDN上的免费算法帖子。
折腾半月余,总算做了个VBA版的“巨大数运算”,一并发在我的EH账号里,作为记录。
模样如图:

界面.JPG

共有7个模块:
一、加法模块HugeNumberPlus
(1)Public Function HNPlus(ByVal a As String, ByVal b As String) As String
  巨大数运算之巨大数加法(加数a,加数b)
二、减法模块HugeNumberSubtract
(2)Public Function HNSubtract(ByVal a As String, ByVal b As String) As String
  巨大数运算之巨大数减法(被减数a,减数b)

三、乘法模块HugeNumberMultiply
(3)Public Function HNMultiply(ByVal a As String, ByVal b As String) As String
  巨大数运算之巨大数乘法(因数a,因数b)

(4)Public Function StringCheck(ByRef str As String) As Boolean
  (公用1)字符串检查(字符串str)

(5)Public Function StringToArray(ByVal str As String, ByVal n As Long, Optional ByVal tf As Boolean = False)
  (公用2)将字符串截断装入数组(字符串str,每组字符个数n,True倒序False顺序)

(6)Public Function StringReplaceHTSame(ByVal str As String, ByVal s As String, Optional ByVal ht As Boolean = True) As String
  (公用3)替换字符串首尾相同的指定字符(字符串str,指定字符s,首True尾False)

四、除法模块HugeNumberDivide
(7)Public Function HNDivide(ByVal a As String, ByVal b As String, Optional ByVal n As Long = 0, Optional ByVal ys As Boolean = False) As String
  巨大数运算之巨大数除法(末位四舍五入)(被除数a,除数b,限制小数位数n,输出余数ys)

五、平方根模块SquareRoot
(8)Public Function SQROOT(ByVal c As Double, Optional ByVal Lenght As Long = 15) As String
  迭代法开平方根(被开方正数c,计算精度Lenght)
  迭代关系式:x1=x0*(1.5-c/2*x0^2)

(9)Public Function SQROOT2(ByVal c As String, Optional ByVal ws As Long = 15) As String
  迭代法开平方根(被开方大正数c,小数位数ws)
  迭代关系式:x1=x0*(1.5-c/2*x0^2)

(10)Public Function SQROOT3(ByVal c As String, Optional ByVal ws As Long = 15) As String
  手算法开平方根(被开方大正数c,小数位数ws)

(11)Public Function SQROOT4(ByVal c As String, Optional ByVal n As Long = 15) As String
  二分法开平方根(被开方大正数c,迭代次数n)

六、立方根模块CubeRoot
(12)Public Function CUROOT(ByVal c As Double, Optional ByVal Lenght As Long = 15) As String
  迭代法开立方根(被开方正数c,计算精度Lenght)
  迭代关系式:x1=x0*(4-c*x0^3)/3

(13)Public Function CUROOT2(ByVal c As String, Optional ByVal ws As Long = 15) As String
  迭代法开立方根(被开方大数c,小数位数ws)
  迭代关系式:x1=x0*(4-c*x0^3)/3

七、测试模块Test
(14)Public Function GenerateHugeNumber(ByVal n As Long, Optional ByVal xs As Long = 0, Optional ByVal fs As Boolean = False) As String
  随机生成巨大数(位数n,小数部分位数xs,输出负数fs)

(15)Public Function CheckStringDifferent(ByVal a As String, ByVal b As String) As String
  检查并返回两个字符串首次出现不同的位置及相应字符(字符串a,字符串b)

(16……)一众test1、test2、……、test7、test71、test72、test73、test74、test8、test81、test82


说说效果:
1.所谓“巨大数”,包含正数、负数、0,整数、小数。
2.我测试了32767位×32767位的乘法,在我现在的电脑上用时15.43秒。由于单元格最大输入32767字符,测试更长数位需在代码中引用变量,而且输出至“立即窗口”,同时要专门写代码调用“比对函数”,以确保结果的正确性。在帖首给出的竞赛帖中,大神lee1892给出了一段当时4秒参照代码,那段代码在我现在的机子上约需0.4秒,快了10倍,放在当时,这道乘法需要150多秒。
3.我测试了16000位÷2位的除法,约需4.5秒,香川大侠在帖首2014年4月的帖子中上传的巨大数除法仅需0.03秒,速度是我的除法的150倍以上。我很好奇,没研究懂,只发现,香川的除法一次可以出12位商,而我的除法就像小学生列竖式一样,从高到低,一位一位往下除,每循环一次,出一位商,所以效率天壤之别。设若我采用香川大侠的除法代码,我的开平方、开立方的算法效率也会提升。不过,我还是不喜欢用不懂的东西,想着以后有可能了学习、优化除法。但是,我的除法比“大数计算器”强。话说回来,香川大侠前后做了两个版本的乘法吧,TM()和TM2(),版本2更高效,但在负数乘正数时会出错,TM()却是对的。(友情提示,我自然找不出原因)
4.开平方算法我尝试了“手算法”(效率低,但结果是百分之百正确的,可以校验其他算法)、“二分法”(效率更低,有点垃圾),最后还是学习了lee1892大师讲解的“牛顿迭代法”,链接如下:

https://club.excelhome.net/forum ... 1075113&pid=7426754

感谢lee1892大师。囫囵吞枣,只得皮毛。我一开始针对Double范围内的数开方,居然用2.97秒开出了根号2后10000位,这并不算是达到了当时竞赛帖中“4秒计算万位”的拔高要求,因为实则相当于当时的29.7秒。后来,我又做了针对“巨大数”的开方,为了平衡“整数部分很大的”、“0.0000000000……”这样很小的数,在迭代终止条件上、有效数字增长控制上,尽量取了一些平衡的策略,这个版本的开根号2到10000位,目前需要33.4秒,可以提速至18秒,但在开大整数时,精度又会下降。如何平衡?我始终是以“大数计算器”做对比的。
5.开立方运算无可避免的在迭代过程内部需要做除法,一个“巨大数除以3”的除法,这使得这个算法效率惨不忍睹,不如“大数计算器”,向TKCB致敬。一个2开立方根至1000位小数,约需44秒。说这些,是为了给大家一些思想准备,否则,大家不吝测试时,急于上巨大数,电脑假死了,大家会骂死我的。
7.加减乘除的正确性,目前我会说“应该是百分之百”,我已修复了许多隐秘的bug。关于求余数,遵循“被减数-除数×商”,正常,余数与被除数同号(这与mod函数是有区别的,mod函数的余数与除数同号),但由于我将求商的“截断法”,改为末位“四舍五入法”,当商五入时,会出现反方向的余数,大家明白以后就忍受下。
8.开平方、开立方的正确性,目前我会说“百分之九十九点九九”,万足金吧,嘿嘿。关于“牛顿迭代法”,有三个关键点,也是难点:
(1)迭代初值的选取。这个初值要与目标值比较接近,否则越过函数的“收敛半径”,值会发散的,比如,开根号2时,初值选为2,结果变成巨大数了,初值选成1.4,居然出现负数根了。对于一个巨大数,我略用下面的方法产生初值,还凑和。
  “一个整数部分ws位的巨大数开n次方,其根的整数部分位数是:int((ws+n-1)/n)位”
(2)迭代关系式的优化。应该尽量避免巨大数除法运算,减少巨大数乘法次数,但是我也只是空想而已。
(3)迭代终止条件。简单化的、固定的“迭代次数”是比较傻的,要么迭代不够,要么迭代过火,浪费算力。合适的终止条件需要把握迭代过程中“有效数字”增长的规律,也不知道有没有这样的规律,我是不知道的,便做了一个实践派,反正有诸路大神的代码结果做保证,便反复测试,加上平衡各种数的考虑,最终在代码中选用的是64%精度。也即:假设迭代一次的值x1的小数部分位数的前64%是精确的,后面36%是近似的,将后面的截掉,从而避免小数部分位数增长过快,在较少的迭代次数下提前退出,精度严重不够的情况发生。这是我对我的开方算法保有一些“谨慎态度”的根本原因。不过,在发帖的今天,我的开方算法还是经受住了我的各种检验和对比的。


头都大了。只为了兴趣,发帖也是为了借用EH归类整理一下自己的东西。有想到不一样的了,后面再说吧。

附件如下:
HugeNumberOperation_by aoe1981.zip (100.4 KB, 下载次数: 84)
(2022.02.16对上面附件作了更新,修复了对“4”这样的完全平方数开方运行出错的问题,原因是第一次的初值x0已经是准确值了,后续x1根本不会产生新的迭代变化,导致程序无法退出,陷入死循环。另外,测试出了程序的一个小问题吧,比如构造一个数:2^6=729,这个数在整数范围内既可以开平方(27),也可以开立方(9),用上面附件得出的结果却是:26.9999999……、8.999999……。本想修复,但这并不是程序的错误,而是在算法中采用了“优化迭代关系式”所产生的副作用。因为所谓“优化”,其实是为了避免迭代过程中产生巨大数除法或者减少巨大数乘法而事先将初值x0求其倒数,这样一来,对于有些数,原本是准确数的,变成了近似数。想了想,就不更改了。TKCB的“大数计算器”不存在这个问题。)



评分

7

查看全部评分

TA的精华主题

TA的得分主题

发表于 2022-2-14 10:32 | 显示全部楼层
学习啦,谢谢!

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2022-2-14 10:56 | 显示全部楼层
收藏支持一下。
08年做过一个用级数展开的方法求圆周率的,自己没大数运算需求也没继续研究。
不过,如果楼主没特殊需求,建议玩玩算了,没必要搞个VB的大数运算数学库。有时间可以去多学点其他的。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2022-2-14 13:43 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2022-2-14 16:55 | 显示全部楼层
收藏支持一下。
谢谢楼主分享。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2022-2-15 10:52 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2022-2-15 11:09 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2022-2-21 20:47 | 显示全部楼层
本帖最后由 aoe1981 于 2022-2-26 21:26 编辑

今天终于可以发布我的巨大数运算2.0版了。
这一版本主要是优化了巨大数除法的效率,同时有力地提升了开立方根的效率。
简易测试效果如下:
(1)16000位除以2位,小数保留1000位
顶楼版(或者1.0版吧):约3.96秒
2.0版:0.59秒
提升约6.7倍,在其他一些测试中会提升约10多倍(比如巨大数除以巨大数),因为本版我终于实现了梦寐以求的快速除法,一次可以求出12位或者13位商,速度理论上应该提升12倍左右。

(2)2开立方根,保留1000位小数
顶楼版:44.92秒
2.0版:5.82秒
提升约7.7倍。

当然,速度、效率倒是其次,正确性才是基础。同样,我修复了许多隐秘的错误,包括避免了香川大侠快速除法的错误:

https://club.excelhome.net/forum ... 108590&pid=10903255

在我还没有完全修复快速除法时,开立方根的结果偶尔是正确的,偶尔是错误的,因此很隐秘。直接用除法寻找一个错误的例子还不易找到,幸好,我用“开立方根”检验了我的2.0“快速除法”,截止目前发布,我觉得,还是相当可靠的。

但是,我依然要说的是:期望坛友不吝测试,暴露出错示例,我进一步修复可能存在的bug。

关于“快速除法”的算理,容我后面整理,主要是看着香川大侠的代码,反复F8加上自己的联想、演算,猜想并验证的一些规律吧。

2.0版附件如下:

由于测试一个错误,此处附件删除。2022.02.26

顶楼附件保留,可以彼此验证。
关于开平方的验算,其实也可以用附件中的巨大数乘法,开立方要辗转一次,这样做就可以查是否“自相矛盾”。

排查完错误的附件如下(最终版)(顶楼附件中除法采取“末位四舍五入法”,余数有点乱。且其中减法的压位就由28位下调为27位,才能防止在极端情况下出错,不再更新):
1.0版:
HugeNumberOperation1.0_by aoe1981_截断法.zip (147.2 KB, 下载次数: 2)

2.0版:
HugeNumberOperation2.0_by aoe1981_截断法.zip (135.11 KB, 下载次数: 16)
(推荐这个版本,采用快速除法,效率高)

以上两个版本的正确性现在相当可靠了,尤其经历了本轮漫长的查错修改后,更是如此。可放心使用了!!!
关于排错详情,另建楼层说明。

评分

2

查看全部评分

TA的精华主题

TA的得分主题

发表于 2022-2-21 21:05 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
留个脚印学习一下。。

TA的精华主题

TA的得分主题

 楼主| 发表于 2022-2-26 21:45 | 显示全部楼层
自研发了“巨大数的快速除法”后,甚是得意,不料,一次偶然测试,居然出现了错误,险些让我对自己的“发明”失去信心!

我偶然间做了以下除法:
被除数:
16328055933142898264333913.729769
除数:
3241274141228298354766292154605432770187188182218630615594651083046868878799466483975478087085091403.18052655
当小数保留10000位时,我的原2.0版快速除法得到的结果会在9100位出错,注意:前9000多位是正确的,后面居然错了!不过,1.0版得到的结果和大数计算器是一样的,我便确信是我的“快速除法”错了。

这个神经一样的错误很少出现,我想选出一些较小的数,便于查错,但是之后很难找出错误示例。只得硬着头皮上,数字长,痛苦啊……经历了好几天,终于定位出了错误原因:
巨大数减法(其实是巨大数加法,因为减法调用的就是加法)出错了!!!

是这样一道减法:
被减数:
168924674406844066491167348528717567094511533149372060271432366352437163147627131853756554638446807438624300000000000000
减数:
168924674406808579455086010844900555366787623032369537991396851129196361203710577701038473627569712233666862955908364285

正确结果是:
35487036081337683817011727723910117002522280035515223240801943916554152718081010877095204957437044091635715

但我的减法会得出错误的结果:
'35487036081337683817011727723910117002522280035515223240801943916554152718081017569648868752114571908364285

出现这个错误的原因是:我在巨大数加法中采用的压位策略是一次压28位,太贪婪了,在极端情况下会出错,只需将其改成27位,其他代码一句不动,就正确了。太扎心了……

更进一步的原因如下示例:
在立即窗口中:
?cdec(18446807438624300000000000000)=18446807438624300000000000000

而:
?cdec("18446807438624300000000000000")=63364914748384000000000

似乎出现了溢出错误。(上面的数字长度是29位,我以为压位选28位,不会溢出有效精度范围)

示例如图:

出错示例.JPG

教训惨痛啊,宝贵的时间,美好的心情……浪费得太多了!!!以作前车之鉴。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-5-10 12:33 , Processed in 0.059213 second(s), 21 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表