第一段:在 Day 13 完成記憶體系統後,使用者開始提出更高階的需求:工程報價需要正弦餘弦、財務模型需要自然對數、品管分析需要絕對值。傳統手機內建計算機雖有科學模式,但按鍵分散、函數呼叫繁瑣,更沒辦法與稅率換算等商務功能共存在同一顆算式裡。
第二段:今日推出完整的科學計算擴充矩陣,包含 sin、cos、tan、ln、log、xʸ、x²、x³、x!、|x|、1/x、π、e 共十三項模組,全部統一透過 safeEvaluate 求值引擎處理;同時解剖智慧括號平衡演算法 autoCloseParentheses 與精細化三分類錯誤診斷 getDetailedErrorMsg,讓科學計算在輸入不完整時也能給出精確的錯誤定位。
線上體驗 Live Demo:https://410355-collab.github.io/Premium-Business-Calculator/
(強烈建議使用 iPhone Safari 或 Android 手機開啟體驗!)
GitHub 開源倉庫:https://github.com/410355-collab/Premium-Business-Calculator
歡迎提出建議與指導
(開發環境:Google Antgravity IDE + Google Gemini 協同開發)
持續更新中...
市面上的計算機 App 通常把科學模式與標準模式分成兩個獨立介面,使用者無法在同一條算式中混合使用稅率換算與三角函數。更麻煩的是,函數輸入後如果括號沒有對齊,任何一個計算機都只丟出「ERROR」,無從判斷問題出在哪一層。
在行動裝置上快速敲入 sin(30×π÷180 這類算式,末尾的括號極容易漏打。若求值引擎直接拋例外,使用者看到的只有「SYNTAX ERROR」,無法判斷是括號問題、除以零問題,還是真正的語法錯誤。
// script.js 第 180 ~ 194 行 – 科學計算鍵位定義
'sqrt': { icon: 'sqrt', action: 'inputFunction', param: 'sqrt(', type: 'cyan', category: 'math', name: '√x' },
'square': { customHTML: '...', action: 'inputPower', param: '^2', type: 'cyan', category: 'math', name: 'x²' },
'cube': { customHTML: '...', action: 'inputPower', param: '^3', type: 'cyan', category: 'math', name: 'x³' },
'power': { customHTML: '...', action: 'inputOperator', param: '^', type: 'operator', category: 'math', name: 'xʸ' },
'reciprocal':{ customHTML: '...', action: 'inputReciprocal', type: 'cyan', category: 'math', name: '1/x' },
'abs': { customHTML: '...', action: 'inputFunction', param: 'abs(', type: 'cyan', category: 'math', name: '|x|' },
'pi': { customHTML: '...', action: 'inputConstant', param: 'π', type: 'cyan', category: 'math', name: 'π' },
'euler': { customHTML: '...', action: 'inputConstant', param: 'e', type: 'cyan', category: 'math', name: 'e' },
'factorial': { customHTML: '...', action: 'inputOperator', param: '!', type: 'cyan', category: 'math', name: 'x!' },
'sin': { label: 'sin', action: 'inputFunction', param: 'sin(', type: 'cyan small-text', category: 'math', name: 'sin' },
'cos': { label: 'cos', action: 'inputFunction', param: 'cos(', type: 'cyan small-text', category: 'math', name: 'cos' },
'tan': { label: 'tan', action: 'inputFunction', param: 'tan(', type: 'cyan small-text', category: 'math', name: 'tan' },
'ln': { label: 'ln', action: 'inputFunction', param: 'ln(', type: 'cyan small-text', category: 'math', name: 'ln' },
'log': { label: 'log', action: 'inputFunction', param: 'log(', type: 'cyan small-text', category: 'math', name: 'log' },
所有函數統一歸屬 category: 'math',在自訂鍵盤的候選庫中集中顯示,使用者可按需拖入主網格,與商務鍵混合排列。
inputFunction(括號函數型)// script.js 第 1142 ~ 1155 行
} else if (funcName === 'inputFunction') {
if (isEvaluated) {
// 已求值狀態:自動把結果包進函數
// 例:結果為 30,按 sin → sin(30)
currentInput = param + currentInput + ")";
isEvaluated = false;
} else {
if (currentInput === "0") currentInput = param;
else {
const lastChar = currentInput.slice(-1);
// 數字或右括號後自動插入 × 避免隱式乘法語法錯誤
if (/[0-9\)]/.test(lastChar)) currentInput += "×" + param;
else currentInput += param;
}
}
cursorPos = null;
animType = 'append';
}
inputConstant(常數型:π、e)// script.js 第 1156 ~ 1166 行
} else if (funcName === 'inputConstant') {
if (isEvaluated || currentInput === "0") {
currentInput = param;
isEvaluated = false;
} else {
const lastChar = currentInput.slice(-1);
// 數字後自動插入 × 保護隱式乘法
if (/[0-9\)]/.test(lastChar)) currentInput += "×" + param;
else currentInput += param;
}
}
inputReciprocal(倒數型:1/x)// script.js 第 1172 ~ 1185 行
} else if (funcName === 'inputReciprocal') {
if (isEvaluated) {
// 已求值:包整個結果 → 1/(result)
currentInput = "1/(" + currentInput + ")";
isEvaluated = false;
} else {
if (currentInput === "0") currentInput = "1/(";
else {
const lastChar = currentInput.slice(-1);
// 完整算式後包整條式子做倒數
if (/[0-9\)]/.test(lastChar)) currentInput = "1/(" + currentInput + ")";
else currentInput += "1/(";
}
}
}
safeEvaluate 的科學函數轉換層// script.js 第 473 ~ 480 行
function safeEvaluate(expr) {
let prepared = expr
.replace(/×/g, '*')
.replace(/÷/g, '/')
.replace(/π/g, 'pi');
// ln → mathjs 的 log();log → mathjs 的 log10()
prepared = prepared
.replace(/\bln\(/g, 'log(')
.replace(/\blog\(/g, 'log10(');
// 白名單過濾,阻擋非法字符注入
const sanitized = prepared.replace(/[^0-9\+\-\*\/\(\)\.%\^!a-zA-Z]/g, '');
if (!sanitized) return math.bignumber(NaN);
const preprocessed = preprocessPercent(sanitized);
return math.evaluate(preprocessed);
}
ln 與 log 的轉換完全發生在求值前,使用者只需照數學慣例輸入,引擎在背後完成映射。
// script.js 第 451 ~ 456 行
function autoCloseParentheses(expr) {
const openCount = (expr.match(/\(/g) || []).length;
const closeCount = (expr.match(/\)/g) || []).length;
const missing = openCount - closeCount;
return missing > 0 ? expr + ')'.repeat(missing) : expr;
}
在所有「按等號前的求值」動作(evaluateExpr、M+、M‑、MS 等)呼叫 safeEvaluate 之前,一律先經過 autoCloseParentheses,自動補全缺少的右括號,確保輸入 sin(30×π÷180 也能正確求值。
// script.js 第 503 ~ 516 行
function getDetailedErrorMsg(expr, err) {
const openCount = (expr.match(/\(/g) || []).length;
const closeCount = (expr.match(/\)/g) || []).length;
// 分類一:括號未閉合
if (openCount !== closeCount) {
return I18N[currentLang].errUnclosedParen || "UNCLOSED PARENTHESIS";
}
// 分類二:除以零
if (/\/\s*0(?![.\d])/.test(expr) || /\/\s*0\.0+(?![1-9])/.test(expr)) {
return I18N[currentLang].errDivideZero || "CANNOT DIVIDE BY ZERO";
}
// 分類三:mathjs 拋出括號相關例外
if (err && err.message && err.message.toLowerCase().includes('parenthes')) {
return I18N[currentLang].errUnclosedParen || "UNCLOSED PARENTHESIS";
}
// 其餘:一般語法錯誤
return I18N[currentLang].errSyntax || "SYNTAX ERROR";
}
三道條件依照優先順序排列:括號計數差值優先判定,再透過正則偵測除以零,最後才落入語法錯誤兜底。
script.js科學鍵位定義(第 180 ~ 194 行):十三項按鍵統一採用 inputFunction、inputConstant、inputPower、inputReciprocal、inputOperator 五類 action
inputFunction 的 isEvaluated 包裹邏輯(第 1142 ~ 1155 行)
safeEvaluate 的 ln/log 轉換與白名單過濾(第 473 ~ 480 行)
autoCloseParentheses 的缺口自動補全(第 451 ~ 456 行)
getDetailedErrorMsg 的三分類診斷(第 503 ~ 516 行)
inputFunction 與 inputConstant 在最後一個字符為數字或右括號時,自動插入 × 隔開。例如輸入 2π 時不會形成非法的 2π 字串,而是自動轉為 2×π,通過白名單後被正確求值為 2*pi。
safeEvaluate 中的正則 /[^0-9\+\-\*\/\(\)\.%\^!a-zA-Z]/g 只允許數字、基本運算符與英文字母,確保算式不會夾帶特殊字符進入 math.evaluate,從根本阻斷注入風險。
autoCloseParentheses 的輕量哲學括號平衡只補右括號、從不刪除,並且只在「即將求值」的前一刻執行,不影響使用者正在編輯中的 currentInput。這樣的設計讓主顯示區始終呈現使用者的原始輸入,而不是被系統偷偷修改的版本。
括號計數優先於正則除零判定,是因為括號未閉合時除零的正則可能誤觸(例如 1/(0),先確認括號平衡再判斷除零,能大幅降低誤判率。
Day 14 完成了十三項科學函數的模組化整合,統一透過 safeEvaluate 的轉換層與 mathjs 求值,讓使用者在同一條算式中混用三角函數、對數、常數與商務運算子。智慧括號平衡與三分類錯誤診斷則讓計算的容錯邊界大幅提升,使用者在行動裝置快速輸入時即使漏括號也能正確求值,輸入錯誤時也能看到明確的錯誤分類。
在 Day 15,我們將深入剖析頂部雙行即時預覽的實作核心:updateDisplay 如何在每次按鍵後對 currentInput 進行零延遲的 safeEvaluate 預估,並顯示在 formula-screen 的第二行;以及連續運算鏈狀態機如何透過 isEvaluated 旗標,讓按下等號後直接輸入運算子就能延續計算,搭配 FLIP WAAPI 動畫讓結果與算式行做出絲滑的上下交換效果。