Board logo

标题: [数值计算] [代码讨论]批处理开平方(计算平方根的值) [打印本页]

作者: SQYSQYSQY    时间: 2018-12-8 14:49     标题: [代码讨论]批处理开平方(计算平方根的值)

本帖最后由 SQYSQYSQY 于 2019-1-29 18:40 编辑

致浏览者,想对大数字(超过1000)计算平方根,请见31楼(约18秒算到150位),想对小数计算平方根,请见34楼(是个文件)(约23秒算到150位)
注意,部分计算器对最后一位进行了四舍五入,本程序可能会与计算器的最后一位相差1。这不是本程序的错误。
致浏览者,被开方数不超过1000,可用下面高效率程序进行计算(约3.5秒算到150位)。
  1. @echo off & SetLocal EnableDelayedExpansion
  2. ::变量c的值是被开方数,不得超过1000!
  3. set c=2
  4. for %%a in (16 8 4 2 1) do (
  5.         set /a "d=(a*%%a<<1)+%%a*%%a+b"
  6.         if !c! geq !d! set /a "a+=%%a","b=d"
  7. )
  8. if !b! equ !c! echo !a! & pause & exit /b
  9. set /p "=!a!."<nul
  10. set "d=0"
  11. for %%a in (512 256 128 64 32 16 8 4 2 1) do (
  12.         set /a "d+=%%a","e=((a*d<<1)+d*d/1000)/1000+b"
  13.         if !e! geq !c! set /a "d-=%%a"
  14. )
  15. set "e=00!d!" & set /p "=!e:~-3!"<nul
  16. set /a "e=(!a!!e:~-3!*!a!!e:~-3!)%%1000000","b=e/1000","e=e%%1000"
  17. set "e=  !e!" & set "e=!e:~-3!"
  18. set "b=000!e!!b!"
  19. set "e=  !d!"
  20. set "a=!e:~-3!!a!"
  21. for /l %%a in (3 3 2147483646) do (
  22.         set "d=0"
  23.         for %%b in (512 256 128 64 32 16 8 4 2 1) do (
  24.                 set /a "d+=%%b"
  25.                 if 1000 gtr !d! (
  26.                         set /a "c=d*d/1000"
  27.                         for /l %%c in (0 3 %%a) do set /a "c=((!a:~%%c,3!*d<<1)+c+!b:~%%c,3!)/1000"
  28.                         set /a "e=%%a+3"
  29.                         for %%c in (!e!) do set /a "c+=!b:~%%c,3!" & if !c! geq 1000 set /a "d-=%%b"
  30.                 ) else set /a "d-=%%b"
  31.         )
  32.         set "e=00!d!" & set /p "=!e:~-3!"<nul
  33.         set "c=" & set /a "f=d*d/1000"
  34.         for /l %%b in (0 3 %%a) do (
  35.         set /a "e=(!a:~%%b,3!*d<<1)+!b:~%%b,3!+f","f=e/1000","e%%=1000"
  36.         set "e=  !e!" & set "c=!c!!e:~-3!"
  37.         )
  38.         set /a "e=(d*d)%%1000" & set "e=  !e!" & set "b=000!e:~-3!!c!"
  39.         set "e=  !d!" & set "a=!e:~-3!!a!"
  40. )
  41. echo. & pause & exit /b
复制代码

作者: flashercs    时间: 2018-12-8 21:23

这是牛顿迭代法吗?
作者: flashercs    时间: 2018-12-15 00:00

bug是有的,比如计算87的平方根,精确到小数点后1000位,不到100位就出错了.
作者: 523066680    时间: 2018-12-15 08:44

本帖最后由 523066680 于 2019-2-2 12:24 编辑

山的那边海的那边有一群小升初的学生已经在用 JAVA PHP JS做网站写算法,做麻省理工的数学题,
每天 RNN,DNN,KNN, A*, 神经网络,深度学习,区块链

楼主还是可以再加强一个。
作者: 523066680    时间: 2018-12-15 19:45

本帖最后由 523066680 于 2018-12-16 10:57 编辑

牛顿迭代法参考:http://www.matrix67.com/blog/archives/361
在支持浮点数加减乘除的环境下,牛顿迭代法比一般方案要高效得多,仅仅是线性地迭代就可以增加精确度。

这种出现多个反复goto,变量命名使用极限缩写的批处理可读性实在不好,所以我估计论坛也没有那么多闲人去充当“人肉解释器”。
在轻度阅读后的一些无关痒痛的关于可读性的建议(如果是想让别人阅读自己的代码并提出建议,而不是单纯的发布作品的话)
  1. :next
  2.     cls
  3.     if "%number%"=="" goto start
  4.     set /a temp=%number%+0
  5.     if not "%temp%"=="%number%" goto error_a
  6.     if not %number% geq 0 goto error_b
  7.     if %number% geq 2147483647 goto error_c
  8.     goto then
复制代码
这个 :next 代码块是用来获取被开方数的,不妨命名为  :GetNum 或者 :get_number
  1. :then
  2.     cls
  3.     echo.&echo 请输入精确位数,然后按回车键:
  4.     echo (指精确到小数点后第几位)
  5.     echo (该程序不会对最后一位进行四舍五入)
  6.     echo (精确位数最高为238609294,超出范围,会计算出错)
  7.     echo (对于结果为整数的,程序会舍去小数部分)
  8.     set bit=
  9.     set /p "bit="
  10.     goto other
复制代码
:then 代码块是用来输入精度的,不妨命名为 :set_precision (找谷歌翻译的)

这样在 ther :error_a(b/c/d/e) 中的 goto then/next 变成 goto get_number/set_precision,就很清楚是要重新获取输入。(谁还记着 then 和 next 是干什么的?)
而 :other 可以叫做 :check_precision

以及 StringLenth 按理说应该叫 StringLength

代码中还有各种  temp  nun num tga tgb tgc tgd 变量名到处“穿越”,可否改成具体含义的名称(可以谷歌翻译呀)?
作者: 523066680    时间: 2018-12-16 11:29

本帖最后由 523066680 于 2018-12-16 13:02 编辑

回复 9# SQYSQYSQY

    我已经在修改和调整,改好再放出来。给标签和变量一个好的名称并不会占用多少空间,而可读性和可维护性会大大提高。
贴前面提到的一小部分


保存文件的代码中 多个echo 可以用括号合并,使代码更简洁


一个很明显的问题:当程序达到“大规模”、存在上亿变量运算的时候,
别的语言多么省事省心,可以分为多个模块,可以写类,不需要各种goto往返,结构清晰,为什么还要用批处理呢?
其他语言可以多线程,并行,甚至可以利用显卡(CUDA OpenCL)计算单元实现高效并行,而批处理…… 只是你编程路上的一个节点。

试想一下这种动图如果用批处理去计算每个像素点,要耗时多久。
独显+Shader,1920*1080分辨率,每个像素的计算存在数百上千次迭代,每秒可以达到30帧:1920*1080像素*100次循环*30帧

(有关这个图像可以参考 http://www.matrix67.com/blog/archives/292 可以只做了解,高中后就会学到相关知识。)

扯远了,退一步说,不管用什么语言,清晰的函数命名、变量命名、模块化才有利于构建更大的程序。否则可能源码还未达到1MB就写不下去了。
作者: 523066680    时间: 2018-12-17 22:01

本帖最后由 523066680 于 2018-12-17 22:19 编辑

:kfmain 的轻度简化
这些常量列表可以创建到一个叫 :init 的函数中,只在开始的时候初始化一次,不会有什么开销。
  1. :kfmain
  2.     set /a u+=1
  3.     rem 设置常量映射表
  4.     set ER_2m=1
  5.     set ER_5m=2
  6.     set ER_4m=3
  7.     set ER_2l=4
  8.     set   U_1=5
  9.     set ER_7m=6
  10.     set ER_5l=7
  11.     set ER_9m=8
  12.     set ER_7l=9
  13.    
  14.     if defined ER_%er% (
  15.         set %kfmain%=!ER_%er%!
  16.         goto bignum_mp
  17.     )
  18.    
  19.     if defined U_%u% (
  20.         set %kfmain%=!U_%u%!
  21.         goto bignum_mp
  22.     )
  23.    
  24.     if "%er%"=="1m" set temp=0
  25.     if "%er%"=="1l" set temp=1
  26.     if "%er%"=="3m" set temp=2
  27.     if "%er%"=="3l" set temp=3
  28.     if "%er%"=="4l" set temp=4
  29.     if "%er%"=="6m" set temp=5
  30.     if "%er%"=="6l" set temp=6
  31.     if "%er%"=="8m" set temp=7
  32.     if "%er%"=="8l" set temp=8
  33.     if "%er%"=="9l" set temp=9
  34.     goto :eof
复制代码
因为末尾的值是序列递增的,在创建列表的时候甚至可以用for,进一步缩减代码(同上面理由一样,放在初始化函数中不会有什么效率影响),但是不建议这么做因为可读性超差
  1. :kfmain
  2.     set /a u+=1
  3.     rem 设置常量映射表
  4.     set /a iter = 1
  5.     for %%a in ( E_2m E_5m E_4m E_2l U_1 E_7m E_5l E_9m E_7l ) do (
  6.         set /a %%a=iter, iter+=1
  7.     )
  8.     set /a iter = 0
  9.     for %%a in ( 1m 1l 3m 3l 4l 6m 6l 8m 8l 9l ) do (
  10.         set /a T_%%a=iter, iter+=1
  11.     )
  12.    
  13.     if defined E_%er% (
  14.         set /a %kfmain% = E_%er%
  15.         goto bignum_mp
  16.     )
  17.    
  18.     if defined U_%u% (
  19.         set /a %kfmain% = U_%u%
  20.         goto bignum_mp
  21.     )
  22.     if defined T_%er% set /a temp=T_%er%
  23.     goto :eof
复制代码
um...  bignum_mp 就是 bignum multiply 的意思
作者: 523066680    时间: 2018-12-18 18:37

本帖最后由 523066680 于 2018-12-19 17:46 编辑

计算字符串长度,这里有个超完整的合集
[代码合集] [更新]求字符串长度,简短高效的批处理代码(多种算法)
(里面有我08年的老贴链接,以前真是语无伦次。)

因为楼主的被开根数受到批处理最大整数限制(10位以内),所以这个方法很好用
http://www.bathome.net/redirect.php?goto=findpost&ptid=1249&pid=26990
  1. @echo off
  2. set test_num=314159
  3. set temp_str=%test_num%fedcba9876543210
  4. set /a len=0x%temp_str:~15,1%
  5. echo %len%
复制代码

作者: 523066680    时间: 2018-12-19 21:40

本帖最后由 523066680 于 2018-12-20 14:13 编辑

回复 14# SQYSQYSQY

    在处理字符串的时候如果出现 & 或者 ()^ 都有可能造成字符串的某一部分被当作代码执行。
特别是提取的时候,那些裸的控制符号容易造成“攻击”。(不过我已经举不出具体的例子了,很久没用过批处理)
还有一些查表法掺入特殊字符作为标记,如果我在测试字符串也加入这些特殊字符,就会造成混淆。

打算另外写一个粗略的实现(不管细节),搞完这波再也不碰批处理了,不划算。

有一个优化方向:
假设当前的进展是:精确到了8个有效数字,m 约等于 a*a,a包含8位有效数字
假设是88888888,这个数的平方是已经知道了的
迭代到下一位数字b的时候,假设b是6,那么就需要计算 (88888888*10+6)^2 。
这个公式展开  (a*10+b)^2
= a^2*100 + 2*a*b + b^2
a^2 是上一步已经知道的,*100 直接后面加两个0,
2*a*b 比大数乘法简单,是大数*单个数*2,
b^2可以直接set/a得到
作者: 523066680    时间: 2018-12-20 17:12

本帖最后由 523066680 于 2018-12-21 19:01 编辑

初步实现。支持大数(整数)开根,小数精确到80位耗时30秒。每个函数各自独立,没有跨函数穿越的 goto。

:init 初始化常量模板(用于计算字符串长度)
:get_int_of_root 获取根的整数部分
:get_dec_of_root 获取根的小数部分
:bignum_mp 大数乘法
:bignum_plus 加法
:bignum_minus 减法
:bignum_div_single 除法(除以单个数字)
:cmp %str1% %str2% %vname% 大数比较,返回值为 -1, 0, 1 之一
:time_used %time_a% %time_b% 粗略计算秒钟消耗

整数部分的开根用大范围二分搜索,例如判断整数根是5位数 min=10000 max=99999 mid=(min+max)/2 三个变量迭代(实际用bignum系列函数计算,消耗还挺大)
浮点数的部分用了上一楼提到的方法,已知某一层结果 a^2 = square (≈ num)
精确到下一位数字的时候可以省去部分的计算,%a%%b% ^ 2 相当于 (a*10+b)^2
展开后 = a^2*100 + 2*(a*10)*b + b^2,
其中 a^2*100 可以直接用 %square%00 表示,
当a达到一定长度的时候,这些建立在之前基础上的计算,比两个相同长度的大数相乘要快得多。
  1. :: Bignum(integer) Square Root
  2. :: 523066680/vicyang
  3. :: 2018-12
  4. @echo off
  5. setlocal enabledelayedexpansion
  6. :init
  7.     rem template for counting string length
  8.     set mod=
  9.     set /a maxlen=2000, half=maxlen/2
  10.     for /l %%a in (1,1,%half%) do set mod=!mod!##
  11.     set time_a=%time%
  12. set num=123456787654322
  13. rem set num=10
  14. call :get_int_of_root %num% int_root cmp
  15. if %cmp% equ 0 (
  16.     set root=%int_root%
  17.     echo num = %num%, root = !root!, !cmp!
  18.     exit /b
  19. )
  20. set precision=80
  21. call :check_first %num% %precision%
  22. call :get_dec_of_root %num% %int_root% %precision% dec_root
  23. call :time_used %time_a% %time%
  24. exit /b
  25. :check_first
  26.     perl -Mbignum=p,-%2 -le "print sqrt(%1)" 2>nul
  27.     goto :eof
  28. :get_dec_of_root
  29.     setlocal
  30.     set num=%1
  31.     set int_root=%2
  32.     set precision=%3
  33.     set root=%int_root%
  34.     rem Show int_root first
  35.     set /p inp="%int_root%."<nul
  36.     call :bignum_mp %root% %root% prev_pow
  37.     set /a dec_len=0
  38.     :decroot_lp
  39.     set /a min=0, max=10, mid=(max+min)/2, quit = 0, dec_len+=1
  40.     :decroot_bin_search
  41.         rem calc [a*10]^2 + 2*[a*10]*b + b^2, part1 part2 part3
  42.         set /a sum = 0
  43.         set part1=%prev_pow%00
  44.         set /a part3 = mid * mid
  45.         set /a double_mid = mid * 2
  46.         call :bignum_mp %root%0 %double_mid% part2
  47.         call :bignum_plus %part1% %part2% sum
  48.         call :bignum_plus %sum% %part3% sum
  49.         rem compare
  50.         call :cmp %sum% %num%00 cmp
  51.         rem echo %root%%mid% %sum% %num%00 min:%min% max:%max% %cmp%
  52.         set /a range=max-min
  53.         if %cmp% gtr 0 ( set /a max=mid )
  54.         if %cmp% lss 0 ( set /a min=mid )
  55.         if %cmp% equ 0 ( set /a quit=1 )
  56.         if %range% leq 1 ( set /a quit=1 )
  57.         set /a mid=(max+min)/2
  58.     if %quit% equ 0 goto :decroot_bin_search
  59.     set prev_pow=%sum%
  60.     set root=%root%%mid%
  61.     set num=%num%00
  62.     set /p inp="%mid%"<nul
  63.     if %dec_len% lss %precision% goto :decroot_lp
  64.     echo,
  65.     endlocal
  66.     goto :eof
  67. :get_int_of_root
  68.     rem get the integer part of root
  69.     setlocal
  70.     set num=%1
  71.     call :length %num% len
  72.     rem initial min and max number
  73.     set /a min = 1, max = 10, root_len = len / 2 + len %% 2
  74.     for /l %%n in (2,1,%root_len%) do (set min=!min!0& set max=!max!9)
  75.     call :bignum_plus %min% %max% sum
  76.     rem middle_number = sum / 2
  77.     call :bignum_div_single %sum% 2 mid
  78.    
  79.     set /a quit = 0
  80.     :binary_search
  81.         call :bignum_mp %mid% %mid% product
  82.         call :cmp %product% %num% cmp
  83.         call :bignum_minus %max% %min% range
  84.         if !cmp! equ 0 (
  85.             set /a quit = 1, cmp=0
  86.         ) else (
  87.             if !cmp! gtr 0 (
  88.                 set max=!mid!
  89.                 set cmp=1
  90.             )   
  91.             if !cmp! lss 0 (
  92.                 set min=!mid!
  93.                 set cmp=-1
  94.             )
  95.             call :bignum_plus !max! !min! sum
  96.             call :bignum_div_single !sum! 2 mid
  97.             rem Using !var!, because we are inside the brackets
  98.         )
  99.         if %range% leq 1 (set quit=1)
  100.     if %quit% == 0 goto :binary_search
  101.     endlocal &set %2=%mid%& set %3=%cmp%
  102.     goto :eof
  103. :bignum_mp
  104.     setlocal
  105.     set num_a=%1
  106.     set num_b=%2
  107.     call :length %num_a% len_a
  108.     call :length %num_b% len_b
  109.     for /l %%b in ( 1, 1, %len_b% ) do ( set ele_b=!ele_b! !num_b:~-%%b,1! )
  110.     for /l %%a in ( 1, 1, %len_a% ) do ( set ele_a=!ele_a! !num_a:~-%%a,1! )
  111.     rem for /l %%a in (0, 1, %attemplen%) do set buff[%%a]=0
  112.     set /a id = 0, sid = 0, maxid = 0
  113.     for %%b in ( %ele_b% ) do (
  114.         set /a sid = id, id += 1
  115.         for %%a in ( %ele_a% ) do (
  116.             set /a buff[!sid!] += %%a * %%b, sid += 1, maxid = sid
  117.         )
  118.     )
  119.     rem Merge
  120.     set /a id = 0
  121.     for /l %%c in ( 0, 1, %maxid% ) do (
  122.         set /a next = %%c+1
  123.         set /a buff[!next!] += buff[%%c]/10, buff[%%c] = buff[%%c] %% 10
  124.     )
  125.     if "!buff[%maxid%]!" == "0" set /a maxid-=1
  126.     set product=
  127.     for /l %%n in (%maxid%, -1, 0) do set product=!product!!buff[%%n]!
  128.     endlocal &set %3=%product%
  129.     goto :eof
  130. :bignum_plus
  131.     setlocal
  132.     set num_a=%1
  133.     set num_b=%2
  134.     call :length %num_a% len_a
  135.     call :length %num_b% len_b
  136.     set /a max = len_a
  137.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  138.     for /l %%n in ( 1, 1, %max% ) do (
  139.         if %%n leq %len_b% (
  140.             set /a buff[%%n] = !num_a:~-%%n,1! + !num_b:~-%%n,1!
  141.         ) else (
  142.             set buff[%%n]=!num_a:~-%%n,1!
  143.         )
  144.     )
  145.     set /a id = 0
  146.     for /l %%c in ( 0, 1, %max% ) do (
  147.         set /a next = %%c+1
  148.         set /a buff[!next!] += buff[%%c]/10, buff[%%c] = buff[%%c] %% 10
  149.     )
  150.     if "!buff[%next%]!" gtr "0" set /a max+=1
  151.     set sum=
  152.     for /l %%a in (%max%, -1, 1) do set sum=!sum!!buff[%%a]!
  153.     endlocal &set %3=%sum%
  154.     goto :eof
  155. :bignum_minus
  156.     setlocal
  157.     set num_a=%1
  158.     set num_b=%2
  159.     call :length %num_a% len_a
  160.     call :length %num_b% len_b
  161.     set /a max = len_a
  162.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  163.     set /a minus = 0
  164.     for /l %%n in ( 1, 1, %max% ) do (
  165.         if %%n leq %len_b% (
  166.             set /a dt = !num_a:~-%%n,1! - !num_b:~-%%n,1! - minus
  167.         ) else (
  168.             set /a dt = !num_a:~-%%n,1! - minus
  169.         )
  170.         if !dt! lss 0 (
  171.             set /a buff[%%n] = dt + 10, minus=1
  172.         ) else (
  173.             set /a buff[%%n] = dt, minus=0
  174.         )
  175.     )
  176.     set delta=#
  177.     for /l %%a in (%max%, -1, 1) do set delta=!delta:#0=#!!buff[%%a]!
  178.     endlocal &set %3=%delta:#=%
  179.     goto :eof
  180. :bignum_div_single
  181.     setlocal
  182.     set num_a=%1
  183.     set num_b=%2
  184.     call :length %num_a% len_a
  185.     set /a max = len_a, mod = 0
  186.     for /l %%n in ( %len_a%, -1, 1 ) do (
  187.         set /a e = !num_a:~-%%n,1! + mod*10
  188.         set /a buff[%%n] = e/num_b, mod = e %% num_b
  189.     )
  190.     if !buff[%max%]! == 0 (set /a max-=1)
  191.     set quotaint=
  192.     for /l %%a in (%max%, -1, 1) do set quotaint=!quotaint!!buff[%%a]!
  193.     endlocal &set %3=%quotaint%
  194.     goto :eof
  195. :length %str% %vname%
  196.     setlocal
  197.     set test=%~1_%mod%
  198.     set test=!test:~0,%maxlen%!
  199.     set test=%test:*_=%
  200.     set /a len=maxlen-(%test:#=1+%1)
  201.     endlocal &set %2=%len%
  202.     goto :eof
  203. :cmp %str1% %str2% %vname%
  204.     setlocal
  205.     call :length %1 len_a
  206.     call :length %2 len_b
  207.     if %len_a% gtr %len_b% (endlocal &set %3=1&goto :eof)
  208.     if %len_a% lss %len_b% (endlocal &set %3=-1&goto :eof)
  209.     set str1=%1
  210.     set str2=%2
  211.     if %len_a% equ %len_b% (
  212.         for /l %%n in (0, 1, %len_a%) do (
  213.             if "!str1:~%%n,1!" gtr "!str2:~%%n,1!" (endlocal &set %3=1&goto :eof)
  214.             if "!str1:~%%n,1!" lss "!str2:~%%n,1!" (endlocal &set %3=-1&goto :eof)
  215.         )
  216.         endlocal &set %3=0
  217.     )
  218.     goto :eof
  219. :time_used %time_a% %time_b%
  220.     rem only for few seconds, not consider minutes
  221.     setlocal
  222.     set ta=%1& set tb=%2
  223.     rem insert 1 befeore 00.00 if first num is zero
  224.     set ta=1%ta:~-5%
  225.     set tb=1%tb:~-5%
  226.     set /a dt = %tb:.=% - %ta:.=%
  227.     set dt=%dt:-=%
  228.     set dt=0000%dt%
  229.     set dt=%dt:~-4%
  230.     echo time used: %dt:~0,2%.%dt:~2,2%s
  231.     endlocal
  232.     goto :eof
复制代码

作者: 523066680    时间: 2018-12-23 19:03

本帖最后由 523066680 于 2018-12-23 19:13 编辑

古人云:思而不学则die (die - 通 "殆")
于是我查了 wikipedia ,发现更快的方法,用程序实现起来,比牛顿迭代法更好用(毕竟牛顿迭代法还要涉及大数除法而且每次迭代的精度还不太好判断)。
使用“蛋疼的批处理”粗略实现,精确到小数点后80位,16秒。而且稍微改动就可以支持浮点数开根。



https://en.wikipedia.org/wiki/Methods_of_computing_square_roots
作者: 523066680    时间: 2018-12-23 22:27

本帖最后由 523066680 于 2019-1-24 16:31 编辑

回复 19# SQYSQYSQY

就是你这程序有点bug,计算1099511627776的平方根,为啥不一会就退出了
还有我看了半天也不明白你的精确位数在那设置的,每次算到80位就退出了

1. 搜索exit语句,在两处exit /b前面加pause来防止直接退出,不是很难想到吧?
2. 既然已经知道精度是80,完全可以 Ctrl+F 搜索一下 80 定位变量位置



两处 exit 和 80 已经在图中标出

补充:
1. 图中所用的编辑器是Sublime Text 3,可以直接显示运行结果,还能显示运行时间,因此没有加pause。
    这编辑器需要安装插件来支持GBK编码,所以没写中文注释,顺便用翻译软件学一些英语。
2. 我的英语也不好,中学水平
3. 我所知道的代码的BUG是 time_used 函数计算的时间差有问题,不过不打算改了。
作者: 523066680    时间: 2018-12-24 21:59

回复 21# SQYSQYSQY

精确到小数点后的是一位一位的。小数点之前的是一次性echo然后退出(:get_int_of_root 函数负责这一块)。
我的电脑,Win7 64位,如果是win8,win10,可能有些微差异吧。

在命令行中调用该脚本看看有没有什么错误提示。

带注释和PAUSE的版本

至于特殊字符的处理,不要浪费时间了,换一种语言,海阔天空。
也许你可以通过各种奇怪的技巧解决这些问题,但在别的语言里,这些都不是问题。
当这个语言运行效率比C低,写起来比C麻烦,写出来的代码还比C难看,为什么不直接用C?
(C可以换成ruby python golang java js lua ... )

Perl 替换方法
  1. use utf8;
  2. use Encode;
  3. my $inp='%&@^"\'?,;<>|\/:() !=';
  4. # 反斜杠因为是剔除而不是替换,用s///处理
  5. $inp=~s/\\//g;
  6. $inp=~tr/%&@^"\'?,;<>|\/:() !=/abcdefghijkl÷÷[]\\mn/;
  7. print encode('gbk', $inp);
复制代码
输出:
abcdefghijkl÷÷[]\mn


换一种玩法,把替换表放在__DATA__字段,这样我就不用手动为某些字符加反斜杠了。第一行是原始数据,第二行、第三行是替换的规则。
  1. my $inp=<DATA>;
  2. my ($from, $to) = <DATA>;
  3. eval "\$inp=~tr/\Q$from\E/\Q$to\E/" or die "$!";
  4. print $inp;
  5. __DATA__
  6. %&@^"'?,;<>|/:() !=\_._\,;=_;=_%_\"=\
  7. %&@^"'?,;<>|/:() !=\_.
  8. abcdefghijklmnopqrst +
复制代码
输出
abcdefghijklmnopqrst + this is a test

作者: 523066680    时间: 2018-12-24 22:43

本帖最后由 523066680 于 2018-12-28 21:01 编辑

手算开根的方法,好用

在之前Wikipedia链接的 Decimal (base 10) 一节  Methods_of_computing_square_roots#Decimal_(base_10)
  1. Find the square root of 2.
  2.     1. 4  1  4  2
  3.   _______________
  4. \/ 02.00 00 00 00
  5.    02                  1*1 <= 2 < 2*2                 x = 1
  6.    01                    y = x*x = 1*1 = 1
  7.    01 00               24*4 <= 100 < 25*5             x = 4
  8.    00 96                 y = (20+x)*x = 24*4 = 96
  9.       04 00            281*1 <= 400 < 282*2           x = 1
  10.       02 81              y = (280+x)*x = 281*1 = 281
  11.       01 19 00         2824*4 <= 11900 < 2825*5       x = 4
  12.       01 12 96           y = (2820+x)*x = 2824*4 = 11296
  13.          06 04 00      28282*2 <= 60400 < 28283*3     x = 2
  14.                        The desired precision is achieved:
  15.                        The square root of 2 is about 1.4142
复制代码
一开始还在想为啥用前面的结果*2作为基础 (2 28 282),一计算发现还是那个平方公式展开的结果,这个方法更进一步,又省去了一些计算量,所以速度更快。


补充一些happy886r 的作品,效率极高
批处理大数浮点乘法 - happy886r
批处理大数加法、以及2的1000次方 - happy886r
我拆了一下happy的批处理大数乘法,原来是3位数字为一段,分段计算,效率翻倍,好方法。

其他的一些
批处理怎么算超过15的阶乘?
作者: tigerpower    时间: 2018-12-25 18:10

回复 20# 523066680

编辑器真漂亮啊,开个新帖介绍介绍吧:)
作者: 523066680    时间: 2018-12-25 18:44

本帖最后由 523066680 于 2018-12-25 18:54 编辑

回复 24# tigerpower

    现在没动力搞分享了,要写文章还要排版(累)。
附Sublime Text 运行批处理的配置文件(batch.sublime-build)
  1. {
  2.     "cmd": ["$file"],
  3.     "file_regex": ".* at (.*) line ([0-9]*)",
  4.     "selector": "source.dosbatch",
  5.     "encoding": "cp936"
  6. }
复制代码
默认是F7运行,如果批处理包含中文(GBK编码),安装 GBK Support 插件。
GBK插件参考自 https://jingyan.baidu.com/article/e75aca8555f216142fdac64b.html
作者: 523066680    时间: 2019-1-4 19:32

本帖最后由 523066680 于 2019-1-4 22:00 编辑

缩进是一种美德

安装 notepad++ ,如果上一行是缩进,按下enter的下一行也会跟着缩进。
sublime_text 则是 括号右边enter自动缩进

没关系,我先写个脚本试试格式化一下缩进的问题,似乎还不需要写parser
  1. use Encode;
  2. use File::Slurp;
  3. STDOUT->autoflush(1);
  4. my $src = read_file( "sample.bat" );
  5. my @lines = split(/\r?\n/, $src);
  6. my $in = 0;
  7. for my $line ( @lines )
  8. {
  9.     if ( $line=~/\)(\s*)$/ ) { $in -= 4; }
  10.     if ( $line=~/\)\s+else\s+\(/ ) { $in -= 4; }
  11.     printf "%s%s\n", " "x$in, $line;
  12.    
  13.     if ( $line=~/\((\s*)$/ ) { $in += 4; }
  14. }
复制代码
输出:
  1. @echo off&SetLocal EnableDelayedExpansion
  2. color f0
  3. set number=2
  4. rem 设置被开方数(number)(不超过20)(别输入正好能开出来的)
  5. set num=1
  6. rem 输入被开方数的整数部分(num)
  7. echo ------------------------
  8. echo 计算!number!的平方根的值
  9. echo ------------------------
  10. set nun=!num!
  11. set /a bv=!number!-(!num!*!num!)+!num!
  12. set q[4]=0000
  13. set q[2]=00
  14. set q[1]=0
  15. set /p=!num!.<nul
  16. set zz=-5
  17. for /l %%a in (0 1 110) do (
  18.     set /a zz+=6
  19.     set temp=0
  20.     for %%b in (512 256 128 64 32 16 8 4 2 1) do (
  21.         set /a temp+=%%b
  22.         set /a c=%%a/2+1
  23.         set d=!num!
  24.         for /l %%c in (1 1 !c!) do (
  25.             set o=!d:~-6!
  26.             if not defined o set o=0
  27.             for %%f in (4 2 1) do (
  28.                 if "!o:~0,%%f!"=="!q[%%f]!" (
  29.                     set o=!o:~%%f!
  30.                     if not defined o set o=0
  31.                 )
  32.             )
  33.             set /a "b[%%c]=!o!*!temp!<<1"
  34.             set d=!d:~0,-6!
  35.         )
  36.         set /a y=!temp!*!temp!
  37.         set /a b[1]+=!y!/1000
  38.         for /l %%c in (!c! -1 1) do (
  39.             set /a d=%%c+1
  40.             set /a b[!d!]+=!b[%%c]!/1000000
  41.         )
  42.         set h=!a!
  43.         set a=
  44.         for /l %%c in (1 1 !c!) do (
  45.             if not !c!==%%c (
  46.                 set /a b[%%c]=!b[%%c]!%%1000000
  47.                 if !b[%%c]! lss 100000 (
  48.                     if !b[%%c]! lss 10000 (
  49.                         if !b[%%c]! lss 1000 (
  50.                             if !b[%%c]! lss 100 (
  51.                                 if !b[%%c]! lss 10 (
  52.                                     set b[%%c]=00000!b[%%c]!
  53.                                 ) else (
  54.                                     set b[%%c]=0000!b[%%c]!
  55.                                 )
  56.                             ) else (
  57.                                 set b[%%c]=000!b[%%c]!
  58.                             )
  59.                         ) else (
  60.                             set b[%%c]=00!b[%%c]!
  61.                         )
  62.                     ) else (
  63.                         set b[%%c]=0!b[%%c]!
  64.                     )
  65.                 )
  66.             )
  67.             set a=!b[%%c]!!a!
  68.         )
  69.         set z=!b!
  70.         set i=!a:~0,-3!
  71.         if not defined i set i=0
  72.         set x=!nun!
  73.         set /a v=!zz!/9+1
  74.         for /l %%v in (1 1 !v!) do (
  75.             set u=!i:~-9!
  76.             set w=!x:~-9!
  77.             for %%f in (4 2 1) do (
  78.                 if "!u:~0,%%f!"=="!q[%%f]!" set u=!u:~%%f!
  79.                 if not defined u set u=0
  80.             )
  81.             for %%f in (4 2 1) do (
  82.                 if "!w:~0,%%f!"=="!q[%%f]!" set w=!w:~%%f!
  83.                 if not defined w set w=0
  84.             )
  85.             set /a k[%%v]=!u!+!w!
  86.             set i=!i:~0,-9!
  87.             set x=!x:~0,-9!
  88.             if not defined i set i=0
  89.             if not defined x set x=0
  90.         )
  91.         for /l %%v in (1 1 !v!) do (
  92.             set /a i=%%v+1
  93.             set /a k[!i!]+=!k[%%v]!/1000000000
  94.         )
  95.         set b=
  96.         for /l %%v in (1 1 !v!) do (
  97.             if not "%%v"=="!v!" (
  98.                 set /a k[%%v]=!k[%%v]!%%1000000000
  99.                 if !k[%%v]! lss 100000000 (
  100.                     if !k[%%v]! lss 10000000 (
  101.                         if !k[%%v]! lss 1000000 (
  102.                             if !k[%%v]! lss 100000 (
  103.                                 if !k[%%v]! lss 10000 (
  104.                                     if !k[%%v]! lss 1000 (
  105.                                         if !k[%%v]! lss 100 (
  106.                                             if !k[%%v]! lss 10 (
  107.                                                 set k[%%v]=00000000!k[%%v]!
  108.                                             ) else (
  109.                                                 set k[%%v]=0000000!k[%%v]!
  110.                                             )
  111.                                         ) else (
  112.                                             set k[%%v]=000000!k[%%v]!
  113.                                         )
  114.                                     ) else (
  115.                                         set k[%%v]=00000!k[%%v]!
  116.                                     )
  117.                                 ) else (
  118.                                     set k[%%v]=0000!k[%%v]!
  119.                                 )
  120.                             ) else (
  121.                                 set k[%%v]=000!k[%%v]!
  122.                             )
  123.                         ) else (
  124.                             set k[%%v]=00!k[%%v]!
  125.                         )
  126.                     ) else (
  127.                         set k[%%v]=0!k[%%v]!
  128.                     )
  129.                 )
  130.             ) else (
  131.                 if "!k[%%v]!"=="0" set k[%%v]=
  132.             )
  133.             set b=!k[%%v]!!b!
  134.         )
  135.         set ew=!b:~0,1!
  136.         if !ew! geq !bv! (
  137.             set /a temp-=%%b
  138.             set b=!z!
  139.             set a=!h!
  140.         )
  141.     )
  142.     set /a g=!temp!*!temp!
  143.     set /a g=!g!%%1000
  144.     if !g! lss 100 (
  145.         if !g! lss 10 (
  146.             set nun=!b!!a:~-3!00!g!
  147.         ) else (
  148.             set nun=!b!!a:~-3!0!g!
  149.         )
  150.     ) else (
  151.         set nun=!b!!a:~-3!!g!
  152.     )
  153.     if !temp! lss 100 (
  154.         if !temp! lss 10 (
  155.             set /p=00!temp! <nul
  156.             set num=!num!00!temp!
  157.         ) else (
  158.             set /p=0!temp! <nul
  159.             set num=!num!0!temp!
  160.         )
  161.     ) else (
  162.         set /p=!temp! <nul
  163.         set num=!num!!temp!
  164.     )
  165.     set a=0
  166.     set y=0
  167.     set r=!nun!
  168. )
  169. pause
  170. exit /b
复制代码

作者: 小程936    时间: 2019-1-5 00:06

你们是不是炫技?
写个几百上千行的代码,
然后计算速度10秒一位,有意思?

那我写一个。0.0001秒算完。

添加至bat末尾,调用方法call :s 数字
输出值为变量b和errorlevel
  1. :s
  2. set /a a=%1,b=a/2
  3. for /l %%a in (1,1,15) do set /a b=(b+a/b)/2
  4. exit /b %b%
复制代码
bug修正版
  1. 修正bug版
  2. :s
  3. if "#%1"=="#"  echo 未输入&set b=&exit /b
  4. set /a a=%1,b=a/2
  5. if "%a:~0,1%"=="-" echo 负数&set b=&exit /b
  6. if "%a%"=="0" set b=0&exit /b 0
  7. for /l %%a in (1,1,15) do set /a b=(b+a/b)/2
  8. exit /b %b%
复制代码

作者: 523066680    时间: 2019-1-5 11:37

本帖最后由 523066680 于 2019-1-5 11:39 编辑

回复 26# SQYSQYSQY

    bat关于计算位数有限制,但是自己采用全套大数加、减、乘、大数比较,就不受这些限制啦。相应的会增加一些消耗。
作者: 523066680    时间: 2019-1-5 17:26

本帖最后由 523066680 于 2019-1-6 16:29 编辑

回复 31# SQYSQYSQY

     确实很快,在探索算法的层面上,用什么语言都可以探索,所以这点没什么反对的,批处理还可以放大不同算法之间的效率差距。
但是在执行效率上,不能说比的上其他编程语言甚至是编译型语言吧?



我又试了一下 python,10000精度,酸爽

作者: Batcher    时间: 2019-1-5 18:14

回复 28# SQYSQYSQY


    推荐用 Notepad++ 写代码试试,不用手工敲那么多空格。
作者: 523066680    时间: 2019-1-5 19:56

手算开根方案的初步实现,有bug待修,支持大数开根,效率没有很高,先刷新16楼那段代码的时间。
精度80位大约耗时8秒。应该可以再改,再看看吧。
  1. :: Bignum(integer) Square Root, Decimal Solution
  2. :: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Decimal_(base_10)
  3. :: 523066680/vicyang
  4. :: 2019-01
  5. @echo off
  6. setlocal enabledelayedexpansion
  7. :init
  8.     rem 创建用于计算字符串长度的模板,长度限制为 2^pow
  9.     set "sharp=#"
  10.     set /a pow=11, maxlen=1^<^<pow
  11.     for /l %%a in (1,1,%pow%) do set sharp=!sharp!!sharp!
  12. set num=2
  13. rem set num=10
  14. rem call :get_int_of_root %num% int_root cmp
  15. set precision=80
  16. rem call :check_first %num% %precision%
  17. call :decimal_solution %num%
  18. pause
  19. exit /b
  20. :check_first
  21.     perl -Mbignum=p,-%2 -le "print sqrt(%1)" 2>nul
  22.     goto :eof
  23. :: 手算开根方案
  24. :decimal_solution
  25.     setlocal
  26.     set num=%1
  27.     set tnum=%1
  28.     call :length %num% len
  29.     set /a mod=len %% 2, tlen=len, base=0
  30.     if %mod% equ 1 (set /a skip=1) else (set /a skip=2)
  31.     set target=!tnum:~0,%skip%!
  32.     set tnum=!tnum:~%skip%!
  33.     set mp_0=0
  34.     rem prec 精度
  35.     set /a prec = 0
  36.     set /a tbase_len = 0, equ = 0
  37.     :dec_loop
  38.         set /a min=0, max=10, mid=5, range=max-min, quit=0, equ=0
  39.         set /a tbase_len+=1
  40.         call :length %target% target_len
  41.         :: 预估下一个可能的数,并限制二分搜索的最大值
  42.         :guess
  43.         if %target_len% gtr 3 (
  44.         if %target_len% equ %tbase_len% (
  45.             set /a t_head = %target:~0,2%, b_head = %base:~0,2%
  46.         ) else (
  47.             set /a t_head = %target:~0,3%, b_head = %base:~0,2%
  48.         )
  49.         ) else (goto :out_of_guess)
  50.         for /l %%a in (0,1,9) do (
  51.             set /a t = %%a * b_head
  52.             rem echo !t! !target:~0,2! %%a
  53.             if !t! gtr %t_head% (
  54.                 set /a max = %%a, mid = ^(min+max^)/2
  55.                 goto :out_of_guess
  56.             )
  57.         )
  58.         :out_of_guess
  59.         rem echo, &echo %base%%mid% %target% %tbase_len% %target_len% max: %max%
  60.         :dec_bin_search
  61.             :: mp = [base*10+mid] * mid
  62.             if "%base%" == "0" (
  63.                 set /a tbase = mid
  64.             ) else (
  65.                 set tbase=!base!!mid!
  66.             )
  67.             set ta=%time%
  68.             call :bignum_mp %tbase% %mid% %tbase_len% 1 mp mp_len
  69.             set mp_%mid%=%mp%
  70.             set mplen_%mid%=%mp_len%
  71.             rem call :cmp %mp% %target% %mp_len% %target_len% cmp
  72.             :: 比较 - 判断是否超出
  73.             :cmp_begin
  74.             if %mp_len% gtr %target_len% (set /a cmp=1&goto :cmp_end)
  75.             if %mp_len% lss %target_len% (set /a cmp=-1&goto :cmp_end)
  76.             :: 如果长度相同,直接按字符串对比
  77.             if "%mp%" gtr "%target%" (set /a cmp=1&goto :cmp_end)
  78.             if "%mp%" lss "%target%" (set /a cmp=-1&goto :cmp_end)
  79.             if "%mp%" equ "%target%" (set /a cmp=0&goto :cmp_end)
  80.             :cmp_end
  81.             rem call :time_delta %ta% %time% bs_tu
  82.             if %cmp% equ 0 (set /a quit=1, equ=1)
  83.             if %cmp% equ 1 (set /a max=mid )
  84.             if %cmp% equ -1 (set /a min=mid )
  85.             if %range% leq 1 ( set /a quit=1 )
  86.             set /a mid=(max+min)/2, range=max-mid
  87.         if %quit% == 0 goto :dec_bin_search
  88.         
  89.         set ta=%time%
  90.         set /p inp="%mid%"<nul
  91.         rem echo, &echo tnum %tnum%, cmp %cmp%, equ %equ%, tg %target%
  92.         if "%tnum%" == "" (
  93.             if %cmp% == 0 (
  94.                 goto :dec_loop_out
  95.             ) else (
  96.                 rem current precision
  97.                 if %prec% equ 0 set /p inp="."<nul
  98.                 set /a prec+=1
  99.             )
  100.         )
  101.         rem echo b=%base% tb=%tbase% tg=%target% mp=%mp% mid=%mid%
  102.         call :bignum_minus %target% !mp_%mid%! target
  103.         if %skip% geq %len% (
  104.             set target=%target%00
  105.         ) else (
  106.             if "%target%" == "0" (
  107.                 set target=!tnum:~0,2!
  108.             ) else (
  109.                 set target=!target!!tnum:~0,2!
  110.             )
  111.             set tnum=!tnum:~2!
  112.             set /a skip+=2
  113.         )
  114.         rem base=base*10+mid*2
  115.         if "%base%" == "0" (
  116.             set /a base=mid*2
  117.         ) else (
  118.             set /a db_mid=mid*2
  119.             call :bignum_plus !base!0 !db_mid! base
  120.         )
  121.         rem call :time_delta %ta% %time% else_tu
  122.     if %prec% leq %precision% (goto :dec_loop)
  123.     :dec_loop_out
  124.     endlocal
  125.     goto :eof
  126. ::大数乘法
  127. :bignum_mp
  128.     setlocal
  129.     set num_a=%1
  130.     set num_b=%2
  131.     set /a len_a=%3, len_b=%4
  132.     for /l %%b in ( 1, 1, %len_b% ) do ( set ele_b=!ele_b! !num_b:~-%%b,1! )
  133.     for /l %%a in ( 1, 1, %len_a% ) do ( set ele_a=!ele_a! !num_a:~-%%a,1! )
  134.     set /a id = 0, sid = 0, maxid = 0
  135.     for %%b in ( %ele_b% ) do (
  136.         set /a sid = id, id += 1
  137.         for %%a in ( %ele_a% ) do (
  138.             set /a buff[!sid!] += %%a * %%b, sid += 1, maxid = sid
  139.         )
  140.     )
  141.     rem Merge
  142.     set /a id = 0
  143.     for /l %%c in ( 0, 1, %maxid% ) do (
  144.         set /a next = %%c+1
  145.         set /a buff[!next!] += buff[%%c]/10, buff[%%c] = buff[%%c] %% 10
  146.     )
  147.     if "!buff[%maxid%]!" == "0" set /a maxid-=1
  148.     set product=
  149.     for /l %%n in (%maxid%, -1, 0) do set product=!product!!buff[%%n]!
  150.     endlocal &set %5=%product%&set /a %6=%maxid%+1
  151.     goto :eof
  152. ::大数加法
  153. :bignum_plus
  154.     setlocal
  155.     set num_a=%1
  156.     set num_b=%2
  157.     call :length %num_a% len_a
  158.     call :length %num_b% len_b
  159.     set /a max = len_a
  160.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  161.     for /l %%n in ( 1, 1, %max% ) do (
  162.         if %%n leq %len_b% (
  163.             set /a buff[%%n] = !num_a:~-%%n,1! + !num_b:~-%%n,1!
  164.         ) else (
  165.             set buff[%%n]=!num_a:~-%%n,1!
  166.         )
  167.     )
  168.     set /a id = 0
  169.     for /l %%c in ( 0, 1, %max% ) do (
  170.         set /a next = %%c+1
  171.         set /a buff[!next!] += buff[%%c]/10, buff[%%c] = buff[%%c] %% 10
  172.     )
  173.     if "!buff[%next%]!" gtr "0" set /a max+=1
  174.     set sum=
  175.     for /l %%a in (%max%, -1, 1) do set sum=!sum!!buff[%%a]!
  176.     endlocal &set %3=%sum%
  177.     goto :eof
  178. ::大数减法
  179. :bignum_minus
  180.     setlocal
  181.     set num_a=%1
  182.     set num_b=%2
  183.     call :length %num_a% len_a
  184.     call :length %num_b% len_b
  185.     set /a max = len_a
  186.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  187.     set /a minus = 0
  188.     for /l %%n in ( 1, 1, %max% ) do (
  189.         if %%n leq %len_b% (
  190.             set /a dt = !num_a:~-%%n,1! - !num_b:~-%%n,1! - minus
  191.         ) else (
  192.             set /a dt = !num_a:~-%%n,1! - minus
  193.         )
  194.         if !dt! lss 0 (
  195.             set /a buff[%%n] = dt + 10, minus=1
  196.         ) else (
  197.             set /a buff[%%n] = dt, minus=0
  198.         )
  199.     )
  200.     set delta=#
  201.     for /l %%a in (%max%, -1, 1) do set delta=!delta:#0=#!!buff[%%a]!
  202.     endlocal &set %3=%delta:#=%
  203.     goto :eof
  204. ::字符串长度计算
  205. :length %str% %vname%
  206.     setlocal
  207.     set test=%~1_%sharp%
  208.     set test=!test:~0,%maxlen%!
  209.     set test=%test:*_=%
  210.     set /a len=maxlen-(%test:#=1+%1)
  211.     endlocal &set %2=%len%
  212.     goto :eof
  213. :: plp626的时间差函数 时间跨度在1分钟内可调用之;用于测试一般bat运行时间
  214. :time_delta <beginTimeVar> <endTimeVar> <retVar> // code by plp626
  215.     setlocal
  216.     set ta=%1&set tb=%2
  217.     set /a "c=1!tb:~-5,2!!tb:~-2!-1!ta:~-5,2!!ta:~-2!,c+=-6000*(c>>31)"
  218.     if defined %3 set /a c+=!%3!
  219.     endlocal&set %3=%c%
  220.     goto:eof
复制代码

作者: 523066680    时间: 2019-1-5 20:35

本帖最后由 523066680 于 2019-1-5 21:02 编辑

回复 35# SQYSQYSQY

    当我看了 happy886r 的乘法,已经知道划分为N位一段而非逐位的处理可以令速度翻倍。参考23楼 http://bbs.bathome.net/redirect. ... 1557&pid=216162
我当然也知道 for 比 goto 快, goto 比 call 快,但我是不会让我的代码变成这样的。

不过这是批处理,如果我不花时间继续优化,我就可以做别的有趣的事情了。
作者: 小程936    时间: 2019-1-5 22:49

回复  SQYSQYSQY

     确实很快,在探索算法的层面上,用什么语言都可以探索,所以这点没什么反对的,批 ...
523066680 发表于 2019-1-5 17:26



   这么说的话,我想到一个软件 super pi
百度就有下载。
可以1分钟计算3200万位。
作者: 523066680    时间: 2019-1-5 23:00

本帖最后由 523066680 于 2019-1-5 23:07 编辑

回复 24# 小程936

    你是说Pi吗,很多大牛为此做过大的量工作。
传奇人物 Fabrice Ballard
有史以来最优秀的程序员有哪些? - absfree的回答 - 知乎
作者: 523066680    时间: 2019-1-5 23:05

本帖最后由 523066680 于 2019-1-29 18:46 编辑

nothing
作者: 小程936    时间: 2019-1-5 23:17

看了下楼主的程序。
330行之前的,换我写不超过100行。
任何重复性代码都应考虑for或if
连err都写五六个标签,有必要?
直接call err 1
:err
if %1==1 (echo ...)
if %1==2 ...
...
exit /b
也行啊?
作者: search_Sudoku    时间: 2019-1-6 00:08

最近看此帖, 然后搜索了一些高精度数学运算的资料, 发现深入学习需要高等数学知识, 本人高等数学待学习.

楼主看来近期对此范畴算法有高度兴趣, 所以我建议楼主查看开源软件的实现: GNU多重精度运算库

GNU Multiple Precision Arithmetic Library (GMP), 简称 GMP 是 GNU 的一部分, 并且为 Maple, Mathematica 这些专业数学软件提供 高精度数学运算 的实现

GMP 的平方根算法说明:
https://gmplib.org/manual/Square-Root-Algorithm.html

Paul Zimmermann, “Karatsuba Square Root”, INRIA Research Report 3805, November 1999,
http://hal.inria.fr/inria-00072854/PDF/RR-3805.pdf

任意精度算术软件列表
https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software
作者: 523066680    时间: 2019-1-6 19:33

本帖最后由 523066680 于 2019-1-7 10:11 编辑

优化一波,没你的快。100位5.6秒,300位35秒 (CPU比较好,换一台机可能是8秒和45秒)
仍然使用逐位的计算,未使用连续N位数字为一段的处理方案,省心。

解决的问题:之前的代码对100开根会出现 10.0000000,现在不会了。
优化的部分:去掉了很多临时数组的操作特别是 buff[] 和与之对应的for循环
  1. :: Bignum(integer) Square Root, Decimal Solution
  2. :: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Decimal_(base_10)
  3. :: 523066680/vicyang
  4. :: 2019-01
  5. @echo off
  6. setlocal enabledelayedexpansion
  7. :init
  8.     rem 创建用于计算字符串长度的模板,长度限制为 2^pow
  9.     set "sharp=#"
  10.     set /a pow=11, maxlen=1^<^<pow
  11.     for /l %%a in (1,1,%pow%) do set sharp=!sharp!!sharp!
  12. set precision=80
  13. set num=2
  14. call :check_one %num%
  15. pause
  16. exit /b
  17. :: 独立测试
  18. :check_one
  19.     set ta=!time!
  20.     rem call :check_first %1 !precision!
  21.     call :decimal_solution %1
  22.     call :time_delta !ta! !time! tu
  23.     echo time used: !tu!
  24.     goto :eof
  25. :: 批量测试
  26. :check_all
  27.     for /l %%a in (1,1,99) do (
  28.         echo test number: %%a
  29.         call :check_first %%a !precision!
  30.         call :decimal_solution %%a
  31.         echo,
  32.     )
  33.     goto :eof
  34. :: 使用其他工具校验/对比结果
  35. :check_first
  36.     perl -Mbignum=p,-%2 -le "print sqrt(%1)" 2>nul
  37.     goto :eof
  38. :: 手算开根方案
  39. :decimal_solution
  40.     setlocal
  41.     set num=%1
  42.     set tnum=%1
  43.     call :length %num% len
  44.     set /a mod=len %% 2, tlen=len, base=0
  45.     if %mod% equ 1 (set /a skip=1) else (set /a skip=2)
  46.     set target=!tnum:~0,%skip%!
  47.     set tnum=!tnum:~%skip%!
  48.     set /a mp_0=0, mplen_0=1
  49.     set /a bstimes=0
  50.     rem prec 当前精度
  51.     set /a prec = 0
  52.     set /a base_len=0, equ=0, target_len=skip
  53.     :dec_loop
  54.         set /a min=0, max=10, mid=5, range=max-min, quit=0, equ=0
  55.         set /a tbase_len=base_len+1
  56.         :: 评估二分搜索的最大值
  57.         :guess
  58.         if %target_len% gtr 3 (
  59.         if %target_len% equ %tbase_len% (
  60.             set /a t_head = %target:~0,2%, b_head = %base:~0,2%
  61.         ) else (
  62.             set /a t_head = %target:~0,3%, b_head = %base:~0,2%
  63.         )
  64.         ) else (goto :out_of_guess)
  65.         for /l %%a in (0,1,9) do (
  66.             set /a t = %%a * b_head
  67.             if !t! gtr %t_head% (
  68.                 set /a max = %%a
  69.                 goto :out_of_guess
  70.             )
  71.         )
  72.         :out_of_guess
  73.         :: 做大致的除法预估 mid 值
  74.         :estimate
  75.         if %target_len% gtr 5 (
  76.             if %target_len% geq %tbase_len% (
  77.                 set /a est=!target:~0,6!/!base:~0,5!
  78.                 rem echo est - !est!
  79.                 set /a mid=!est:~0,1!
  80.                 rem echo,&echo %base% !target! !est! !mid! !target:~0,5!/!base:~0,5!
  81.             )
  82.         )
  83.         :: 如果预估max等于1,说明结果只能为0,跳过 bin_search
  84.         if %max% equ 1 (set /a mid=0& goto :out_bin_search )
  85.         rem echo, &echo %base%%mid% %target% %tbase_len% %target_len% max: %max%
  86.         set ta=%time%
  87.         :dec_bin_search
  88.             set /a bstimes+=1
  89.             :: mp = [base*10+mid] * mid
  90.             if "%base%" == "0" (
  91.                 set /a tbase = mid
  92.             ) else (
  93.                 set tbase=!base!!mid!
  94.             )
  95.             call :bignum_mp_single %tbase% %mid% %tbase_len% 1 mp mp_len
  96.             set mp_%mid%=%mp%
  97.             set mplen_%mid%=%mp_len%
  98.             :: 比较 - 判断是否超出
  99.             :cmp_begin
  100.             if %mp_len% gtr %target_len% (set /a cmp=1&goto :cmp_end)
  101.             if %mp_len% lss %target_len% (set /a cmp=-1&goto :cmp_end)
  102.             :: 如果长度相同,直接按字符串对比
  103.             if "%mp%" gtr "%target%" (set /a cmp=1&goto :cmp_end)
  104.             if "%mp%" lss "%target%" (set /a cmp=-1&goto :cmp_end)
  105.             if "%mp%" equ "%target%" (set /a cmp=0&goto :cmp_end)
  106.             :cmp_end
  107.             rem call :time_delta %ta% %time% bs_tu
  108.             if %cmp% equ 0 (set /a quit=1, equ=1)
  109.             if %cmp% equ 1 (set /a max=mid)
  110.             if %cmp% equ -1 (set /a min=mid)
  111.             if %range% leq 1 ( set /a quit=1 )
  112.             set /a mid=(max+min)/2, range=max-mid
  113.         if %quit% == 0 goto :dec_bin_search
  114.         :out_bin_search
  115.         rem echo, &echo est: %est%, act mid: %mid%
  116.         set /p inp="%mid%"<nul
  117.         if "%tnum%" == "" (
  118.             :: 如果target只剩下 00,方案结束
  119.             if "%target%" == "00" ( goto :dec_loop_out )
  120.             if %cmp% == 0 (
  121.                 goto :dec_loop_out
  122.             ) else (
  123.                 :: 当前精度
  124.                 if %prec% equ 0 set /p inp="."<nul
  125.                 set /a prec+=1
  126.             )
  127.         )
  128.         rem echo b=%base% tb=%tbase% tg=%target% mp=%mp% mid=%mid%
  129.         set ta=%time%
  130.         call :bignum_minus %target% !mp_%mid%! %target_len% !mplen_%mid%! target target_len
  131.         if %skip% geq %len% (
  132.             set target=%target%00
  133.         ) else (
  134.             if "%target%" == "0" (
  135.                 set target=!tnum:~0,2!
  136.             ) else (
  137.                 set target=!target!!tnum:~0,2!
  138.             )
  139.             set tnum=!tnum:~2!
  140.             set /a skip+=2
  141.         )
  142.         set /a target_len+=2
  143.         rem base=base*10+mid*2
  144.         if "%base%" == "0" (
  145.             set /a base=mid*2
  146.             if !base! geq 10 (set /a base_len=2) else (set /a base_len=1)
  147.         ) else (
  148.             set /a db_mid=mid*2
  149.             if !db_mid! geq 10 (set /a dbmidlen=2) else (set /a dbmidlen=1)
  150.             call :bignum_plus !base!0 !db_mid! !base_len!+1 !dbmidlen! base base_len
  151.         )
  152.     if %prec% leq %precision% (goto :dec_loop)
  153.     :dec_loop_out
  154.     echo,
  155.     echo search times: %bstimes%
  156.     endlocal
  157.     goto :eof
  158. :: 大数 乘以 单位数
  159. :bignum_mp_single
  160.     setlocal
  161.     set num_a=%1
  162.     set num_b=%2
  163.     set /a pool = 0, maxid = %3
  164.     set "res="
  165.     for /l %%a in ( 1, 1, %maxid% ) do (
  166.         set /a mp = !num_a:~-%%a,1! * num_b + pool, t = mp %% 10, pool = mp / 10
  167.         set res=!t!!res!
  168.     )
  169.     if %pool% neq 0 (
  170.         set /a maxid+=1
  171.         set res=!pool!!res!
  172.     )
  173.     endlocal&set %5=%res%&set %6=%maxid%
  174.     goto :eof
  175. ::大数加法
  176. :bignum_plus
  177.     setlocal
  178.     set num_a=%1
  179.     set num_b=%2
  180.     set /a len_a=%3, len_b=%4
  181.     set /a max = len_a
  182.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  183.     set /a pool=0
  184.     set res=
  185.     for /l %%n in ( 1, 1, %max% ) do (
  186.         if %%n leq %len_b% (
  187.             set /a t = !num_a:~-%%n,1! + !num_b:~-%%n,1! + pool
  188.         ) else (
  189.             set /a t = !num_a:~-%%n,1! + pool
  190.         )
  191.         set /a mod = t %% 10, pool = t / 10
  192.         set res=!mod!!res!
  193.     )
  194.     if %pool% gtr 0 (set /a max+=1 &set res=1%res%)
  195.     endlocal &set %5=%res%&set %6=%max%
  196.     goto :eof
  197. ::大数减法
  198. :bignum_minus
  199.     setlocal
  200.     set num_a=%1
  201.     set num_b=%2
  202.     set /a len_a=%3, len_b=%4
  203.     set /a max = len_a
  204.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  205.     set /a minus = 0
  206.     set "res="
  207.     for /l %%n in ( 1, 1, %max% ) do (
  208.         if %%n leq %len_b% (
  209.             set /a dt = !num_a:~-%%n,1! - !num_b:~-%%n,1! - minus
  210.         ) else (
  211.             set /a dt = !num_a:~-%%n,1! - minus
  212.         )
  213.         if !dt! lss 0 (
  214.             set /a t = dt + 10, minus=1
  215.         ) else (
  216.             set /a t = dt, minus=0
  217.         )
  218.         set res=!t!!res!
  219.         if !t! equ 0 (set /a zero+=1) else (set /a zero=0)
  220.     )
  221.     set res=!res:~%zero%!
  222.     endlocal &set %5=%res%&set /a %6=%max%-%zero%
  223.     goto :eof
  224. ::字符串长度计算
  225. :length %str% %vname%
  226.     setlocal
  227.     set test=%~1_%sharp%
  228.     set test=!test:~0,%maxlen%!
  229.     set test=%test:*_=%
  230.     set /a len=maxlen-(%test:#=1+%1)
  231.     endlocal &set %2=%len%
  232.     goto :eof
  233. :: plp626的时间差函数 时间跨度在1分钟内可调用之;用于测试一般bat运行时间
  234. :time_delta <beginTimeVar> <endTimeVar> <retVar> // code by plp626
  235.     setlocal
  236.     set ta=%1&set tb=%2
  237.     set /a "c=1!tb:~-5,2!!tb:~-2!-1!ta:~-5,2!!ta:~-2!,c+=-6000*(c>>31)"
  238.     if defined %3 set /a c+=!%3!
  239.     endlocal&set %3=%c%
  240.     goto:eof
复制代码

作者: 523066680    时间: 2019-1-8 16:41

回复 30# SQYSQYSQY

优化效率一时爽,添加功能火葬场。(就目前来说浮点数开根还没搞进去,也没有经过大量的数值测试BUG)
观点:功能完善前写给人看,方便调整,完善后再优化给机器看。

发现自己代码优化后有问题,29 26 开根会出现5.99999,29楼已经纠正,顺便提速,estimate 模块用于提前估值减少二分搜索次数。
在我的主机 80位 3.5s 300位 32s,手算法似乎不怎么需要二分,可以快速判定下一位数,绕了很大的弯路,修改中。
作者: 523066680    时间: 2019-1-8 18:02

本帖最后由 523066680 于 2019-1-9 09:40 编辑

改好了,去掉了二分搜索,5位以内的情况下粗暴遍历。
通过0到100的数字开根测试,支持大数字。在这个基础上再去优化会舒服很多,但还是要做出浮点数开根以后再说。

CPU: i7 4GHz
SQRT 2 精度 80 耗时约 2.3s。精度 300,耗时约 20s
  1. :: Bignum(integer) Square Root, Decimal Solution
  2. :: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Decimal_(base_10)
  3. :: 523066680/vicyang
  4. :: 2019-01
  5. @echo off
  6. setlocal enabledelayedexpansion
  7. :init
  8.     rem 创建用于计算字符串长度的模板,长度限制为 2^pow
  9.     set "sharp=#"
  10.     set "serial=9876543210"
  11.     set /a pow=11, maxlen=1^<^<pow
  12.     for /l %%a in (1,1,%pow%) do set sharp=!sharp!!sharp!
  13. set precision=80
  14. rem call :check_one 2
  15. call :check_all
  16. pause
  17. exit /b
  18. :: 独立测试
  19. :check_one
  20.     set ta=!time!
  21.     rem call :check_first %1 !precision!
  22.     call :decimal_solution %1
  23.     call :time_delta !ta! !time! tu
  24.     echo time used: !tu!
  25.     goto :eof
  26. :: 批量测试
  27. :check_all
  28.     for /l %%a in (1,1,99) do (
  29.         echo test number: %%a
  30.         rem call :check_first %%a !precision!
  31.         call :decimal_solution %%a
  32.         echo,
  33.     )
  34.     goto :eof
  35. :: 使用其他工具校验/对比结果
  36. :check_first
  37.     perl -Mbignum=p,-%2 -le "print sqrt(%1)" 2>nul
  38.     goto :eof
  39. :: 手算开根方案
  40. :decimal_solution
  41.     setlocal
  42.     set num=%1
  43.     set tnum=%1
  44.     :: 计算长度,判断需要截取的目标长度(1 or 2)
  45.     call :length %num% len
  46.     set /a mod=len %% 2, skip = 2 - mod
  47.     set target=!tnum:~0,%skip%!
  48.     set tnum=!tnum:~%skip%!
  49.     set "base="
  50.     :: prec 当前精度
  51.     set /a prec = 0, base_len=0, target_len=skip
  52.     :dec_loop
  53.         :: 推算下一个数
  54.         :estimate
  55.             :: 如果目标值 小于 基数,下一个数字判定为0
  56.             call :cmp %target% %base%0 %target_len% %base_len%+1 cmp
  57.             if !cmp! equ -1 (
  58.                 set /a mid=0, mp=0, mplen=0
  59.                 goto :out_estimate
  60.             )
  61.             if %base_len% gtr 5 (
  62.                 set /a est=!target:~0,6!/!base:~0,5!
  63.             ) else (
  64.                 :: 在set/a计算范围内的,[粗暴]遍历
  65.                 for /l %%a in (0,1,10) do (
  66.                     set /a mp=%base%%%a*%%a
  67.                     if !mp! gtr !target! (set /a est=%%a-1 &goto :out_est_for)
  68.                 )
  69.             )
  70.             :out_est_for
  71.             :: 199999996400/1999999988 = 99.9999988
  72.             :: but 199999/19999 = 10
  73.             if %est% geq 10 (
  74.                 set /a tbase_len=base_len+1
  75.                 if !target_len! gtr !tbase_len! (set /a est=9)
  76.             )
  77.             set /a mid=!est:~0,1!
  78.             call :bignum_mp_single !base!!mid! !mid! !base_len!+1 1 mp mplen
  79.             call :cmp !mp! !target! !mplen! !target_len! cmp
  80.             :: 如果mp超出目标范围
  81.             if !cmp! equ 1 (
  82.                 set /a mid-=1
  83.                 call :bignum_mp_single !base!!mid! !mid! !base_len!+1 1 mp mplen
  84.             )
  85.         :out_estimate
  86.         set /p inp="%mid%"<nul
  87.         rem echo,&echo tg !target!, mp !mp!, base !base!, mid !mid!, est !est!
  88.         if "%tnum%" == "" (
  89.             :: 如果target只剩下 00,方案结束
  90.             if "%target%" == "00" ( goto :dec_loop_out )
  91.             if %cmp% == 0 ( goto :dec_loop_out )
  92.         )
  93.         :: 计算下一段target的值
  94.         call :bignum_minus %target% %mp% %target_len% %mplen% target target_len
  95.         :: 扩充target,如果被开根数已经截取完,直接补0,精度+1
  96.         if %skip% geq %len% (
  97.             set target=%target%00
  98.             set /a prec+=1
  99.             if !prec! equ 1 set /p inp="."<nul
  100.         ) else (
  101.             if "%target%" == "0" (set target=!tnum:~0,2!
  102.                           ) else (set target=!target!!tnum:~0,2!)
  103.             set tnum=!tnum:~2!
  104.             set /a skip+=2
  105.         )
  106.         set /a target_len+=2
  107.         :: 更新基数 - base
  108.         rem base=base*10+mid*2
  109.         if "%base%" == "0" (
  110.             set /a base=mid*2, base_len=1+base/10
  111.         ) else (
  112.             set /a db_mid=mid*2, dbmidlen=1+db_mid/10
  113.             call :bignum_plus !base!0 !db_mid! !base_len!+1 !dbmidlen! base base_len
  114.         )
  115.     if %prec% leq %precision% (goto :dec_loop)
  116.     :dec_loop_out
  117.     echo,
  118.     endlocal
  119.     goto :eof
  120. :: 比较
  121. :cmp
  122.     set /a La=%3, Lb=%4
  123.     if %La% gtr %Lb% (set /a %5=1&goto :eof)
  124.     if %La% lss %Lb% (set /a %5=-1&goto :eof)
  125.     :: 如果长度相同,直接按字符串对比
  126.     if "%1" gtr "%2" (set /a %5=1&goto :eof)
  127.     if "%1" lss "%2" (set /a %5=-1&goto :eof)
  128.     if "%1" equ "%2" (set /a %5=0&goto :eof)
  129. :: 大数 乘以 单位数
  130. :bignum_mp_single
  131.     setlocal
  132.     set num_a=%1
  133.     set num_b=%2
  134.     set /a pool = 0, maxid = %3
  135.     set "res="
  136.     for /l %%a in ( 1, 1, %maxid% ) do (
  137.         set /a mp = !num_a:~-%%a,1! * num_b + pool, t = mp %% 10, pool = mp / 10
  138.         set res=!t!!res!
  139.     )
  140.     if %pool% neq 0 (
  141.         set /a maxid+=1
  142.         set res=!pool!!res!
  143.     )
  144.     endlocal&set %5=%res%&set %6=%maxid%
  145.     goto :eof
  146. ::大数加法
  147. :bignum_plus
  148.     setlocal
  149.     set num_a=%1
  150.     set num_b=%2
  151.     set /a len_a=%3, len_b=%4
  152.     set /a max = len_a
  153.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  154.     set /a pool=0
  155.     set res=
  156.     for /l %%n in ( 1, 1, %max% ) do (
  157.         if %%n leq %len_b% (
  158.             set /a t = !num_a:~-%%n,1! + !num_b:~-%%n,1! + pool
  159.         ) else (
  160.             set /a t = !num_a:~-%%n,1! + pool
  161.         )
  162.         set /a mod = t %% 10, pool = t / 10
  163.         set res=!mod!!res!
  164.     )
  165.     if %pool% gtr 0 (set /a max+=1 &set res=1%res%)
  166.     endlocal &set %5=%res%&set %6=%max%
  167.     goto :eof
  168. ::大数减法
  169. :bignum_minus
  170.     setlocal
  171.     set num_a=%1
  172.     set num_b=%2
  173.     set /a len_a=%3, len_b=%4
  174.     set /a max = len_a
  175.     if %len_b% gtr %len_a% (set /a max=len_b, len_b=len_a&set num_a=%num_b%&set num_b=%num_a%)
  176.     set /a minus = 0
  177.     set "res="
  178.     for /l %%n in ( 1, 1, %max% ) do (
  179.         if %%n leq %len_b% (
  180.             set /a dt = !num_a:~-%%n,1! - !num_b:~-%%n,1! - minus
  181.         ) else (
  182.             set /a dt = !num_a:~-%%n,1! - minus
  183.         )
  184.         if !dt! lss 0 (
  185.             set /a t = dt + 10, minus=1
  186.         ) else (
  187.             set /a t = dt, minus=0
  188.         )
  189.         set res=!t!!res!
  190.         if !t! equ 0 (set /a zero+=1) else (set /a zero=0)
  191.     )
  192.     set res=!res:~%zero%!
  193.     endlocal &set %5=%res%&set /a %6=%max%-%zero%
  194.     goto :eof
  195. ::字符串长度计算
  196. :length %str% %vname%
  197.     setlocal
  198.     set test=%~1_%sharp%
  199.     set test=!test:~0,%maxlen%!
  200.     set test=%test:*_=%
  201.     set /a len=maxlen-(%test:#=1+%1)
  202.     endlocal &set %2=%len%
  203.     goto :eof
  204. :: plp626的时间差函数 时间跨度在1分钟内可调用之;用于测试一般bat运行时间
  205. :time_delta <beginTimeVar> <endTimeVar> <retVar> // code by plp626
  206.     setlocal
  207.     set ta=%1&set tb=%2
  208.     set /a "c=1!tb:~-5,2!!tb:~-2!-1!ta:~-5,2!!ta:~-2!,c+=-6000*(c>>31)"
  209.     if defined %3 set /a c+=!%3!
  210.     endlocal&set %3=%c%
  211.     goto:eof
复制代码

作者: 523066680    时间: 2019-1-13 18:58

本帖最后由 523066680 于 2019-1-13 19:07 编辑

回复 33# SQYSQYSQY

是的哟,if 在某些情况下会进行字符串对比。bbbbbbbbbbbbbbb gtr aaaaaaaaaaaaaaa 是成立的,换成数字,只要长度相同就会得到对的结果

42行是我用来校验结果是否正确的,调用 Perl 显示对应的开根结果。要正确执行需要安装 Perl 环境。
http://strawberryperl.com/releases.html 这玩意儿好像都不自带环境配置,装完还要自己把perl.exe执行路径添加到PATH中

对应的校验语句是临时注释掉的:
rem call :check_first %1 !precision!
作者: 523066680    时间: 2019-1-13 19:22

本帖最后由 523066680 于 2019-1-24 16:32 编辑

回复 35# SQYSQYSQY

    查阅 ASCII 字符表,如果想学习更多关于字符编码,建议:学习 python,用python去读写各种网页文件。
关键词:Encode, Decode, Unicode, UTF8, UTF16, GBK, CP936

用其他语言显示数字符号对应的ASCII码。
  1. >perl -e "grep {printf qq('%d' - %d\n), $_, ord($_) } (0..9)"
  2. '0' - 48
  3. '1' - 49
  4. '2' - 50
  5. '3' - 51
  6. '4' - 52
  7. '5' - 53
  8. '6' - 54
  9. '7' - 55
  10. '8' - 56
  11. '9' - 57
复制代码

作者: 523066680    时间: 2019-1-15 21:14

回复 38# SQYSQYSQY

非常短(难道是受到了27楼小朋友的刺激?他短任他短,清风拂山冈。他横任他横,明月照大江)。可能有小细节需要完善:
1.414找不到操作数。
999999999999999999

    不打算先把大数和浮点数做进去再精简吗。

浮点数版本,效率老样子,代码不打算优化了,要也用Perl,或者换C艹
作者: 523066680    时间: 2019-1-15 21:44

本帖最后由 523066680 于 2019-1-17 20:23 编辑

回复 40# SQYSQYSQY

      你这起跑线多好,我那会儿没零钱,严重的时候一周只有1元上一个小时网吧,要做点什么得在稿纸上写好,不然刷的一小时没了。

空间换时间/时间换空间
查表就是为了"空间换时间",几百个上千元素的数组查询都会慢的也就是批处理了,不如趁早换用自由度高的语言。

那个例子可以用函数表达,x^2,和 5*x 赛跑,一开始 5*x 比较快,后来 x^2 …… 这俩太陡了,应该有更合适的曲线,暂时先这样


浮点数版本补充在39楼。
作者: 523066680    时间: 2019-1-17 22:51

本帖最后由 523066680 于 2019-1-17 23:14 编辑

1月17日
补充 —— 是楼层穿越问题。此信息PASS
作者: 523066680    时间: 2019-1-18 10:09

本帖最后由 523066680 于 2019-1-18 11:40 编辑

回复 37# SQYSQYSQY

      关于自由度,我不说数独,你用批处理做一做猜数字游戏的搜索树就知道了,但凡跟递归、多重镶嵌数据结构有关的东西,用批处理都不划算。
http://bbs.bathome.net/thread-49436-1-2.html
(非杠,我就是觉得你学其他语言,起跑线就会有一个质的飞跃。也不推荐Perl,现在的主流推荐就是Python,Java,C)

百度百科的描述
https://baike.baidu.com/item/%E7 ... 97/83200?fr=aladdin

举一个简单的例子:
我要生成50个随机字符,内容可以是数字和小写字母;将字符串拆割重组、输出显示整个数组:
  1. use Modern::Perl;
  2. # 生成 50 个随机字符,范围是数字和小写字母
  3. my @arr = map { ('0'..'9', 'a'..'z')[rand(36)] } (1 .. 50 );
  4. my $s = "bathomenet";
  5. say join(",", @arr);
  6. say join(",", split //, $s);
  7. print length($s);
复制代码
输出
  1. j,x,c,z,7,k,u,p,d,w,u,k,d,0,s,9,l,a,7,n,i,9,h,s,l,5,1,q,d,m,j,i,p,m,3,x,4,h,s,d,r,s,3,q,w,i,d,z,f,y
  2. b,a,t,h,o,m,e,n,e,t
  3. 10
复制代码
这些在 python ruby perl 里面都是常规操作,字符串长度允许范围几乎就是对应内存的剩余空间。

批处理可以做到,但是会很烦,vbs同样烦,js、Powershell应该好写

回复 39# SQYSQYSQY

      我也是业余的,工作和IT不相关。可以预见的是你的路线可以是 x^2。
而我这种职业不相关的,路线基本就只能是平缓直线甚至是上下波动了
作者: 523066680    时间: 2019-1-18 14:48

回复 40# SQYSQYSQY

    先写一个测试模块,发布前测试过会比较妥。更新后的代码测试26,37,50,65,82不通过。更新前测试通过(精确到80位)。
  1. for /l %%a in (1,1, 100%或者其他数字%) do ( call :开根函数 )
  2. exit/b
  3. :开根函数
  4. setlocal
  5. endlocal& goto :eof
复制代码

作者: 523066680    时间: 2019-1-19 12:07

本帖最后由 523066680 于 2019-1-24 21:48 编辑

回复 28# search_Sudoku

    将手算法原样撸了一个C++初始版本,未优化,1W精度(含输出)0.9秒。

返回来看gmp库的说明,这理论好深(估计happy能看懂,我只能看看概述)
Introduction
The current asymptotically fastest known method to compute the square-root of a n-word number is using Fast Fourier Transform (FFT) multiplication and Newton'smethod,
with a complexity of 5M(n)' [1, p. 155].2 The algorithm presented here is basedon Burnikel-Ziegler Karatsuba division [2].
这里面用到 快速傅里叶变换乘法、牛顿迭代法,以及 Burnikel-Ziegler Karatsuba 除法。

牛顿迭代法的精度增长很快(差不多翻倍增长),需要用到大数除法。实践起来,具体的精度不太好确定,因此迭代到一定次数后,除法的精度取舍也是个问题,
我也发现引用了牛顿迭代法的方案大多是给出一个预判的精度,
比如 gmp 中设置 mpf_set_default_prec (10000);  开根后 gmp_printf ("Square root: %.10000Ff\n\n", sq_out);
实际有效数字只有3068位,末尾由大量的0填充。

几种接口效率对比(i7 8核 4GHz)
Perl bignum 模块,1W精度 耗时4.5s
Python Decimal 模块,10W精度 耗时 1.0s
C++ 使用 GMP库,100W有效精度,含输出,1.5秒;不含输出,0.2秒
                           1000W位有效精度,不含输出, 2.5秒。

libgmp太凶悍

C++ 测试GMP速度以及判断有效精度的代码:
  1. #include <iostream>
  2. #include <sstream>
  3. #include <iomanip>
  4. #include <chrono>
  5. #include "gmpxx.h"
  6. #include "gmp.h"
  7. using namespace std;
  8. int main(int argc, char *argv[] )
  9. {
  10.     ostringstream os;
  11.     mpf_class n(2, 10), r(1, 3600000);
  12.     auto start = chrono::system_clock::now();
  13.         r = sqrt(n);
  14.         os << setprecision(10000000) << r;
  15.     auto end = chrono::system_clock::now();
  16.     chrono::duration<double> diff = end-start;
  17.     cout << "Actual length of r: " << os.str().length() << endl;
  18.     cout << "Time Used: " << setprecision(3) << diff.count() << " s" << endl;
  19.     return 0;
  20. }
复制代码
Actual length of r: 1083710
Time Used: 0.561 s (这里包含 os << r 产生的时间)

GNU MP 库中用到的算法和理论,完整版:
《Modern Computer Arithmetic》
http://maths.anu.edu.au/~brent/pd/mca-cup-0.5.9.pdf
作者: 523066680    时间: 2019-1-24 21:06

本帖最后由 523066680 于 2019-1-25 20:38 编辑

用了两天 VS2015,不愧是宇宙最强编辑器(是我墨守成规,一直装在系统,从来不用)

s_minus 函数和 vec_minus 函数性能分析,1W位数,各调用2W次的分析结果:





可以看到涉及数组[]操作的语句消耗较高,采用高的进制(base)处理势在必行。

再聊聊《Modern Computer Arithmetic》(后面简称MCA)
对于64位平台 unsigned long long int 支持的最大数字是 2^64-1=18446744073709551615,20位,如果我们充分利用,使用2^64作为进制,或者10^19作为进制,
很容易会遇到溢出问题,MCA中给出了几种方案:
Let T be the number of different values taken by the data type representing the coefficients ai, bi. (Clearly, β ≤ T, but equality does not necessarily hold,
for example β = 10^9 and T = 2^32.) At step 3, the value of s can be as large as 2β − 1, which is not representable if β = T. Several workarounds
are possible:
either use a machine instruction that gives the possible carry of ai + bi,
or use the fact that, if a carry occurs in ai + bi, then the computed sum – if performed modulo T – equals t := ai +bi −T < ai; thus, comparing t and ai will determine if a carry occurred.
A third solution is to keep a bit in reserve, taking β ≤ T/2.

用 T 表示一个存储单元所能表示的数的量,则有 β <= T (这里 β 表示采用的进制,以及β不一定等于T,例如T=2^32,但采用的进制为10^9)。
考虑加法操作 s=a+b+d (d为1或0,是上一次加法补进的数值),s的最大可能值为 2*β-1,当 β = T 时该公式无法正确计算。考虑以下方案:
1. 内部编码实现 (水平有限,暂时这么翻译)
2. 通过-T取余数判断,t := a + b - T < a ,对比 t 和 a 断定是否进 1
3. 采用一个保守的进制数 β,令 β ≤ T/2.
作者: 老刘1号    时间: 2019-1-26 13:56

贴吧吧友写的(Q群里聊天得知,感谢小程)
可开开方后结果为整数的整数
  1. @set /p a=输入a=
  2. @set /a b=a
  3. @for /l %%a in (1,1,30) do @set /a b=(b+a/b)/2
  4. @set b
复制代码

作者: 老刘1号    时间: 2019-1-26 14:04

这贴的楼层有毒吧,看的好蛋疼
作者: 523066680    时间: 2019-1-26 14:40

回复 44# 老刘1号

      这个牛跌前面楼层有提到过呀。
我们主要在折腾大数字和浮点数。如果只在signed int范围内开整数根,早就可以结帖了。
小程说他要写一个版本的时候我以为“硬核”要来了,结果 ……
那本《MCA》非常硬核!打算用C艹实践其中一小部分算法(大部分看不懂,小部分似懂非懂)。

回复 43# SQYSQYSQY

批处理的“数组”属于伪数组,和其他语言的数组有本质的区别,因为批的“索引”不只是数字,还有字母和符号,变量长度亦不确定,这和其他语言的哈希表(hash table)相似。
在C/C++里面的数组操作不需要“搜索”,可直接通过计算偏移量取内存地址,因为定长的数组在初始化后占用一段连续内存空间,且每个单元占用相同字节,
给定一个编号,通过 编号*字节大小+起点地址 可得目标内存地址,直接存取。
C++ vector容器的 [] 操作符消耗占用高,和其独立的实现有关(可能做了各种判断和转换处理),换用更纯粹的C语言数组可以减少这种消耗。

实测,元素数量为80W的容器/数组,1000次遍历每一个元素并写入int值,
  1. #include <iostream>
  2. #include <vector>
  3. #include <chrono>
  4. using namespace std;
  5. using namespace std::chrono;
  6. const int SIZE = 800000;
  7. void vec_test(void);
  8. void c_array_test(void);
  9. void c_pointer_test(void);
  10. void time_used(system_clock::time_point& time_a);
  11. vector<int> vec(SIZE);
  12. int array1[SIZE];
  13. int array2[SIZE];
  14. int main(int argc, char *argv[])
  15. {
  16.     system_clock::time_point start = system_clock::now();
  17.     for (int i = 0; i < 1000; i++) vec_test();
  18.     time_used(start);
  19.     for (int i = 0; i < 1000; i++)  c_array_test();
  20.     time_used(start);
  21.     for (int i = 0; i < 1000; i++)  c_pointer_test();
  22.     time_used(start);
  23.     return 0;
  24. }
  25. void vec_test(void) {
  26.     register int it;
  27.     for (it = 0; it < SIZE; it++) vec[it] = it;
  28. }
  29. void c_array_test(void) {
  30.     register int it;
  31.     for (it = 0; it < SIZE; it++) array1[it] = it;
  32. }
  33. void c_pointer_test(void) {
  34.     register int it;
  35.     register int *pt = array2;
  36.     for (it = 0; it < SIZE; it++) *(pt + it) = it;
  37. }
  38. void time_used(system_clock::time_point& time_a) {
  39.     duration<double> diff;
  40.     diff = chrono::system_clock::now() - time_a;
  41.     cout << "Time used: " << diff.count() << endl;
  42.     time_a = chrono::system_clock::now();
  43. }
复制代码
g++编译测试结果:
Time used: 1.24807
Time used: 0.400023
Time used: 0.393022

Visual Studio编译,差距更明显
Time used: 3.92722
Time used: 0.0410024
Time used: 0.0360021


如果一定要用 vector 又不想用它的 [] ,可以申请一个指针,int *vp = vec.data()
通过*vp指针直接算地址读写内存,速度和c_array一样快,想要更快,申请一个寄存器指针 register int *vp。
这就是自由度,可定制,可接管。

讨论算法,可以不分语言。谈论模块化思想,也可以不分语言,用批处理同样可以表达。
但要说极限压榨性能,怎么也轮不到批处理。
作者: 老刘1号    时间: 2019-1-26 19:17

本帖最后由 老刘1号 于 2019-1-26 19:26 编辑

回复 46# SQYSQYSQY
回复 47# 523066680


    哈哈发现了,我大致扫了一遍回帖没注意到,就贴了一下
没专门研究过这个算法,不知是牛跌
作者: 523066680    时间: 2019-1-26 19:34

本帖最后由 523066680 于 2019-2-9 21:35 编辑

回复 48# 老刘1号


      这都是牛顿的功劳呀,牛迭是N次方,1/N次方都可以算,很多其他方程的根也可以算。因为他是在一个函数曲线任意一点(x)上求切线,然后这个切线不断迭代,向函数曲线和x轴的交点(根或者函数的解)逼近。
逼近速度非常快(差不多精度翻倍)。

现在回想,happy886r 当时写了好多大工程
happy886r - 数学计算工具 i 的重构版new i
作者: 老刘1号    时间: 2019-1-26 20:59

回复 49# 523066680


    是啊,准备有空的时候把happy的代码研究一遍




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