掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板1

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧

No.7619

関数から数値を返せないでエラー
投稿者---ギャラ(2007/06/27 20:21:58)


趣味程度で勉強している初心者です。よろしくお願いします。

今回の題材は、文字列に数式を入れ、それを計算するコードについてです。
だらだらと長いコードを書きながらうまく動作している事を確認していたのに、
ある時突然エラーがでて終了してしまう事がある事に気づき、
コードが長い分エラーコードの特定に非常に苦労しました。

エラーがあると思われるコードは以下の通りです。
(エラー部分を特定するためかつ書き込みを短くするために、
プログラムの骨格を崩さない程度にエラー処理等は削除しました。)

#include <iostream>
#include <cctype>
#include <cstring>
#include <cmath>
using namespace std;

/*クラス動作イメージ
 ポインタを受け取り cal5 まで一旦関数を開き数値を獲得します。
  その後、専用の演算処理関数が見つかるまで cal1 に向かって移動をし、
  演算処理後?(前と言った方がいいのかも)再び cal5 で数値を獲得します。
  数式上の計算優先順位が高ければ cal5 の付近で処理を繰り返し、
  優先順位が低ければ cal1 の方向に向かって処理を繰り返します。
*/
class calculate{
    char *p;       // p++ しながら式を処理していきます
    double ans;    // 答えの格納
    bool b;        // 動作チェックメッセージ用 本来は無用
public:
    void cal(char *ptr);// 数式を受け取ります
    double cal1();      // + - の処理
    double cal2();      // * / の処理
    double cal3();      // log の処理
    double cal4();      // ^   の処理
    double cal5();      // 数値の獲得 括弧の処理
    double getans(){ return ans; }    //答えを引き出す
};
void calculate::cal(char *ptr)
{
    p=ptr;        // ポインタを式の先頭にセット
    b=false;      // 初期化 真になるとメッセージ出力
    ans=cal1();   // 処理結果を格納
}
double calculate::cal1()
{
    double num;
    
    num=cal2();
    while(*p){
        if(*p=='+'){          // + の処理
            p++;
            num+=cal2();
        }else if(*p=='-'){    // - の処理
            p++;
            num-=cal2();
        }else{
            break;
        }
    }
    return num;
}
double calculate::cal2()
{
    double num;
    
    num=cal3();
    while(*p){
        if(*p=='*'){         // * の処理 ここからの動作が怪しい
            cout<<"* 処理開始\n";
            b=true;          // この処理後に詳細メッセージ表示
            p++;
            num=num*cal3();
            cout<<"* 処理終了\n";
        }else if(*p=='/'){        // '/' の処理
            p++;
            num/=cal3();
        }else{
            break;
        }
    }
    return num;
}
double calculate::cal3()
{
    double num;
    
    num=cal4();
    if(!strncmp(p,"log",3)){        // log の処理
        p+=3;
        num=log(cal4());
    }
    return num;
}
double calculate::cal4()
{
    double num;
    
    num=cal5();
    if(b) cout<<"cal4 内通過\n";
    if(*p=='^'){                           // ^ の処理
        p++;
        num=pow(num,cal5());
    }
    return num;
}
double calculate::cal5()
{
    double num;
    
    if(b){                                     // * 処理後表示
        cout<<"* 処理のため cal5 に数値を取りにくる\n";
        cout<<"p="<<p<<endl;
    }
    if(isdigit(*p) || *p=='.'){        // 数値獲得
        num=strtod(p,&p);
    }else if(*p=='(' || *p=='['){      // 括弧処理
        p++;
        num=cal1();
        p++;
    }
    if(b) cout<<"cal5 内処理終了\n"; // * 処理後表示
    
    return num;
}
int main()
{
    calculate ob;
                                               // 2.718 は自然対数e
    ob.cal("[2+1]^2*log2.71828182845904509");  // log(e)=1
    cout<<"処理結果:"<<ob.getans()<<endl;
    cout<<endl;
    ob.cal("[2.01855182604945327+1]^2*log2.01855182604945327");
    cout<<"処理結果:"<<ob.getans()<<endl;
    
    return 0;
}

プログラムの処理結果:

* 処理開始
* 処理のため cal5 に数値を取りにくる
p=log2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理のため cal5 に数値を取りにくる
p=2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:9

* 処理開始
* 処理のため cal5 に数値を取りにくる
p=log2.01855182604945327
cal5 内処理終了


このプログラムの目的
数式を ob に渡してその答えを表示する動作を2回繰り返します。
上記のコードでは、2回目の処理がエラー発生のため途中で終了してしまいます。

私自身の考察
最初の処理は答えが3^2*1なので処理結果が正しいことがわかります。
(途中経過は、面倒なので考えなくてもいいと思います。)
2回目の処理は、表示されるメッセージを確認しながら動作を考えると、
ポインタが'*'に達した後、処理がcal2,cal3,cal4,cal5に移動し、
「cal5 内処理終了」の表示でcal5内の処理を全て実行している事が分かります。
これが正しく終了していれば、次の処理は、cal4に戻ります。ところが、ここで、
「このプログラムは不正な処理を行ったので強制終了されます。」のメッセージが出て、
終了してしまいます。
実行結果に「cal4 内通過」が表示されないことから、
cal5 の if(b) cout<<"cal5 内処理終了\n"; 以降から
cal4 の if(b) cout<<"cal4 内通過\n"; 以前の処理にエラーがあると考えているのですが、
原因の見当が全く付きません。

今までの経験上、大体が打ち込みミスか簡単な勘違いでしたが、
今回は完全に意味不明でどうしようもない状態です。

その他に分かっていることは、
a を任意の数値として (a+1)^2*log(a) を上記のクラスで計算させた場合、稀にエラーが発生します。
今回このエラーに気付いたのは、a の値を変えながら数百回以上の計算をさせる時に、
エラーが発生しそれを追及したら今回のような数値で、必ずエラーが発生してしまう事に気付きました。

動作環境
windows98se
Borland C++ Compiler 5.5


この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:関数から数値を返せないでエラー 7620 RAPT 2007/06/27 21:29:49
<子記事> Re:関数から数値を返せないでエラー 7621 かずま 2007/06/27 22:57:59


No.7620

Re:関数から数値を返せないでエラー
投稿者---RAPT(2007/06/27 21:29:49)
http://rapt21.com


WindowsXP sp2 / VC++6sp6 ですが、エラーにはなりませんでした。
ソースを見て、cal5() 内で double num; が不定値でリターンされる
可能性があることが気になりました。
他にエラーになりうるとしたら、スタックオーバーフローでしょうか。


----------  以下、出力結果
* 処理開始
* 処理のため cal5 に数値を取りにくる
p=log2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理のため cal5 に数値を取りにくる
p=2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:9

* 処理開始
* 処理のため cal5 に数値を取りにくる
p=log2.01855182604945327
cal5 内処理終了
cal4 内通過
* 処理のため cal5 に数値を取りにくる
p=2.01855182604945327
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:6.39985



この投稿にコメントする

削除パスワード

No.7621

Re:関数から数値を返せないでエラー
投稿者---かずま(2007/06/27 22:57:59)


そのプログラムでは、ob.cal("log2"); すら計算できません。
とりあえず、cal3() を修正しますが、log2^3 は log(2^3) で、
log2*3 は (log2)*3 でいんですか?
double calculate::cal3()
{
    if (strncmp(p, "log", 3)) return cal4();
    p += 3;
    return log(cal3());
}



この投稿にコメントする

削除パスワード

No.7622

Re:関数から数値を返せないでエラー
投稿者---bugs(2007/06/27 23:04:31)


>そのプログラムでは、ob.cal("log2"); すら計算できません。

当方(WindowsXP sp2, BCC 5.82)では、0.693147という
答えを出しました。

環境によって動きが違うのかどうかはわかりません。



この投稿にコメントする

削除パスワード

No.7623

Re:関数から数値を返せないでエラー
投稿者---かずま(2007/06/27 23:07:40)


> そのプログラムでは、ob.cal("log2"); すら計算できません。

すみません。ob.cal("log2"); は計算できます。

cal3() で、cal4() を呼び出すと、cal5() まで行って、
不定の値 num を返しますが、cal3() に戻ると、その値は
使わずに num = log(cal4()); を計算しているからです。


この投稿にコメントする

削除パスワード

No.7624

Re:関数から数値を返せないでエラー
投稿者---ギャラ(2007/06/28 08:29:46)


RAPTさん、かずまさん、bugsさん有難うございます。

かずまさんの
>log2^3 は log(2^3) で、
>log2*3 は (log2)*3 でいんですか?

その通りです。


>double calculate::cal3()
>{
> if (strncmp(p, "log", 3)) return cal4();
> p += 3;
> return log(cal3());
>}
を利用すると処理結果は

* 処理開始
* 処理のため cal5 に数値を取りにくる
p=2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:9

* 処理開始
* 処理のため cal5 に数値を取りにくる
p=2.01855182604945327
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:6.39985

となり正常な動作をします。


RAPTさんの
>他にエラーになりうるとしたら、スタックオーバーフローでしょうか。

このクラスよりもっと複雑なクラスでしかも遥かに複雑な計算を実行出来る事を確認しています。


>ソースを見て、cal5() 内で double num; が不定値でリターンされる
>可能性があることが気になりました。

cal5() 内の double num; は使用していないため全く関係無いと思いつつ、cal5() 内の始めに、

num=0;

をいれて実行してみました。
なんと!!!!  実行できました。!!!!! 驚きです。!!!!
これを踏まえて cal5() を下記のようしてみました。

double calculate::cal5()
{
    double num;
    
    cout<<"cal 内通常処理:初期値 d="<<num<<endl;            // 追加項目
    if(b){                // * 処理後表示
        cout<<"* 処理のため cal5 に数値を取りにくる\n";
        cout<<"p="<<p<<endl;
    }
    if(isdigit(*p) || *p=='.'){   // 数値獲得
        num=strtod(p,&p);
    }else if(*p=='(' || *p=='['){      // 括弧処理
        p++;
        num=cal1();
        p++;
    }
    if(b) cout<<"cal5 内処理終了\n"; // * 処理後表示
    
    return num;
}

実行結果:
cal 内通常処理:初期値 d=1.78993e-307
cal 内通常処理:初期値 d=1.89465e-307
cal 内通常処理:初期値 d=2
cal 内通常処理:初期値 d=3
* 処理開始
cal 内通常処理:初期値 d=0
* 処理のため cal5 に数値を取りにくる
p=log2.71828182845904509
cal5 内処理終了
cal4 内通過
cal 内通常処理:初期値 d=9.78601e-307
* 処理のため cal5 に数値を取りにくる
p=2.71828182845904509
cal5 内処理終了
cal4 内通過
* 処理終了
処理結果:9

cal 内通常処理:初期値 d=2.01217e-307
cal 内通常処理:初期値 d=1.89465e-307
cal 内通常処理:初期値 d=2.01855
cal 内通常処理:初期値 d=3.01855
* 処理開始
cal 内通常処理:初期値 d=

となり、エラー終了しました。
cal5() 内の num が何か悪さをしている事がわかり、エラー回避方法は分かったのですが、
どうしてエラーになるのかが不明ですっきりしません。
因みに、かずまさんのコードに同様の確認をしたところ結果が

* 処理開始
cal 内通常処理:初期値 d=9.78541e-307

となり、使用できる初期値を持っているようで、何か変化を与えるとdouble num; の初期値が変化して、
この初期値に依存しながらエラーが出るかどうか決まってくるようです。


以上の事を踏まえると
初期化していないdouble型を使用してはいけないものなのでしょうか、
それとも他に原因が隠れているのでしょうか?
int型やchar型は初期化しなくても異常が起こるわけが無いと考えられるのですが、
他の型はどんなビット配置をしているのか知識が有りません、そういう型を使用するときは、
初期化するのが無難な方法と考えるべきなのでしょうか?


この投稿にコメントする

削除パスワード

No.7625

Re:関数から数値を返せないでエラー
投稿者---bugs(2007/06/28 14:38:30)


>int型やchar型は初期化しなくても異常が起こるわけが無いと考えられるのですが、

関数やブロック内部のローカル変数は、定義しただけでは値は不定です。
何が入っているかはわかりません。
「int型やchar型は…」という話は、お考え違いです。
むしろ、型によって扱いを変える方がむずかしいです。


この投稿にコメントする

削除パスワード

No.7627

Re:関数から数値を返せないでエラー
投稿者---ギャラ(2007/06/28 20:04:05)


bugsさんアドバイスありがとうございます。

今回のエラーについて色々考えて下記のように考えたのですが正しいと言えるのでしょうか?

cal5() を下記のようにします。
double calculate::cal5()
{
    union{          // double のビットデータを調べる為の準備
        double num;
        char ch[sizeof(double)];
    };
    if(b){                // * 処理後表示
        cout<<"* 処理のため cal5 に数値を取りにくる\n";
        cout<<"p="<<p<<endl;
        cout<<"バイト毎のデータ−:d=";
        for(int i=0; i<sizeof(double); i++) cout<<(int)ch[i]<<' ';  // d の正体を確認
        cout<<endl;
    }
    if(isdigit(*p) || *p=='.'){   // 数値獲得
        num=strtod(p,&p);
    }else if(*p=='(' || *p=='['){      // 括弧処理
        p++;
        num=cal1();
        p++;
    }
    if(b) cout<<"cal5 内処理終了\n"; // * 処理後表示
    
    return num;
}

実行結果
 中略(2つ目の計算過程から)
* 処理開始
* 処理のため cal5 に数値を取りにくる
p=log2.01855182604945327
バイト毎のデータ−:d=0 0 0 0 -48 78 -15 127
cal5 内処理終了

ここでエラー終了してしまいます。
バイト毎のデータ−を利用して下記のプログラムで d の値を再現します。
#include <iostream>
using namespace std;

double f()
{
    union{
        char ch[sizeof(double)];
        double d;
    };
    char str[]={0,0,0,0,-48,78,-15,127};    // バイト毎のデータ−
    int i;
    
    cout<<"f() 関数開始\n";
    cout<<"エラーが発生する前に生成された d をバイト単位で表示 及びコピー\n";
    for(i=0; i<sizeof(double); i++){
        ch[i]=str[i];            // 問題となる d をコピー
        cout<<(int)ch[i]<<' ';    // バイト単位で d の表示
    }
    cout<<endl;
    cout<<"f() 関数終了\n";
//  d=0;              // これを実行すると問題無く動作します
    
    return d;            // 返すことが出来なければ、d の値がエラーの原因になります
}
int main()
{
    cout<<"メイン関数内:f() 実行前\n";
    cout<<"f() 関数戻り値:"<<f()<<endl;  // 私の環境では、返ってくることが出来ません
    cout<<"メイン関数内:f() 実行後\n";
    
    return 0;
}

処理結果:

メイン関数内:f() 実行前
f() 関数開始
エラーが発生する前に生成された d をバイト単位で表示 及びコピー
0 0 0 0 -48 78 -15 127
f() 関数終了

ここでエラーが発生して終了してしまいます。
以上から不定値を持つ d の条件によってエラーが発生する事になり、
double を不定値のままで使ってはいけない事になります。


最後に、
>因みに、かずまさんのコードに同様の確認をしたところ結果が
>
>* 処理開始
>cal 内通常処理:初期値 d=9.78541e-307
>
>となり、使用できる初期値を持っているようで、何か変化を与えるとdouble num; の初期値が変化して、
>この初期値に依存しながらエラーが出るかどうか決まってくるようです。

かずまさんのコードが難しくてどのように動作するか現在思考中なのですが、
このコードの場合不定値処理を発生させないような気がするので、
これを比較する意味がないような気がしてます。


この投稿にコメントする

削除パスワード

No.7628

Re:関数から数値を返せないでエラー
投稿者---かずま(2007/06/28 20:48:16)


> バイト毎のデータ−:d=0 0 0 0 -48 78 -15 127
CPI がリトルエンディアンなので、そのバイト列を逆順にしてつなぐと、
0x7ff14ed000000000 となります。
これは、符号が 0(正)、指数が 0x7ff、仮数が 0x14ed000000000 の
浮動小数点数を表しています。

Borland の処理系では、次のように規定されているようです。

000 0000000000000                       ゼロ
000 0000000000001 〜 000 fffffffffffff  正規化されていない数
001 0000000000000 〜 7fe fffffffffffff  正規化されている数
7ff 0000000000000                       Inf (無限大)
7ff 0000000000001 〜 7ff 7ffffffffffff  sNaN (signaling NaN 非数)
7ff 8000000000000 〜 7ff fffffffffffff  qNaN (quiet NaN 非数)

0x7ff14ed000000000 は sNaN ですから、これを操作しようとすると、
浮動小数点例外が発生し、CPU に割り込みがかかります。
割り込みをマスクするためには、main() に入ったときに
_control87(MCW_EM, MCW_EM); を実行すればよいでしょう。
#include <float.h> が必要です。	

> かずまさんのコードが難しくてどのように動作するか現在思考中なのですが、

ギャラさんのコードのほうが難しいと思います。

元の cal3() だと、cal4() の処理の後 log(cal4()) を処理しますから、
"25log34" をエラーにせず "log34" と同じ扱いにしてしまいます。

それから、cal5() の else if (...) のあとに else がないのはおかしいと思います。
"-56" に対して (不定値 - 56) の値を計算してしまいます。


この投稿にコメントする

削除パスワード

No.7629

Re:関数から数値を返せないでエラー
投稿者---ギャラ(2007/06/29 06:47:04)


かずまさん有難うございます。これですっきりしました。

コードの適切なアドバイスをもらえて嬉しいです。
今後の参考にしたいと思います。


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧





掲示板提供:(有)リアル・インテグリティ