計算式ライブラリを作ってみた。

かなり昔に計算式をUIで設定したり設定ファイル等に書いておいて実行できると便利かもと思って何度か作ってみたことがある。基本的なアルゴリズムというのは自分の好みにあってるらしく興味深いものを感じるし、例えば...計算式自体を変更しなければならなくなったときプログラムを修正することなく設定変更だけで対応することができたりしたら便利なはずだ。

Shunting Yard Algorithmについて
Shunting-yard algorithm

あまりに久しぶりすぎて全て忘れてる状態だったので上記を参考にさせてもらったがとても分かりやすくまとめられている。

今回のはShunting-yard algorithm に、関数(可変長引数&引数省略対応)、シンボル参照、構文エラーチェック、定数式や結果が不変となる関数のコンパイル時評価などを追加してみたが、このアルゴリズムはわかりやすくシンプルでとても良い。これ一択でいいんじゃね?と思ってしまった。

演算子は、C言語の代入演算子、配列演算子、ドット演算子、アドレス&ポインター演算子、三項演算子を除く演算子に対応。三項演算子は実装を複雑にしてまで対応するメリットがないというかそもそもIF()関数を実装すれば必要ないものなので三項演算子はあえて非対応としてみた。

おまけの関数はエクセルを参考に汎用的に使えそうなものを実装してみたがメリットがあるのかどうかは不明。

[単項演算子]

[二項演算子]

[比較演算子]

[論理演算子]

[括弧演算子]

[関数]

[シンボル参照]

[データ型]

データ型は必要とされる型に自動変換するので型についてはあまり気にしなくても良い。例えば、1 + 2 と “1” + “2” の結果は同じく 3 である。文字型から数値型への変換は問題ないが、小数桁を含む数値型を文字型に変換した場合に精度落ちすることがあるので注意すること。ブランク型を変換した場合はゼロ(0)或いは空文字(“”)として扱われる。

C++17以降に対応したコンパイラとコンパイラ・オプション(-std=c++17)が必要となることと、時刻系の関数の実装にlocaltime_r()を使ってるのが原因でインクルードの順番によってlocatime_rがエラーになる場合がある。もし、localtime_rがエラーになったら、expr.hを一番最初にインクルードしてみるべし。
もしくは、-d_POSIX_THREAD_SAFE_FUNCTIONSをコンパイラ・オプションに追加しても良い。

【修正】
2024-11-27
最適化というか気になった部分を清書してみた。ついでにtrunc()関数を追加。

2024-11-16
小数部を含む浮動小数点値の文字列変換での精度落ちを防ぐためtoString()を指数形式に変更。これに伴い新たに表示用のtoPrint()を追加。計算結果を表示する場合はtoPrint()を使うこと。

2024-11-15
sexec()の追加。使い方はchoose()と全く同じであるが全ての引数を順次実行していく点が異なる。
sexec(3,set(‘a’,2),set(‘b’,3),a*b) –> 6
それと、math.hに定義されているM_PIなどの浮動小数点定数を名前参照できるように改良。何かと便利かも。

2024-11-14
min/max/log1p/log2/logb/asinh/acosh/atanh関数の追加と、call()に引数($1,$2,…)を渡せるようにしてみた。
call($1+$2,1,2) –> 3

2024-11-13
最適化したときのミスで単項演算子がエラーになるので修正。ついでに一般的なmath関数を追加してみた。バイナリーがデカくなった気がするが気のせいかな?

2024-11-12
jsonの構造化データを扱うための改良を行った。サンプルプログラム側の改良内容(resolver)と合わせること。
aaa.bbb.ccc
aaa[0].bbb[1].ccc[2]
配列のインデックスを動的に変更したいときはget(concats(“aaa[“,0,”]”))のようにすることもできる。

2024-11-11
いろいろと最適化。
ちなみに今日はaliexpressの日。(笑)

2024-11-10
いろいろと変更。

2024-11-09
linux対応&全体的な修正と最適化を行った。

2024-11-08
iferror()関数とコールバック関数とは別に名前解決ができるようにstd::mapを追加。std::map->コールバック関数の順に名前解決する。

2024-11-07
だいぶバグバグしてたので修正。実行時間が妙に早すぎたのもやはりバグだった。v(-_-;) とはいえ、簡単な計算式なら100ns程度で実行できたから高速なほうかも。
あとデバッグ用としてparse()が返す計算実行用オブジェクトのツリー構造を表示する機能を追加してみた。関数名とか表示されないから見ずらいかもだけどないよりはましかな。

2024-11-06
比較演算子の文字列対応と細かいバグ修正を行った。
それとcall()関数を試験的に追加。call(“1 + 2 * 3”)のように他の計算式の実行結果を参照することができる。
expr1: “1 + 2 * 3”
call(expr1)+3=10
計算式に名前を付けておくとサブルーチンのような使い方ができそう。まだバグッてるがとりあえず公開。
ついでにサンプルプログラムに実行時間を計測する機能を追加してみたら...1億回回しても100ms程度しかかからない。ということは1回当たり1ns程度となるが何か間違ってるのかな...

【ダウンロード】
ソースコード
お試し用のサンプルプログラム(formula.exe)

【サンプル・プログラム】

【ライブラリ】