プログラミングのメモ帳(C/C++/HSP)

日々のプログラミングで気づいた点や小技集を紹介します。(Windows 10/XP/Vista、VC2017、HSP)

数式文字列の四則演算(整数値)

2012年03月24日 16時54分00秒 | アルゴリズム関連

数式文字列を評価して整数値を得る方法を紹介します。(戻る)

数式文字列の四則演算

いろいろなソフトのオプションで数値などを指定すると思います。
そのオプション文字列を解読して、数値に解釈するとき普通の使い方なら atoi、atol、atof 関数などを使います。
しかし、このままでは分かりやすく「16 * 16」とか、「16 * 16 + 100」という数式での文字列が使えません。
いちいち手元の電卓で「256」とか、「356」と計算してからオプションに指定しないといけません。
それに後で「256」が「16 * 16」を意味していた数値か分からなくなることがあります。
それでは困ります。

そこで、ちょっとした足し算、引き算、掛け算、割り算なら数式として指定できれば便利です。
単純に手元の電卓で計算してから数値を指定しなくても良い。
それに数値の意味や構造を分かりやすく数式で指定できるのが一番便利な点です。
また、数式文字列を計算する電卓ソフト開発の基礎となります。

数式文字列の応用

この数式文字列を機能拡張して変数の参照・代入、関数の呼び出し機能を追加すると関数電卓も作成できます。
また、フロー制御(条件分岐、繰り返し処理、ジャンプ命令)などを付け足せばオリジナル言語を開発することもできます。
つまり、オリジナル・インタプリタ、オリジナル・スクリプト言語、オリジナル・コンピュータ言語などが製作可能です。
ちまたでは HSP、Ruby、ひまわり、なでしこ、ActiveBasic などが有名ですね。

過去に RPG ゲームのシナリオを処理させる「ゲーム言語」を開発した事があります。
この時に使った技術(アルゴリズム)こそ数式文字列の評価アルゴリズムです。
ゲームのスクリプト・ファイルを処理するときに思いっきり役に立ちました。
そこで、ちょっと昔を思い出して数式文字列の評価関数を紹介します。

数式文字列の評価関数

//------------------------------------------------
// 関数のプロトタイプ宣言
//------------------------------------------------
extern long calcExpress( const char string[] );
extern long calcErrCode();
  • calcExpress() が数式文字列を評価して結果を返す関数です。
  • calcErrCode() は数式文字列の評価で発生したエラーコードを取得する関数です。

下請け関数

//------------------------------------------------
// 関数のプロトタイプ宣言
//------------------------------------------------
static long funcValue();
static long funcFactor();
static long funcMulDiv();
static long funcAddSub();
  • funcValue() は整数値を評価します。
  • funcFactor() は括弧・数字を評価します。
  • funcMulDiv() は乗算・除算・剰余を評価します。
  • funcAddSub() は加算・減算を評価します。

再帰処理の呼び出し

数式文字列を評価するとき、次の順で関数を呼び出します。

  1. calcExpress() で解析ポインタの初期化を行う。
  2. funcAddSub() で加算・減算の計算を行う。
  3. funcMulDiv() で乗算・除算・剰余の計算を行う。
  4. funcFactor() で括弧・数字・プラス・マイナスの計算を行う。
  5. funcValue() で数値文字列を整数値に変換する。

括弧の評価は、括弧内の数式文字列を評価するので funcAddSub() 関数を再帰呼び出しします。
この方法だけで加算、減算、乗算、除算、剰余、括弧の優先順位を考慮して計算を行います。
また、プラス記号、マイナス記号で数値の符号を指定できるようになってます。

サンプル

下のソースコードをコピー&ペーストしてコンパイルすると数式文字列の評価を確認できます。
ぜひ、試してみて下さい。

//------------------------------------------------------------------------------
// 数式評価のサンプル(整数値)
//------------------------------------------------------------------------------
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//------------------------------------------------
// break 付きのキーワード
//------------------------------------------------
#define CASE        break;case
#define DEFAULT     break;default

//------------------------------------------------
// 関数のプロトタイプ宣言
//------------------------------------------------
static long funcAddSub();

//--[数式評価]------------------------------------------------------------------

//------------------------------------------------
// 記号定数
//------------------------------------------------
#define BLKTOP      '('
#define BLKEND      ')'
#define SPC         ' '
#define TAB         '\t'

//------------------------------------------------
// 列挙定数(エラーコード)
//------------------------------------------------
enum eERRCODE {
    ERRCODE_SUCCESS,        // 正常
    ERRCODE_FACTOR,         // 未サポートの演算子です。
    MAX_ERRCODE,
};

//------------------------------------------------
// グローバル変数
//------------------------------------------------
static const char *g_parse;
static       long  g_error;

//------------------------------------------------
// 数値文字列の評価
//------------------------------------------------
static long funcValue()
{
    return strtol( g_parse, (char**)&g_parse, 10 );
}

//------------------------------------------------
// 括弧・数値の評価
//------------------------------------------------
static long funcFactor()
{
    long ans;
    
    while ( (*g_parse == SPC) || (*g_parse == TAB) ){
        g_parse++;
    }
    if ( *g_parse == BLKTOP ){
        g_parse++;
        ans = funcAddSub();
        g_parse++;
        return ans;
    }
    if ( isdigit(*g_parse) || (*g_parse == '+') || (*g_parse == '-') ){
        return funcValue();
    }
    g_error = ERRCODE_FACTOR;
    return 0;
}

//------------------------------------------------
// 乗算・除算・剰余の評価
//------------------------------------------------
static long funcMulDiv()
{
    long ans = funcFactor();
    
    for ( ; ; ){
        switch ( *g_parse ){
            CASE '*':       g_parse++; ans *= funcFactor();
            CASE '/':       g_parse++; ans /= funcFactor();
            CASE '%':       g_parse++; ans %= funcFactor();
            CASE SPC:       g_parse++;
            CASE TAB:       g_parse++;
            DEFAULT:        return ans;
        }
    }
}

//------------------------------------------------
// 加算・減算の評価
//------------------------------------------------
static long funcAddSub()
{
    long ans = funcMulDiv();
    
    for ( ; ; ){
        switch ( *g_parse ){
            CASE '+':       g_parse++; ans += funcMulDiv();
            CASE '-':       g_parse++; ans -= funcMulDiv();
            CASE SPC:       g_parse++;
            CASE TAB:       g_parse++;
            DEFAULT:        return ans;
        }
    }
}

//------------------------------------------------
// 数式文字列の評価
//------------------------------------------------
extern long calcExpress( const char string[] )
{
    g_parse = string;
    g_error = ERRCODE_SUCCESS;
    return funcAddSub();
}

//------------------------------------------------
// 数式文字列のエラー取得
//------------------------------------------------
extern long calcErrCode()
{
    return g_error;
}

//------------------------------------------------
// メイン関数
//------------------------------------------------
int main( void )
{
    char    buff[ 256 ];
    char*   find;
    
    while ( fgets(buff,sizeof(buff),stdin) != NULL ){
        if ( (find = strchr(buff,'\n')) != NULL ){
            *find = '\0';
        }
        if ( buff[0] != '\0' ){
            printf( "%s = %d\n", buff, calcExpress(buff) );
            
            switch ( calcErrCode() ){
                CASE ERRCODE_FACTOR:    printf( "ERRCODE_FACTOR\n\n" );
                DEFAULT:                printf( "\n" );
            }
        }
    }
    return 0;
}

//------------------------------------------------------------------------------
// End of express1.cpp
//------------------------------------------------------------------------------
  • コマンド・プロンプトを起動して実行すると数式入力になります。
  • そこで「((1 + 3) * 5 + 20)」とか、「10 * -3」と入力すると次のように計算されます。

実行結果

((1 + 3) * 5 + 20)
((1 + 3) * 5 + 20) = 40

10 * -3
10 * -3 = -30

16 * 16 + 100
16 * 16 + 100 = 356

1 + 2 - 3 * 4 / 5
1 + 2 - 3 * 4 / 5 = 1
  • 上記の実行結果は、ほんの一例です。
  • 特に最後の数式「1 + 2 - 3 * 4 / 5」を優先順位を考えずに計算すると「0」になります。
  • しかし、ちゃんと掛け算、割り算を先に計算してるので答えは「1」になってます。
  • なお、整数値ですから「1」ですが小数で計算すると「0.6」となります。


コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« PlgBltで図形を回転するサンプル | トップ | 数式文字列の四則演算(実数値) »
最新の画像もっと見る

コメントを投稿

アルゴリズム関連」カテゴリの最新記事