本帖最后由 hongrk 于 2019-5-3 14:32 编辑
1.加法函数
①自然数范围(但会受限于call所能够传递的位数),效率较高(4416位+2496位平均用时94ms),支持开头含0与加数未定义情况。
②整数加减法合一,效率相对上一个微低(2496位-4416位平均用时115ms, 4416位+2496位平均用时100ms),支持开头含0(包括第一位为负号然后一串0)与未定义情况
2.除法函数 限制较大,只能做到 自然数除以一个8位数以内的自然数,得到商与余数。
2019.5.3 修了一个BUG
3.阶乘分解 随手写,没啥用。而且没封装
自然数加法函数:
①长数版,效率不如短数版,尤其两数位数差距大时远远远不如,但更小巧。(目前还没找到一个比短数版快的例子……建议用短数版。这个嘛……作对照好了)- @echo off&setlocal enabledelayedexpansion
- set a=456456521000210000000000000000874112115674511111111111111110019999999
- set b=923451344221111111111111111000000000001
- for /l %%a in (1,1,6)do set a=!a!!a!
- for /l %%b in (1,1,6)do set b=!b!!b!
-
- set t1=%time%
- call :add "%a%" "%b%" "P"
- ::【引号可有可无;但当加数可能未定义,或结果变量名里含有空格时,必须有】
- call :tt %t1% %time% t
- echo %t%
- echo %P%&pause&exit
-
- :add
- setlocal enabledelayedexpansion&set c1=%~1&set c2=%~2&set c=0&set s=
- ::c1、c2为加数,c为进位标识符,s储存结果。
- for %%i in (1 2)do set "$=!c%%i!#"&set N%%i=0&for %%j in (4608 2304 1152 576 288 144 72 36 18 9)do if !$:~%%j!. NEQ . set/aN%%i+=%%j&set $=!$:~%%j!
- ::数出c1、c2的位数,去成9的倍数,即节数*9
- if %N1% GEQ %N2% (set N=21%N1%) else set N=12%N2%
- ::取其大者存在N,N的第一位用于标识短数,第二位用于标识长数
- for /l %%i in (9 9 %N:~2%)do set c%N:~,1%=000000000!c%N:~,1%!&set/ac=(t=1!c1:~-9!-2000000000+1!c2:~-9!+c)/1000000000&set c%N:~,1%=!c%N:~,1%:~9,-9!&set c%N:~1,1%=!c%N:~1,1%:~,-9!&set t=00000000!t!&set s=!t:~-9!!s!
- ::一直算到长数只剩0-8位。此过程中长数永远能截到9位不须补0,省掉一个大set,而短数就要。因此切短数要去掉开头补的9个0,切长数就不能去。
- ::“长数”未定义时,则短数位数必在0-8之间,不会进此循环故不会出问题,因为下一行两数都前补9个0,未定义会被变成0.“短数”未定义时,可能会进此循环,但此循环中短数前补了9个0.
- set c1=000000000%c1%&set c2=000000000%c2%&set/ac=(t=1!c1:~-9!-2000000000+1!c2:~-9!+c)/1000000000&set s=!t!!s!&if !s! NEQ 0 for /f "tokens=* delims=0" %%i in ("!s!") do set "s=%%i"
- ::算掉长数的最后一节,如果结果开头有0则去掉。
- endlocal&set %~3=%s%&goto :eof
-
- :tt
- setlocal&set be=%1:%2
- for /f "delims=: tokens=1-6" %%a in ("%be:.=%")do set/at=(%%d-%%a)*360000+(1%%e-1%%b)*6000+1%%f-1%%c,t+=-8640000*("t>>31")
- endlocal&set %3=%t%0ms&exit/b
复制代码 ②短数版,用时主要取决于短数节数。 因为注释太多了所以特地分开。
效率(for计算10/100/1000次取均值)(后跟长数版比较):
4416位+2496位 94.5ms (270ms)
2496位+2496为 78ms (130ms)
4416位+1位 4.9ms (96ms) (这个极端例子差距比较明显)
552位+624位 11.9ms (18ms)
63位+1位 2.7ms (3.8ms)
9位+9位 2.68ms (2.70ms)- @echo off&setlocal enabledelayedexpansion
- set a=456456521000210000000000000000874112115674511111111111111110019999999
- set b=923451344221111111111111111000000000001
- for /l %%a in (1,1,6)do set a=!a!!a!
- for /l %%b in (1,1,6)do set b=!b!!b!
-
- set t1=%time%
- call :add "%a%" "%b%" "P"
- ::【引号可有可无;但当加数可能未定义,或结果变量名里含有空格时,必须有】
- ::【去开头0与防未定义的都揉进去了,反正基本不影响速度】
- call :tt %t1% %time% t
- echo %t%
- echo %P%&pause&exit
-
- ::【有2个标签:add与:sad.若不会出现两加数的位数差超过488+8=496位的,可把"call :sad %D:9= %"一处改为"for %%i in (%D:9= %)do set E=%%~ni"并把:sad标签删除,这同时还能微微加速。】
- :add
- if %1=="" (
- if %2=="" (set %~3=0)else set %~3=%~2
- exit/b)else if %2=="" set %~3=%~1&exit/b
- setlocal enabledelayedexpansion&set c1=%~1&set c2=%~2&set/ac=uh=0&set s=
- for %%i in (1 2)do (for /f "tokens=* delims=0" %%j in ("!c%%i!") do set "c%%i=%%j")&set "$=!c%%i!#"&set N%%i=0&for %%j in (4608 2304 1152 576 288 144 72 36 18 9)do if !$:~%%j!. NEQ . set/aN%%i+=%%j&set $=!$:~%%j!
- if %N1% LEQ %N2% (set/aM=9+N1,N=2%N1%) else set/aM=9+N2,N=1%N2%
- (if %N:~1% NEQ 0 set uh=,-%N:~1%)&for /l %%i in (9 9 %N:~1%)do set/ac=(t=1!c1:~-%%i,9!-2000000000+1!c2:~-%%i,9!+c)/1000000000&set t=00000000!t!&set s=!t:~-9!!s!
- set D=0!c%N:~,1%:~,-%M%!&set c1=000000000!c1:~%uh%!&set c2=000000000!c2:~%uh%!&set/ac=(t=1!c1:~-9!-2000000000+1!c2:~-9!+c)/1000000000&(if %N1% NEQ %N2% set t=00000000!t!)&if !t!. NEQ 0. set s=!t:~-9!!s!
- if %c%==0 endlocal&set %~3=%D:~1%%s%&exit/b
- call :sad %D:9= %&set E=!E: =9!&set/ap=!E:~-1!+1
- set q=!D:%E%=!&if defined q set q=!q:9=0!
- endlocal&set %~3=%E:~1,-1%%p%%q%%s%&exit/b
- :sad
- set E=%*&exit/b
-
- :tt
- setlocal&set be=%1:%2
- for /f "delims=: tokens=1-6" %%a in ("%be:.=%")do set/at=(%%d-%%a)*360000+(1%%e-1%%b)*6000+1%%f-1%%c,t+=-8640000*("t>>31")
- endlocal&set %3=%t%0ms&exit/b
复制代码 ③短数版注释版- @echo off&setlocal enabledelayedexpansion
- set a=456456521000210000000000000000874112115674511111111111111110019999999
- set b=923451344221111111111111111000000000001
- for /l %%a in (1,1,6)do set a=!a!!a!
- for /l %%b in (1,1,6)do set b=!b!!b!
-
- set t1=%time%
- call :add "%a%" "%b%" "P"
- ::【引号可有可无;但当加数可能未定义,或结果变量名里含有空格时,必须有】
- ::【去开头0与防未定义的都揉进去了,反正基本不影响速度】
- call :tt %t1% %time% t
- echo %t%
- echo %P%&pause&exit
-
- ::【有2个标签:add与:sad.若不会出现两加数的位数差超过488+8=496位的,可把"call :sad %D:9= %"一处改为"for %%i in (%D:9= %)do set E=%%~ni"并把:sad标签删除,这同时还能微微加速。】
- :add
- if %1=="" (
- if %2=="" (set %~3=0)else set %~3=%~2
- exit/b)else if %2=="" set %~3=%~1&exit/b
- ::防加数未定义。没事找事写成3行是怕“输入行过长”
- setlocal enabledelayedexpansion&set c1=%~1&set c2=%~2&set/ac=uh=0&set s=
- ::c1、c2为加数,c为进位标识符,s储存结果。uh是为了破莫名其妙的":~,-0什么都不取"
- for %%i in (1 2)do (for /f "tokens=* delims=0" %%j in ("!c%%i!") do set "c%%i=%%j")&set "$=!c%%i!#"&set N%%i=0&for %%j in (4608 2304 1152 576 288 144 72 36 18 9)do if !$:~%%j!. NEQ . set/aN%%i+=%%j&set $=!$:~%%j!
- ::前面的for /f作用是去掉开头的0.然后数出c1、c2的长度(去成9的倍数),分别存在N1、N2。c1或c2未定义时对应值给出0.
- if %N1% LEQ %N2% (set/aM=9+N1,N=2%N1%) else set/aM=9+N2,N=1%N2%
- ::把较小的“伪长度值”,其实就是完整节的数目*9,存到变量N。N前面标记好哪个是长数。M就1个用处:后面用来截D
- (if %N:~1% NEQ 0 set uh=,-%N:~1%)&for /l %%i in (9 9 %N:~1%)do set/ac=(t=1!c1:~-%%i,9!-2000000000+1!c2:~-%%i,9!+c)/1000000000&set t=00000000!t!&set s=!t:~-9!!s!
- ::前补1再减是为了除0,免得被当成八进制。此for循环计算结束后,短数尚未参与计算的位数为0-8.长数不确定。全过程可以保证9位相加,从而减少循环中执行的代码量。
- set D=0!c%N:~,1%:~,-%M%!&set c1=000000000!c1:~%uh%!&set c2=000000000!c2:~%uh%!&set/ac=(t=1!c1:~-9!-2000000000+1!c2:~-9!+c)/1000000000&(if %N1% NEQ %N2% set t=00000000!t!)&if !t!. NEQ 0. set s=!t:~-9!!s!
- ::set D前面添0是为了防止未定义问题。这里要取的是长数经2行运算后剩下的部分,也可能根本没有。
- ::再经一次运算后,短数必结束。因为此行中不能保证9位相加,所以需执行的代码变多。
- ::if !t!. NEQ 0.中的点,是为了防止Bat把它们当数算(不然000000000=0了)。之所以来这一个检测,是为了防2个数正好都算完了(即节数相同,这是最常见的情况了)的情况,同时也把c处理掉了方便下一行迅速结束。
- ::有的服Bat的设定,%c2:~,-0%直接什么都不取了。难道不应该是取"总长度-0"位,取全部吗? 变量uh破此坑。
- if %c%==0 endlocal&set %~3=%D:~1%%s%&exit/b
- ::c=0,下面不用进位了,于是直接把剩下的长数部分堆上去就好。去1位是因为D开头有个补的0
- ::以下操作的耗时稳定在10ms以下。但仍颇感可惜。需要执行下面的,正常需求下貌似不会很多。比如本例。
- call :sad %D:9= %&set E=!E: =9!&set/ap=!E:~-1!+1
- ::call的目的是去末尾空格,联合后一句即为去末尾的9.
- ::本想用set E=%%~ni来去末空格,但这样最多只能处理488位字符串,超过会出错。其他能用的就只有call法了,无奈之举
- ::效率差距:call法100次慢约100ms(此数值基本不随运算数长度变化),即1次会慢上1ms.在接受范围内。
- ::for循环没有while和do until真是严重差评,中间插goto还不肯马上跳出。不然用循环也是不错的选择
- ::D自第二位起全为9时,因为第一位固定了0,set/a一步可以把p算成1
- set q=!D:%E%=!&if defined q set q=!q:9=0!
- ::set q是把一串被切掉的原本在末尾的9弄出来变成0.本可以更简单,但未定义时字符串替换的乱搞实在是很气人
- endlocal&set %~3=%E:~1,-1%%p%%q%%s%&exit/b
- :sad
- set E=%*&exit/b
-
- :tt
- setlocal&set be=%1:%2
- for /f "delims=: tokens=1-6" %%a in ("%be:.=%")do set/at=(%%d-%%a)*360000+(1%%e-1%%b)*6000+1%%f-1%%c,t+=-8640000*("t>>31")
- endlocal&set %3=%t%0ms&exit/b
复制代码 ③整数加减法合一函数- @echo off&setlocal enabledelayedexpansion
- set a=456456521000210000000000000000874112115674511111111111111110019999999
- set b=923451344221111111111111111000000000001
- for /l %%a in (1,1,6)do set a=!a!!a!
- for /l %%b in (1,1,6)do set b=!b!!b!
-
- set t1=%time%
- call :add "-%a%" "%b%" "P"
- ::【%a%、%b%也可以是负数,call时再在前面添-则是减去负数,可以正常处理。】
- ::【引号可有可无;但当加数可能未定义,或结果变量名里含有空格时,必须有】
- call :tt %t1% %time% t
- echo %t%
- echo %P%&pause&exit/b
-
- ::【有2个标签:add与:sad.若不会出现两加数的位数差超过488+8=496位的,可把"call :sad !D:%u%= !"一处改为"for %%i in (!D:%u%= !)do set E=%%~ni"并把:sad标签删除,这同时还能微微加速。】
- :add
-
- setlocal enabledelayedexpansion&set c1=%~1&set c2=%~2&set/ac=v=0,k=-2000000000,u=9&for %%i in (s f j uh)do set %%i=
- ::啧,壮观的设置。c1、c2为加数,c为进/退位标识符,v、u、k在加减法模式不同具体作用见下,s存结果,f用于定结果正负号,j用于定加减法模式,uh用于破字符串截取中的-0=0大坑
- for %%i in (1 2)do set c%%i=!c%%i:--=!&if !c%%i!==--= set c%%i=0
- ::因为要去掉加数的双负,不得不在setlocal后再检测加数是否定义。
- (for %%i in (1 2)do (for /f "tokens=* delims=0,-" %%j in ("!c%%i!")do set "c%%i=%%j")&set "$=!c%%i!#"&set N%%i=0&(for %%j in (4608 2304 1152 576 288 144 72 36 18 9)do if !$:~%%j!. NEQ . set/aN%%i+=%%j&set $=!$:~%%j!)&set $=!$!876543210&set/a$%%i=N+!$:~9,1!)&set f=%c1:~,1%&(if !f! NEQ - set f=)&(if !f!%c2:~,1%1 LSS 0 set j=-&set k=&set/au=0,v=9)&(if !$1! LSS !$2! (set f=%c2:~,1%&for %%i in (!c1!)do set c1=!c2!&set c2=%%i)else if !$1!==!$2! if !c1! LSS !c2! set f=%c2:~,1%&for %%i in (!c1!)do set c1=!c2!&set c2=%%i)&if !f! NEQ - set f=
- ::定号需要位数信息,所以在那之前必须除负号与开头0,但定号又需要看大数开头有没有负号,所以很麻烦。偏偏又找不到只用百分号而不引起歧义的变量嵌套。"if !f!%c2:~,1%1 LSS 0"的作用是判断c1、c2是不是只有1个是负数,是则改为减法模式。要比c1、c2大小,先比位数,位数相等则可用Bat自带if(数大时当字符串,从左往右逐位比)直接比较;c1<c2则易位。最后根据大数的正负来定结果的正负。
- if not defined c1 endlocal&set %~3=0&exit/b
- ::c1未定义,即2个数都未定义,直接结果为0然后踢回去。Bat对未定义变量的处理实在太不友好,留着后面会很烦
- if !$1! LSS !$2! (set c1=!c2!&set c2=%c1%
- )else if !$1!==!$2! if !c1! LSS !c2! set c1=!c2!&set c2=%c1%
- ::分成2行正常运行的代码合成一行就“输入行太长。”+“命令语法不正确。”,所以得分开
- if %N1% LEQ %N2% (set/aM=9+(N=%N1%^))else set/aM=9+(N=%N2%)
- (if %N% NEQ 0 set uh=:~,-%N%)&for /l %%i in (9 9 %N%)do set/ac=(t=1!c1:~-%%i,9!%k%+%j%1!c2:~-%%i,9!+c)/1000000000&(if !t:~^,1!==- set/at+=1000000000,c=-1)&set t=00000000!t!&set s=!t:~-9!!s!
- set D=0!c1:~,-%M%!&set c1=000000000!c1%uh%!&set c2=000000000!c2%uh%!&set/at=1!c1:~-9!%k%+%j%1!c2:~-9!+c&(if !t:~^,1!==- set/at+=1000000000)&set/ac=(!t:~,1!1!t:~9!-50)/50&(if %N1% NEQ %N2% set t=00000000!t!)&if !t!. NEQ 0. set s=!t:~-9!!s!
- (for /f "tokens=* delims=0" %%j in ("%s%")do set "s=%%j")&if not defined s set s=0&set f=
- ::除去s开头的0,若去完后什么都没了,说明结果为0.
- if %c%==0 endlocal&set %~3=%f%%D:~1%%s%&exit/b
- ::减法时,下面得把末尾的0去掉换成9,再把E的最后一位-1.这是变量u、v设置的原因。
- call :sad !D:%u%= !&set E=!E: =%u%!&set/ap=!E:~-1!+%j%1
- set q=!D:%E%=!&if defined q set q=!q:%u%=%v%!
- endlocal&set %~3=%f%%E:~1,-1%%p:0=%%q%%s%&exit/b
- ::%p:0=%防减法模式下E的最后一位恰好为1
- :sad
- set E=%*&exit/b
-
- :tt
- setlocal&set be=%1:%2
- for /f "delims=: tokens=1-6" %%a in ("%be:.=%")do set/at=(%%d-%%a)*360000+(1%%e-1%%b)*6000+1%%f-1%%c,t+=-8640000*("t>>31")
- endlocal&set %3=%t%0ms&exit/b
复制代码 除法函数:
这个除法函数限制很大,只能做到 自然数除以一个8位数以内的自然数,得到商与余数。
(不过对我来说够用了,除数根本用不到那么大)
需要更通用的除法函数,请浏览随风的http://www.bathome.net/viewthread.php?tid=3372,有一个“500位内整数除法函数”。- @echo off
- call :chu 11212121132132132121444 44444444 a b
- echo %a% %b%&pause&exit
-
- :div
- setlocal enabledelayedexpansion&set c1=%~1&set c2=%~2&set c3=&set t=
- ::c1被除数,c2除数,c3储存余数,t储存商。
- :c
- set c1=%c3%%c1%&(if "!c1:~9!"=="" (set L=123456789%c1%) else set L=9876543210%c3%)&set c3=0000000!c1:~0,9!&set/ac3=1!c3:~-9!-1000000000&set c1=!c1:~9!
- ::第一句把余数补回到被除数前面,若补完后被除数位数≦9,则测出补回之前c1的长度(因为后面!t!截取多少关键看被除数被用掉了几位);否则测出9-c3的长度,此即被除数在此轮中被截走的位数。之后同样去除c3开头的0以准备运算,并切去新c1的9位。
- set/at=c3/c2,c3%%=c2&(if not "%t%"=="" set t=00000000!t!)&set t=%t%!t:~-%L:~-10,1%!&if defined c1 goto c
- ::计算商、余,若在此轮计算前t是已定义的(if defined会延迟变量),则给t补0(因为商前面不应该出现一串0,故第一轮不能补0)。之后截取此轮商附到之前的结果后面,具体取多少,取决于此轮c1之前被截了多少位。
- endlocal&set %~3=%t%&set %~4=%c3%&goto :eof
复制代码 阶乘质因子分解(随手写没啥用)- @echo off&setlocal enabledelayedexpansion
- set/p n=请输入(0-1000):
- if %n% LEQ 1 (set Q=1 ) else for %%i in (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997) do (
- set m=&set n=%n%&for /l %%j in (1 1 6) do set/a n/=%%i,m+=n
- if !m! GTR 0 set Q=!Q!%%ix!m! )
- echo %Q:~0,-1%
- pause
复制代码 阶乘奇数降幂分解(随手写没啥用)- @echo off&setlocal enabledelayedexpansion
- set/p n=请输入(>0):
- set/a b=n+(n"&"1)-1,n">>"=1
- :a
- set/a t+=1,m+=n,b=n+(n"&"1)-1,a=b+2,n">>"=1&echo 从!a!到%b%的奇数其幂为!t!
- if not %a%==3 goto a
- echo 2的幂为%m%
- pause
复制代码 若代码有BUG或您有什么建议或看法,请不吝赐教。谢谢。 |