數學表示式求值

輸入數學表示式即刻得到結果,支援弧度或角度制三角函式、對數、階乘、常量以及完整的運算子優先順序。

像在紙上一樣書寫表示式。 解析器能正確處理巢狀括號、一元負號、階乘和函式呼叫的優先順序,因此 2 + 3 * 4 等於 14,-3^2 等於 -9,與數學慣例完全一致。

支援的語法

運算子
+ - * / % ^ ! ( )
常量
pi e tau phi inf
三角函式
sin cos tan asin acos atan sinh cosh tanh
通用函式
sqrt cbrt abs sign exp ln log log2 round floor ceil trunc
多引數函式
min max hypot sum avg pow

表示式是如何變成數字的

優先順序就是全部問題

讀懂 2 + 3 * 4 需要知道乘法比加法結合得更緊,因此答案是 14 而不是 20。每個運算子都帶有優先順序和結合性。乘法、除法和取餘高於加減;冪運算高於以上全部,並且是右結合的,所以 2^3^2 意為 2^(3^2),結果是 512 而不是 64。

一元負號是微妙的情形。數學慣例把它排在冪運算之下,這就是 -3^2 等於 -9 的原因:指數作用於 3,取負作用於結果。電子表格軟體以持不同意見著稱,它會返回 9;如果你曾見過兩個工具對同一公式給出不同答案,通常正是這個原因。本求值器遵循數學慣例。

括號可以覆蓋上述一切,而且值得放心大膽地使用。一個需要查優先順序表才能讀懂的表示式,遲早會被人讀錯。

簡述排程場演算法

表示式首先被拆分為記號——數字、名稱、運算子和標點——然後由 Dijkstra 的排程場演算法重排為字尾形式,即運算子跟在運算元之後。2 + 3 * 4 會變成 2 3 4 * +。字尾形式不需要括號也不需要優先順序規則,因為記號的順序已經編碼了結構,一臺簡單的棧式機器一趟就能求值。

該演算法維護兩個結構:一個輸出列表和一個運算子棧。數字直接進入輸出;運算子在自己入棧之前,先彈出棧中所有結合力不低於它的運算子。括號在棧上充當標記,右括號會一路彈出直到遇見它的配對。函式呼叫則作為帶引數計數的運算子一同參與,每遇到一個逗號計數加一。

電子表格引擎和查詢規劃器內部用的正是同一套技術。它以線性時間執行,無需遞迴,並且能自然地報出格式錯誤:如果最後棧沒有乾淨地清空,說明輸入本身不平衡。

關於浮點數,說實話

結果以 IEEE 754 雙精度計算,這與所有瀏覽器和多數程式語言使用的算術相同。它以二進位制儲存數字,而 0.1 這樣的十進位制小數沒有精確的二進位制表示,這正是 0.1 + 0.2 眾所周知會得到 0.30000000000000004 而不是 0.3 的原因。

把顯示結果舍入到設定的有效數字位數,可以在不改變底層值的前提下隱藏這些噪聲,這正是這裡精度控制元件的作用。設為 12 位既能保持可讀,又距離雙精度真正能區分的 15 到 17 位留有充足餘量。

有兩個限制值得記住。整數只在 2^53 以內保持精確,因此非常大的階乘和乘積會產生漂移;超過 170! 會直接溢位為無窮大。另外,兩個幾乎相等的數相減會摧毀精度——前導數字相互抵消,只剩下帶噪聲的尾部數字——因此重新整理公式以避開這類減法,往往會比直觀寫法精確得多。

開源說明:使用原生 JavaScript 實現,不依賴第三方庫。

常見問題

為什麼 -3^2 得到 -9 而不是 9?
數學慣例中冪運算比一元負號結合得更緊,因此平方作用於 3,取負在之後進行。若想得到 9 請寫 (-3)^2,這也是電子表格預設的理解方式。
為什麼 0.1 + 0.2 不等於精確的 0.3?
二進位制浮點無法精確表示 0.1,因此加法後殘留了極小誤差。舍入到 12 位有效數字會顯示 0.3,而儲存值仍保留完整精度。
如何把三角函式切換到角度制?
把角度單位設為「角度」。此後 sin、cos、tan 的輸入按角度解釋,asin、acos、atan 返回角度,無需手動乘除 pi/180。
支援哪些常量?
pi、e、tau(2 pi)、phi(黃金比例)以及表示無窮大的 inf。名稱不區分大小寫,PI 和 pi 都可以。
能計算大數的階乘嗎?
最大到 170!,這是雙精度能容納的最大階乘,超過之後結果會溢位為無窮大。階乘還要求引數是非負整數。
表示式是通過 eval 執行的嗎?
不是。它由專門的解析器分詞並轉換為字尾式,再在一臺小型棧式機器上求值,你輸入的內容永遠不會作為 JavaScript 執行。