Fortran

Guide To Learn

Writing the specific functions

In this subsection, we’ll implement all three specific functions, one for each data type that we intend to parse: integerreal, and logical. We’ll need the following specific functions:

  • average_real(x)–Returns an average value of a one-dimensional real array x. This function will operate on temperature and humidity time series.
  • average_int(x)–Returns a real average value of a one-dimensional integer array x. This function will operate on wind speed time series.
  • average_logical(x)–Returns a real average value of a one-dimensional logical array x, where True values are represented as ones, and False values as zeros. This function will operate on time series of clear sky data.

Let’s write the first specific function, which will operate on real arrays. Note that we already wrote this function in section 5.3, when analyzing stock price time series.

The real implementation

The implementation of the average function for real numbers is the simplest of the three. As noted, it’s simple enough that we already implemented one in chapter 5. The following listing reproduces that function.

Listing 9.1 A function to average real arrays

pure real function average_real(x) result(res)
  real, intent(in) :: x(:)                      ❶
  res = sum(x) / size(x)                        ❷
end function average_real

❶ Assumed size, one-dimensional real array

❷ Divides the sum by the number of elements

This function takes a real, one-dimensional array x as input, computes the sum of all its elements (sum(x)), and divides it by the total number of elements (size(x)) to get us to the arithmetic average. Recall the rules about type coercion and mixed mode arithmetic from section 5.2.2. When you mix different numeric types (such as integer and real) in an expression, a variable or expression of a lower type is always promoted to the higher type (integer < real < complex) before evaluating the operation. Here, sum(x) evaluates to a real number because x is a real array, while size(x) always returns an integer. However, since here we’re dividing a real number by an integer, the integer is promoted to a real before the division operation is evaluated. Unlike before, I’ve appended the name of the input type (real) to the function name so we can set it apart from other specific functions.

The integer implementation

The analogous averaging functions on integers should work the same way as for reals, thanks to both sum and size functions supporting either type. However, unlike with average_real, here we have to be careful about integer division when evaluating sum(x) / size(x). For example, if x is [1., 1., 2.] (real numbers), sum(x) / size(x) will evaluate to 1.66666663, as expected. However, if x is [1, 1, 2] (integers), sum(x) / size(x) will evaluate to 1, because dividing one integer with another always returns an integer. Now that x is an integer array, we can’t rely on automatic type coercion to get the correct result. We need to do some extra work to make sure we don’t fall prey to unintended integer division, like in the example we just considered.

A dilemma comes up about whether the result should be an integer or a real, considering the integer array as input. Ultimately, this depends on your application and how the result will be used. In certain applications, an integer average result is desired (for example, passing an average age of a population to a function that expects an integer). For our example, we’ll just stick to a real-typed result for all specific implementations of the average function, as shown in the following listing.

Listing 9.2 A function to average an array of integers

pure real function average_int(x) result(res)
  integer, intent(in) :: x(:)                      ❶
  res = real(sum(x), kind=kind(res)) / size(x)     ❷
end function average_int

❶ Assumed size, one-dimensional real array

❷ Explicitly promotes the sum to a real number before dividing

While at its core similar to the average_real implementation, here we need to take special care with the conversion from integer to real. For an integer array x, both sum(x) and size(x) return an integer by definition. As integer division always returns an integer, we’d actually get an incorrect result in any case where sum(x) is not divisible by size(x). We can work around this by explicitly promoting sum(x) to a real number before dividing by size(x).

Here we’ve used two built-in functions:

  • real–Given input variable or expression xreal(x) returns its value as a real number. x must be of type integerreal, or complex. This function accepts an optional kind parameter, where you can specify the desired type kind of the result (for example real32real64, or real128). Note the distinction between the built-in function real() and the real data type.
  • kind –Given input variable or expression xkind(x) returns the type kind value of x. Use this whenever you need to make sure that the function real promotes to the kind that you need, rather than the default one (real32 on most compilers and architectures).

In listing 9.2, I used the built-in function real to explicitly promote the integer to a real number. If you remember the type coercion rules from section 5.2.2, I could’ve done this implicitly:

res = (1.0 * sum(x)) / size(x)

Here, we implicitly promote sum(x) to a real number by multiplying it by 1.0 (a real number). We carefully enclose this operation in parentheses to ensure that it evaluates before the division with size(x), which would otherwise return an integer–not what we intended. Whether you choose to promote types explicitly or implicitly is a matter of style. Implicit usually leads to more concise code, while explicit clearly communicates the intent. Although it may take a few thought cycles to understand why we’re multiplying a number by 1.0, real(sum(x)) is as clear as you can get. This especially makes a difference when the person reading the code is your colleague from the office next door, your open source contributor in Japan, or yourself a few years from now.

Tip We repeat Tim Peters’s mantra from the Zen of Python: “Explicit is better than implicit.” Explicit type promotion, while more verbose, will almost always be clearer and easier to understand.

This takes care of integer and real data. On to logical.

The logical implementation

For the logical average implementation, we need to do something a little bit different. This is because the average value of a logical array is not well defined. The most intuitive interpretation of an average of True or False values is perhaps the probability of occurrence. If an array has 99 elements that are False and one that’s True, the average value could be interpreted as 0.01 truth probability, or 1%. For our weather average application, where we want to quantify how often the skies are clear or cloudy, this is just the right meaning, as shown in the following listing.

Listing 9.3 A function to average an array of logical values

pure real function average_logical(x) result(res)
  logical, intent(in) :: x(:)
  res = real(count(x), kind=kind(res)) / size(x)      ❶
end function average_logical

❶ Counts the number of True elements, casts it to real, then divides by the total number

Here, we’re using the built-in function count, which returns the number of elements in a logical array x (whether a variable or an expression) that evaluate as .true.. By counting the number of True elements and dividing that by the size of the array, we effectively define our average of the logical array as a real number between 0, if all elements are .false., and 1, if all elements are .true..

And that’s all as far as specific procedures are concerned. Our next step is to write the generic interface that will override these procedures. If you’re following along with the code checked out from the GitHub repo, these functions are implemented in src/mod_average.f 90.

Writing the specific functions

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top