老刘 发表于 2019-11-5 23:45

[批处理][算法实战007]逆波兰四则计算器

特点:计算精确无误差。
例:(3*3*3/2)/((-9)/(11111*3*(1-99998/99999)))
VBS计算结果:-.499999999998098
该程序计算结果:-1/2

逆波兰四则计算器.bat::转载请注明出处。
@Echo Off
Setlocal Enabledelayedexpansion
::CODER BY 老刘 POWERD BY iBAT
Title 【老刘编写】逆波兰四则计算器
Path "%~dp0"
If Not Exist "Stack(Linked Storage Structure).Bat" (
        Echo "Stack(Linked Storage Structure)"缺失!
        Pause&Exit
)

Set "Stack=Call "Stack(Linked Storage Structure)""
!Stack! :Init 运算符
!Stack! :Init 操作数
Echo Code By OldLiu
Echo.
Echo 表达式中只能出现“0123456789+-*/()”这些字符。
Echo 计算支持分数、括号可以嵌套。
Echo 支持负数,但负号前有运算符时需加括号。
Echo 错误举例:6/-3        5--3*-8
Echo 正确举例:6/(-3)        5-(-3*(-8))
Echo 输入Quit或Exit退出。
Echo.
Set /p IsDebug=是否开启DEBUG模式(y/n):
If /i "!IsDebug!" Neq "y" (
        Set "Hide=Rem"
) Else (
        Set "Hide="
)

:Loop
        Echo. & Set /p "中缀表达式=键入表达式>>> "
        If /i "!中缀表达式!" Equ "Quit" Exit
        If /i "!中缀表达式!" Equ "Exit" Exit
       
        Rem 中缀转后缀。
        !Stack! :Clear 运算符
        Set 后缀表达式=
        Set 可能出现负数=True
        :读取中缀表达式
        If "!中缀表达式!" == "" Goto 中缀转后缀完成
        Set "字符=!中缀表达式:~,1!"
        %Hide% Set 中缀表达式
        Set "中缀表达式=!中缀表达式:~1!"
       
        Rem 处理数字。
        Set /A "IsNum=!字符!" 2>Nul
        If "!IsNum!" EQU "!字符!" (
                Set 数字=!数字!!字符!
                Set 可能出现负数=False
                Goto 读取中缀表达式
        ) Else (
                If Defined 数字 (
                        Set "后缀表达式=!后缀表达式! !数字!"
                        %Hide% Set 后缀表达式
                        Set 数字=
                )
        )
       
        Rem 处理左括弧。
        If "!字符!" == "(" (
                Rem 左括弧不在栈中时优先级最高,直接压栈。
                !Stack! :Push 运算符 字符
                Set 可能出现负数=True
                Goto 读取中缀表达式
        )

        Rem 处理右括弧。
        If "!字符!" == ")" (
                Rem 运算符出栈,直到遇到左括弧。
                :括弧中运算符输出
                !Stack! :Pop 运算符 栈顶运算符
                If "!栈顶运算符!" Neq "(" (
                        Set "后缀表达式=!后缀表达式! !栈顶运算符!"
                        %Hide% Set 后缀表达式
                        Goto 括弧中运算符输出
                )
                Goto 读取中缀表达式
        )
       
        Rem 处理运算符。
        If "!字符!" == "-" (
                If !可能出现负数! == True (
                        Rem “-”号前面没数字,补一个“0”避免后缀表达式运算错误。
                        Set "后缀表达式=!后缀表达式! 0"
                        %Hide% Set 后缀表达式
                )
        )
        !Stack! :IsEmpty 运算符
        If !ErrorLevel! Equ 0 (
                Rem 栈是空的,任何运算符优先级高于空栈,入栈。
                !Stack! :Push 运算符 字符
                Goto 读取中缀表达式
        ) Else (
                :运算符优先级比对
                !Stack! :Pop 运算符 栈顶运算符
                If !ErrorLevel! Equ 0 (
                        Set 优先级高=False
                        Rem 一切运算优先级高于栈中的左括弧。
                        If "!栈顶运算符!" == "(" Set 优先级高=True
                        Rem 乘除法优先级高于加减法。
                        If "!栈顶运算符!" == "+" (
                                If "!字符!" == "*" Set 优先级高=True
                                If "!字符!" == "/" Set 优先级高=True
                        )
                        If "!栈顶运算符!" == "-" (
                                If "!字符!" == "*" Set 优先级高=True
                                If "!字符!" == "/" Set 优先级高=True
                        )
                        If "!优先级高!" == "True" (
                                Rem 当前运算优先级高于栈顶运算优先级,入栈。
                                !Stack! :Push 运算符 栈顶运算符
                                !Stack! :Push 运算符 字符
                                Goto 读取中缀表达式
                        ) Else (
                                Rem 当前运算优先级不高于栈顶运算优先级,栈顶的运算可以计算了。
                                Set "后缀表达式=!后缀表达式! !栈顶运算符!"
                                %Hide% Set 后缀表达式
                                Rem 继续出栈,直到栈空或当前运算优先级高于栈顶运算。
                                Goto 运算符优先级比对
                        )
                ) Else (
                        Rem 栈已经清空,任何运算符优先级高于空栈,入栈。
                        !Stack! :Push 运算符 字符
                        Goto 读取中缀表达式
                )
                Echo 输入的表达式有误。
                Goto Loop
        )
       
        :中缀转后缀完成
        Rem 写入最后一个数字。
        Set "后缀表达式=!后缀表达式! !数字!"
        Set 数字=
        Rem 弹出栈中所有运算符。
        !Stack! :Pop 运算符 栈顶运算符
        :运算符弹出
                Set "后缀表达式=!后缀表达式! !栈顶运算符!"
                !Stack! :Pop 运算符 栈顶运算符
        If !ErrorLevel! Equ 0 Goto 运算符弹出
        %Hide% Set 后缀表达式
       
        Rem 开始计算结果。
        !Stack! :Clear 操作数
        Call :计算结果 !后缀表达式!
        Goto 计算完成
        :计算结果
                Set 字符=%1
                Set /A "IsNum=!字符!" 2>Nul
                If "!IsNum!" EQU "!字符!" (
                        Rem 数字入栈。
                        !Stack! :Push 操作数 字符
                ) Else (
                        Rem 遇到运算符,计算。
                        Rem 从栈中弹出操作数,注意操作数2先出来。
                        !Stack! :Pop 操作数 操作数2
                        If !ErrorLevel! Neq 0 (
                                Echo 输入的表达式有误。
                                Goto Loop
                        )
                        !Stack! :Pop 操作数 操作数1
                        If !ErrorLevel! Neq 0 (
                                Echo 输入的表达式有误。
                                Goto Loop
                        )
                        Rem 整数转为分数。
                        If "!操作数1:/=!" Equ "!操作数1!" (
                                Set "操作数1=!操作数1!/1"
                        )
                        If "!操作数2:/=!" Equ "!操作数2!" (
                                Set "操作数2=!操作数2!/1"
                        )
                        Rem 进行运算。
                        If "!字符!" Equ "*" (
                                Rem 分子互乘、分母互乘。
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数1!") do (
                                        Set /A 当前结果分子=%%a
                                        Set /A 当前结果分母=%%b
                                )
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数2!") do (
                                        Set /A 当前结果分子*=%%a
                                        Set /A 当前结果分母*=%%b
                                )
                        )
                        If "!字符!" Equ "/" (
                                Rem 除以一个数等于乘其倒数。
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数1!") do (
                                        Set /A 当前结果分子=%%a
                                        Set /A 当前结果分母=%%b
                                )
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数2!") do (
                                        Set /A 当前结果分子*=%%b
                                        Set /A 当前结果分母*=%%a
                                )
                        )
                        Set 加减法=False
                        If "!字符!" Equ "+" Set 加减法=True
                        If "!字符!" Equ "-" Set 加减法=True
                        If "!加减法!" Equ "True" (
                                Rem        母互乘子,并以为实,母相乘为法,实如法而一。
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数1!") do (
                                        Set /A 当前结果分子=%%a
                                        Set /A 当前结果分母=%%b
                                )
                                For /f "tokens=1,2 delims=/" %%a in ("!操作数2!") do (
                                        Set /A 当前结果分子=当前结果分子*%%b!字符!当前结果分母*%%a
                                        Set /A 当前结果分母*=%%b
                                )
                        )
                        Rem 分数化简。
                        Set /A 被除数=当前结果分子,除数=当前结果分母
                        :求最大公约数
                                If !除数! Equ 0 (
                                        Echo 除以0错误。
                                        Goto Loop
                                )
                                Set /A 余数=被除数%%除数
                        If !余数! Neq 0 (
                                Set /A 被除数=除数,除数=余数
                                Goto 求最大公约数
                        )
                        Rem 结果约分。
                        Set /A 当前结果分子/=除数,当前结果分母/=除数
                        Set 当前结果=!当前结果分子!/!当前结果分母!
                        %Hide% Echo ^(!操作数1!^)!字符!^(!操作数2!^)=!当前结果!
                        !Stack! :Push 操作数 当前结果
                )
                Shift /1
                If "%1" Neq "" Goto 计算结果
        Goto :Eof
       
        :计算完成
        !Stack! :Pop 操作数 计算结果
        For /f "tokens=1,2 delims=/" %%a in ("!计算结果!") do (
                Set /A 结果分子=%%a,结果分母=%%b
                If !结果分母! Lss 0 Set /A 结果分子*=-1,结果分母*=-1
                If !结果分子! Equ 0 (
                        Echo 结果:0
                ) Else If !结果分母! Equ 1 (
                        Rem 结果为非0整数。
                        Echo 结果:!结果分子!
                ) Else If !结果分子! Gtr !结果分母! (
                        Rem 结果绝对值大于1。
                        Set /A 商=结果分子/结果分母,余数=%%a%%结果分母
                        Echo 结果:!结果分子!/!结果分母!=!商!…!余数!
                ) Else (
                        Rem 结果绝对值小于1。
                        Echo 结果:!结果分子!/!结果分母!
                )
        )
Goto LoopStack(Linked Storage Structure).BAT::Code By OldLiu

Call %*
Goto :Eof

:GetRandom
        Set /A ErrorLevel=%random%%%1000+%random%*10000
        If Defined Memory[!ErrorLevel!].Data Goto GetRandom
Goto :Eof

:Init StackName
        Call :GetRandom
        Set /A %~1=ErrorLevel
        Set /A Memory[!ErrorLevel!].Data=0
        Set /A Memory[!ErrorLevel!].Next=0
        Set /A ErrorLevel=0
Goto :Eof

:IsEmpty StackName
:GetLength StackName
        Set /A ErrorLevel=Memory[!%~1!].Data
Goto :Eof

:Clear StackName
        Set /A _TMP_Length_=Memory[!%~1!].Data
        Set /A Memory[!%~1!].Data=0
        Set /A _TMP_Next_Node_Ptr_=Memory[!%~1!].Next
        Set /A Memory[!%~1!].Next=0
        For /L %%a In (1 1 !_TMP_Length_!) Do (
                Set /A _TMP_This_Node_Ptr_=_TMP_Next_Node_Ptr_
                Set /A _TMP_Next_Node_Ptr_=Memory[!_TMP_This_Node_Ptr_!].Next
                Set Memory[!_TMP_This_Node_Ptr_!].Data=
                Set Memory[!_TMP_This_Node_Ptr_!].Next=
        )
        Set _TMP_This_Node_Ptr_=
        Set _TMP_Next_Node_Ptr_=
        Set _TMP_Length_=
        Set /A ErrorLevel=0
Goto :Eof

:Push StackName VarToPush
        Set /A Memory[!%~1!].Data+=1
        Call :GetRandom
        Set "Memory[!ErrorLevel!].Data=!%~2!"
        Set /A Memory[!ErrorLevel!].Next=Memory[!%~1!].Next
        Set /A Memory[!%~1!].Next=ErrorLevel
        Set /A ErrorLevel=0
Goto :Eof

:GetTopElem StackName VarToReturn
        Set /A _TMP_Node_Ptr_=Memory[!%~1!].Next
        If "!_TMP_Node_Ptr_!" Equ "0" (
                Set /A ErrorLevel=1
                Goto :Eof
        )
        Call Set "%~2=%%Memory[!_TMP_Node_Ptr_!].Data%%"
        Set _TMP_Node_Ptr_=
        Set /A ErrorLevel=0
Goto :Eof

:Pop StackName VarToReturn
        Set /A _TMP_Node_Ptr_=Memory[!%~1!].Next
        If "!_TMP_Node_Ptr_!" Equ "0" (
                Set /A ErrorLevel=1
                Goto :Eof
        )
        Call Set "%~2=%%Memory[!_TMP_Node_Ptr_!].Data%%"
        Set "Memory[!_TMP_Node_Ptr_!].Data="
        Set /A Memory[!%~1!].Next=Memory[!_TMP_Node_Ptr_!].Next
        Set "Memory[!_TMP_Node_Ptr_!].Next="
        Set /A Memory[!%~1!].Data-=1
        Set _TMP_Node_Ptr_=
        Set /A ErrorLevel=0
Goto :Eof

:EditTopElem StackName VarToReplaceWith
        Set /A _TMP_Node_Ptr_=Memory[!%~1!].Next
        If "!_TMP_Node_Ptr_!" Equ "0" (
                Set /A ErrorLevel=1
                Goto :Eof
        )
        Set "Memory[!_TMP_Node_Ptr_!].Data=!%~2!"
        Set _TMP_Node_Ptr_=
        Set /A ErrorLevel=0
Goto :Eof

:Delete StackName
        Call :Clear
        Set "Memory[!%~1!].Data="
        Set "Memory[!%~1!].Next="
        Set /A ErrorLevel=0
Goto :Eof
计算测试(DEBUG模式开)键入表达式>>> (3*3*3/2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=(3*3*3/2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=3*3*3/2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=*3*3/2)/((-9)/(11111*3*(1-99998/99999)))
后缀表达式= 3
中缀表达式=3*3/2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=*3/2)/((-9)/(11111*3*(1-99998/99999)))
后缀表达式= 3 3
后缀表达式= 3 3 *
中缀表达式=3/2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=/2)/((-9)/(11111*3*(1-99998/99999)))
后缀表达式= 3 3 * 3
后缀表达式= 3 3 * 3 *
中缀表达式=2)/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=)/((-9)/(11111*3*(1-99998/99999)))
后缀表达式= 3 3 * 3 * 2
后缀表达式= 3 3 * 3 * 2 /
中缀表达式=/((-9)/(11111*3*(1-99998/99999)))
中缀表达式=((-9)/(11111*3*(1-99998/99999)))
中缀表达式=(-9)/(11111*3*(1-99998/99999)))
中缀表达式=-9)/(11111*3*(1-99998/99999)))
后缀表达式= 3 3 * 3 * 2 / 0
中缀表达式=9)/(11111*3*(1-99998/99999)))
中缀表达式=)/(11111*3*(1-99998/99999)))
后缀表达式= 3 3 * 3 * 2 / 0 9
后缀表达式= 3 3 * 3 * 2 / 0 9 -
中缀表达式=/(11111*3*(1-99998/99999)))
中缀表达式=(11111*3*(1-99998/99999)))
中缀表达式=11111*3*(1-99998/99999)))
中缀表达式=1111*3*(1-99998/99999)))
中缀表达式=111*3*(1-99998/99999)))
中缀表达式=11*3*(1-99998/99999)))
中缀表达式=1*3*(1-99998/99999)))
中缀表达式=*3*(1-99998/99999)))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111
中缀表达式=3*(1-99998/99999)))
中缀表达式=*(1-99998/99999)))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 *
中缀表达式=(1-99998/99999)))
中缀表达式=1-99998/99999)))
中缀表达式=-99998/99999)))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1
中缀表达式=99998/99999)))
中缀表达式=9998/99999)))
中缀表达式=998/99999)))
中缀表达式=98/99999)))
中缀表达式=8/99999)))
中缀表达式=/99999)))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998
中缀表达式=99999)))
中缀表达式=9999)))
中缀表达式=999)))
中缀表达式=99)))
中缀表达式=9)))
中缀表达式=)))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999 /
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999 / -
中缀表达式=))
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999 / - *
中缀表达式=)
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999 / - * /
后缀表达式= 3 3 * 3 * 2 / 0 9 - 11111 3 * 1 99998 99999 / - * //
(3/1)*(3/1)=9/1
(9/1)*(3/1)=27/1
(27/1)/(2/1)=27/2
(0/1)-(9/1)=-9/1
(11111/1)*(3/1)=33333/1
(99998/1)/(99999/1)=99998/99999
(1/1)-(99998/99999)=1/99999
(33333/1)*(1/99999)=1/3
(-9/1)/(1/3)=-27/1
(27/2)/(-27/1)=1/-2
结果:-1/2

孤狼微博 发表于 2019-11-6 05:44

这特么真是高手,我操

bnxf 发表于 2019-11-6 09:15

真是将bat功能发挥到极致

cj13888 发表于 2019-11-6 17:11

太感谢了,正想学习这方面的知识呢。谢谢分享!
页: [1]
查看完整版本: [批处理][算法实战007]逆波兰四则计算器