How useful is selected_int_kind and selected_real_kind?

As I’m building my Fortran lecture, I’m asking myself if it’s worth to teach selected_*_kind.
When I want to explain why they exist, my first idea was to say it’s because of data types weren’t standardized, but selected_*_kind was added in Fortran 90. What I found is that today’s de facto standard data types already were common when these functions got added. Please correct me if I’m wrong, when Fortran 90 was released I was -5 years old. :sweat_smile:

Relevance of selected_int_kind und selected_real_kind
  • I use selected_*_kind() in my programs
  • I use iso_fortran_env for kinds in my programs
  • I use both
  • I don’t use either
  • It’s worth teaching
  • It’s not worth teaching
  • Don’t know / It depends
0 voters
3 Likes

Interesting poll!
selected-*-kind used to be very useful, all my old programs have a “Types” module with it. But with the introduction of iso_fortran_env (and when that was accessible by common mortals using common compilers) I didn’t feel the need to use it anymore. Still worth teaching (or at least mentioning) though.
And who knows, maybe we will need it again, when real512 or something starts to be meaningless and iso_fortran_env didn’t catch up yet.

4 Likes

In a “throwaway” code I may write

dp = kind(1.0d0)

In more serious codes I typically have a line

use kind_mod, only: dp

where kind.f90 defines dp using selected_real_kind.

An advantage of selected_real_kind is that it will work on a compiler that supports Fortran 90 or any later standard. Dr. Fortran (@sblionel) recommends using selected_real_kind over say real64 even when the iso_fortran_env module is available:

Fortran 2008 extended intrinsic module ISO_FORTRAN_ENV to include named constants INT8, INT16, INT32, INT64, REAL32, REAL64 and REAL128 whose values correspond to the kinds of integer and real kinds that occupy the stated number of bits. More than one response in that newsgroup thread recommended using these instead of hard-coding integer and real kinds. In my view, this is little better than the old *n extension in that it tells you that a type fits in that many bits, but nothing else about it. As an example, there’s one compiler where REAL128 is stored in 128 bits but is really the 80-bit “extended precision” real used in the old x87 floating point stack registers. If you use these you might think you’re using a portable feature, but really, you’re not and may get bitten when the kind you get doesn’t have the capabilities you need. My advice is to not use those constants unless you truly understand what they do and do not provide. Most applications should use the SELECTED_xxx_KIND intrinsics as I described above. If you want to call your kind DP to indicate “double precision”, that’s fine – just use SELECTED_REAL_KIND to get it.

6 Likes

I think it is a good idea for people involved in numeric programming to have some understanding of the reality of computer arithmetic, regardless of programming language. I worked in Computing Services at Imperial College (1978-1986) and at King’s College (1986-2002) and there was quite a wide range of real types in use. The DEC VAX was a popular machine during this period. The latest IEEE standard IEEE 754 - Wikipedia mentions support for binary 16, and decimal 32, decimal 64 and decimal 128. The Nag compiler already supports 16 bit reals. gfortran supports 80 bit reals.

2 Likes

This has been discussed many times here, just search for “selected_real_kind”, some links from quick search:

I personally do not like the current situation in Fortran, I think the selected_real_kind is a leaky abstraction, because it seems to suggest that one can use it to write Fortran code that is independent of the machine floating point implementation details. But in practice based on my experience you can’t, you typically have to assume single or double IEEE implementation. Others are rarely used, but if instead of IEEE double it was some other implementation (that some machines used to have 40 years ago), my codes would almost for sure not run correctly.

I do think the goal of Fortran should be to allow writing code that is platform independent and highly performing.

But for that, you would need better compiler (and possibly language) support that would emulate other floating point implementations (such as Cray floating point and others), or somehow guarantee the code to give accurate answers, either by compile time guarantees, or runtime guarantees. It might still be a research problem.

That being said, I do use language constructs like epsilon, tiny, etc. But just using those is not enough to guarantee that the code will work with any floating point implementation.

6 Likes

I guess the idea of selected_xxx_kind is that you’ll get -1 if the kind-specs are unavailable on the target machine and, testing for that, you can catch the issue without running subtly incorrect code.

I agree, however, that (although it was great for the time) selected_xxx_kind needs to be improved nowadays as you describe.

1 Like

From my experience, almost all newcomers in Fortran think selected_real_kind will do “the right thing” no matter what, and independent of the machine. It’s easy to interpret it as such.

For what it is, however, selected_real_kind did help a lot. Remember the countless *MACH variables in legacy code?

I think the best choice depends on what kind of code you are writing. I usually write code that runs with a single working precision. The parameter wp is defined in a single place, and then propagated. That precision is chosen for what I think are the requirements for my application. This allows me to change that definition in a single place, recompile, and test the lower or higher precision results to verify my choice. If I ever run on a 36-bit, 60-bit, or 64-bit computer again, I just change that value of wp and I’m good to go. :wink:

I do very little actual multiple-precision calculations. These are where some calculations within an application are done in one precision, and others are done in a different precision. For example, say you want to use a GPU that supports only 32-bit floating point within an otherwise 64-bit computational environment. These calculations were more popular about 20 years ago when GPU programming first became popular and the hardware was limited. However, it seems like it is becoming popular again today even when GPUs support both 32- and 64-bit floating point. In these cases, it is necessary for the fortran programmer to target the hardware, he does not have the luxury of choosing the precision to fit the application.

If you are writing a library that is intended to support every possible REAL_KINDS(:) value, then that places even different demands on the programmer. I would characterize this as targeting the compiler rather than either the application or the hardware.

So from this perspective, SELECTED_REAL_KIND() is appropriate for targeting the application, REAL32/REAL64 is appropriate for targeting the hardware, and REAL_KINDS(:) is appropriate for targeting the compiler. There are, of course, many in-between situations. In teaching a course, it seems like all three approaches are indicated.

1 Like

With iso_fortran_env, the kind will be negative (-2 or -1) if unavailable.

Well, I’m not surprised about this, since I think iso_fortran_env has been added a few years after selected_xxx_kind.

However, I don’t see how this is relevant to what has been discussed. As already mentioned above (citing @sblionel 's excellent blog post), you can select REAL128 with iso_fortran_env, do not get -1 or -2, and run code under the false impression that you’re using 128 bits reals, while you really get 80 bit “extended precision”…until you get bitten by it. You don’t have that problem with selected_real_kind.

No one else has mentioned this, but there is one fairly common “gotcha” with new fortran programmers related to the KIND system and numerical precision. Many programmers use other languages such as Mathematica where the precision can be specified within each expression. If you want a 10-digit representation of pi, then you get it with N[Pi,10]. If you want a 100-digit representation, it would be N[Pi,100], and so on. So when new programmers first see the fortran kind system, they sometimes think that selected_real_kind(10) gives them 10 digits. If that isn’t enough, then they think that selected_real_kind(14) will give them then the extra digits that they need.

Of course, if you print out the kind values, or use the intrinsic inquiry functions, you will see that on most compilers those two function calls return the same kind value, with of course the same precision and exponent range. So it doesn’t take long to see what is really happening in fortran and then begin to use the KIND system correctly, but this is sometimes a little bit of a learning curve for new fortran programmers.

I’m curious about some of the discussion above. Which compiler is it that returns 80-bit precision for REAL128?

2 Likes

It was(?) gfortran. I don’t have the opportunity to check now, but a few years ago I vaguely recall that it was fixed to map real128 to the actual 128 bits storage size. I remember that there was some resistance caused by ABI breakage to approve the patch. gfortran developers on this Discourse might correct my vague recollection.

This is not really the point, though: as mentioned by Dr Fortran, trusting the mapping of iso_fortran_env is fragile and we have better means here and now.

To me, neither selected_real_kind nor iso_fortran_env are fully satisfactory.

selected_real_kind pros

  • completely hardware agnostic

selected_real_kind cons

  • you can’t know if you get a native hardware type or a software emulated type
  • no “fallback” to a kind with less precision/range in the case no kind exit with the desired precision/range

iso_fortran_env pros

  • convenient to quickly get the right kind that work on most current hardware

iso_fortran_env cons

  • you still don’t know if you get a native hardware type or a software emulated type
  • still no fallback
  • not hardware agnostic at all: assuming that all types are 32/64/128 bits can look reasonable in an era where virtually all machines follow the IEEE754 standard, nonetheless it breaks the universality of the codes. What will happen to all these codes full of int32, real64, etc, if in the future some more exotic hardware show up again, with different storage sizes? Maybe unlikely, but definitely not impossible.

I actually see little added value to the kinds provided by iso_fortran_env, apart for quickly writing some test codes.

3 Likes

I think that gfortran supports true 128 bit reals via libquadmath for several years now.

It might have been that it is since 2017 that real128 maps actual 128 bits storage size (if the patch was finally accepted). Probably 128 bits reals via libquadmath might have been available from well before 2017. But don’t trust too much my memory… :slight_smile:

I don’t really trust my memory on this either, but gfortran has supported kind values of 4, 8, 10, and 16 for well over a decade now. The screwy thing to me about the gfortran implementation is that the 10-byte reals are stored in 16 bytes, so both of those kind values are stored in 128 bits, just with different precision and exponent ranges. I guess the REAL128 issue is then which of those two kind values, 10 or 16, was mapped to REAL128. I forget, when was REAL128 added to iso_fortran_env?

This issue of multiple kind values for a given storage size is not going away. Consider 32, 64, and 128-bit IEEE binary and decimal floating point kinds.

Regarding the question of when are the ISO parameters useful, consider this. What is the best way to access 64-bit integer and real kind values that can be storage sequence associated? One easy way is to use INT64 and REAL64. If instead you try to use selected_xxx_kind() or the xxx_KINDS(:) arrays, it requires further trickery to get the right matches.

This fallback can be done using merge() or algebraic expressions, but it isn’t pretty. Here is an example from one of my codes (dated about 2004 I think).

   integer, parameter :: COLUMBUS_WP            = selected_real_kind(14)  
   integer, parameter :: COLUMBUS_EP_REQUESTED  = selected_real_kind(17) 
   integer, parameter :: COLUMBUS_EEP_REQUESTED = selected_real_kind(30) 

   ! the following initialization expressions are equivalent to:
   !  if ( COLUMBUS_EP_REQUESTED < 0 ) then
   !     COLUMBUS_EP = COLUMBUS_WP
   !  else
   !     COLUMBUS_EP = COLUMBUS_EP_REQUESTED
   !  endif
   integer, parameter :: COLUMBUS_EP = (1+sign(1,COLUMBUS_EP_REQUESTED))/2 * COLUMBUS_EP_REQUESTED + & 
        &                              (1-sign(1,COLUMBUS_EP_REQUESTED))/2 * COLUMBUS_WP
   integer, parameter :: COLUMBUS_EEP = (1+sign(1,COLUMBUS_EEP_REQUESTED))/2 * COLUMBUS_EEP_REQUESTED + & 
        &                               (1-sign(1,COLUMBUS_EEP_REQUESTED))/2 * COLUMBUS_EP

The *_REQUESTED values can be negative, the other ones are the fallback kind values. I think the merge() equivalent expression is probably cleaner now, but I don’t think merge() was allowed in an initialization expression when I first wrote this. With gfortran on intel hardware, these kind values are 8, 10, and 16, which is what I was after. On other compilers or other hardware, there are usually just two or sometimes even one distinct kind value.

I always define single and double precision kind parameters as, e.g., dp = kind(1.d0). I do this because I often call old libraries that use real or double precision declarations. For me, guaranteeing my kind parameters match up with those interfaces is most important.

With the increasing adoption of fpm, I will eventually adapt my practice to match whatever the prevailing convention is in the “modern” ecosystem. So far, the iso_fortran_env constants seem to be where people are moving.

3 Likes

I think the use of select_*_kind totally back to front.

I select what precision and storage I require, based on the IEEE number formats available then use integer*4, integer*8 or real*8. I use these definitions, as they are the most portable for the multiple compilers I use. These are used in the IEEE definition !

If I use select_*_kind, I will determine what precision value to provide, based on what kind value I want.
I have never been in the position of having a desired precision value, then use select_*_kind.

The other significant problem with this KIND definition is there is not a defined integer kind that corresponds with the memory address format. All 64-bit Fortran compilers I have used, fail in this respect, as the 4-byte default integer kind does not provide for the 64-bit memory address. The Fortran Standard definition is happy that the default kind of SIZE gives an incorrect estimate of an array size. This default KIND is not fit for purpose. This is not fixed as this is what the Fortran Standard says !

How about INTEGER(SIZE_T) or INTEGER(C_PTRDIFF_T)? In practice, those KIND values are probably the same, so I’m not sure exactly when a fortran programmer should use one vs. the other. Of course, these are signed integer types, as fortran does not support directly unsigned integer types, so they store the bits, not necessarily the values.

Regarding the use of REAL*8 and so on, this was the most portable way to write code in f77 and before. But the f90 KIND system is much better, more portable, easier to use, more flexible, more open ended, and on and on. If the programmer needs to interface to legacy code that uses those definitions, then this approach

real*8 r8dummy
integer, parameter :: R8K = kind(r8dummy)

gives you the KIND value that corresponds to the legacy code type. Any new code can then be written in the modern way with the R8K parameterized kind value.

We use real(8), real(4), complex(8), complex(4), integer(4), integer(8) in our codes. I personally like having the number in parenthesis meaning the number of bytes. I know that this is not portable, but it now seems to be part of the de facto standard: most compilers (except NAG) will take this to mean the number of bytes. Some use this convention in their manuals:

Intel: REAL(8) or DOUBLE PRECISION Constants

Nvidia: HPC Compiler Reference Manual Version 23.11 for ARM, OpenPower, x86

I have a feel for the useful precision four and eight byte reals will give me: they have enough ‘granularity’ for that. selected_real_kind(p,r) is effectively an elaborate map from two integers to either real(4) or real(8), this just adds a layer of complexity and uncertainty, which (at least for our work) is unnecessary.

Not only that, but modern CPU and GPU hardware seems to have settled on 2, 4 and 8 byte floating point storage for the sake of efficiency. It makes sense to be able to select these directly rather than indirectly via selected_real_kind.

1 Like