Sep 6, 2016

C Programming #88: stdio - insane formatting in printf

C Programming #88: stdio - insane formatting in printf

We have covered simple format specifier for printf and scanf here [C Programming #87: stdio - printf, scanf]. This article tries to give incrementally with example most complex and insane formatting that could be achieved. I don't expect you to remember all of it. My idea to here is to show how beautiful and complex the construct are.


constant width

Say you want to show print something like below. Table of number of something per year.

2010  107
2011    9
2012   22
2013  120
2014   34

Now say i write a C program where the year and number is stored in array of struct.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   int num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, 107},
                     {2011, 9},
                     {2012, 22},
                     {2013, 120},
                     {2014, 34}};
   int i;

   for(i = 0; i < 5; i++){
     printf("%d %d\n", list[i].year, list[i].num);
   }

   return 0;
}
2010 107
2011 9
2012 22
2013 120
2014 34

If you see the output, it is not properly aligned. Issue is that we need all the number aligned to its right. This can be ac hive by specifying the number between % and d. This number represents the width. Since it is constant number it is called constant width.

Now re-writing the above program with the appropriate width.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   int num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, 107},
                     {2011, 9},
                     {2012, 22},
                     {2013, 120},
                     {2014, 34}};
   int i;

   for(i = 0; i < 5; i++){
     printf("%d %3d\n", list[i].year, list[i].num);
   }

   return 0;
}
2010 107
2011   9
2012  22
2013 120
2014  34

Now the o/p exactly looks as we would like it to be.

variable width

In the above C program we assumed that the width as constant by looking at the numbers. Say in some program we don't know the numbers before hand. Then variable width specifier comes into help. Between % and d we need to put * suggesting that width is variable. Now the width is passed as one of the argument to printf. Hence for %*d we need to pass two arguments first the width and second the number itself to be printed.

Now re-writing the same above program to make it more generic.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   int num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, 1072345},
                     {2011, 9},
                     {2012, 22},
                     {2013, 120},
                     {2014, 34}};
   int i, width = 0, tw = 0;
   char temp[100];

   /* This loop is for width calculation */
   /* Note how snprintf is used to find number 
      of digits in a number */
   for(i = 0; i < 5; i++){
      tw = snprintf(temp, 100, "%d", list[i].num);
      if(tw > width) {
         width = tw;
      }
   }

   for(i = 0; i < 5; i++){
     printf("%d %*d\n", list[i].year, width, list[i].num);
   }

   return 0;
}
2010 1072345
2011       9
2012      22
2013     120
2014      34

You can try changing the num and see how the o/p gets adapted.

left or right justify

Say you want to print the some items in tabular format, say something like below

James Smith Michael Smith Maria Garcia Maria Rodriguez

To make something like above formatting we need to start second name at same place. Hence First Name and spaces following it form a const width. We know the format specifier for string is %s. Above const width can be specified by adding a number in between % and s. Example %20s, here it will print with the constant width 20 characters. And we want it left justified we need to use -. What would happen if the string is bigger than 20 characters, it would overflow the width boundary.

Let me give the example by storing the first name and second name in struct.

#include <stdio.h>

typedef struct name_tag{
   char *first;
   char *second;
}name_t;

int main()
{
   name_t list[4] = {{"James", "Smith"},
                     {"Michael","Smith"},
                     {"Maria", "Garcia"},
                     {"Maria", "Rodriguez"}};
   int i;

   for(i = 0; i < 4; i++) {
      printf("%-15s %-15s\n", list[i].first, list[i].second);
   }

   return 0;
}

James Smith Michael Smith Maria Garcia Maria Rodriguez

Same thing could be applied to numbers as well, but it is customary to align number right justified and string left justified.

zero fill

Lets take another example where you want numbers as follows.

2010 107
2011 009
2012 022
2013 120
2014 034

See that each number is padded with zero. This can be easily achieved by using 0 in between % and d for zero padding.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   int num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, 107},
                     {2011, 9},
                     {2012, 22},
                     {2013, 120},
                     {2014, 34}};
   int i;

   for(i = 0; i < 5; i++){
     printf("%d %03d\n", list[i].year, list[i].num);
   }

   return 0;
}
2010 107
2011 009
2012 022
2013 120
2014 034

compulsory +

Now say we have following table to be formatted.

2010 +107
2011   -9
2012  +22
2013 -120
2014  +34

Above formatting can be achieved by mentioning + in between % and d.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   int num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, +107},
                     {2011, -9},
                     {2012, +22},
                     {2013, -120},
                     {2014, +34}};
   int i;

   for(i = 0; i < 5; i++){
     printf("%d %+4d\n", list[i].year, list[i].num);
   }

   return 0;
}
2010 +107
2011   -9
2012  +22
2013 -120
2014  +34

Note that here width also includes the +/- sign also.

precision

This is applicable to floating point number. Say i want to print following table.

2010   10.7
2011    9.0
2012   22.1
2013  120.3
2014   34.0

If you see the above number number following . tells the precision of it. Here width of precision is 1. Above formatting can be achieved as follows.

#include <stdio.h>

typedef struct stat_tag {
   int year;
   float num;
}stat_t;

int main()
{
   stat_t list[5] = {{2010, 10.7},
                     {2011, 9.0},
                     {2012, 22.1},
                     {2013, 120.3},
                     {2014, 34.0}};
   int i;

   for(i = 0; i < 5; i++){
     printf("%d %6.1f\n", list[i].year, list[i].num);
   }

   return 0;
}
2010   10.7
2011    9.0
2012   22.1
2013  120.3
2014   34.0

Note here the width is total width of number including all numbers and .

entire spec

Overall spec can be found here - http://man7.org/linux/man-pages/man3/printf.3.html I don't want to repeat it as such this article is quite big enough.

No comments :

Post a Comment