優先順位こそが問題のすべて
2 + 3 * 4 を読むには、乗算が加算より強く結合することを知っている必要があります。だから答えは 20 ではなく 14 です。すべての演算子は優先順位と結合性を持ちます。乗算・除算・剰余は加算・減算より上位にあり、べき乗はそれらすべてより上位で右結合です。したがって 2^3^2 は 2^(3^2) を意味し、64 ではなく 512 になります。
微妙なのが単項マイナスです。数学の慣例ではべき乗より下位に置かれるため -3^2 は -9 になります。指数は 3 に作用し、符号反転はその結果に作用するからです。表計算ソフトはこれに異を唱えることで有名で 9 を返します。同じ式に対して 2 つのツールが違う答えを出す場面を見たことがあるなら、たいていこれが理由です。この評価ツールは数学の慣例に従います。
括弧はこれらすべてを上書きしますし、遠慮なく使う価値があります。優先順位表を引かないと読めない式は、いずれ必ず誤読される式です。
操車場アルゴリズムの概略
式はまずトークン——数値、名前、演算子、区切り記号——に分解され、次に Dijkstra の操車場アルゴリズムによって、演算子がオペランドの後に来る後置形式へ並べ替えられます。2 + 3 * 4 は 2 3 4 * + になります。後置形式には括弧も優先順位規則も不要です。トークンの順序が既に構造を表しているため、単純なスタックマシンが 1 回の走査で評価できます。
このアルゴリズムは出力リストと演算子スタックの 2 つを保持します。数値は直接出力へ送られ、演算子は自身を積む前に、同等以上に強く結合するスタック上の演算子をすべて取り出します。括弧はスタック上の目印として働き、閉じ括弧は対応する開き括弧まですべてを取り出します。関数呼び出しは引数カウンタを持つ演算子として同じ流れに乗り、カンマを見るたびにカウントが増えます。
これは表計算エンジンやクエリプランナーの内部でも使われている技術です。線形時間で動作し、再帰も不要で、不正な式も自然に検出できます。最後にスタックがきれいに空にならなければ、入力が釣り合っていなかったということです。
浮動小数点について正直に
結果は IEEE 754 倍精度で計算されます。これはすべてのブラウザと大半のプログラミング言語が使う演算と同じものです。数値は 2 進数で保持され、0.1 のような 10 進小数には厳密な 2 進表現が存在しません。0.1 + 0.2 が 0.3 ではなく 0.30000000000000004 になるのはこのためです。
表示を決まった有効桁数に丸めれば、内部の値を変えずにこのノイズを隠せます。ここの精度コントロールがしているのはまさにそれです。12 に設定すれば読みやすさを保ちつつ、倍精度が実際に区別できる 15〜17 桁まで十分な余裕が残ります。
覚えておくべき限界が 2 つあります。整数が厳密なのは 2^53 までなので、非常に大きな階乗や積はずれていきます。170! を超えるとそのまま無限大へオーバーフローします。もう 1 つ、ほぼ等しい 2 数の引き算は精度を破壊します。上位桁が打ち消し合い、ノイズを含む下位桁だけが残るためです。その引き算を避けるように式を組み替えると、素直な書き方よりはるかに正確になることがよくあります。