Skip to main content

PWR071: Prefer real(kind=kind_value) for declaring consistent floating types

Issue

Floating-point variables declared with constructs other than real(kind_value) or real(kind=kind_value) may not adhere to Fortran standards, and even offer inconsistent behavior across platforms.

Actions

To improve code portability and readability, prefer defining floating point types using real(kind=kind_value), selecting a kind value derived from either selected_real_kind() or the iso_fortran_env module.

Relevance

The standard-conforming real(kind=kind_value) construct allows developers to clearly define the desired precision, improving code readability, while also ensuring consistent behavior of the program across different development environments.

Therefore, avoid commonly used floating point declarations such as:

  • real: The precision is unspecified and may vary depending on the compiler or compilation flags, such as GNU's -fdefault-real-8.

  • double precision: Just an alias for a high-precision kind. It might also vary depending on the compiler or compilation flags, such as GNU's -fdefault-real-8.

  • real*4, real*8, etc.: Common extensions derived from the character type, specifying the byte size of the variable. They are not recognized by the Fortran standard, potentially limiting portability.

Code example

Consider the following floating-point declarations, which are poor in portability, flexibility, and explicitness in precision:

! example.f90
program test_discouraged_real_types
implicit none

real :: single_precision_variable
real*8 :: double_precision_variable
! Or `double precision`

! Note the `_d0` suffix, indicating the precision of the value
single_precision_variable = 0.123456
double_precision_variable = 0.123456789012345d0

print *, 'Single precision variable:', single_precision_variable
print *, 'Double precision variable:', double_precision_variable
end program test_discouraged_real_types

To conveniently control the program's precision and ensure portability, it is recommended to use a central module where different kind parameters are defined, either using selected_real_kind(), or the iso_fortran_env module.

Let's start with an example using selected_real_kind(), which allows to specify a minimum amount of significant digits and exponent range:

! solution_selected_real_kind.f90
module my_kinds
implicit none
public

! single precision (sp): 6 significant digits, 37 exponent range
integer, parameter :: sp = selected_real_kind(6, 37)
! double precision (dp): 15 significant digits, 307 exponent range
integer, parameter :: dp = selected_real_kind(15, 307)
end module my_kinds

program test_selected_real_kind
use my_kinds, only: sp, dp
implicit none

real(kind=sp) :: single_precision_variable
real(kind=dp) :: double_precision_variable

! Note the `_sp` and `_dp` suffixes, indicating the precision of the value
single_precision_variable = 0.123456_sp
double_precision_variable = 0.123456789012345_dp

print *, 'Single precision variable:', single_precision_variable
print *, 'Double precision variable:', double_precision_variable
end program test_selected_real_kind

Notice how the encapsulation of sp and dp inside my_kinds allows to use a consistent kind value across the codebase. The flexibility of the program is significantly increased, allowing to rapidly adjust the desired compute precision. Consider, for instance, that during testing you recognize the need for higher precision computations due to the accumulation of residual numerical errors. The central module of kinds allows to update the declaration of tens or hundreds of variables by changing a single line of code.

Another option, depending on your preferences, is using the iso_fortran_env module. It results in a "lower-level" approach that allows to select the byte size of the variables (from Fortran 2008 onwards):

! solution_iso_fortran_env.f90
module my_kinds
use iso_fortran_env, only: real32, real64
implicit none
public

! single precision (sp)
integer, parameter :: sp = real32
! double precision (dp)
integer, parameter :: dp = real64
end module my_kinds

program test_iso_fortran_env
use my_kinds, only: sp, dp
implicit none

real(kind=sp) :: single_precision_variable
real(kind=dp) :: double_precision_variable

! Note the `_sp` and `_dp` suffixes, indicating the precision of the value
single_precision_variable = 0.123456_sp
double_precision_variable = 0.123456789012345_dp

print *, 'Single precision variable:', single_precision_variable
print *, 'Double precision variable:', double_precision_variable
end program test_iso_fortran_env
warning

Avoid specifying kind with hard-coded values. While many compilers interpret these as the number of bytes required by the variable, other compilers might not, leading to potential bugs. For instance, NAG's compiler uses sequential values for kind (kind=1 = 4 bytes, kind=2 = 8 bytes), unless -kind=byte is also specified in the compilation flags.

tip

Check out the kind values provided by the iso_c_binding module to ensure proper floating-point interoperability when working with both C and Fortran codes.

References