In this subsection, we’ll implement all three specific functions, one for each data type that we intend to parse: integer, real, and logical. We’ll need the following specific functions:
average_real(x)–Returns an average value of a one-dimensional real arrayx. This function will operate on temperature and humidity time series.average_int(x)–Returns a real average value of a one-dimensional integer arrayx. This function will operate on wind speed time series.average_logical(x)–Returns a real average value of a one-dimensional logical arrayx, whereTruevalues are represented as ones, andFalsevalues 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 expressionx,real(x)returns its value as a real number.xmust be of typeinteger,real, orcomplex. This function accepts an optionalkindparameter, where you can specify the desired type kind of the result (for examplereal32,real64, orreal128). Note the distinction between the built-in functionreal()and therealdata type.kind–Given input variable or expressionx,kind(x)returns the type kind value ofx. Use this whenever you need to make sure that the functionrealpromotes to the kind that you need, rather than the default one (real32on 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.