1

I hope you are having a nice day. Thank you for taking the time to read my question. I am still a beginner in the C language. My professor has asked me to program a scientific calculator. The calculator should be able to read and store user-defined variables and work with them. for example, I should be able to enter a=5 b=9 etc. after that the program should calculate, for instance, a+1= 6, or a+b=14 and show it to the user. Of course, the user decides if the operation is addition, subtraction, division or multiplication. The user should also be able to enter such input: e.g. c=5+9. I have started working on the calculator, unfortunately, I have just been able to only allow the user to define one variable at a time and work with it.

For example:
a=7
7+a=14

That's all I could do. I asked my professor for help and he keeps telling me that I have to to teach the program how to separate between what is before the "=" and what's after it. Thank you in advance for every help or piece of advice you give This is the code I came up with

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

int main() {
    char situation;
    float operand1, operand2;
    char i = 0;
    char ch;
    char op;
    int operand = 0;
    int j, digit, number[10] = {};
    int j2 = 0;
    char str[100] = { 0 };
    while (1) {
        printf("\n\na) To calculate choose me :)");
        printf("\n\nb) If you want to calculate with variables, choose me:");
        printf("\n\nc) To Exit choose me :(\n\n");
        scanf_s("%c", &situation, 1);
        while ((getchar()) != '\n');
        switch (situation) {
        case 'a':
            printf("type in some arithmetic terms : \n");
            scanf_s("%f", &operand1);
            scanf_s("%c", &op, 1);
            scanf_s("%f", &operand2);
            while ((getchar()) != '\n');
            switch (op) {
            case '+':
                printf("\n\n%.2f + %.2f = %.2f\n\n", operand1, operand2, operand1 + operand2);
                break;

            case '-':
                printf("\n\n%.2f - %.2f = %.2f\n\n", operand1, operand2, operand1 - operand2);
                break;

            case '*':
                printf("\n\n%.2f * %.2f = %.2f\n\n", operand1, operand2, operand1 * operand2);
                break;

            case '/':
                printf("\n\n%.2f / %.2f = %.2f\n\n", operand1, operand2, operand1 / operand2);
                break;

            default:
                printf("\n\nERROR!\n\n");
            }
            break;

        case 'b':
            printf("\n\nTo return to the main menu please enter any capital letter of your choosing\n\n");
            printf("\n\nWhen calculating with a variable, please always write the variable on the right side, Thank you. You may start:\n\n");
            do {
                scanf_s("%s", &str, 99);

                for (i = 0; i < 100; i++) {
                    if (str[i] == '=') {
                        for (j = 0; j < strlen(str); j++) {
                            ch = str[j];
                            if (ch >= '0' && ch <= '9') {
                                digit = ch - '0';
                                number[j2] = j2 * 10 + digit;
                                //printf("%d", number);
                            }
                        }
                        scanf_s("%d", &operand);
                        scanf_s("%c", &op, 1);
                        scanf_s("%d", &number[j2]);
                        while ((getchar()) != '\n');
                        switch (op) {
                        case '+':
                            printf("\n\n%d + %c = %d\n\n", operand, str[0], operand + number[j2]);
                            break;

                        case '-':
                            printf("\n\n % d - % c = % d\n\n", operand, str[0], operand - number[j2]);
                            break;

                        case '*':
                            printf("\n\n % d * % c = % d\n\n", operand, str[0], operand * number[j2]);
                            break;

                        case '/':
                            printf("\n\n % d / % c = % d\n\n", operand, str[0], operand / number[j2]);
                            break;

                        default:
                            printf("\n\nERROR!\n\n");
                        }
                        break;
                    }

                }
            } while (islower(str[0]));
            while ((getchar()) != '\n');
            break;
        case 'c':
            printf("\n\goodbye\n\n");
            exit(0);
            break;
        default:
            printf("\n\nThis is not an acceptable input. Please Try again!");
        }
    }
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
Charbel
  • 13
  • 2
  • 2
    You have told a story, rather than ask a specific question. – paddy Jul 02 '22 at 11:27
  • Apart from the "variable storage" I would a) use `double` because `float` can't accurately hold the typical 8 digits of even a basic calculator, b) put a space before `%c` format spec to make the code forgiving of spaces in the input, c) remove the `getchar()` kludges, preferably understand how whitespace is handled. – Weather Vane Jul 02 '22 at 11:33
  • 2
    Essentially, what you need is a parser and I woudl STRONGLY advice you to separate user input from parsing. – klutt Jul 02 '22 at 11:41
  • 1
    stop using `scanf()` (or `scanf_s()`). Read user input with `fgets()` then parse it (with `sscanf()` or, **much better**, with a parser). – pmg Jul 02 '22 at 11:41
  • To make this work at all, I suspect you're going to need a more general approach to parsing the input expressions. Right now, your program has a big fat assumption firmly baked into the middle of it, namely that all expressions are of the form " ". So you've got no way to do " = " or " = " or " ". – Steve Summit Jul 02 '22 at 11:57
  • But in answer to your question: The way to add variables to your calculator is in two or three main parts. (1) Rig it up so that anywhere your parser expects a number, it can accept a number of a variable name. (That is, a subexpression is " ".) (2) Add a new operator: '='. Then rig it up so that a subexpression of the form " = – Steve Summit Jul 02 '22 at 12:01
  • And then the third thing is that you'll need some kind of a [*symbol table*](https://en.wikipedia.org/wiki/Symbol_table) associating the names of variables and the values they currently hold. That will be much easier at first if you limit variable names to being single alphabetic characters, `a` through `z`. – Steve Summit Jul 02 '22 at 12:01
  • For information on one way of writing the kind of "more general" parser I was talking about, see [this answer](https://stackoverflow.com/questions/14472680#66104290). – Steve Summit Jul 02 '22 at 12:05
  • Or a simpler approach would be: (1) Read a line of input from the user as a string. (2) See if the line begins with a variable name followed by an `=`. If so, remember that for later, and consider only the text to the right of the `=` as the expression to be parsed in the next step. (3) Take the rest of the line and split it into a string, an operator character, and a string. (4) If either string is numeric, use it as a number. (5) If either string is alphabetic, look up its value in your symbol table. (6) Evaluate your expression. (7) If you saw a `=` back in step 2, complete the assignment. – Steve Summit Jul 02 '22 at 12:24
  • 1
    But the other thing, as other posters have alluded to, is that it's time to graduate beyond `scanf`, which is a toy function suitable for only very beginning programs. What you've got here is a more advanced program, and more advanced programs require more advanced input techniques. `scanf` will never do. See [What can I use for input conversion instead of scanf?](https://stackoverflow.com/questions/58403537). – Steve Summit Jul 02 '22 at 12:26
  • Thank you everyone for your pieces of advice. – Charbel Jul 04 '22 at 15:49
  • @Charbel Tip: `"%.17g"` gives a more informative output than `"%.2f"`. – chux - Reinstate Monica Jul 04 '22 at 16:50

1 Answers1

1

There are multiple problems in your code:

  • int number[10] = {}; is an invalid initializer in C. You should write:

      int j, digit, number[10] = { 0 };
    
  • you should use double instead of float

  • scanf_s("%c", &situation, 1); is not portable: the Microsoft version of this function expects the size argument 1 as an UNSIGNED whereas the Standard C function defined as optional in Annex K specifies that the size argument 1 must be passed as a size_t, hence as (size_t)1. Avoid using this function and read user input as a line with fgets() and use the standard function sscanf() instead and do test the return value to detect and report invalid and/or missing input.

    Add these lines before including <stdio.h> at the top of your source file to prevent compiler warnings:

    #ifdef _MSC_VER
    #define _CRT_SECURE_NO_WARNINGS
    #endif
    
  • scanf_s("%c", &op, 1); does not skip white space, such as spaces and newlines. You should write

      scanf(" %c", &op);
    
  • while ((getchar()) != '\n'); is risky: this loop will run forever if the end of file occurs before a newline can be read.

  • instead of j < strlen(str), which may rescan the string at every iteration, you should write:

      for (j = 0; str[j] != '\0'; j++)
    
  • i is used as an index, it should have type int or size_t, not char.

  • the format string in printf("\n\n % d - % c = % d\n\n", ...); is incorrect: the space between the % and the c is not supported. You probably meant to write this anyway:

      printf("\n\n%d - %c = %d\n\n", operand, str[0], operand - number[j2]);
    
  • printf("\n\goodbye\n\n"); is incorrect: \g is an invalid escape sequence. You should write:

      printf("\n\nGoodbye\n\n");
    

Here is a modified version using functions to parse the line and handle variable assignment separately from evaluating expressions:

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 26 global variables */
double variables['z' - 'a' + 1];

/* the function skip_spaces() updates `*pos`, skipping any white 
 * space in `str` at the corresponding index
 */
void skip_spaces(const char *str, int *pos) {
    while (isspace((unsigned char)str[*pos]))
        *pos += 1;
}

/* the function trim_spaces() updates `*pos`, skipping any white
 * space in `str` at the corresponding index. In addition, it
 * removes trailing white space from the string, ie: newlines,
 * spaces, TABs and other white space characters 
 */
void trim_spaces(char *str, int *pos) {
    int len = strlen(str);
    while (len > *pos && isspace((unsigned char)str[len - 1]))
        str[--len] = '\0';
    skip_spaces(str, pos);
}

/* the function parse_operand() reads the next operand from `str`
 * at index `*pos`. It recognises floating point numbers and
 * variable names, which are replaced with their value. The value is
 * stored into `*value`. `*pos` is updated past the operand and any
 * white space after it.
 */
int parse_operand(char *str, int *pos, double *value) {
    char *endp;
    skip_spaces(str, pos);
    char ch = str[*pos];
    if (ch >= 'a' && ch <= 'z') {
        *value = variables[ch - 'a'];
        *pos += 1;
        skip_spaces(str, pos);
        return 1;  // variable
    }
    *value = strtod(str + *pos, &endp);
    if (endp > str + *pos) {
        *pos = endp - str;
        skip_spaces(str, pos);
        return 2;  // number
    }
    return 0;
}

/* parse_expression: parse an expression with basic operators,
 * no precedence: the function expects at least one operand and
 * keeps parsing and evaluating as long as there is a supported
 * operator that follows. The result is stored into `*result`.
 */
int parse_expression(char *str, int *pos, double *result) {
    double operand2;
    char op;

    if (!parse_operand(str, pos, result)) {
        printf("missing operand: %s\n", str + *pos);
        return 0;
    }
    while ((op = str[*pos]) == '+' || op == '-' || op == '*' || op == '/' || op == '%') {
        *pos += 1;
        if (!parse_operand(str, pos, &operand2)) {
            printf("missing operand: %s\n", str + *pos);
            return 0;
        }
        switch (op) {
        case '+':
            *result += operand2;
            break;
        case '-':
            *result -= operand2;
            break;
        case '*':
            *result *= operand2;
            break;
        case '/':
            *result /= operand2;
            break;
        case '%':
            *result = fmod(*result, operand2);
            break;
        }
    }
    return 1;
}

int main() {
    char str[100];

    printf("type some expressions:\n");
    while (fgets(str, sizeof str, stdin)) {
        double result, result2;
        int pos = 0;

        /* strip trailing whitespace, skip initial whitespace */
        trim_spaces(str, &pos);
        if (!str[pos]) {
            /* stop on empty line */
            break;
        }
        /* test for a variable assignment */
        if (str[pos] >= 'a' && str[pos] <= 'z' && str[pos + 1] == '=') {
            /* variable assignment */
            int v = str[pos] - 'a';
            pos += 2;
            if (parse_expression(str, &pos, &result) && !str[pos]) {
                variables[v] = result;
                printf("%s -> %.2f\n", str, result);
            } else {
                printf("invalid expression: %s\n", str);
            }
        } else {
            /* other expression */
            if (parse_expression(str, &pos, &result)) {
                skip_spaces(str, &pos);
                if (str[pos] == '\0') {
                    printf("%s -> %.2f\n", str, result);
                } else
                if (str[pos] == '=') {
                    /* comparison of expressions */
                    pos += 1;
                    if (parse_expression(str, &pos, &result2) && !str[pos]) {
                        if (result == result2) {
                            printf("%s -> true (%.2f == %.2f)\n", str, result, result2);
                        } else {
                            printf("%s -> false (%f != %f, delta: %e)\n",
                                   str, result, result2, result2 - result);
                        }
                    } else {
                        printf("invalid expression: %s\n", str);
                    }
                } else {
                    printf("invalid syntax: %s\n", str);
                }
            }
        }
    }
    return 0;
}

Output:

b=2/3
b=2/3 -> 0.67
2/3=b
2/3=b -> true (0.67 == 0.67)
20/3=10*b
20/3=10*b -> false (6.666667 != 6.666667, delta: -8.881784e-16)
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    Thank you @chqrlie so much for this amazing functioning code. For a beginner like me, it will take time to understand everything. Thank you again, and have a nice day. – Charbel Jul 04 '22 at 15:49
  • 1
    Sorry that I am asking too much. I have been trying to understand each function today. Could I ask you to tell me what the objective of each function is? So that I can know if I understood them the right way. Thank you again very much for your help. – Charbel Jul 05 '22 at 14:33
  • @Charbel: I have updated the code with explanatory comments. – chqrlie Jul 05 '22 at 16:45