[C] Gleichungen lösen - Equation Solver

Hi.
Ich dachte auch schon darüber nach, die Funktionen mit der Ungarischen Notation zu benennen.
Ernsthaft? :eek:
Warum ich die Ungarische Notation überhaupt anwende liegt meines Erachtens auf der Hand:
1. Ich habe ständig vor Augen was das für eine Variable is, was sie für einen Sinn hat und wofür ich sie brauche
Warum mußt du dafür aber einen kryptischen Code verwenden? Benenne die Variablen doch einfach richtig.
2. Keine Type-Mismatching
Das verhinderst du nur wenn du die Variablen richtig typisierst, nicht indem du sie benennst. (wobei man es natürlich an dem Präfix-Code feststellen kann).
3. Compiler-Errors stören mich irgendwie, wenn sie wegen solchen Kleinigkeiten auftreten
Dafür ist der Compiler doch da. Du machst dir zuviel Arbeit.
4. Das Projekt ist transparenter, und ich kann mich besser wieder einfinden.
Gut, wenn dir das wirklich hilft.
Aber ich glaube, über das Thema lässt sich ewig diskutieren.
Allerdings. :)

http://www.ddj.com/cpp/184403804

Gruß

\edit: Zurück zum Thema. Ich hab mal einen kleinen Testtreiber geschrieben:
C++:
extern "C" {
#include "termcalc.h"
}
#include <cstdio>

int testnr = 1;

#define TEST_EVAL(expr)  test_calc(#expr, (expr))

#define RED   "\x1b[31m"
#define GREEN "\x1b[32m"
#define BOLD  "\x1b[1m"
#define RESET "\x1b[0m"

void test_calc(char* str, double result) {
    double r;
    int code = 0;
    printf("TEST %2d %15s ", testnr++, str);

    r = calcString(str, &code);

    if (code == 0) {
        printf("= %# 9.4g %5s %s\n" RESET,
               r, "...",
               (r == result) ? (BOLD GREEN "PASS") : (BOLD RED "FAIL"));
    } else {
        printf(BOLD RED "%17s FAILED. (Error: %d)\n" RESET, "...", code);
    }
}

void test_fail(char* str) {
    double r;
    int code;
    printf("TEST %2d %15s", testnr++, str);

    r = calcString(str, &code);
    if (code == 0) {
        printf(" = %# 9.4g %5s " BOLD RED "XPASS\n" RESET, r, "...");
    } else {
        printf(BOLD GREEN "%7s XFAIL\n" RESET, "...");
    }
}

int main(int argc, char *argv[])
{
    TEST_EVAL(3 + 3);
    TEST_EVAL(5);
    TEST_EVAL(33 - 5 * 4);
    TEST_EVAL((3 - 4) * 5);
    TEST_EVAL(64 / 8 + 1);
    TEST_EVAL(-2 + 4);
    TEST_EVAL(-2 + 4 / 2);
    TEST_EVAL((-2) + 3);
    TEST_EVAL(3 + (-2));
    TEST_EVAL(3.5e11 + 33);

    test_fail("(3 + 2");
    test_fail("3 + ");
    test_fail("3 # 5");
    test_fail("");
}
Ergebnis:
Code:
TEST  1           3 + 3 =     6.000   ... PASS
TEST  2               5 =     5.000   ... PASS
TEST  3      33 - 5 * 4 =     13.00   ... PASS
TEST  4     (3 - 4) * 5               ... FAILED. (Error: 2)
TEST  5      64 / 8 + 1 =     9.000   ... PASS
TEST  6          -2 + 4 =     2.000   ... PASS
TEST  7      -2 + 4 / 2 =    0.0000   ... PASS
TEST  8        (-2) + 3               ... FAILED. (Error: 2)
TEST  9        3 + (-2)               ... FAILED. (Error: 2)
TEST 10     3.5e11 + 33 =  3.500e+011 ... PASS
TEST 11          (3 + 2 =     2.000   ... XPASS
TEST 12            3 +  =     3.000   ... XPASS
TEST 13           3 # 5 =     3.000   ... XPASS
TEST 14                 =    0.0000   ... XPASS
Wobei die FAILED Fälle wieder mal auf undefiniertes Verhalten hinauslaufen.

Gruß
 
Zuletzt bearbeitet:
Zu deinen Tests:
Mir scheint du verwendest eine version des headers die etwas älter ist.
Wieso des Headers, da steht doch überhaupt nichts relevantes drin?
Was mir auffällt ist, dass du bei den Klammern keine Leerzeichen gemacht hast. Das geht so nicht.
Warum muss man denn da Leerzeichen setzen? :-o Das ist etwas unintuitiv.

Gruß

PS: Du solltest übrigens const char* als Parametertyp verwenden, Stringliterale sind nicht änderbar.
 
Hey,

da ich schon länger wieder kein C mehr programmiert hab und heute ohnehin noch ausnüchtern muss und nichts wirklich produktives schaffe, hab ich eben auch mal zwei Funktionen geschrieben, die eine Formel nehmen, parsen und evaluieren. Einschränkungen sind jedoch dass ausschließlich positive Ganzzahlen (dank strtol) geparsed werden können und kein "präfix-minus" erlaubt ist.

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>

#define TOK_NUMBER 1
#define TOK_OPERATOR 2

typedef struct {
    int type;
    int value;
} token_t;

int get_next_char( const char **data ) {
    while( **data && isspace( **data ) )
        ++*data;
    
    return **data;
}

int strpos( const char *str, char c ) {
    const char *p = strchr(str, c);
    if( p == 0 )
        return -1;
    
    return p - str;
}

static const char *operators = "+-/*^";
static const char *precedence = "+-/*^(";

bool parse( const char *expr, token_t **tokenptr ) {
    token_t *tokens = malloc( sizeof(token_t) * (strlen(expr) + 1) );
    memset( tokens, 0, sizeof(token_t) * (strlen(expr) + 1) );
    *tokenptr = tokens;
    
    int *stack = malloc( sizeof(int) * (strlen(expr) + 1) );
    int *stackbase = stack;
    *stack = 0;
    
    char c, *error = NULL;
    bool number_expected = true;
    while( c = get_next_char( &expr ) ) {
        if( isdigit(c) ) {
            if( !number_expected ) {
                error = "number token not allowed here";
                break;
            }
            
            tokens->type = TOK_NUMBER;
            tokens->value = strtol( expr, (char**)&expr, 0 );
            tokens++;
            
            number_expected = false;
        } else
        if( strchr( operators, c ) ) {
            if( number_expected ) {
                error = "operator not allowed here";
                break;
            }
            
            while( *stack && strchr(operators, *stack) ) {
                int p1 = strpos( precedence, c );
                int p2 = strpos( precedence, *stack );
                
                if( p1 <= p2 ) {
                    tokens->type = TOK_OPERATOR;
                    tokens->value = *stack--;
                    tokens++;
                    
                    continue;
                }
                
                break;
            }
            
            *++stack = c;
            
            expr++;
            number_expected = true;
        }
        else
        if( c == ')' ) {
            while( *stack && *stack != '(' ) {
                tokens->type = TOK_OPERATOR;
                tokens->value = *stack--;
                tokens++;
            }
            
            if( !*stack ) {
                error = "unexpected right parenthesis";
                break;
            }
            
            stack--;
            expr++;
        }
        else
        if( c == '(' ) {
            *++stack = c;
            expr++;
        } else {
            error = "unexpected character occured";
            break;
        }
    }
    
    if( !error && number_expected ) {
        error = "number expected";
    }
    
    while( *stack && !error ) {
        if( *stack == '(' ) {
            error = "mismatched parenthesis";
            break;
        }
        
        tokens->type = TOK_OPERATOR;
        tokens->value = *stack;
        tokens++;
        
        stack--;
    }
    
    free( stackbase );
    
    if( error ) {
        fprintf( stderr, "error occured near '%s': %s\n",
            *expr ? expr : "end of expression",
            error );
        
        free( *tokenptr );
        return false;
    }
    
    return true;
}

typedef double value_type;

value_type evaluate( const token_t* tokens ) {
    int n = 0;
    while( (tokens + n)->type )
        n++;
    
    value_type *stackbase;
    value_type *stack = stackbase = malloc( sizeof(value_type) * (n + 1) );
    value_type result;
    
    const token_t *iter = tokens;
    for( ; n > 0; n--, iter++ ) {
        switch( iter->type ) {
            case TOK_NUMBER:
                *++stack = iter->value;
                break;
            
            case TOK_OPERATOR:
                switch( iter->value ) {
                    case '-':
                        result = *(stack - 1) - *stack;
                        break;
                        
                    case '+':
                        result = *(stack - 1) + *stack;
                        break;
                    
                    case '*':
                        result = *(stack - 1) * *stack;
                        break;
                    
                    case '/':
                        result = *(stack - 1) / *stack;
                        break;
                    
                    case '^':
                        result = pow( *(stack - 1), *stack );
                        break;
                }
                
                *--stack = result;
                break;
        }
    }
    
    result = *stack;
    free( stackbase );
    
    return result;
}

int main( int argc, const char *argv[] ) {
    if( argc != 2 ) {
        fprintf( stderr, "usage: %s expression\n", argv[0] );
        return 1;
    }
    
    token_t *tokens;
    const char *expr = argv[1];
    if( parse( expr, &tokens ) ) {
        double result = evaluate( tokens );
        printf( "%s = %1.2f\n", expr, result );
        free( tokens );
    }
    
    return 0;
}
 
Hey.

Du hast sowas vermutich noch nie gemacht oder? Das sieht man ein wenig, es wirkt alles recht selbst ausgedacht und nach learning-by-doing... Das find ich sehr gut, so lernt man gut selbst solche Probleme anzugehen und zu bewältigen. Natürlich sollte man sich danach dann aber auch noch anschauen, wie sowas normalerweise gelöst wird bzw. wie andere das gelöst haben, um noch mehr zu lernen.

Ansonsten zum Code:
  • benutz Konstanten oder ähnliches. Du hast höftes sowas wie: (*code) = 9; oder so sehen um einen Fehler zu kennzeichnen. Aber niemand weiß wofür diese 9 steht bzw. was dass für ein Fehler ist. Besser wäre sowas wie *code = OPERATOR_NOT_EXPECTED; oder so
  • deine Variablenbenennung gefällt mir immer noch nicht ;)
  • wenn du schon Kommentare schreibst für schließende geschweifte Klammern, dann halt sie auch aktuell. Ich hab eben schon welche gefunden, wo das if bei der öffnenden Klammer nachträglich geändert wurde, du aber den Kommentar nicht aktualisiert hast
  • verbesser dein Englisch oder Kommentier auf deutsch, es ist teilweise wirklich schwer zu verstehen, was du sagen möchtest.
  • Verwende die Funktionen der Standardbibliotek. zB. finde ich gleich mehrere stellen, wo du strchr oder memmove nutzen könntest.

Whitespaces jeglicher Art werden einfach übersprungen. Dafür ist die Methode
C:
int get_next_char( const char **data ) {
    while( **data && isspace( **data ) )
        ++*data;
    
    return **data;
}


So, nun mit Kommazahlen und negativen Zahlen.
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>

#define TOK_NUMBER 1
#define TOK_OPERATOR 2

typedef struct {
    int type;
    char opcode;
    double value;
} token_t;

char get_next_char( const char **data ) {
    while( **data && isspace( **data ) )
        ++*data;
    
    return **data;
}

char peek_next_char( const char *data ) {
    return get_next_char( &data );
}

int strpos( const char *str, char c ) {
    const char *p = strchr(str, c);
    if( p == 0 )
        return -1;
    
    return p - str;
}

static const char *operators = "+-/*^";
static const char *precedence = "+-/*^n(";

double parse_number( const char **expr ) {
    if( **expr == '0' && tolower( *(*expr + 1) ) == 'x' )
        return strtol( *expr, (char**)expr, 0 );
    
    return strtod( *expr, (char**)expr );
}

bool parse( const char *expr, token_t **tokenptr ) {
    token_t *tokens = malloc( sizeof(token_t) * (strlen(expr) + 1) );
    memset( tokens, 0, sizeof(token_t) * (strlen(expr) + 1) );
    *tokenptr = tokens;
    
    int *stack = malloc( sizeof(int) * (strlen(expr) + 1) );
    int *stackbase = stack;
    *stack = 0;
    
    char c, *error = NULL;
    bool number_expected = true;
    while( c = get_next_char( &expr ) ) {
        if( isdigit(c) ) {
            if( !number_expected ) {
                error = "number token not allowed here";
                break;
            }
            
            tokens->type = TOK_NUMBER;
            tokens->value = parse_number(&expr);
            tokens++;
            
            number_expected = false;
        } else
        if( strchr( operators, c ) ) {
            if( number_expected ) {
                if( strchr( "-", c ) ) {
                    c = 'n';
                } else {
                    error = "operator not allowed here";
                    break;
                }
            }
            
            while( *stack && (strchr(operators, *stack) || *stack == 'n') ) {
                int p1 = strpos( precedence, c );
                int p2 = strpos( precedence, *stack );
                
                if( p1 <= p2 ) {
                    tokens->type = TOK_OPERATOR;
                    tokens->opcode = *stack--;
                    tokens++;
                    
                    continue;
                }
                
                break;
            }
            
            *++stack = c;
            
            expr++;
            number_expected = true;
        }
        else
        if( c == ')' ) {
            while( *stack && *stack != '(' ) {
                tokens->type = TOK_OPERATOR;
                tokens->opcode = *stack--;
                tokens++;
            }
            
            if( !*stack ) {
                error = "unexpected right parenthesis";
                break;
            }
            
            stack--;
            expr++;
        }
        else
        if( c == '(' ) {
            *++stack = c;
            expr++;
        } else {
            error = "unexpected character occured";
            break;
        }
    }
    
    if( !error && number_expected ) {
        error = "number expected";
    }
    
    while( *stack && !error ) {
        if( *stack == '(' ) {
            error = "mismatched parenthesis";
            break;
        }
        
        tokens->type = TOK_OPERATOR;
        tokens->opcode = *stack;
        tokens++;
        
        stack--;
    }
    
    free( stackbase );
    
    if( error ) {
        fprintf( stderr, "error occured near '%s': %s\n",
            *expr ? expr : "end of expression",
            error );
        
        free( *tokenptr );
        return false;
    }
    
    return true;
}

double evaluate( const token_t* tokens ) {
    int n = 0;
    while( (tokens + n)->type )
        n++;
    
    double *stackbase;
    double *stack = stackbase = malloc( sizeof(double) * (n + 1) );
    double result;
    
    const token_t *iter = tokens;
    for( ; n > 0; n--, iter++ ) {
        switch( iter->type ) {
            case TOK_NUMBER:
                printf( "%1.2f ", iter->value );
                
                *++stack = iter->value;
                break;
            
            case TOK_OPERATOR:
                printf( "%c ", iter->opcode );
                
                switch( iter->opcode ) {
                    case '-':
                        result = *(stack - 1) - *stack;
                        break;
                        
                    case '+':
                        result = *(stack - 1) + *stack;
                        break;
                    
                    case '*':
                        result = *(stack - 1) * *stack;
                        break;
                    
                    case '/':
                        result = *(stack - 1) / *stack;
                        break;
                    
                    case '^':
                        result = pow( *(stack - 1), *stack );
                        break;
                    
                    case 'n':
                        result = -*stack;
                        stack++;
                        break;
                }
                
                *--stack = result;
                break;
        }
    }
    
    printf( "\n" );
    
    result = *stack;
    free( stackbase );
    
    return result;
}

int main( int argc, const char *argv[] ) {
    if( argc != 2 ) {
        fprintf( stderr, "usage: %s expression\n", argv[0] );
        return 1;
    }
    
    token_t *tokens;
    const char *expr = argv[1];
    if( parse( expr, &tokens ) ) {
        double result = evaluate( tokens );
        printf( "%s = %1.2f\n", expr, result );
        free( tokens );
    }
    
    return 0;
}
 
Zuletzt bearbeitet:
Ich werde die Kommentare noch einmal durcharbeiten. Das problem ist, dass ich in meinem kopf genau weiß, was ich da mache, aber es nicht einmal auf deutsch gescheit ausdrücken kann.

Das kann meiner Meinung nach absolut nicht sein. Wenn du richtig gut programmieren kannst kannst du in eins, zwei Worten direkt verständlich schreiben was dort gemacht wird.

MFG
 
Was gut hilft ist auch "comment first". Also vorher einen Kommentar schreiben was eine Methode genau tut und danach die Methode oder die logische Code Einheit (gibts sowas? LCU?) schreiben.
Außerdem muss nicht jede Addition&Co oder so kommentiert werden, sondern lieber sinnvolle kleine Code abschnitte. Also schlecht:
C:
   // x im Array speichern
   stack[pos] = x;
   // stack position um eins erhöhen
   pos += 1;

besser
C:
    // Aktuellen Operator auf dem Operatoren-Stack ablegen
    stack[pos] = x;
    pos += 1;

Ich hab jetzt allerdings nicht geschaut ob du das so machst. Aber das ist nen Fehler den man gerne tut wenn sich beim Kommentieren denkt "Viel hilft viel". So kann man die Kommentare lesen und weiß warum etwas getan wird, und nicht wie... Weil das steht ja schon im Code.
 
Zurück