3 Formatted I/O

Notes from C Programming a Modern Approach

code
C
CPMA
Published

February 14, 2026

Modified

February 15, 2026

The printf function

The printf function is designed to change the contents of a string with values inserted at specific points in the string.

  • This is known as the format string

  • printf(string, exp1, exp2, ...)

The format string contains standard characters as well as conversion specifications, which begin with %.

  • The conversion specification is a placeholder that is filled when printing.

  • The % character specifies how the value is converted from the internal from (binary) to the printed form (character).

    • The conversion specification %d is used to convert an int from binary to a string of decimal digits .

    • %f does the same for a float value.

  • C compilers are not required to check the number of conversion specifications with the number of output types.

    • If there are more output items, they will print a random value.

    • If there are more conversion specifications, it will likely not print the values

  • Compilers are also not required to check if the conversion spec matches the the output type. The wrong type will print a random value.

Conversion specifications

Conversion specifications have the form %m.pX or %-m.pX , where m and p are integers and X is a letter.

  • m is the minimum field width, specifies the minimum number of characters to print.

    • If there are less values than m, the value will be right justified with the remaining being spaces on the left.

      • e.g. %4d will display the number 123 as 123.
    • If the value is greater than m, the value will expand to display all the digits.

      • e.g. %4 will display the number 12345 as 12345
    • Putting a minus sign before m will left justify.

      • e.g -%4 will display the number 123 as 123
  • p is the precision, which depends on the conversion specifier (X). The most common specifiers:

  • d displays the integer as a decimal (base 10).

    • p is the number of digits to display, adding extra zeros to the beginning of the number.

      • The default is p is 1 (%d = %.1d )
  • e displays a floating-point number in an exponential format (scientific notification).

    • p shows the number of digits that appear after the decimal point.

      • The default is 6.
  • f displays a floating-point number in a “fixed-decimal” format, without an exponent.

    • p means the same for the e specifier.
  • g displays a floating-point number as either an exponent or a decimal format depending on the number’s size.

    • p indicates the maximum number of significant digits, not digits after the decimal point.

    • Will also not display trailing zeros like f

    • If there are no digits after the decimal, it won’t show the decimal point.

    • g is very helpful when displaying number when you don’t know the length of the numbers beforehand or very in size.

Using printf to format numbers

 /* Prints int and float values in various formats*/

 #include <stdio.h>

 int main(void)
 {
     int i;
     float x;

     i = 40;
     x = 839.21f;

     printf("|%d|%5d|%-5d|%5.3d|\n", i, i, i, i);
     printf("|%10.3f|%10.3e|%-10g|\n", x, x, x);

     return(0);
 }
|40|   40|40   |  040|
|   839.210| 8.392e+02|839.21    |
  • %10.3f , displays x in a fixed format using 10 characters, 3 before the decimial, 3 after, and one for the actual decimal point.

    • That means p = 3 in this example takes up 7 characters, 3 before, 3 after, and the decimal.

Escape sequences

Here’s a few of the common escape sequences used in format strings:

  • Alert (bell) - \a

  • Backspace = \b

  • New line - \n

  • Horizontal tab - \t

Another common escape sequence is \", which represents the ” character.

  • Since ” is used for the beginning/end of strings, it needs to be escaped.

The scanf function

scanf reads inputs according to a particular format.

  • A scanf format string can contain both ordinary and conversion specifications, essentially the same as printf.

For example:

int i, j;
float x, y;

scanf("%d%d%f%f", &i, &j, &x, &y);
  • If a user inputs 1, -20, 0.3, -4.0e3, the results will be 1,-20, 0.3 and -4000.0 tightly packed because of the format string.

  • The format string for scanf is very similar to print, except the & , which precedes each variable in the call.

    • This is generally required and forgetting it will likely to unpredictable results.

    • It could crash, the input not updating the variables value, etc.

How scanf works

scanf is essentially a pattern-matching function that tries to match groups of input characters with conversion specifications.

  • For each conversion specification in the format string, scanf tries to locate an item of the appropriate type in the input data, skipping blank space if necessary.

  • scanf then reads the item, topping when it encounters a character that can’t belong to the item.

  • If the item is successful read, scanf continues processing the rest of the string format.

  • If any item is not read successfully, scanf returns immediately without looking at the rest of the format string.

  • scanf ignores white space characters so it will read items even is spread over multiple lines.

  • scanf “peeks” at the final new-line character without actually reading it. The new-line will be the character read by the next call to scanf

  • When reading an integer, scanf first looks for a digit, a +, or -, then it reads digits until it reaches a non-digit.

  • When reading a floating-point number, scanf looks for a + or - followed by a series of digits (optional decimal point), followed by a possible exponent.

    • The exponent would be an e or E with a possible sign/digits

Example of how scanf works

We write a program to read the following 4 numbers: 1, -20.3, -4.0e3 \n. The scanf could look like:

scanf("%d%d%f%f", &i, &j, &x, &y)

scanf’s processes:

  1. Conversion specification: %d
    • The first non-blank character is 1, which can be an integer.

    • It reads the next character which is a -, which scanf knows cannot be within an integer, so it puts it back.

  2. Conversion specification: %d
    • scanf reads the -, which is an acceptable start to an integer. It will continue going until it reads a character that cannot be included in an integer: 2 and 0

      • scanf stops when it reaches . because it is not a valid value for an integer. It puts it back.
  3. Conversion specification: %f
    • scanf reads the characters ., which is a valid character for the beginning of a float, then 3, and stops at the - because it is not a valid character for the middle of a float.

    • scanf stores 0.3 in to x and puts back the - character

  4. Conversion specification %f
    • scanf now reads -,4, .,0, e, 3. \n

    • It stores all values but the \n, which it puts back.

Ordinary characters in Format strings

The action scanf takes when it process ordinary characters depends on whether it is a white-space character:

  • White-space characters

    • When scanf reads all the white-space characters until it reaches a non-white-space character, which it puts back.

    • scanf doesn’t differentiate between one or more white-space characters, even the fact of having none.

  • Non-white-space characters

    • When scanf encounters a non-white-space character, it compares it to the next input character.

    • If the two characters match, it disregards it and continues processing the format string.

    • If the characters do not match, scanf puts the offending character back into the input, and aborts without further processing the format string.

scanf white-space example

Suppose the format string is " %d/ %d" with an input of “5/ 96". scanf

  1. Skips the first space since it is looking for an integer
  2. Matches %d with 5
  3. Attempts to match / because it is in the format string
  4. Skips the space
  5. Matches %d with 96 because it is a valid integer.

If the input is " 5 / 96" on the other hand, scanf

  1. Skips the first space
  2. Matches %d with 5
  3. Attempts to match the / with / but there is no match with a space before a / in the format string.
  4. The / 96 characters remain to be read by the next call of scanf
    • We should have used the format string %d /d% to properly read this string.

Adding fractions

/* Adds two fractions */

#include <stdio.h>

int main(void)
{
    int num1, denom1, num2, denom2, result_num, result_denom;

    printf("Enter first fraction: ");
    scanf("%d/%d", &num1, &denom1);

    printf("Enter second fraction: ");
    scanf("%d/%d", &num2, &denom2);

    result_num = num1 * denom2 + num2 * denom1;
    result_denom = denom1 * denom2;

    printf("The sum is %d/%d\n", result_num, result_denom);

    return 0;
}

Q&A

The \t escape causes printf to advance to the next tab stop. How does one know how far apart the tabs are?

  • We don’t as it isn’t defined by C, but the operating system. It is typically 8 characters.

What happens when a scanf is expecting a number but is given a non-numeric input?

  • If it starts with a number followed by character, it reads in the number and leaves the characters for the next scanif

  • If it is just characters, then scanf will likely store a random value in the variable.

What does it mean when scanf puts back characters and reads them again later?

  • Programs don’t actually read user input as its typed.

  • The input is actually stored in a hidden buffer that scanf can access.

  • scanf can put characters back into the buffer for subsequent reading.

Exercises

2. What calls of printf that display a float variable x in the following formats:

a) Exponential notation; left-justified in a field of size 8, one digit after the decimal point.

  • printf("%-8.1e", x)

b) Exponential notation; right-justified in a field size 10; 6 digits after the decimal point

  • printf("%10.6e", x)

c) Fixed decimal notation; left-justified of a field size 8, with 3 digits after the decimal point.

  • printf("-8.3f", x)

d) Fixed decimal notation; right justified of size 6; no digits after the decimal point.

  • printf("6.0f", x)

5. Suppose that we call scanf as follows:

scanf("%f%d%f", &x, &i, &y)

And the user enters: 12.3 45.6 789

What are the values of x, i, and y after each call? Assume x and y are float and i is an int

  • x = 12.3

  • i = 45

  • y = 0.6 scanf stops after the 6 because of the space.

Programming projects

1. Write a program that accepts a date from a user in the form of mm/dd/yyyy and responds back in the form yyyymmdd:

Enter a date (mm/dd/yyyy): 2/17/2011
You entered the date 20110217
/* Convert date from mm/dd/yyyy to yyyymmdd
 * NOTE: 2/02/1999 should be 19990202
 */

 #include <stdio.h>

 int main(void)
 {
    int day, month, year;

    printf("Enter a date (mm/dd/yyyy): ");
    scanf("%2d/%2d/%4d", &month, &day, &year );
    printf("You entered, %4d%2.2d%2.2d \n", year, month, day);

    return 0;
 }

3. Books are identified by the ISBN, which after 01/01/07 contain 13 digits that are arranged in 5 groups (e.g. 978-0-393-97950-3).

  • The first group (GSI prefix) is either 978 or 979

  • The next is group identifier for language of origin

    • 0 and 1 are for English speaking countries
  • The publisher code identifies the publisher (393 is W. W. Norton)

  • The item number is assigned by the publisher to identify the book

  • The last digit is a check digit to verify the accuracy of the of the preceding digits.

Write a program that parse the ISBN number:

Enter ISBN: 978-0-393-97850-3
GSI prefix: 978
Group identifier: 0
Publisher code: 393
Item number: 97950
Check digit: 3
/* Create a program that parses */

#include <stdio.h>

int main(void)
{
    int gsi, group, pub, item, check;
    printf("Enter ISBN: ");
    scanf("%3d-%d-%3d-%5d-%3d", &gsi, &group, &pub, &item, &check);
    printf("GSI prefix: %3d \n", gsi);
    printf("Group identifier: %d\n", group);
    printf("Publisher code: %3d\n", pub);
    printf("Item number: %5d\n", item);
    printf("Check digit: %d\n", check);

    return 0;
}
  • Note: the printf statements could be combined into one statement