核心演算法:中綴 → 後綴 (RPN) → 堆疊求值
- 詞法掃描:保留數字、小數點、運算子
+-*/%^!、括號,其餘捨棄。
- 負號預處理:將一元負號(行首或
( 後)替換為 !,避免與減號混淆。
- Shunting-yard 產生 RPN:運算子優先級
!^% > */ > +-,左括號入棧,右括號彈出至左括號。
- RPN 求值:遇數字入棧,遇二元運算子彈兩數運算再入棧,遇一元
! 彈一數取反。
關鍵程式碼結構
public sealed class SimpleRPN
{
// 1. 建構 RPN
private static string BuildingRPN(string s) { ... }
// 2. 運算子優先級
private static int Power(char op) { ... }
// 3. 計算 RPN
public static string ComputeRPN(string expr) { ... }
// 4. 格式化空白
private static string FormatSpace(string s) { ... }
}
負號處理細節
// 預處理:行首或 '(' 後的 '-' 改為 '!'
for (int i = 0; i < sb.Length - 1; i++)
if (sb[i] == '-' && (i == 0 || sb[i-1] == '('))
sb[i] = '!';
// 求值時處理一元 '!'
else if (c == '!')
stack.Push(-(double)stack.Pop());
支援功能與限制
- ✅ 四則運算、乘方
^、取模 %、一元正負號
- ✅ 多位數、浮點數、巢狀括號
- ❌ 三角函數、常數 (π, e)、變數、三元運算子
- ❌
-(3+5) 原作者 w3eval 亦有此 Bug,本實作透過 ! 解決
測試案例
ComputeRPN("(6+9-8+5-8)*(2+5+8)/7+5") // 正確
ComputeRPN("(-3+4+9)*(-3)*7/(-5*2)-(6+9)...") // 負號巢狀正確
ComputeRPN("1+2+3+4+5+6+7+8+9") // 45
ComputeRPN("1*2*3*4*5*6*7*8*9") // 362880
擴充指南
- 新增運算子:在
Power() 加入優先級,BuildingRPN 的 switch 加入字元,ComputeRPN 加入 case 邏輯。
- 支援函數:詞法階段識別字母序列 → 查表取得委派 → 視為運算子處理(需處理參數逗號與括號)。
- 效能優化:將
char 改為 byte、避免 StringBuilder 重複配置、改用 Span<char> 零拷貝解析。