Board logo

标题: [数值计算] [原创]通用批处理开平方程序,理论上支持任意数字 [打印本页]

作者: techon    时间: 2011-5-26 01:23     标题: [原创]通用批处理开平方程序,理论上支持任意数字

本帖最后由 techon 于 2011-5-27 19:15 编辑

废话一大堆,删了
方法原理见算数开平方法则
通用性处理,可支持任意数字输入,结果保留8-9位有效数字

其他算法:
二楼 版主的 牛顿迭代法快速求平方根或近似值,只能计算1-214748,保留两位小数
七楼 caruko的 使用二进制位移法开平方,支持1-2147483646,最多保留4位小数


代码如下:
保留了部分测试代码和注释,欢迎批评指正
用法 call :_Sqrt 被开方数字
  1. @echo off
  2. SETLOCAL ENABLEDELAYEDEXPANSION
  3. rem 这是测试部分
  4. set x=1.4233599273013201
  5. set max=14233599273013201
  6. rem set Max = 2147483647 小于2的31次方-1(2^31=2147483648)32位有符号数可表示的最大值 set/a 可处理的最大数(32位系统是这个,64位系统未测试)
  7. rem 11930464 * 20 * 9 + 9^2 = 2147483601 11930464^2 = 142335971255296
  8. rem 119304649 119304649^2 = 14233599273013201 (可正常开完的最大被开方数)
  9. rem %1=被开方数 这是结果写在这方便比对
  10. call :_Sqrt 2250000000000 1500000
  11. echo --------jg===%jg%---------&pause
  12. call :_Sqrt 1000000 1000
  13. echo --------jg===%jg%---------&pause
  14. call :_Sqrt 0 0
  15. echo --------jg===%jg%---------&pause
  16. call :_Sqrt 00000.00000001 0.0001
  17. echo --------jg===%jg%---------&pause
  18. call :_Sqrt 26 5.0990195135927848300282241090228
  19. echo --------jg===%jg%---------&pause
  20. call :_Sqrt %x% 1.19304649
  21. echo --------jg===%jg%---------&pause
  22. call :_Sqrt %max% 119304649
  23. echo --------jg===%jg%---------&pause
  24. call :_Sqrt 11112233445643274231572 105414578904.64332842651254712323
  25. echo --------jg===%jg%---------&pause
  26. call :_Sqrt 2.250000000000000 1.5
  27. echo --------jg===%jg%---------&pause
  28. call :_Sqrt 0.000001 0.001
  29. call :_Sqrt 01111.5643274231572 33.340130884913412409133565742286
  30. call :_Sqrt 1048576 1024
  31. call :_Sqrt 0.0132010 0.114895604
  32. call :_Sqrt 0.00000064 0.0008
  33. echo --------jg===%jg%---------&pause
  34. call :_Sqrt 55 7.4161984870956629487113974408007
  35. call :_Sqrt 4.00000000 2
  36. echo --------jg===%jg%---------&pause
  37. rem 做成子程序了,方便调用
  38. pause&pause
  39. goto :EOF
  40. rem 开方程序开始
  41. :_Sqrt
  42.   SETLOCAL ENABLEDELAYEDEXPANSION
  43. rem 检查输入是否有效
  44.   set a=0&set sr=0&set tmpin=%1
  45.   if "!tmpin!"=="" set sr=-1&goto :chkend
  46.   :chkloop
  47.   if "!tmpin:~%a%,1!"=="." (set/a sr+=1&if !sr! gtr 1 set sr=-1&goto :chkend) ^
  48.   else (if !tmpin:~%a%^,1! lss 0 set sr=-1&goto :chkend)&if !tmpin:~%a%^,1! gtr 9 set sr=-1&goto :chkend
  49.   set/a a+=1
  50.   if not "!tmpin:~%a%,1!"=="" goto :chkloop
  51.   :chkend
  52. rem 初始化变量
  53.   set jg=0&set xs=0&set xsl=0&set fxs=0&set of=0&set pl=0&set d=0&set x1=&set x2=&set tlc=
  54.   if %sr% equ -1 ENDLOCAL&set jg=被开方数字非法&echo Call :_Sqrt 输入的参数无效&goto :EOF
  55.   if %sr% equ 0 ( rem 无小数点
  56.     call :_dez l %tmpin% xa
  57.     if "!xa!"=="" set xa=0
  58.   ) else ( rem 有小数点输入
  59.     for /f "tokens=1,2 delims=." %%C in ("0%tmpin%") do (
  60.       call :_dez l %%C
  61.       set x1=!dez!
  62.       call :_dez r %%D
  63.       set x2=!dez!
  64. rem 处理输入数 可将 0.xxx 简略为 .xxx 输入,取输入的整数部分为 x1,小数部分为x2
  65.       if "!x2!"=="" ( rem 没有小数
  66.         if "!x1!"=="" (set xa=0) else set xa=!x1!
  67.       ) else ( rem 有小数
  68.         call :_lenc !x2!
  69.         set/a tlc=lenc%%2
  70.         if !tlc! equ 1 set x2=!x2!0
  71. rem 小数位数如果为奇数,将其后补0,凑为偶数位
  72.         set/a xsl=^(lenc+1^)/2
  73. rem 输入小数的位数除以2,开完平方后需要补回的小数位数
  74.         if "!x1!"=="" call :_dez l !x2! x2
  75.         set xa=!x1!!x2!
  76.       )
  77.     )
  78.   )
  79.   if "%x2%"=="" ( rem 如果没有小数部分,去掉整数低位2n个0,设置小数位数为负
  80.     :lx1
  81.     if "!xa:~-2!"=="00" set xa=!xa:~0,-2!&set/a xsl-=1&goto :lx1
  82.   )
  83.   set j19=0
  84.   for /l %%C in (0,1,9) do ( rem 如果是1-9的平方数直接给出结果
  85.     set/a jc=%%C*%%C
  86.     if "!xa!"=="!jc!" (
  87.       if %xsl% equ 0 echo 结果=%%C&ENDLOCAL&set jg=%%C&goto :EOF
  88.       set _jg=%%C&set jg=%%C
  89.       :xsub
  90.       if !xsl! lss 0 set jg=!jg!0&set/a xsl+=1&goto :xsub
  91.       :xsad
  92.       if !xsl! gtr 0 set jg=0.!_jg!&set _jg=0!_jg!&set/a xsl-=1&goto :xsad
  93.       set j19=1
  94.     )
  95.   )
  96.   if "%j19%"=="1" echo 结果=%jg%&ENDLOCAL&set jg=%jg%&goto :EOF
  97.   call :_lenc %xa%
  98.   set/a la="lenc%%2"
  99. rem 将xa补位为偶数位
  100.   if %la% equ 1 set xa=0%xa%
  101. rem 开方循环开始
  102.   :_sqr
  103.   set p=!xa:~%pl%,2!
  104.   if %d% equ 0 (
  105.     if "%p%"=="" (
  106.       echo 开方正好开尽^^_^^&goto :_endsqr
  107.     ) else (
  108.       if %jg% gtr 11930464 set of=1&echo 产生溢出!!!&goto :_endsqr
  109.       if "!p:~0,1!"=="0" (set ps=%p:~1,1%) else (set ps=%p%)
  110.     )
  111.   ) else (
  112.     if %jg% gtr 11930464 set of=1&echo 产生溢出!!!&goto :_endsqr
  113.     if "%p%"=="" set p=00&set/a xs+=1
  114.     set ps=%d%!p!
  115.   )
  116.   for /l %%C in (9,-1,0) do (
  117.     set/a ts=20*%%C*jg+%%C*%%C
  118.     if !ts! leq %ps% (
  119.       if not "%jg%"=="0" (set jg=%jg%%%C) else set jg=%%C
  120.       set/a d=ps-ts&goto :_sh
  121.     )
  122.   )
  123.   :_sh
  124.   set/a pl+=2&goto :_sqr
  125. rem 开方循环结束
  126.   :_endsqr
  127.   if not "!xa:~%pl%!"=="" (
  128.     call :_lenc !xa:~%pl%!
  129.     if "!tlc!"=="1" (set/a fxs=lenc-1) else (set fxs=!lenc!)
  130. echo 忽略位数:fxs===!fxs! rem 溢出后将被开方数忽略的位数
  131.   )
  132.   set/a xsl=xsl+xs-(fxs+1)/2
  133. rem 修正小数位数
  134. echo 小数位数:%xsl% rem 开方结果小数的位数
  135.   if %fxs% gtr 0 if %xsl% lss 0 echo 非有效位数 %xsl:~1%
  136.   if %xsl% gtr 0 (
  137.     call :_lenc %jg%
  138.     if !lenc! gtr %xsl% (
  139.       set jg=!jg:~0,-%xsl%!.!jg:~-%xsl%!
  140.     ) else (
  141.       set/a jgl=xsl-lenc
  142.       :ld0
  143.       if !jgl! gtr 0 set jg=0%jg%&set/a jgl-=1&goto :ld0
  144.       set jg=0.%jg%
  145.     )
  146.   )
  147.   :ad0
  148.   if %xsl% lss 0 set jg=%jg%0&set/a xsl+=1&goto :ad0
  149.   if %of% equ 1 (set fh=≈) else (set fh==)
  150. echo 结果%fh%%jg%&ENDLOCAL&set jg=%jg%&goto :EOF
  151. rem End_Sqrt
  152. rem 计算字串长度子程序
  153. :_lenc
  154.   SETLOCAL ENABLEDELAYEDEXPANSION
  155.   set n=0&set _#=^"&set "stmp=%1"
  156.   :asa
  157.   if not "!stmp:~%n%,1!"=="" set/a n+=1&goto asa
  158.   if "!stmp:~0,1!"=="!_#!" set/a n-=2
  159.   ENDLOCAL&set "lenc=%n%"
  160. goto :EOF
  161. rem End_lenc
  162. rem 删除数字串两端0的子程序,三个参数:%2 为待处理的数字串;%1=l时删除数字串左端的0,%1=a时删除两端的0
  163. rem 其他值时删除右端的0;%3 为变量名,保存处理完的字串。用法:Call :_dez %1=l|r|a [%2=String [%3=var]]
  164. :_dez
  165.   SETLOCAL ENABLEDELAYEDEXPANSION
  166.   set str=%2&if "%3"=="" (set var=dez) else set var=%3
  167.   if "%1"=="l" (set ts1=0,1&set tss=1) else set ts1=-1&set tss=0,-1
  168.   :loopz
  169.   if "!str:~%ts1%!"=="0" set str=!str:~%tss%!&goto :loopz
  170.   if "%1"=="a" if "!ts1!"=="-1" set ts1=0,1&set tss=1&goto :loopz
  171.   ENDLOCAL&set %var%=%str%&goto :EOF
  172. rem End_dez
  173. rem ---------------------------------------------------All End
复制代码
-----------------------------------------------------------------------------------

另附除法计算部分,仅支持整数输入
用法 call :_div 被除数 除数 [保留小数位,如精度不够则不保留]
返回结果 保存在变量quo中
例:
  1. call :_div 5 3 3
  2. echo 5÷3 结果 %quo%
  3. rem 5除以3 保留3位小数
  4. call :_div 16 9
  5. echo 16÷9 结果 %quo%
  6. rem 16除以9 能算几位算几位
  7. pause:goto :eof
  8. rem 除法计算
  9. :_div
  10.   if "%1"=="" echo 请输入被除数(参数 %%^1)&goto :EOF
  11.   if "%2"=="" echo 请输入除数(参数 %%^2)&goto :EOF
  12.   SETLOCAL ENABLEDELAYEDEXPANSION
  13.   set Maxd=2147483647&set scp=0&set ded=%1&set dvr=%2&set quo=&set psc=%3
  14.   if "%psc%"=="" set psc=%Maxd%
  15.   if %dvr% equ 0 ENDLOCAL&echo 错误,除数为零!&goto :EOF
  16.   if %ded% equ 0 ENDLOCAL&set quo=0&goto :EOF
  17.   if "%ded%"=="%1" (if not "%dvr%"=="%2" ENDLOCAL&echo 参数 %%^2 输入错误或数值超限&goto :EOF)^
  18.   else (echo 参数 %%^1 输入错误或数值超限
  19.     if not "%dvr%"=="%2" ENDLOCAL&echo 参数 %%^2 输入错误或数值超限&goto :EOF)
  20.   :divbg
  21.   set/a quo=ded/dvr, rdd=quo*dvr
  22.   if %rdd% neq %ded% if %ded% leq %Maxd:~0,-1% (
  23.       if %scp% lss %psc% set ded=%ded%0&set/a scp+=1&goto :divbg
  24.     )
  25.   if %quo% equ 0 ENDLOCAL&set quo=0&goto :EOF
  26.   if %scp% neq 0 if "!quo:~-%scp%!"=="!quo!" (
  27.       set quo=00000000%quo%&set quo=0.!quo:~-%scp%!)^
  28.     else set quo=!quo:~0,-%scp%!.!quo:~-%scp%!
  29. ENDLOCAL&set quo=%quo%&goto :EOF
复制代码

作者: batman    时间: 2011-5-26 01:32

楼主用这种算法好些不?
http://www.bathome.net/viewthrea ... hlight=%B5%FC%B4%FA
作者: techon    时间: 2011-5-26 01:44

本帖最后由 techon 于 2011-5-26 02:13 编辑
楼主用这种算法好些不?
http://www.bathome.net/viewthrea ... hlight=%B5%FC%B4%FA
batman 发表于 2011-5-26 01:32



近似值算法,简单快捷,对于精度要求不高和计算不太大的数非常好用

算个这个数就不行了:1099511627776

2的40次方 1048576的平方
作者: caruko    时间: 2011-5-26 02:20

开方,即将数字的2进制位截取一半..
使用移位算法,复杂度会大幅下降。

比如 4*4 = 16 , 2^2=4, 2^4=16; 把4位二进制数变成2位即可。
又如,2^4=16, 16*16=256, 2^(4*2)=256 。
反过来开方 256 就是把2^(8/2)。
作者: techon    时间: 2011-5-26 02:24

请问楼上的兄弟?
7的二进制怎么处理? 0111
如何处理小数点呢?
作者: caruko    时间: 2011-5-26 02:51

本帖最后由 caruko 于 2011-5-26 03:59 编辑

任何整数都是 1 2 4 8 ... 这些数字之和
7 = 2^2 + 2^1 +2^0 ,即 set /a "(1<<2)+(1<<1)+(1<<0)"
(2^2 + 2^1 +2^0)*(2^2 + 2^1 +2^0) = (2^4 + 2^3 + 2^2) + (2^3 + 2^2 + 2^1) + (2^2 + 2^1 + 2^0)

就相当于嵌套的 for %%i in (2 1 0) do for %%j in (2 1 0) do set /a v+=1"<<"(%%i+%%j)


好吧,完全平方的多项式分解很麻烦。

算法如下
实际上,得出 1<<5 小于  49 小于 1<<6,可以得出 49的根为 x=(5/2) ,即1<<2位, 3位2进制数。
for /l %%i in (x,-1,0) do (
               set /a v+=1<<%%i
               if v*v  gtr 49 set /a v-=1<<%%i
               if v*v equ 49 ( break & echo v )
)
如果仍然未出结果,就涉及小数。
那么把 v 左移 2位,把 49 左移4位,继续计算。在未溢出的情况下,算完之后右移2位,就能得到 2位小数的根了。
作者: caruko    时间: 2011-5-26 12:49

本帖最后由 caruko 于 2011-5-26 14:37 编辑

按照上面的算法,我也放一个开方代码。
32位限制,精确度在5位左右。
修改了一个数值接近MAX时,导致变量k为空的,无法输出结果的小错误。
  1. @echo off&SETLOCAL ENABLEDELAYEDEXPANSION
  2. ::最大被开方数,2147483647  < 2^31
  3. ::最大开方数, < 2^16
  4. ::每次都将输入数值增加10^2n次,尽量增加到接近MAX的数,
  5. ::检测输入
  6. :loop
  7. 2>nul set /a 1/%1 &&(set "input=%1")||(set /p input=请输入被开方数字:)
  8. 2>nul set /a 1/input ||goto :loop
  9. call :kf !input!
  10. goto :eof
  11. :kf
  12. setlocal
  13. ::在溢出范围内提高位数,增加精确度。
  14. set /p=被开方数:!input!,结果:<nul
  15. set /a input=%1,cs=1
  16. for /l %%i in (1,1,8) do (
  17.     set /a cs*=10
  18.     if "!cs:00=!"=="1" (
  19.         set /a x=input*cs,y=x/cs
  20.         if !input! equ !y! set /a tp=x,k=%%i/2
  21. ))
  22. if "!tp!"=="" (
  23.   set /a k=0
  24.   ) else (
  25.   set /a input=tp
  26. )
  27. :: 检测被开方数2进制的位数。
  28. set "ofset="
  29. for /l %%i in (0,1,31) do (
  30.     set /a v=1"<<"%%i
  31.     if %%i equ 31 set /a v=v+1&set v=!v:-=!
  32.     if not defined ofset if !v! gtr !input! set /a ofset=%%i-1
  33. )
  34. if not defined ofset (
  35.     echo,输入数字超标!
  36.     exit /b -1
  37. )
  38. set /a kofset=ofset/2,v=0
  39. ::求被开方数,整数部分。
  40. for /l %%i in (!kofset!,-1,0) do (
  41.     set /a v+=1"<<"%%i,flag=v*v
  42.     if !flag! gtr !input! set /a v-=1"<<"%%i
  43. )
  44. for /f %%a in ("!k!") do set ecode=!v:~0,-%%a!.!v:~-%%a!
  45. if !k! equ 0 set ecode=!v!.0
  46. echo,!ecode!
  47. endlocal
复制代码

作者: applba    时间: 2011-5-26 18:25

本帖最后由 applba 于 2011-5-26 18:26 编辑

批处理做浮点运算,真是是强人所难啊……
不过我还是对两位数学强人表示由衷的钦佩……
我只主张应用为王……




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2