質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.80%

[C] 入力数値が不安定

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 279

Gurt

score 14

前提・実現したいこと

逆ポーランド記法で入力された数式の計算を行いたいと考えています。
また、制約としてスタックを用いて実装し、そのスタックは連結リストを用いて実装するというものです。
試作段階としてスタックのみを使用して実装した逆ポーランド記法の計算は問題なく計算できるのですが、スタックを連結リストで実装した際に、入力した数値が小数点以下6位や7位あたりで少しずれてしまい、計算結果が大きく変わってしまいました。数値の変動はGDBで確認しました。
入力方法として逆ポーランド記法の数式を文字列として入力しました。その際、数値と演算子の後にはスペースを入れています。これはスペースまでを一区切りにし、演算子だった場合はその演算子の演算を行い、数値だった場合はatofで数値へ戻しています。

これは、単純にプログラムが間違っているのかC言語やPCの特性なのかそのほかの要因なのか教えていただきたいと考えています。

該当のソースコード

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define STACK_SIZE 8
#define DATA_SIZE 64

struct cell{
    float value;
    struct cell *next;
};

struct cell *head = NULL;
struct cell **p;

bool first_flag = false;

int PostfixNotation(char pnData_c[STACK_SIZE]);
int Push(float data);
int Pop(float *data, int n);
void InsertCell(struct cell **p_pointer, float new_value);
float DeleteCell(struct cell **p_pointer, int n);

int main(){
    char pnData[64];
    float ans = 0;
    int n = 0;

    printf("input data(input space after number and operator)\n");
    scanf("%[^\n]%*c", &pnData);

    PostfixNotation(pnData);

    Pop(&ans, n - 1);
    printf("ans : %.2f\n", ans);

    return 0;
}

int PostfixNotation(char pnData_c[STACK_SIZE]){
    char temp[8] = "";
    int i = 0, j = 0, n = 0;
    float pnData_f, first = 0, second = 0;
    while(pnData_c[i] != '\0'){
        if(pnData_c[i] == ' '){
            if(pnData_c[i - 1] == '+'){
                Pop(&second, n - 1);
                n--;
                Pop(&first, n - 1);
                n--;
                Push(first + second);
            }else if(pnData_c[i - 1] == '-'){
                Pop(&second, n - 1);
                n--;
                Pop(&first, n - 1);
                n--;
                Push(first - second);
                n++;
            }else if(pnData_c[i - 1] == '*'){
                Pop(&second, n - 1);
                n--;
                Pop(&first, n - 1);
                n--;
                Push(first * second);
                n++;
            }else if(pnData_c[i - 1] == '/'){
                Pop(&second, n - 1);
                n--;
                Pop(&first, n - 1);
                n--;
                Push(first / second);
                n++;
            }else{
                pnData_f = atof(temp);
                Push(pnData_f);
                n++;
            }
            j = 0;
            i++;
            for(int k = 0; k < STACK_SIZE; k++){
                temp[k] = '\0';
            }
        }
        temp[j] = pnData_c[i];
        j++;
        i++;
    }
}

int Push(float data){
    if(!first_flag){
        p = &head;
        first_flag = true;
    }
    InsertCell(p, data);
    p = &((*p)->next);
    return 1;
}

int Pop(float *data, int i){
    p = &head;
    *data = DeleteCell(p, i - 1);
    return 1;
}

void InsertCell(struct cell **p_pointer, float new_value){
    struct cell* new_cell;
    new_cell = malloc(sizeof(struct cell));
    new_cell->value = new_value;
    new_cell->next = *p_pointer;
    *p_pointer = new_cell;
}

float DeleteCell(struct cell **p_pointer, int i){
    struct cell *delete_cell;
    float pop_data = 0;
    while(i > 0 && *p_pointer != NULL){
        p_pointer = &((*p_pointer)->next);
        i--;
    }
    delete_cell = *p_pointer;
    pop_data = delete_cell->value;
    *p_pointer = delete_cell->next;
    free((void *)delete_cell);
    return pop_data;
}

入力例として
(1.7 + 2.8) * (2.5 - 4.7) + (-1)
を計算する場合は
1.7 2.8 + 2.5 4.7 - * -0.1 + 
を入力しています。(最後の+の後にもスペースが入っています)

試作段階のスタックのみを使用したプログラム

#include <stdio.h>
#include <stdlib.h>

#define STACK_SIZE 8

float stack[STACK_SIZE];
int sp = 0;

int Push(float data);
int Pop(float *data);
int PostfixNotation(char pnData_c[STACK_SIZE]);

int main(){
    char pnData[64];
    float ans = 0;

    printf("input data(input space after number and operator)\n");
    scanf("%[^\n]%*c", &pnData);

    PostfixNotation(pnData);

    Pop(&ans);
    printf("ans : %.2f\n", ans);

    return 0;
}

int Push(float data){
    if(sp >= STACK_SIZE){
        return 0;
    }else{
        stack[sp] = data;
        sp++;
        return 1;
    }
}

int Pop(float *data){
    if(sp <= 0){
        return 0;
    }else{
        sp--;
        *data = stack[sp];
        return 1;
    }
}

int PostfixNotation(char pnData_c[STACK_SIZE]){
    char temp[8] = "";
    int i = 0, j = 0;
    float pnData_f, first = 0, second = 0;
    while(pnData_c[i] != '\0'){
        if(pnData_c[i] == ' '){
            if(pnData_c[i - 1] == '+'){
                Pop(&second);
                Pop(&first);
                Push(first + second);
            }else if(pnData_c[i - 1] == '-'){
                Pop(&second);
                Pop(&first);
                Push(first - second);
            }else if(pnData_c[i - 1] == '*'){
                Pop(&second);
                Pop(&first);
                Push(first * second);
            }else if(pnData_c[i - 1] == '/'){
                Pop(&second);
                Pop(&first);
                Push(first / second);
            }else{
                pnData_f = atof(temp);
                Push(pnData_f);
            }
            j = 0;
            i++;
            for(int k = 0; k < STACK_SIZE; k++){
                temp[k] = '\0';
            }
        }
        temp[j] = pnData_c[i];
        j++;
        i++;
    }
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

ええと、根本的な原因は、連結リスト版でスタックの実装をミスっていることです。
1 2 - 」とか「1 2 / 」とかを試してみれば分かりますが、LIFO(FILO)が実現できていません。

連結リストを使ったスタックは、提示されているコードよりももっと単純に書けます。

Push: headに新たな値を持ったセルを挿入する(現在のheadのセルがnextになる)。
Pop:  headのセルの値を取り出し、そのセルを削除する(nextのセルがheadになる)。

これだけです。連結リスト内を走査する必要はありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/06 14:57

    横から失礼します。
    float型見て安易に丸め誤差と回答した自分が恥ずかしいです。
    コードをちゃんと読まないとダメですね。

    キャンセル

  • 2020/08/06 18:49

    スタックのpush,popはそのように実装することができるのですね。
    確かに詳しく調べてみると計算の順序が関係ない加算乗算はできていましたが、減算除算ができていませんでした。
    参考にした教本では、pushは配列にただ追加して配列の番号をポインタのようにpopで参照するというものでした。
    そのため連結リストの挿入と削除においてイメージの差異が生まれたのだと思います。
    もう一度やり直してみます。
    完全に解決したらBAにさせていただきます。

    キャンセル

  • 2020/08/08 05:38

    すみません、お待たせいたしました。
    無事に解決できました。

    キャンセル

0

float使っているので浮動小数点の丸め誤差が発生したのでは?
doubleにすれば少し精度が高くなりますが、、、

以下をご覧ください
FLP02-C. 精度の高い計算が必要な場合は浮動小数点数の使用を避ける
C言語 計算の誤差

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/06 14:46

    連結リストを使う前はたまたまfloatでも計算できていたということなのですかね・・・
    doubleにしても計算結果変わらずでした。
    ただ、GDBでみた数値はfloatよりはブレ幅が小さいように感じました。
    他に何か要因があるのでしょうか・・・

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る