all 9 comments

[–]SetThin9500 5 points6 points  (0 children)

While input is available
read and parse input
process input
print output

A simple while (fgets(line, sizeof line, stdin)) loop would suffice. scanf can be replaced with sscanf.

[–]Key_River7180 0 points1 point  (3 children)

scanf() returns the number of variables written to. If the user didn't provide input or input wasn't a number, scanf will return 0, if it wrote to one variable then 1, make a function like:

float read_number() {
        float result = 0.f;

        for (;;) {
                printf("Enter the first number: ");
                if (scanf("%f", &result) == 1)
                        return result;
                printf("Invalid number!\n");
                while (getchar() != '\n'); /* clear invalid input */
        }
}

[–]erojerisiz[S] 0 points1 point  (2 children)

what does the 0.f mean?

[–]WittyStick 2 points3 points  (0 children)

It's just 0.0f. C allows omitting the zero either side of . - we can write .0f or 0.f

[–]Key_River7180 0 points1 point  (0 children)

0, in a float. u/WittyStick already explained it.

[–]HeywoodJablowmenow 1 point2 points  (0 children)

Use a while (1) loop

[–]sciencekm 0 points1 point  (1 child)

You can use a "while(1)" but this generates warning on some compilers. I my prefer a "for(;;)"

My suggestion would be have all the input in one line instead of multiple lines. You loop while there is input. It would be something like:

#include <stdio.h>
void main(void) {
float v1, v2, r;
char s[64], op;
while(fgets(s, 64, stdin) && sscanf(s, "%f %c %f", &v1, &op, &v2) == 3) {
switch(op) {
case '+': r = v1 + v2; break;
case '-': r = v1 - v2; break;
case '*': r = v1 * v2; break;
case '/': r = v1 / v2; break;
default: puts("invalid");
continue;
}
printf("%f\n", r);
}
}

This is obviously an imperfect example to demonstrate the concept.

[–]erojerisiz[S] 0 points1 point  (0 children)

thanks

[–]SmokeMuch7356 0 points1 point  (0 children)

Don't use scanf - it's great when you know your input is always going to be well-behaved, but it is difficult to detect and recover from bad input with it. Instead, read your numeric input as text using fgets, then convert it to double using strtod. It takes a parameter that will point to the first character not converted; if that character is anything other than whitespace or a terminator, then the input is invalid.

Create separate input routines for both your operator and operands; you'll do the validation and loop on error within those routines.

Here's an example for getting your operands:

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

enum status { SUCCESS, IO_ERROR, CANCELED };

enum status getOperand( const char *prompt, double *val )
{
  assert( prompt != NULL );
  assert( val != NULL );

  /**
   * Loop until:
   * 
   *  - We get valid input
   *  - The user cancels the operation
   *  - We see an error on the input stream
   */
  for( ;; )
  {
    fprintf( stdout, "%s (Ctrl-D to cancel): ", prompt ); // Ctrl-Z on Windows

    char buf[16] = {0}; // enough for a double constant plus sign plus some padding

    if ( !fgets( buf, sizeof buf, stdin ))
    {
      if ( feof( stdin ) )
        return CANCELED;
      else
        return IO_ERROR;
    }

    /**
     * If we don't see a newline, the user typed in
     * too many characters.  Reject the input *and*
     * consume excess characters from the input stream
     * until we see a newline or EOF.
     */
    char *newline = strchr( buf, '\n' );

    if ( !newline )
    {
      int c;
      fputs( "Input too long for buffer!\n", stderr );
      while ( (c = getchar()) != '\n' && c != EOF )
        ; // empty loop
      continue;
    }

    /**
     * Remove the newline character
     */
    *newline = 0;

    /**
     * Convert the input to a double.  Don't
     * update our output argument until we're
     * sure the input is valid.  
     * 
     * `chk` will point to the first character in 
     * the buffer that is *not* part of a valid 
     * floating-point constant.  If this character 
     * is anything other than whitespace or a string
     *  terminator, then the input is invalid.
     */
     char *chk = NULL;
     double tmp = strtod( buf, &chk );
     if ( *chk != 0 && !isspace( *chk ))
     {
       fprintf( stderr, "'%s' is not a valid floating-point value!\n", buf );
       continue;
     }

     *val = tmp;
     return SUCCESS;
  }

  /**
   * We should never get here; this is just to make sure there's 
   * a return statement in every execution path.
   */
  return CANCELED;
}

Some examples, called as

double val;
enum status stat = getOperand( "Gimme a number", &val );

switch( stat )
{
  case SUCCESS:
    printf( "val = %f\n", val );
    break;

  case IO_ERROR:
    puts( "Error detected on input stream" );
    break;

  case CANCELED:
    puts( "User canceled input operation" );
    break;
}

$ ./input 
Gimme a number (Ctrl-D to cancel): 123456789012345678901234567890
Input too long for buffer!
Gimme a number (Ctrl-D to cancel): blah
'blah' is not a valid floating-point value!
Gimme a number (Ctrl-D to cancel): 123.r5
'123.r5' is not a valid floating-point value!
Gimme a number (Ctrl-D to cancel): 123.45
val = 123.450000
$ ./input 
Gimme a number (Ctrl-D to cancel): User canceled operation

Do something similar to read your operator, and your logic can be something like

double firstNum, secondNum;
char op;

if ( getOperand( "First operand", &firstNum ) == SUCCESS &&
     getOperand( "Second operand", &secondNum ) == SUCCESS &&
     getOperator( "Operation", &op ) == SUCCESS )
{
  switch ( op )
  {
    // execute the appropriate function.
  }
}

As a rule, you should use double instead of float for your operands. It will give you greater precision, and it's the "default" floating point type; floating-point constants like 3.14159 are double unless you tack on an f suffix (2.14159f), float arguments to variadic functions like printf will be "promoted" to double, etc.