www.mikrocontroller.net

Forum: PC-Programmierung Berechnungen mit Strings unter C


Autor: OlFi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

ich möchte mir selbst ein Matheprogramm unter C schreiben und habe mir 
selbst schon ein Textfeld programmiert in dem der Anwender eine Zeile 
eingeben kann, die berechnet werden soll.

Bsp.:
Benutzereingabe:
(12+3)*2

Nach der Eingabe steht die ganze Zeile in einem String wodurch Ziffern 
die zu einer Zahl gehören auf mehrere Felder verteilt wurden. .

Bsp.: 12
Ziffer 1 steht jetzt in Feld 1 und die Ziffer 2 in Feld 2

Mein Ziel ist es jetzt das ich mit einer Funktion jede Zahl erkenne, 
diese in einem double Feld abspeichere (in der Reihenfolge wie sie 
gefunden wurden) und die ursprüngliche Zahl im char Feld wird durch 
einen Platzhalter ersetzt.

Bsp.: (Trennstriche um die einzelnen Felder sichtbar zu machen)
char math[]
|(|1|2|+|3|)|*|2|\0|

Nach dem alle Ziffern und Zahlen erkannt wurden soll in double zahl[] 
folgendes stehen:
double zahl[]
|12|3|2|\0|

math soll anschließend folgendermaßen aussehen:
|(|a| |+|a|)|*|a|\0|

Ich habe das mit diesem Quellcode versucht zu programmieren, aber ich 
kriege dauernd Abstürze.
(Ich vermute der Fehler liegt in der pow() Zeile)
void Zahl(char *math, double *zahl)
{
  int laenge=0;
  int Schleife=0;
  int stelle=0;
  //int stelle2=0;
  int grad=0;
  int Position=0;
  int Position2=0;

  laenge=strlen(math);

  while (Schleife < laenge)
    {
      //stelle=0;
      Position2=Position;
      grad=0;
      zahl=0;

      for (int n=Schleife; n<laenge; n++)
        {
          //stelle++;
          Schleife++;    //Raussprungvariable der while Schleife

          if (math[n]>='0' && math[n]<='9')
            {
              grad++;
            }

          else if (grad != 0)
            {
              n=laenge+1;
              Position++;
            }
        }


      stelle=grad;
      //grad--;
      //stelle2=stelle-grad;
      if (Position > 0 && Position != Position2)
      {
      for (int n=Schleife-grad-1;n<Schleife-1;n++)
        {
          zahl[Position-1]=zahl[Position-1]+math[n]*pow(10,grad-1);
          zahl[Position]='\0';
          grad--;
        }

      if (zahl != 0)
        {
          math[Schleife-stelle-1]='a';
          for (int n=Schleife-stelle;n<Schleife-1;n++)
            {
              math[n]=' ';
            }
        }

      }
    }

Würde mich sehr über Hilfe freuen!

Gruß
    Olaf

Autor: Arc Net (arc)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Einige Auffälligkeiten:
1. zahl=0;
   zahl ist ein Zeiger auf double, nach der Zuweisung...

2. zahl[Position-1]=zahl[Position-1]+math[n]*pow(10.0, grad-1);
   math[n] ist so noch ASCII-Code...

3. zahl[Position] = '\0';
   ?

4. if (zahl != 0)
   wenn der Zeiger zahl hier wieder null wäre, ...

Autor: OlFi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke für die Hinweise!

Da habe ich mir glaube ich ein paar grobe Fehler geleistet.

Punkt 1. und 4. sind natürlich totaler Quatsch.

2. Da habe ich die nötige Konvertierung übersehen.

3. Ein Stringende Zeichen für ein Feld in dem eine unbestimmte Anzahl 
double Werte steht fand ich schon sinnvoll.

Gruß
    Olaf

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du zäumst das Pferd am falschen Ende auf.

Du kannst diesen Parser hier verwenden.
Er funktioniert nach dem Prinzip des rekursiven Abstiegs.
Die Hauptfunktion heist Parse. Du übergibst ihr einen
String, der den auszuwertenden Ausdruck enthält, und
kriegst als Ergebnis den Zahlenwert der diesem Ausdruck
entspricht.

Bsp:
  long Result = Parse( "(3+2)*4" );
liefert in Result den Wert 20

  long Result = Parse( "3+2)*4" );
setzt die globale Variable HaveError auf einen Wert ungleich
NO_SYNTAX_ERROR, weil in diesem Ausdruck ein Fehler enthalten ist

Achtung: Es findet ausschliesslich Ganzzahlarithmetik statt!
const char* pInput;
char  NextChar;

unsigned char HaveError;
const char* Errors[] =
{
  ") expected",
  "Number expected",
  "illegal character at or near"
};

#define NO_SYNTAX_ERROR          0xFF
#define PAREN_EXPECTED_ERROR     0
#define NUMBER_EXPECTED_ERROR    1
#define ILLEGAL_CHARACTER_ERROR  2

long Expr();

void GetNextChar()
{
  pInput++;
  while( *pInput == ' ' ||
         *pInput == '\t' ||
         *pInput == '\n' ||
         *pInput == '\r' )
    pInput++;

  NextChar = *pInput;
}

long Faktor()
{
  // Faktor := [ '-' ] ( Number | ( '(' Expression ')' ) ).

  long Result;
  unsigned char Sign = FALSE;

  if( NextChar == '-' ) {
    Sign = TRUE;
    GetNextChar();
  }

  if( NextChar >= '0' && NextChar <= '9' ) {
    Result = NextChar - '0';
    GetNextChar();
    while( NextChar >= '0' && NextChar <= '9' ) {
      Result = 10 * Result + NextChar - '0';
      GetNextChar();
    }
  }

  else if( NextChar == '(' ) {
    GetNextChar();
    Result = Expr();
    if( NextChar != ')' ) {
      HaveError = PAREN_EXPECTED_ERROR;
      Result = 0;
    }
    else
      GetNextChar();
  }

  else {
    HaveError = NUMBER_EXPECTED_ERROR;
    Result = 0;
  }

  if( Sign )
    return -Result;

  return Result;
}

long Term()
{
  // Term := Faktor { [ '*' | '/' | '%' ] Faktor }.

  long Result;
  char c;

  Result = Faktor();

  while( ( c = NextChar ) == '*' || c == '/' || c == '%' ) {
    GetNextChar();
    if( c == '*' ) {
      Result *= Faktor();
    }
    else if( c == '/' ) {
      Result /= Faktor();
    }
    else
      Result %= Faktor();
  }

  return Result;
}

long Expr()
{
  // Expression := Term { [ '+' | '-' ] Term }.

  long Result;
  char c;

  Result = Term();

  while( ( c = NextChar ) == '+' || c == '-' ) {
    GetNextChar();
    if( c == '+' ) {
      Result += Term();
    }
    else {
      Result -= Term();
    }
  }

  return Result;
}


long Parse(const char* Input)
{
  long Result;

  HaveError = NO_SYNTAX_ERROR;
  pInput = Input - 1;
  GetNextChar();

  Result = Expr();

  if( HaveError == NO_SYNTAX_ERROR && NextChar != '\0' ) {
    HaveError = ILLEGAL_CHARACTER_ERROR;
  }

  return Result;
}


Verwendet werden kann das Ganze zb. so
  char Expr[] = "( 2 + 3 ) * ( 4 / 2 )"

  long Result = Parse( Expr);

  if( HaveError != NO_SYNTAX_ERROR ) {
    char Msg[60];
    strcpy( Msg, Errors[HaveError] );
    strcat( Msg, " \'" );
    strcat( Msg, pInput );
    strcat( Msg, "\'" );

    printf( "%s\n", Msg );
  }
  else
    printf( "%ld\n", Result );

Für printf musst du natürlich dann eine Ausgabe an deine
Oberfläche einbauen.

Autor: OlFi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Karl Heinz:

Danke für den Tipp! Das wäre wirklich eine elegante Lösung, aber ich 
will im Endeffekt darauf hinaus das der Anwender eine mathematische 
Funktion mit einer Variablen (z.B. 3x+2) eingibt und das Programm diese 
zeichnet.

Natürlich könnte da auch eine Funktion wie 2/3x+(-3)*2 stehen.

Ich muß ausprobieren ob ich es schaffe so eine Gleichung in berechenbare 
Teilblöcke umzuwandeln, dann könnte ich deine Parse Funktion verwenden.

Gruß
    Olaf

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OlFi wrote:
> @Karl Heinz:
>
> Danke für den Tipp! Das wäre wirklich eine elegante Lösung, aber ich
> will im Endeffekt darauf hinaus das der Anwender eine mathematische
> Funktion mit einer Variablen (z.B. 3x+2) eingibt und das Programm diese
> zeichnet.
>
> Natürlich könnte da auch eine Funktion wie 2/3x+(-3)*2 stehen.
>
> Ich muß ausprobieren ob ich es schaffe so eine Gleichung in berechenbare
> Teilblöcke umzuwandeln, dann könnte ich deine Parse Funktion verwenden.

Den kannst du sowieso verwenden. Du musst lediglich das x noch
zur Verfügung stellen:
long Faktor()
{
  // Faktor := [ '-' ] ( Number | ( '(' Expression ')' ) | 'x' ).

  long Result;
  unsigned char Sign = FALSE;

  //
  // Schau an, schau an: Ein '-' als Vorzeichen
  //
  if( NextChar == '-' ) {
    Sign = TRUE;
    GetNextChar();
  }

  //
  // Fall: Eine hundsordinaere Zahl
  //
  if( NextChar >= '0' && NextChar <= '9' ) {
    Result = NextChar - '0';
    GetNextChar();
    while( NextChar >= '0' && NextChar <= '9' ) {
      Result = 10 * Result + NextChar - '0';
      GetNextChar();
    }
  }

  //
  // Fall: Ein Ausdruck in Klammern
  //
  else if( NextChar == '(' ) {
    GetNextChar();
    Result = Expr();
    if( NextChar != ')' ) {
      HaveError = PAREN_EXPECTED_ERROR;
      Result = 0;
    }
    else
      GetNextChar();
  }

  //
  // Fall: die einzige Variable, die in Funktionen erlaubt ist
  //       nämlich 'x'
  //       wird von der Umgebung global zur Verfügung gestellt
  //
  //       Wenn mehrere Variablen möglich sein sollen, muesste
  //       man hier eine Variablenverwaltung einbauen
  //
  else if( NextChar == 'x' ) {
    GetNextChar();
    Result = ValueOfX;
  }

  //
  // Fall: Nichst von alledem. Ein Fehler
  //
  else {
    HaveError = NUMBER_EXPECTED_ERROR;
    Result = 0;
  }

  if( Sign )
    return -Result;

  return Result;
}

Du willst also einen Funktionsplotter bauen.
OK.
D.h. du bestimmst, oder lässt dir vom Benutzer die Grenzen für X geben,
sowie eine Schrittweite.
Dann gehst du in einer Schleife die X von der unteren Grenze bis
zur oberen Grenze durch, und rufst jedesmal den Formelparser auf
(der laut oben schon für die Behandlung einer Variablen 'x' vorbereitet
wurde)

long ValueOfX;  // Variable 'x' für den Parser

int main()
{
  char Expr[] = "( 2 + 3 ) * ( 4 / 2 )"
  ...

  for( x = Minimum; x < Maximum; x += Schrittweite ) {
    ValueOfX = x;
    Result = Parse( Expr );

    PlotPoint( x, Result );
  }
  ...
}

Allerdings wäre es für einen Funktionsplotter besser, die ganzen
Berechnungen von long auf double umzustellen.

Die andere Sache ist: Klarerweise ist der Parser in der jetzigen
Form nicht optimal für einen Funktionsplotter. Es ist natürlich
eine Verschwendung von Rechenzeit, wenn die Formel jedesmal erneut
geparst werden muss, nur um sie auszuwerten.
Für einen Funktionsplotter würde man den Parser soweit umbauen,
dass er Maschinencode für eine gedachte, selbst implementierte,
fiktive CPU generiert und ein zusätzliches Programmmodul machen,
welches diese CPU implementiert und den generierten Code dann
immer wieder abarbeitet.
Aber: Soweit bist du noch nicht. Das wäre der übernächste Schritt.

Der logische nächste Schritt wäre, den Parser so wie er jetzt ist
in einen lexikalischen Scanner und den eigentlichen Parser zu trennen.
Denn: math. Standard-Funktionen stehen bereits in den Startlöchern
und ohne lexikalischen Scanner ist es eher mühsam, die Funktionsnamen
aus der Formel zu parsen. Es geht, ist aber mühsam.

  //
  // Fall: die einzige Variable, die in Funktionen erlaubt ist
  //       nämlich 'x'
  //       wird von der Umgebung global zur Verfügung gestellt
  //
  //       Wenn mehrere Variablen möglich sein sollen, muesste
  //       man hier eine Variablenverwaltung einbauen
  //
  else if( NextChar == 'x' ) {
    GetNextChar();
    Result = ValueOfX;
  }

  //
  // Fall: die Funktionen sqrt, sin
  //       Also Funtkionsnamen, die mit 's' anfangen
  //
  else if( NextChar == 's' ) {
    GetNextChar();
    if( NextChar == 'q' ) {
      GetNextChar();
      if( NextChar == 'r' ) {
        GetNextChar();
          if( NextChar == 't' ) {
            GetNextChar();

            if( NextChar == '(' ) {
              GetNextChar();
              Result = sqrt( Expr() );
              if( NextChar != ')' ) {
                HaveError = PAREN_EXPECTED_ERROR;
                Result = 0;
              }
              else
                GetNextChar();
            }
          }
        }
      }
    }

    else if( NextChar == 'i' ) {
      GetNextChar();
      if( NextChar == 'n' ) {
        GetNextChar();

        if( NextChar == '(' ) {
          GetNextChar();
          Result = sin( Expr() );
          if( NextChar != ')' ) {
            HaveError = PAREN_EXPECTED_ERROR;
            Result = 0;
          }
          else
            GetNextChar();
        }
      }
    }
  }

Sinnvollerweise wird man natürlich den Abschnitt, der einen
geklammerten Ausdruck parst und berechnet, in eine eigene
Funktion herausziehen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.