Text formatting

This lesson explains text formatting by means of examples, in the context of print output. It does not explain all uses of text formatting, nor all details of text formatting. For those details, see the reference manual’s text formatting details documentation instead.

In this lesson, only the output from print declarations is shown when console output is presented. All other output, such as that from the CIF simulator, is omitted.

While we explain text formatting in the context of print declarations, text formatting by means of the fmt function can also be used to create string values in other contexts.

Introduction

Consider the following CIF specification:

print "Time=" + <string>time;

When simulated, it could give the following output:

Time=0.0
Time=3.333333333333336
Time=5.000000000000001
Time=10.0

As you can see, due to rounding and simulation imprecision, the length of the textual representations of the values of time can vary wildly during simulation. Furthermore, the explicit casts (conversions) from real to string (<string>) and string concatenation (the + operator) can quickly become cluttered, and hard to read.

To improve readability of the print declaration, we could adapt the CIF specification to the following:

print fmt("Time=%s", time);

Here, manual text construction has been replaced by the use of the fmt standard library function. The first argument is a format pattern, and the remaining arguments are the values that are to be included in the text, in this case the value of variable time. The fmt function ensures that we no longer have to use casts. We also no longer have to create pieces of text and concatenate them (using the + operator).

This is a typical use of a format pattern. The print declaration prints the value that results from text formatting. The value that is used is the value of variable time. The value of this variable is not used 'as is', but is instead converted to a textual representation using the format pattern. This format pattern specifies that the resulting text should start with Time=. It also specifies that the value (of variable time) should be included at the end of the textual representation. The %s part of the format pattern is a format specifier. The %s specifier is the generic specifier that can convert any type of value to a textual representation.

When simulated, this new specification gives the exact same output as the previous version.

Real value formatting

As we saw in the previous section, due to rounding and simulation imprecision, the length of the generic textual representations of the values of variable time can vary wildly during simulation. This reduces readability of the results, as a lot of irrelevant digits are included. Consider the following alternative CIF specification:

print fmt("Time=%.2f", time);

Here, instead of a %s specifier, a %f specifier is used. This specifier can only be used for real numbers, and indicates that the number should be formatted as a floating point number in decimal representation. The .2 part specifies the precision, and indicates that the floating point number in decimal notation should have exactly two digits after the decimal point. When simulated, this could give the following output:

Time=0.00
Time=3.33
Time=5.00
Time=10.00

Putting values in columns

In the examples above, we included a single value in the output. Now consider the following CIF specification:

print fmt("%.2f %.2f %.2f", x, y, z);

Here, the values of variables x, y, and z are included in the formatted result. The format pattern includes each of the values (the first %.2f includes the value of variable x, the second %.2f includes the value of variable y, etc). The format pattern includes spaces between the format specifiers (between the %.2f parts), and thus the formatted values include spaces between them as well. When simulated, this could give the following output:

1.50 0.00 -3.57
2.34 3.75 5.78
4.71 12345.34 -3.12
-3.25 1.25 99.20

Due to some larger values being mixed with shorter values, as well as due to having both positive and negative values, the output is not so easy to read. This can be solved by using the following CIF specification instead:

print fmt("%10.2f %10.2f %10.2f", x, y, z);

By including a width of 10, the text is now nicely formatted into columns of ten characters wide:

---------- ---------- ----------
      1.50       0.00      -3.57
      2.34       3.75       5.78
      4.71   12345.34      -3.12
     -3.25       1.25      99.20

The first line is not actual output, but is included to make it easier to see the columns. Not only is the output now nicely put in columns, but due to the exactly two digits after the dot (.), the dots are now also nicely aligned.

Large numbers

Consider the following CIF specification:

print fmt("%.2f", time);

When simulated, this could give the following output:

0.00
1.34
5000.23
2147185402.17

As the values of variable time get larger and larger, their textual representations become longer and longer, and it becomes more and more difficult to see exactly how large the values are. The following alternative CIF specification solves this problem:

print fmt("%,.2f", time);

The addition of the comma (,) flag ensures that we get the following output instead:

0.00
1.34
5,000.23
2,147,185,402.17

That is, a comma is used in the result as thousand separator, making it easier to see that the value of variable time is just over two billion, rather than for instance just over 200 million.

Left alignment

Earlier, we put values in columns. By default, if a width is used, text is right-justified (aligned to the right). We can also justify it to the left, as in this CIF specification:

print fmt("%-10.2d %-10.2d %-10.2d", g, h, i);

Here the %d specifier is used instead of the %f specifier. The %d specifier can only be used for integer values, while the %f specifier can only be used for real values. Besides a different specifier, the minus (-) flag is added, and different variables are used as values. After the changes, the CIF specification could result in the following output:

---------- ---------- ----------
184        3675       2
19350      29         -2956
-17        -964563    235
2946       567        -25072563

Once again, the first line is not actual output, but is included to make it easier to see the columns. Observe how the addition of the - flag resulted in the right-justified output being changed to left-justified (aligned to the left) output. The presence of negative numbers makes that the left-most digits of the columns are not nicely aligned. The following CIF specification solves this:

print fmt("%-+10.2d %-+10.2d %-+10.2d", x, y, z);

The addition of the plus (+) flag means that for non-negative numbers, a plus (+) character is always included:

---------- ---------- ----------
+184       +3675      +2
+19350     +29        -2956
-17        -964563    +235
+2946      +567       -25072563

If preferred, a space can be used instead of a plus, resulting in the following CIF specification:

print fmt("%- 10.2d %- 10.2d %- 10.2d", x, y, z);

and the following output:

---------- ---------- ----------
 184        3675       2
 19350      29        -2956
-17        -964563     235
 2946       567       -25072563

More formatting

In this lesson, we’ve seen a few forms of text formatting using the fmt function. However, CIF supports various other specifiers, besides the %s, %f, and %d specifiers that were used in this lesson. All those specifiers support various flags, widths, and precisions, and allow them to be combined in various ways. For the complete details of text formatting, see the reference manual’s text formatting details documentation.