iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Build on Google AI

液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端系列 第 14

Day 14:【高階商務計算機】手機計算機也能跑三角函數!科學計算擴充矩陣與智慧括號平衡

  • 分享至 

  • xImage
  •  

第一段:在 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',在自訂鍵盤的候選庫中集中顯示,使用者可按需拖入主網格,與商務鍵混合排列。

三類 action 的差異設計

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);
}

lnlog 的轉換完全發生在求值前,使用者只需照數學慣例輸入,引擎在背後完成映射。

智慧括號平衡演算法

// 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 行):十三項按鍵統一採用 inputFunctioninputConstantinputPowerinputReciprocalinputOperator 五類 action

inputFunctionisEvaluated 包裹邏輯(第 1142 ~ 1155 行)

safeEvaluateln/log 轉換與白名單過濾(第 473 ~ 480 行)

autoCloseParentheses 的缺口自動補全(第 451 ~ 456 行)

getDetailedErrorMsg 的三分類診斷(第 503 ~ 516 行)

四、深度調校剖析

隱式乘法保護

inputFunctioninputConstant 在最後一個字符為數字或右括號時,自動插入 × 隔開。例如輸入 時不會形成非法的 字串,而是自動轉為 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 動畫讓結果與算式行做出絲滑的上下交換效果。


上一篇
Day 13:【高階商務計算機】跨步驟計算不再靠死記!五鍵記憶體系統與螢幕發光 [M] 指示燈
下一篇
Day 15:【高階商務計算機】解決長算式盲打與中斷重算的焦慮——雙行即時預覽與連續運算鏈狀態機
系列文
液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言