Fortran from the ground up

Capstone: a stock report

A table of records, a total and an average, and a report whose columns line up — the whole course working in one file, from the type declaration to the formatted write.

Part 12 of 12

Advanced22 min readBack to Courses

The shape of the program

This program keeps a small stock list. Each part is a record with a name, a count of units and a unit price. The records sit in one array, sized by a named constant, and are created in a single array constructor.

From there it is arithmetic and output: multiply units by price for each line, add the lines for the total, divide by the number of lines for the average, and print the rows under a header. Nothing here is new, and that is the point of a capstone — the earlier pieces, arranged deliberately.

The named constant n is used three times: the array declaration, the loop bound and the average divisor. Changing it in one place changes all three, which is the point of a parameter. A literal 3 in any of those places would be a second source of truth, and the two would drift the first time a part was added.

Where the chapters land

The type :: part block and the constructor calls come from the records chapter. The array, its size and the do loop over 1, n come from the arrays and loops chapters. sum(stock%units * stock%price) is the array-function habit from chapter 5 applied to a record component — the multiplication produces an array of line totals and sum collapses it.

The report lines are the formatted output chapter doing what it was introduced for: an A field for the name, I for the count, F for the money, and a fixed width for each so the columns cannot drift. The declared widths in the header line and in the row format are the same numbers, which is what keeps them together.

What a real one adds

A production version reads the table from a file or a database instead of building it in code, which needs an allocatable array and a read loop; this runtime has no input, so the data is in the program. It would also sort the rows, which is one call to a library routine in modern Fortran rather than a hand-written loop.

A sorted report needs either a library routine or a permutation. The permutation keeps the records in place and reorders only an index array with a hand-written pass, which is cheap when the records are large.

The other change is money. real holds about seven digits and cannot represent 0.10 exactly, so a long list of prices accumulates a small error. Real accounting code keeps cents in an integer and divides only when printing — a change worth making the day the numbers matter.

The line that does the arithmetic

! one array of line totals, then one total
total = sum(stock%units * stock%price)

! one row, with every width fixed
write(*, '(A8,I8,F8.2,F9.2)') stock(i)%name, &
  stock(i)%units, stock(i)%price, &
  stock(i)%units * stock(i)%price

Reading the report as a pipeline

The program is three passes over the same data. First the records are built, one constructor per line, with a named constant fixing how many there are. Then the arithmetic runs over whole components: stock%units * stock%price is an array of line values, sum collapses it to one total, and maxloc on the same expression finds the row worth the most. Finally the format turns each value into a fixed-width field.

Keeping the passes separate is what makes the report easy to change. Add a column and you add one expression to the arithmetic and one descriptor to two format strings; change the data and the loop follows n. The one thing that does not scale by itself is the width of a field, which is why the header and the rows must be written from the same numbers and checked together.

Check the total against the rows by adding them by hand once. A report that prints the right shape and the wrong number is the failure mode this chapter is about.

Three ways this report goes wrong: a header width that differs from the row width, so the columns drift apart; an A field narrower than the longest name, which silently cuts the name; and an average computed as total / count when both are integers, which drops the fraction. The first two are visible the moment you run it; the third is visible only if you check the number.

Try these next

  • Add a fourth part and let n, the array and the loop follow.
  • Use maxloc to print the name of the most valuable line.
  • Convert the prices to integer cents and print the total without a decimal point.

A table, a total, an average

Run it and read the report. Then add a fourth part to the constructor, change n to 4, and check that the total and average follow without another change.

Output
Click "Run" to execute your code

The same report, with a best seller

Run it and check the best-seller line. Then add a fourth part, change `n` to 4, and see which lines need no edit at all.

Output
Click "Run" to execute your code

Change one width in the header line and the same width in the row format, run it, and watch the columns move together. A report is a format and a loop, and the format is the part worth reading twice.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does stock%units * stock%price produce?