【Rust】計算機自作:演算と比較を評価するEvaluatorの実装(式指向の設計) #7

前回、計算機のParserは比較演算子を理解し、より複雑な「計算の木」を作れるようになりました。しかし、木は作っただけではただの構造体です。

今回は、この木を根っこから辿り、最終的な計算結果を導き出す Evaluator(評価器) のコア部分を実装していきます。

比較演算子のAST化とパーステスト : 【Rust】電卓に「論理」を。比較演算子の実装と優先順位の階層設計 #6

計算機のコア部分、評価機を実装していきます

今回から評価(計算部分)の実装です


1. 評価の基本:再帰的なマッチング

Rustの match 式は、ASTのような列挙型(enum)を処理するのに最適です。

実装の核となる evaluate 関数は、以下のようなロジックで動きます。

  • 数値(Number) が来たら、そのまま返す。
  • 演算(BinaryOp) が来たら、左の枝と右の枝をそれぞれ先に計算(評価)し、その結果同士を演算する。

この「自分自身を呼び出して枝を解いていく」再帰的なプロセスで計算を行っていきます。


2. 実装のポイント:論理演算を数値で表す

今回の実装で面白いのは、5 < 10 といった比較演算の結果をどう扱うかという点です。

この電卓では、多くのプログラム言語や計算ツールの慣習に従い、「真(True)なら 1.0」「偽(False)なら 0.0」 という数値として扱うことにしました。

// 論理演算(比較)の実装:真なら1.0、偽なら0.0を返す
BinOp::Less      => Ok(if l < r  { 1.0 } else { 0.0 }),
BinOp::Greater   => Ok(if l > r  { 1.0 } else { 0.0 }),

これにより、将来的に「比較結果に 10 を掛ける」といったトリッキーな計算も可能になります。

計算機の比較演算は bool? 数値?

計算機の比較演算について調べてみました。最近の計算機は True / False の bool 値を導入して動くものが多く、(思い返してみると Python なども)基本的に bool 値で扱うのが主流です。

ここで面白いのが、「if 文そのものの扱い」です。

多くの入門的なイメージでは、if 文は「条件によって処理を分岐するための構文」として説明されます。しかし言語によっては、if は単なる“文”ではなく、「値を返す式」として扱われます。こうした設計は 式指向言語 と呼ばれます。

例えば Rust や Haskell では、if は次のように「値を返すもの」です:

let x = if a > b { 10 } else { 20 };

この場合、if 全体が 10 または 20 という値を持つため、そのまま代入に使えます。

これは一見すると小さな違いですが、設計としてはかなり本質的です。
「分岐は処理の流れを変えるもの」ではなく、

分岐もまた“値を計算する式の一部”である

という考え方になります。

自作の電卓をどのように設計するかによって、こうした思想のどこに立つかが自然と決まってきます。単純な数値計算の道具として作ることもできますし、すべてを式として扱う“小さな言語”として設計することもできます。

その意味では、電卓を作るという作業自体が、小さな言語設計の入り口なのかもしれません。


3. 浮動小数点数の「等価性」

計算機を作るうえで誰もが一度はハマるのが、「0.1 + 0.2 == 0.3 が偽になる」という浮動小数点数の誤差問題です。計算機の内部では、二進数で扱うため0.1 や 0.2 といった十進数の小数は、内部的には無限小数として近似されています。そのため単純な “==”だと偽になることがあります。今回の実装では、f64::EPSILON を使用して、微小な誤差を許容する安全な比較を行っています。

// 単なる == ではなく、差が極めて小さいかどうかで判定する
BinOp::Equal => Ok(if (l - r).abs() < f64::EPSILON { 1.0 } else { 0.0 }),
【注意】実用性と引き換えにした「誤判定」のリスク

ただし、この実装には注意が必要です。f64::EPSILON(約 \(2.22 \times 10^{-16}\))は「1.0において表現可能な最小の差」を基準にしています。

  • 微小すぎる値の混同: もし非常に小さな値(例えば \(10^{-20}\) 単位)同士を比較しようとすると、それらは本来別物であっても、この基準ではすべて「同じ(0.0)」と判断されてしまいます。
  • スケールの問題: 非常に大きな数(\(10^{15}\) など)を扱う場合、逆にイプシロンの幅が小さすぎて、本来「ほぼ同じ」はずのものが「違う」と判定されることもあります。

参考 keyword:計算機イプシロン


4. 実行結果:計算機らしくなってきた!

新しく実装した Evaluator を動かしてみました。

入力式内部の木構造 (evaluate_simple)計算結果
1 + 1(1 Add 1)2.0
10 > 5(10 Greater 5)1.0 (True)
3 * 2 == 6((3 Mul 2) Equal 6)1.0 (True)
(1 + 2) * 4 < 10(((1 Add 2) Mul 4) Less 10)0.0 (False)

四則演算だけでなく、論理的な判断も数値として出力されるようになりました。これで、複雑な条件式を解く準備が整いました。

簡易版評価機の動作チェック

※ <=のparse errorは定義漏れによるもので、解決済みです。


次回予告:関数の展開とifの処理。

ここまでで「演算」と「比較」は完璧です。しかし、まだ課題が残っています。TOMLで定義した conv(W, K) のような関数呼び出しや、$1 といった履歴の参照です。

次回は、いよいよこれらを環境(Context)から引っ張ってきて動的に展開する、「関数評価編」に進みます。

関数評価編の実装 : 関連記事は、2026年5月6日に公開予定 (あと10時間)

ここまで読んでいただきありがとうございます。

では、次の記事で。 lumenHero

関連記事

第1回 構想編【Rustで作る】ターミナル関数電卓を設計する(アーキテクチャ編) #1
第2回 Lexer実装:【Rust】自作計算機エンジンへの道:Lexer(字句解析)で数式をトークンに分解する #2
第3回 tomlの読み込み:【Rust】TOMLで関数を自由に追加!自作電卓に設定読み込みとホットリロードを実装する #3
第4回 Parserの実装(基礎):【Rust】数式を「計算の木」へ:ASTと再帰下降構文解析で電卓エンジンの心臓部を作る #4
第5回 Parserの実装(関数):【Rust】関数の「事前パース」で電卓を高速化!AST管理とデバッグ出力の実装 #5
第6回 比較演算子の実装 :【Rust】電卓に「論理」を。比較演算子の実装と優先順位の階層設計 #6