0

I'm trying to write a program that reads a five digit zip code, then outputs the numbers in word form. The program would also only accept a 5 digit number. So if the input was 123, the output would be "You entered 3 digits" and loop back until the user entered five digits. Once they did, it would output the number in word format. So if 12345 was entered, the output would be one two three four five. This is what I have so far:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

int main(void) {
int ac, count = 0;

printf("Enter a 5 digit area code: ");
scanf("%d", &ac);


do {
    ac /= 10;
    ++count;

} while (ac != 0);

while (count != 5) {
        printf("You intered in %d digits.\n", count);
        printf("Enter a 5 digit area code: ");
        scanf(" %d", &ac);
}

while (count == 5) {


}

return(0);

}

There are two issues I need help with. The first is that whenever a number that doesn't have five digits is input, it outputs correctly once, and then the loop is infinite. If I entered 123, output would be "You entered 3 digits," and then it would prompt me to enter another number. If I then entered 12345 it would output "You entered 3 digits" again. I'm new to loops not sure where this problem is coming from.

The second issue is just the conversion from numbers to words, I've never encountered this issue and I'm not sure where to start.

  • I suggest you read and operate on the number as a string. It is simpler to extract digits from a string than a number. Either way, once you extract a digit then start by using a `switch` statement with 10 cases to print the digit in words. Write a function to do that and then call it for each digit. There are smarter ways than that but that would be a start. – kaylum Sep 16 '21 at 20:31
  • 1
    As for the `while (count != 5)` clearly it is wrong because `count` never changes inside the loop body. So once the loop is entered it will never exit. You need to re-evaluate `count`. Write a function to do that (seeing a trend here regarding breaking the code up into functions?). – kaylum Sep 16 '21 at 20:33
  • Also, the second while loop does not pass control back to the first loop, even if they have same variable in the loop body. – Sourav Ghosh Sep 16 '21 at 20:35
  • 1
    The zip code 01027 has 5 digits, but if you read it as an integer it will be 1027 which has 4. You must read it as a string. – stark Sep 16 '21 at 20:35
  • 1
    Consider asking just one question (not two) so you get a focused answer. – anatolyg Sep 16 '21 at 20:52

4 Answers4

1

As already pointed out in the comments section, the logic in your code will not work with codes that start with a zero.

If you store the code as an integer, then you cannot distinguish the code 01234 from the code 1234. Therefore, if you want to be able to distinguish these two codes, you must, at least initially, read the number as a string, not as a number:

char buffer[100];

//prompt user for input
printf("Enter a 5 digit area code: ");

//attempt to read one line of input
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
    fprintf( stderr, "error reading input!\n" );
    exit( EXIT_FAILURE );
}

Note that the code above additionally requires the header file stdlib.h to be included.

Now, you must count the number of characters entered and verify that there were exactly 5. Also, you must verify that all entered characters were digits. If the input is not valid, then you must provide an appropriate error message and prompt the user again. This can be best accomplished using an infinite loop, which is repeated until the input is valid, at which point an explicit break statement is executed, which will break out of the infinite loop.

Converting the individual digits to words can easily be done by creating an array of pointers to strings, so that the 0th element points to the string "zero", the 1st element points to "one", etc:

const char * const digit_names[] = {
    "zero", "one", "two", "three", "four",
    "five", "six", "seven", "eight", "nine"
};

Since the ISO C standard guarantees that all numbers are consecutive in the character set (irrespective of which character set you are using), you can simply subtract '0' (the character code of the digit 0) from the character code in order to convert the digit from a character code to an actual number between 0 and 9. Now, you can use that number to index into the array digit_names to print the corresponding word.

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

int main( void )
{
    const char * const digit_names[] = {
        "zero", "one", "two", "three", "four",
        "five", "six", "seven", "eight", "nine"
    };

    char buffer[100];

    for (;;) //infinite loop, equivalent to while(1)
    {
        int i;
        char *p;

        //prompt user for input
        printf("Enter a 5 digit area code: ");

        //attempt to read one line of input
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable error reading input!\n" );
            exit( EXIT_FAILURE );
        }

        //verify that entire line of input was read in
        p = strchr( buffer, '\n' );
        if ( p == NULL )
        {
            fprintf( stderr, "Unrecoverable error: Line too long!\n" );
            exit( EXIT_FAILURE );
        }

        //remove newline character
        *p = '\0';

        //verify that exactly 5 characters were entered and that
        //each characters is a digit
        for ( i = 0; i < 5; i++ )
        {
            //verify that we are not yet at end of line
            if ( buffer[i] == '\0' )
            {
                printf( "Too few characters!\n" );

                //we cannot use "continue" here, because that would apply to
                //the innermost loop, but we want to continue to the next
                //iteration of the outer loop
                goto continue_outer_loop;
            }

            //verify that character is digit
            if ( !isdigit( (unsigned char)buffer[i] ) )
            {
                printf( "Only digits allowed!\n" );

                //we cannot use "continue" here, because that would apply to
                //the innermost loop, but we want to continue to the next
                //iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        //verify that we are now at end of line
        if ( buffer[i] != '\0' )
        {
            printf( "Too many characters!\n" );
            continue;
        }

        //everything is ok with user input, so we can break loop
        break;

    continue_outer_loop:
        continue;
    }

    printf( "You entered this valid input: %s\n", buffer );

    printf( "In words, that is: " );

    for ( int i = 0; i < 5; i++ )
    {
        //don't print space on first iteration
        if ( i != 0 )
            putchar( ' ' );

        //casting to "unsigned char" is necessary to prevent
        //negative character codes
        fputs( digit_names[ ((unsigned char)buffer[i]) - '0' ], stdout );
    }

    printf( "\n" );
}

Here is some sample input and output from the program:

Enter a 5 digit area code: 1234
Too few characters!
Enter a 5 digit area code: 123456
Too many characters!
Enter a 5 digit area code: 12h45
Only digits allowed!
Enter a 5 digit area code: 12345
You entered this valid input: 12345
In words, that is: one two three four five

Also, as you can see, codes starting with 0 work, too:

Enter a 5 digit area code: 1234
Too few characters!
Enter a 5 digit area code: 01234
You entered this valid input: 01234
In words, that is: zero one two three four
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0

For the first issue, the second while loop condition always evaluates false if the number is not 5 digits. You have to update count each time you get a new number, this means you have to repeat the do ... while loop. A function will be great in this case, and never forget to check the return value of scanf() is the only you know the conversion succeeded (you scanned a number).

Your function can be something like this,

int count_digits(int num)
{
  int digits = 0;

  do
  {
    digits++;
    num /= 10;
  } while (num != 0);

  return digits;
}

and your updated main()

int main(void)
{
  int ac = 0, count = 0;

  printf("Enter a 5 digit area code: ");
  if (scanf("%d", &ac) != 1) /* you read one int */
  {
    /* conversion error - maybe display error message */
    exit(EXIT_FAILURE);
  }

  count = count_digits(ac);

  while (count != 5)
  {
    printf("You intered in %d digits.\n", count);
    printf("Enter a 5 digit area code: ");
    if (scanf(" %d", &ac) != 1)
    {
      exit(EXIT_FAILURE); /* include <stdlib.h> for exit */
    }

    count = count_digits(ac);
  }
}

As for the second part of your question, its not clear if you want to perform some operations on the number. If you need to have it as a int, you may use snprintf() and large enough buffer to convert it to a string.

  char buff[100] = "";

  snprintf(buff, sizeof(buff), "%d", ac);

If you don't need to perform any operations on the number, you can read it as string right away, with fgets().

  char buff[100] = "";

  fgets(buff, sizeof(buff), stdin);

(This will also work for zip codes starting with 0)

alex01011
  • 1,670
  • 2
  • 5
  • 17
0

Sometimes it helps to simplify. In this case eliminating one while loop and using a helper function in the remaining loop will help. The following enables the code to iterate until the right input is entered with a single loop. (note, example contains no error handling, just concept illustration.)

The main:

int main(void)
{
    int num;
    char buf[20]={0};
    printf("Enter a 5 digit number:");
    while (!isNumeric(buf))//helper function 
    {
        printf("try again\n:");
        scanf("%s", buf); 
    }
    //convert if needed
    num = atoi(buf);

    printf("%s\n%d", buf, num);
    
    return 0;
}

The helper function:

bool isNumeric(char *number)
{
    if(strlen(number) != 5) return false;
    
    while(*number)
    {
        if(!isdigit(*number))
            return false;
        number++;
    }
    
    return true;
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • According to [§7.4 ¶1 of the ISO C standard](http://port70.net/~nsz/c/c11/n1570.html#7.4p1), the function call `!isdigit(*number)` may invoke undefined behavior if `*number` is negative. The proper way to write this is `!isdigit( (unsigned char)*number )`. See [this answer to another question](https://stackoverflow.com/a/45007070/12149471) for the reason behind this. – Andreas Wenzel Sep 17 '21 at 00:52
  • It does not seem appropriate to print "try again" in the initial prompt, before the user entered anything. – Andreas Wenzel Sep 17 '21 at 01:11
  • The function `atoi` [should generally not be used](https://stackoverflow.com/q/2565727/12149471), as it invokes undefined behavior on a range error (see [§7.22.1 ¶1 of the ISO C standard](http://port70.net/~nsz/c/c11/n1570.html#7.22.1p1)). The function `strtol` should be used instead. Even if a range error can be excluded in this case, I'm not sure if it is good advice to recommend using the function `atoi`. – Andreas Wenzel Sep 17 '21 at 04:24
  • @AndreasWenzel - First,, the phrase _try again_ does not occur during the initial prompt. Next, _most_ functions in the standard libraries invoke undefined behavior _when not used according to their design_. But you already know this, and I wonder at your motivation for citing them. Also, fwiw I use them here to keep the concept I am suggesting uncluttered. You might try doing that on your own answer. Suggesting the use of `goto` and `continue`, are legal syntax and its your choice to use them, but most C programming instruction _generally_ discourages their use. – ryyker Sep 20 '21 at 13:08
  • In your previous comment, you wrote: `"First,, the phrase try again does not occur during the initial prompt."`. This statement is incorrect. It prints `"Enter a 5 digit number:try again"` on the initial prompt. See [this link](https://godbolt.org/z/MdjG6vKfx) for an demonstration of your program's execution. – Andreas Wenzel Sep 20 '21 at 17:31
  • Using `goto` to break out of a nested loop [is an accepted use](https://stackoverflow.com/a/24476/12149471) of that statement. The alternative would be to add an additional variable and to add an additional check of that variable. In my opinion, using `goto` in this case is the cleanest solution. – Andreas Wenzel Sep 20 '21 at 17:44
  • @AndreasWenzel - My bad. The code I posted, unfortunately was not the latest in my compiler. – ryyker Sep 20 '21 at 19:10
0

As ZIP codes can begin with a '0', using scan("%d",... loses leading 0 information.

Code needs to detect digits and not just an int to disallow input like "123\n", "000123\n", "+1234\n", "12345xyz\n", yet allow "000001\n", " 000001\n", "000001 \n", "000001".

Below uses "%n" to record the offset of the scan.

  char buf[100];
  if (fgets(buf, sizeof buf, stdin)) {
    int left, right, end;
    long zip;
    if (sscanf(buf, " %n%6ld%n %n", &left, &zip, &right, &end) != 1) {
      puts("Non-numeric input");
    } else if (right - left != 5 || !isdigit((unsigned char ) buf[left])) {
      puts("Not 5 digits");
    } else if (buf[end] != '\0') {
      puts("extra junk at the end");
    } else {
      printf("Success %05ld\n", zip);
    }
  }

Alternative code may also want to disallow leading/trailing white-space.


" %n%6ld%n %n" details

" ": Consume optional white-space.

"%n" Record offset of scan

"%6ld" Read upto 6 characters in forming a long - no need for more (and prevent overflow). As int may be 16-bit, use long for 00000-99999.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256