Taschenrechner Programmieren: Problem Fließkommazahlen



  • Wir haben ein Assignment bekommen, einen relativ einfachen Taschenrechner in CPP zu programmieren.

    Dieser muss die 4 Grundrechenarten beherrschen und außerdem beliebig viele Zahlen und Operatoren verarbeiten können (80er-String)
    Das Problem an der Sache ist aber erst, dass dieser auch rationale Zahlen beherrschen muss.

    BSP: 4.78*3.22+5-4.99/4.3

    Hierbei is natürlich elementare Punkt-vor-Strich-Rechnung zu beachten.

    Unser Problem hierbei dreht sich mittlerweile nur noch um die Fließkommazahlen, wir haben ein paar Ideen, wäre aber schön wenn hier vielleicht jemand eine konkrete Idee oder nen Vorschlag hat, wie man das möglichst einfach umsetzen könnte...

    Vielen Dank im Voraus

    DerDuffman



  • Mit einem RDP bekommst du Punkt-vor-strich sozusagen kostenlos dazu! 😉



  • ich kann an dieser stelle nur betonen, dass ich eigentlich in dem themengebiet nicht besonders bewandelt bin 😃

    Was genau ist ein RDP?

    Dazu sei gesagt, dass wir keine zusätzlichen "libraries" (oder wie man das nennt, aka <iostream> etc.) benutzen dürfen

    das mit der PvS is auch garnich das eigentliche Problem, wir müssen nur irgendwie die Kommazahlen in unser Array bekommen



  • RDP.
    Was für eine Funktionalität darf denn verwendet werden? Und wo liegt das Problem bei den Fließkommazahlen?



  • ich weiß nich genau was wir benutzen dürfen, wahrscheinlich nix 😃

    also außer dem standard namespace, iostream und cstrings

    Also momentan haben wir noch das Programm von nem Kollegen, der sowas ähnliches machen soll, nur ohne Double-Typen

    Wenn wir da nun ne Kommazahl einsetzen, wird das komma/punkt geflissentlich ignoriert und die Zahl 2.3 wird als 23 erkannt.

    Edit:

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

    // declaration of the functions:
    bool isNum(char c);
    bool isOperator(char c);
    int calculation(char input[81]);

    int main()
    {
    char input[81];
    cin.getline(input,81);
    cout << "\n=\n\n";
    int output = calculation(input);
    cout << output << endl << endl;

    return 0;
    }

    bool isNum(char c){ //function to determinate if a string contains numbers
    if(((int) c > 47) && ((int) c < 58)) return true;
    else return false;
    }

    bool isOperator(char c){ //function to determinate if a string contains mathematical operators
    switch ((int) c){
    case 42:
    case 43:
    case 45:
    case 47: return true;
    default: return false;
    }
    }

    int calculation(char input[81]){ //function to calculate the result
    char operators[40];
    int numbers[40];
    int counter = 0;
    int tmp = 0;
    int inputLength = strlen(input);
    for (int i = 0; i < inputLength; i++){
    if (isOperator((int) input[i])){
    operators[counter] = input[i];
    numbers[counter] = tmp;
    counter++;
    tmp = 0;
    }
    if (isNum((int) input[i])){
    tmp = 10 * tmp + (((int) input[i]) - 48);
    }
    }
    numbers[counter] = tmp;
    int solution = 0;
    for (int i = 0; i <= counter; i++){
    switch (operators[i]){
    case '-': numbers[i+1] = -numbers[i+1];
    break;
    case '*': numbers[i+1] = numbers[i] * numbers[i+1];
    numbers[i] = 0;
    break;
    case '/': if (numbers[i+1] == 0)
    cout << "Error! Division by Zero cannot be evaluated!" << endl;
    else {
    numbers[i+1] = numbers[i] / numbers[i+1];
    numbers [i] = 0;
    }
    break;
    }
    solution = solution + numbers[i];
    }
    return solution;
    }



  • Dann dürfte es vermutlich reichen den Parser dahingehend abzuändern?!



  • ich stelle mich selbst zwar ungern als idioten dar, aber was ist denn ein parser? haben wir einen? falls nich, dürfen wir wahrscheinlich auch keinen benutzen



  • Parser.

    Der Fehler ist das ihr nicht strikt genug seid:

    if (isNum((int) input[i])){ 
    tmp = 10 * tmp + (((int) input[i]) - 48); 
    }
    


  • Wenn du jetzt diese Ascii Geschichte meinst, die wollten wir eh noch rausnehmen

    Aber das löst unser Problem doch auch nich oder? Wie kriegen wir denn die Kommazahlen erkannt und als einen einzelnen Wert ins Array?



  • DerDuffman schrieb:

    Wenn du jetzt diese Ascii Geschichte meinst, die wollten wir eh noch rausnehmen

    Aber das löst unser Problem doch auch nich oder? Wie kriegen wir denn die Kommazahlen erkannt und als einen einzelnen Wert ins Array?

    Nein, das mein ich überhaupt nicht. Im Moment ist es egal, was für ein Zeichen (außer +,-,* und /) zwischen zwei Ziffern steht. Das Programm liest einfach fröhlich weiter und ignoriert alles dazwischen. Aus 4xxxx2 wird also 42 usw...

    if (isOperator((int) input[i])){ 
       operators[counter] = input[i]; 
       numbers[counter] = tmp; 
       counter++; 
       tmp = 0; 
    } 
    else if (isNum((int) input[i])){ 
       tmp = 10 * tmp + (((int) input[i]) - 48); 
    } 
    else
    {
       cout << "Syntax error...";
       return -1; // irgendwas sinnvolles zurückgeben ;)
    }
    

    Dadurch bricht das Ganze ab, sobald ein Fehler in der Syntax festgestellt wurde (allerdings auch bei Whitespaces, das wär ggf ein Spezialfall).

    Ansonsten müsst ihr halt den "." noch mitberücksichtigen. Und sobald einer (!) gefunden wurde die Nachkommastellen an tmp (=float) anfügen.



  • ich hatte sowas in der Vergangenheit schon mal probiert, aber ohne boost.spirit oder Vergleichbarem keine wirklich befriedigende Lösung gefunden.
    Da kam der Tipp von David_pb genau richtig. Nachdem ich mir den Recursive Descent Parser genau angesehen hatte und eine Stunde später kam dann das dabei heraus:

    #include <iostream>
    
    // --   liest 'C'; sonst wird 'in' auf Fehler gesetzt
    template< char C >
    std::istream& Char( std::istream& in )
    {
        char c;
        if( in >> c && c != C )
            in.setstate( std::ios_base::failbit );
        return in;
    }
    
    // --   liefert 'true' falls das Zeichen 'e' in 'in' folgt
    template< typename E, typename Traits >
    bool ctest( std::basic_istream< E, Traits >& in, E e )
    {
        if( in.good() )
        {
            const std::ctype< E >& ctype_fac = std::use_facet< std::ctype< E > >( in.getloc() );
            for( Traits::int_type m = in.rdbuf()->sgetc(); ; m = in.rdbuf()->snextc() )
            {
                if( Traits::eq_int_type( m, Traits::eof() ) )
                {
                    in.setstate( std::ios_base::eofbit );
                    break;
                }
                const char c = Traits::to_char_type( m );
                if( c == e )
                {   // --   gesuchtes Zeichen folgt im Stream
                    in.rdbuf()->sbumpc();       // Zeichen konsumieren -> ++Lesezeiger
                    return true;                // ok; Zeichen war da
                }
                if( (in.flags() & std::ios_base::skipws) == 0 || !ctype_fac.is( std::ctype< E >::space, c ) )
                    break;
            }
        }
        return false;
    }
    
    // --   Der Ausdruck-Parser
    template< typename T >
    struct Expression
    {
        typedef T value_type;
        explicit Expression( T& value ) : m_value( value ) {}
        friend std::istream& operator>>( std::istream& in, const Expression& expr )
        {
            expr.parse( in );
            return in;
        }
    private:
        void parse( std::istream& in ) const;
        T& m_value;
    };
    
    template< typename T >
    Expression< T > expression( T& value ) { return Expression< T >( value ); }
    
    // --   Der Factor-Parser
    template< typename T >
    struct Factor
    {
        explicit Factor( T& value ) : m_value( value ) {}
        friend std::istream& operator>>( std::istream& in, const Factor& x )
        {   // factor = number | "(" expression ")" .
            if( ctest( in, '(' ) )
                return in >> expression( x.m_value ) >> Char<')'>;
            return in >> x.m_value;
        }
    private:
        T& m_value;
    };
    
    // --   Der Term-Parser
    template< typename T >
    struct Term
    {
        explicit Term( T& value ) : m_value( value ) {}
        friend std::istream& operator>>( std::istream& in, const Term& x )
        {   // term = factor {("*"|"/") factor} .
            in >> Factor< T >( x.m_value );
            for( T value2;; )
            {
                if( ctest( in, '*' ) )
                {
                    if( in >> Factor< T >( value2 ) ) x.m_value *= value2;
                }
                else if( ctest( in, '/' ) )
                {
                    if( in >> Factor< T >( value2 ) ) x.m_value /= value2;
                }
                else
                    break;
            }
            return in;
        }
    private:
        T& m_value;
    };
    
    template< typename T >
    void Expression< T >::parse( std::istream& in ) const
    {   // expression = ["+"|"-"] term {("+"|"-") term} .
        const bool minus = !ctest( in, '+' ) && ctest( in, '-' ); // @Edit: Multiplikation mit -1 durch operator-() ersetzt
        in >> Term< T >( m_value );
        if( minus ) m_value = -m_value;
        for( T value2;; )
        {
            if( ctest( in, '+' ) )
            {
                if( in >> Term< T >( value2 ) ) m_value += value2;
            }
            else if( ctest( in, '-' ) )
            {
                if( in >> Term< T >( value2 ) ) m_value -= value2;
            }
            else
                break;
        }
    }
    
    int main()
    {
        using namespace std;
        cout << "Taschenrechner mit den 4 Grundrechenarten:" << endl;
        cin >> noskipws;
        for( double d; cout << "> ", cin >> ws >> expression( d ); )
            cout << " = " << d << endl;
        return 0;
    }
    

    Man erhält zum Beispiel:

    Taschenrechner mit den 4 Grundrechenarten:
    > 4.78*3.22+5-4.99/4.3
     = 19.2311
    > 6.6-(3+1.2*2)
     = 1.2
    

    Der Parser endet bei EOF oder einem fremden Zeichen (hier auch ein Whitespace also z.B. LF). Sollen innerhalb des Ausdrucks auch Whitespaces zugelassen werden, so ist die main-Funktion wie folgt zu ändern.

    int main()
    {
        using namespace std;
        cout << "Taschenrechner mit den 4 Grundrechenarten (Ausdruck mit ';' beenden):" << endl;
        for( double d; cout << "> ", (cin >> expression( d )).ignore( 999, '\n' ); )
            cout << " = " << d << endl;
        return 0;
    }
    

    Beispiel:

    Taschenrechner mit den 4 Grundrechenarten (Ausdruck mit ';' beenden):
    > 3 + 1.4* -2;
     = 0.2
    

    Gruß Werner


Anmelden zum Antworten