JustJohn
JustJohn

Reputation: 59

How do I properly format printing row of numbers in c?

I have a structure defined as such

   typedef struct box
{
   float x;
   float y;
   float z;
   float volume;
   int order;
}box;

and the file I'm given presents the data this way:

3   2.5 3
4.4 5   6
8.7 6.5 9.5
2.5 6.5 7.3
7.6 5.1 6.2

with each number representing a dimension of a parallelepiped

printf("%f  %f  %f      %f\n", A[i]->x, A[i]->y, A[i]->z, A[i]->volume)

with i being a for loop and A the array where I store the structures gives me this output

3  2.5  3      22.5
4.4  5  6      132
8.7  6.5  9.5      537.225
2.5  6.5  7.3      118.625
5.7  8.7  9.8      485.982
7.6  5.1  6.2      240.312

How can I print it in an organized way like the input file?

one problem is that the input might also look like differently, for example.

5.244231 2.231432 7.232432

so I need my printout to accommodate the different lengths of numbers, the formats don't mix though, so for example it can't be 5 4.52653 3.674

Upvotes: 2

Views: 898

Answers (3)

chux
chux

Reputation: 153517

accommodate the different lengths of numbers

float values typically range from +/-1.4...e-45F to +/-3.4...e+38F, +/-0, infinity and various not-a-numbers.

"%f" prints with 6 digits after the . as so prints 0.000000 for many small float and with many uninformative digits when very large as is thus not satisfactory.

For a general printing, begin by using "%g" which adapts its output (using exponential notation for large and wee values) and truncates trailing zeros.

Use a width, which specifies the minimum characters to print.

float typical requires up to 9 significant digits to distinguish from other float. Consider a precision.

Use source concatenation to allow for only 1 definition of the desired format.

// printf("%f  %f  %f      %f\n", A[i]->x, A[i]->y, A[i]->z, A[i]->volume)

#define FMT_F "%-15.9g" 
//                  ^ precision
//               ^^   width
//              ^     left justify   

printf(FMT_F " " FMT_F " " FMT_F " " FMT_F "\n", 
    A[i]->x, A[i]->y, A[i]->z, A[i]->volume)

// Example output
4.4             5               6               132            
8.7             6.5             9.5             537.225        
1.40129846e-45  -3.40282347e+38 123.4           -5.88417018e-05

More advanced code would use a compiler dependent width and precision.

Upvotes: 1

idz
idz

Reputation: 12988

You can specify the column width by putting a number between the % and f so, for example:

printf("||%10f|%10f|%10f|%10f||\n", box.x, box.y, box.z, box.volume);

would print:

||  3.100000|  2.000000|  9.500000| 58.899998||

Notice these are right aligned. If you put a minus sign in front of the column width, you can make it left aligned.

printf("||%10f|%10f|%10f|%-10f||\n", box.x, box.y, box.z, box.volume);

will print:

||  3.100000|  2.000000|  9.500000|58.899998 ||

It's also useful to know that you can control the number of digits shown after the decimal point by specifying .<count> after the column width:

printf("||%10.2f|%10.2f|%10.2f|%10.4f||\n", box.x, box.y, box.z, box.volume);

will print

||      3.10|      2.00|      9.50|   58.9000||

Update: It looks like you've added more details to your question. Here's additional information that you could use.

The width and precision numbers do not have to be constants in the format string. You can replace any or all of them with an asterisk '*' and supply a variable like this:

    for (int precision = 1; precision < 5; ++precision) {
        int width = precision * 4;
        printf("||%*.*f|%*.*f|%*.*f|%*.*f||\n",
               width, precision, box.x,
               width, precision, box.y,
               width, precision, box.z,
               width, precision, box.volume);
    }

This will produce:

|| 3.1| 2.0| 9.5|58.9||
||    3.10|    2.00|    9.50|   58.90||
||       3.100|       2.000|       9.500|      58.900||
||          3.1000|          2.0000|          9.5000|         58.9000||

A quick way of determining the number of digits before the decimal point is:

int digits = (int)log(values[i])/log(10) + 1;

so if you run through your data beforehand you can decide an appropriate column width.

As for digits after the decimal point, that decision is up to you; the quirks of floating point representations can lead to result an end-user might find confusing. For example in the data I used above 3.1 * 2.0 * 9.5 is printed as 58.899998 if allowed to print all digits, whereas 5.9 is probably what end-users would expect.

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141145

I need my printout to accommodate the different lengths of numbers, the formats don't mix though, so for example it can't be 5 4.52653 3.674

That's simple (ok, it's not that simple)! Just:

  • Allocate a 2d array of output strings.
  • Print all elements to this array.
  • For each column in the output array:
    • get the longest element of this column.
  • Print each column padded with spaces to the longest element.
  • Print spaces between columns and newlines between rows.

For inspiration, see util-linux/column.c utility.

And, well, a code sample: with array of two ints with different values, let's print them:

#define _GNU_SOURCE 1 // for asprintf
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    int a;
    int b;
} data_t;
#define DATA_ELEMS  2  // the count of output columns
#define COUNT       3  // the count of output rows

int main() {
    // some data we are going to print
    const data_t data[COUNT] = {
        {rand(),rand()},
        {rand(),rand()},
        {1,2},
    };
    // Print all elements to this array.
    char *strs[COUNT][DATA_ELEMS] = {0};
    for (size_t i = 0; i < COUNT; ++i) {
        const data_t *it = &data[i];
        asprintf(&strs[i][0], "%d", it->a);
        asprintf(&strs[i][1], "%d", it->b);
        // TODO: error checking
    }
    // For each column in the output array:
    // get the longest element of this column.
    size_t collens[DATA_ELEMS] = {0};
    for (size_t i = 0; i < COUNT; ++i) {
        for (size_t j = 0; j < DATA_ELEMS; ++j) {
            if (collens[j] < strlen(strs[i][j])) {
                collens[j] = strlen(strs[i][j]);
            }
        }
    }
    
    for (size_t i = 0; i < COUNT; ++i) {
        // Print each column padded with spaces to the longest element.
        for (size_t j = 0; j < DATA_ELEMS; ++j) {
            printf("%*s", (int)collens[j], strs[i][j]);
            // Print spaces between columns.
            putchar(j + 1 == DATA_ELEMS ? '\n' : ' ');
        }
    }

    for (size_t i = 0; i < COUNT; ++i) {
        for (size_t j = 0; j < DATA_ELEMS; ++j) {
            free(strs[i][j]);
        }
    }
}

will for example output on godbolt:

1804289383  846930886
1681692777 1714636915
         1          2

Upvotes: 1

Related Questions