
1. はじめに
前回までの全10回にわたる連載で、文字列をバラバラに分解し(字句解析)、木構造を組み立て(構文解析)、そこに環境という命を吹き込む(評価)という、計算機のコア部分を実装してきました。
今回からは 実装ロードマップのPhese 1として、基礎的な関数を作り込んでいこうと思います。まずは、今後の拡張に耐えうる「整理整頓術」と、実数処理の基本関数を実装していきます。
前回 第10回 基本実装まとめとロードマップ:【Rust自作TUI電卓】再帰を越えて:実装まとめと進化のロードマップ #10
2. フォルダ構成:mod.rs で「パッケージ化」する
関数を数十、数百と追加していくにあたって、main.rs が肥大化するのは避けたいところです。そこで、数学関連の処理を math フォルダにまとめ、外部からは一つのモジュールとして扱えるように整理します。
Rustのモジュール管理
Rustでは、ディレクトリ内に mod.rs を置くことで、そのディレクトリ自体をモジュールとして定義できます。これは Python でいうところの __init__.py に近い役割を果たします。
今回のディレクトリ構成:
src/
├── main.rs
└── math/
├── mod.rs # mathモジュールの「入り口」
├── basic.rs # 基本的な実数処理関数(今回実装)
└── power.rs # 指数・対数など(今後実装予定)実装のポイント
まず、math/mod.rs で「どのファイルを公開するか」を宣言します。
math/mod.rs
// basic.rs と power.rs を公開する
pub mod basic;
pub mod power;これにより、main.rs 側では mod math; と一行書くだけで、math::basic::関数名 という形で簡単に管理できるようになります。
3. 実装:実数処理の基本(basic.rs)
次に、math/basic.rs へ具体的な関数を書き込んでいきます。
すべてを f64 で統治する
この電卓の実装には、一つ大きな特徴を持たせます。それは、「真偽値(bool)も数値(0.0 / 1.0)として扱う」というポリシーです。bool値は便利ですが、その分、型の管理などが大変になってしまいます。
電卓の内部スタックや評価エンジンにおいて、型を
f64一つに絞り込むことで、処理を極限までシンプルに保つためです。判定関数の結果をそのまま他の計算に組み込める(例:x * isnan(y))という、柔軟な使い勝手も実現できます。
math/basic.rs (今回新規実装)
/// 絶対値
pub fn abs(x: f64) -> f64 {
if x < 0.0 { -x } else { x }
}
/// 符号(正なら1.0, 負なら-1.0, 0なら0.0)
pub fn signum(x: f64) -> f64 {
if x < 0.0 { -1.0 } else if x > 0.0 { 1.0 } else { 0.0 }
}
// --- 丸め処理系 ---
pub fn ceil(x: f64) -> f64 { x.ceil() }
pub fn floor(x: f64) -> f64 { x.floor() }
pub fn round(x: f64) -> f64 { x.round() }
pub fn trunc(x: f64) -> f64 { x.trunc() }
/// 小数部分の抽出
pub fn fract(x: f64) -> f64 {
x - x.trunc()
}
/// 整数部分と小数部分をタプルで分離
pub fn modf(x: f64) -> (f64, f64) {
(x.trunc(), fract(x))
}
/// 符号のコピー(xにyの符号を付与)
pub fn copysign(x: f64, y: f64) -> f64 {
x.copysign(y)
}
// --- 特殊判定系(boolを数値 0.0 or 1.0 で返す) ---
pub fn isfinite(x: f64) -> f64 {
if x.is_finite() { 1.0 } else { 0.0 }
}
pub fn isinf(x: f64) -> f64 {
if x.is_infinite() { 1.0 } else { 0.0 }
}
pub fn isnan(x: f64) -> f64 {
if x.is_nan() { 1.0 } else { 0.0 }
}基本は、rustの組み込み関数を呼び出しているだけで、返り値をf64にそろえただけです。
実装のコンセプトとして、関数として maxやmin、graterやlessは実装できますが、関数電卓の機能として、>= や <での比較演算子処理を実装しており、これらと競合する(無駄に関数が増えてきれいではない)ため、あえて実装は見送ることにしました。
第6回 比較演算子の実装 :【Rust】電卓に「論理」を。比較演算子の実装と優先順位の階層設計 #6
4. 呼び出し側の変化:main.rs でのインポート
ディレクトリ構成と関数が整ったら、main.rs から呼び出してみます。
main.rs
mod math; // mathディレクトリ(mod.rs)を読み込む
fn main() {
let val = -3.5;
// math::basic 経由で呼び出し
let absolute = math::basic::abs(val);
let is_nan = math::basic::isnan(val);
println!("値: {}, 絶対値: {}, NaN判定: {}", val, absolute, is_nan);
}mod math; という宣言だけで、math/ 以下の機能が整然と利用可能になりました。まるで標準ライブラリを扱っているかのような、見通しの良いコードになります。
5. まとめと次回の展望
今回は、電卓の「語彙」を増やすための土台作りを行いました。
- モジュール化:
mod.rsを使って、Pythonのように直感的にコードを整理。 - 設計ポリシー:内部処理を
f64に統一し、判定結果も0.0/1.0で扱う。
これによって、今後どれだけ関数が増えても、それぞれを必要に応じたサイズのファイルに分け、mod.rsに追加するだけで実装できるようになります。
次回からは、さらに一歩進んで power.rs を作成し、指数関数を実装していきます。次回は指数関数のうち平方根 (sqrt)に焦点を絞り、rustの関数と、ニュートン法、シフトを用いた最適化手法など様々な方法で実装し検証を行っていこうと思います。結構面白くまとまったので、良ければ見ていってください。
次回 : 【Rustで作る電卓】平方根の深淵に挑む。標準関数 vs ニュートン法 + 魔法の数字 #12
では、次の記事で。 lumenHero
関連記事
第1回 構想編 :【Rustで作る】ターミナル関数電卓を設計する(アーキテクチャ編) #1
第2回 Lexer実装:【Rust】自作計算機エンジンへの道:Lexer(字句解析)で数式をトークンに分解する #2
第3回 tomlの読み込み:【Rust】TOMLで関数を自由に追加!自作電卓に設定読み込みとホットリロードを実装する #3
第4回 Parserの実装(基礎):【Rust】数式を「計算の木」へ:ASTと再帰下降構文解析で電卓エンジンの心臓部を作る #4
第6回 比較演算子の実装 :【Rust】電卓に「論理」を。比較演算子の実装と優先順位の階層設計 #6
第7回 Evaluatorの実装 :【Rust】計算機自作:演算と比較を評価するEvaluatorの実装(式指向の設計) #7
第10回 基本実装まとめとロードマップ:【Rust自作TUI電卓】再帰を越えて:実装まとめと進化のロードマップ #10