How useful is selected_int_kind and selected_real_kind?

I think they all give different perspectives.

  1. Physical perspective

Using (kind = 4) or (8) for instance, gives the physical interpretation of the data. The higher the kind number the greater the amount of data can be stored. So it is intuitive to use it physically.

  1. Mathematical perspective

The next one, selected_int_kind (precision, range) gives more intuition for mathematical aspect. It can tell me how many significant digits I can get using which precision. That seems more accurate to know exactly the precision of the data.

  1. Computer Science perspective

The latest one iso_fortran_env gives impression of using the standard digital information units. To me, int32 does sound very compute machine oriented.

In the end, the user is supposed to know what he is trying to achieve on which platform under which conditions!

1 Like

My codes often need to exchange data with python, therefore I use c_int and c_double from iso_c_binding.

1 Like

Sure, it’s possible to handle the fallback in the code, but it’s a bit cryptic, even with merge. It would be nice to have it as an option of selected_real_kind():

wp = selected_real_kind(p=30,fallback=.true.) 

returns a kind with at least 30 digits precision if it exists, or the kind with the highest precision otherwise. But I admit it would be ambiguous if both precision and range were specified.

2 Likes

This seems to be exactly the use case for the constants INT32, INT64, REAL32, and REAL64, with the exceptions that these constants are standardized and the literals 4 and 8 aren’t.

This convention was before the constants were defined as a way to document the kind values for the compilers. It is confusing, and nowadays I wish that they would switch to the other convention as a way to encourage programmers to write code in a more portable way.

As mentioned earlier in this thread, we all know that decimal floating point arithmetic is coming, and that will necessarily break the “de facto convention” that the kind values are the byte counts.

This seems more abstract than necessary. If this option were to be added to the intrinsic functions, I think as a programmer I would prefer something like

selected_real_kind(p=30,fallback=value)`

where value is a constant or initialization expression that is a valid real kind value. Selecting precisions with decimal digit counts is too coarse, and arguably too abstract, for some applications. If the programmer has a desired fallback value, then allow him to just specify it directly.

This convention was before the constants were defined as a way to document the kind values for the compilers. It is confusing, and nowadays I wish that they would switch to the other convention as a way to encourage programmers to write code in a more portable way.

As mentioned earlier in this thread, we all know that decimal floating point arithmetic is coming, and that will necessarily break the “de facto convention” that the kind values are the byte counts.

Here I disagree with you: no compiler writer is going to break large amounts of code by redefining what real(8) means.

I personally find real*8 and real(8) natural and easy on the eye and, just like with spoken languages, Fortran users ultimately have a say in what the standard is by using the language as they like it.

(Incidentally, the Nvidia link above refers to real(2), i.e. 2-byte half-precision reals, which is a recent development for GPUs and currently only available for nvfortran.)

Exactly! No legacy compiler will break that backwards compatibility. Thus where will be, for example, an 8-byte decimal floating point kind that does not have KIND=8.

Elsewhere, it has already been shown that some compiler(s) implement two different binary 128-bit floating point kinds, with different precisions and exponent ranges. So the “de facto standard” of associating the kind value with the storage size in bytes is already broken.

Another often-requested convention regarding KIND values is that there are no duplicate values across different types. This convention would eliminate many common programmer errors such as expecting 1234_wp (where wp is a floating point kind) to be a floating point constant.

Having that extra layer of abstraction solves many programmer problems.

NAG fortran also supports 16-bit floating point. It has a KIND value of either 16 or 2, depending on compiler options. It is IEEE standard (ISO/IEC/IEEE 60559:2011). I expect that support will be more common among compilers in the future, along with the aforementioned decimal floating point kinds, and also possibly even things like 256-bit floating point.

Yes, probably more.

Yes, hence the suggestion in the blog post that I referred a few times in this discussion: specifying the desired precision and range with selected_*_kind is more robust.

Exactly.

Sorry for the basic question, but what is the scenario and the practical use case when it is important to ensure that kind values can be storage sequence associated?

Finally, concerning the “fallback strategy”, I like better the later proposal to extend the selected_*_kind capabilities. I understand that you need to implement something here and now, but (as already mentioned) it appears a bit obscure and convoluted also to me.

Is there a single Fortran compiler in use today that does NOT map REAL32 to an IEEE binary32 and REAL64 to an IEEE binary64?

Few programmers, and fewer students, use quad precision. Even those that use REAL128 are unlikely to run into trouble (I can confirm that my GFortran maps REAL128 to a quad and not extended precision). Are there even any current compilers that do NOT map REAL128 to IEEE binary128?

It seems to me like emphasizing selected_real_kind is putting a lot of teaching effort to protect against something that is never going to happen.

That was just an example. One must sometimes read/write data structures that match an API that is already defined, perhaps in another language, or perhaps even in a language independent way just in terms of bit positions. Reading and writing data directly to/from some electronic device (say a spectrometer, or satellite telemetry) is another example of this. I could have used 16- or 32-bit quantities instead of 64 bit quantities. The ISO_FORTRAN_ENV module provides that functionality in a portable way with its INT8, INT16, INT32, INT64, REAL32, REAL64, and REAL128 KIND values. Another approach mentioned above is to use C interoperability data types, which are also standardized at least for the companion C processor. On the other hand, matching sizes of entities using selected_xxx_kind() or xxx_kinds(:) is not a good fit for this kind of task, but they are useful in other ways.

It isn’t just here and now, the code I posted above was written over 20 years ago. This functionality was needed then, it is needed now, and I expect it to be needed in the forseeable future. The code above isn’t really obscure, it is relatively straightforward, just a little messy. It could be simplified in the future if that functionality were included in a natural way within the selected_xxx_kind() functions. The way I use this code, it is placed in a single module, and then exported from there to the rest of the codes that need these KIND values; it is not repeated multiple times for each program.

Yes, NAG compiler maps it to Dekker’s double-double https://csclub.uwaterloo.ca/~pbarfuss/dekker1971.pdf

1 Like

For me, it’s about expressing intent. There are two, orthogonal attributes associated with a floating point value, 1) how are the bits interpreted? and 2) how much space does it take up?

The standard has these aspects defined for 1)

The model set for real x is defined by
x = 0
or
x = s * b^e * \sum_{k=1}^pf_k *b^{-k}
where b and p are integers exceeding one; each f_k is a nonnegative integer less than b, with f_1 nonzero; s is +1 or −1; and e is an integer that lies between some integer maximum e_{max} and some integer minimum e_{min} inclusively. For x = 0, its exponent e and digits f_k are defined to be zero. The integer parameters b, p, e_{min} , and e_{max} determine the set of model floating-point numbers.

PRECISION (X) The result has the value INT ((p − 1) * LOG10 (b)) + k, where b and p are as defined in 16.4 for the model representing real numbers

RADIX(X) The result has the value … b if X is of type real, where … b are as defined in 16.4

RANGE (X) If X is of type real, the result has the value INT (MIN (LOG10 (HUGE (X)), −LOG10 (TINY (X))))

Those aspects say absolutely zero about how much space a variable of a given kind occupies in memory. You’ll have to use storage_size on such a variable to know for sure.

The constants in iso_fortran_env basically dictate what storage_size will return for variables of those kinds, but says absolutely nothing about the other aspects of the kind, you’ll have to use those other inquiry functions to find out about them.

So declarations of the form real(selected_real_kind(...)) are expressing to the reader, I care about what numbers are representable in this variable, but not about the storage size, while real(real64) says I care that this variable takes up 64 bits of storage, but not at all about how those bits are interpreted. If you care about both, you’ll see some forms of the trickery already shown to define the kind constants you want to use.

3 Likes

The Nag compiler quad precision has the benefit of being fast. Here is a comparison
of gfortran, Nag, Intel ifort, and Intel ifx.

ch2502_gfortran.exe

2023/12/ 6 15:42: 0 562
n = 50000000
Single precision
Allocate 0.000087
Random 0.102544
Statistics 0.552635
Mean = 0.335544
Standard deviation = 0.442759
Median = 0.500077
Double precision
Allocate 0.007234
Random 0.254907
Statistics 0.572615
Mean = 0.499997
Standard deviation = 0.288688
Median = 0.500022
Quad precision
Allocate 0.012020
Random 3.103971
Statistics 4.857061
Mean = 0.499947
Standard deviation = 0.288697
Median = 0.499927
2023/12/ 6 15:42:10 49
Total time = 9.485984

ch2502_nag.exe

2023/12/ 6 15:42:16 630
n = 50000000
Single precision
Allocate 0.001291
Random 0.155155
Statistics 0.474265
Mean = 0.335544
Standard deviation = 0.442663
Median = 0.499982
Double precision
Allocate 0.010545
Random 0.150459
Statistics 0.595317
Mean = 0.499992
Standard deviation = 0.288698
Median = 0.500029
Quad precision
Allocate 0.016495
Random 0.300828
Statistics 1.608753
Mean = 0.500031
Standard deviation = 0.288675
Median = 0.500099
2023/12/ 6 15:42:19 974
Total time = 3.339368

ch2502_ifort.exe

2023/12/ 6 15:51:48 752
n = 50000000
Single precision
Allocate 0.000000
Random 0.219000
Statistics 0.515000
Mean = 0.500025
Standard deviation = 0.286143
Median = 0.499965
Double precision
Allocate 0.000000
Random 0.391000
Statistics 0.609000
Mean = 0.499931
Standard deviation = 0.288691
Median = 0.499889
Quad precision
Allocate 0.016000
Random 1.281000
Statistics 3.406000
Mean = 0.499995
Standard deviation = 0.288660
Median = 0.499994
2023/12/ 6 15:51:55 221
Total time = 6.469000

ch2502_ifx.exe

2023/12/ 6 15:52: 7 768
n = 50000000
Single precision
Allocate 0.000000
Random 0.516000
Statistics 0.500000
Mean = 0.417785
Standard deviation = 0.382398
Median = 0.499965
Double precision
Allocate 0.015000
Random 0.547000
Statistics 0.547000
Mean = 0.499931
Standard deviation = 0.288691
Median = 0.499889
Quad precision
Allocate 0.016000
Random 1.375000
Statistics 3.484000
Mean = 0.499995
Standard deviation = 0.288660
Median = 0.499994
2023/12/ 6 15:52:14 799
Total time = 7.031000

If a vendor wants to propose 2 different 64 bits reals, they will have no other choice than breaking this convention.

Do you have special reasons to believe vendors are going to implement decimal floating point? The Wikipedia page lists some hardware supporting it from IBM and Fujitsu, but both look kind of niche and mainly relevant for applications running on mainframes (finance).

I see there is an open proposal to implement it in Clang: RFC: Decimal floating-point support (ISO/IEC TS 18661-2 and C23) - Clang Frontend - LLVM Discussion Forums

The C23 standard has also introduced decimal types, and some work has been done in GCC: Decimal Float (Using the GNU Compiler Collection (GCC))

Perhaps the Unum (or Posit) format? I have no clue what it’s good for, but it has some supporters in the Julia community. Personally, I put my trust in Kahan.

I haven’t heard of this before. I know it was Kahan’s goal to see quadruple precision in routine use. In his interview for SIAM he says the following:

So one of the things that I urge and we urge in the [IEEE] standard and in the standardized quadruple precision is that, if quadruple-precision runs fast enough, then round off-induced anomalies will diminish in frequency (a frequency which, at present, we don’t know because we don’t keep score. Most of them don’t even know if the result is right or wrong).

The best way to deal with the precision problem is to design hardware that runs quadruple-precision almost as fast as double so that people would use quadruple routinely

I don’t see this as being achieved yet; if anything, the popularity of interpreted languages and AI seem to be pulling us away from a better understanding of what even the IEEE floating point formats have to offer. I’ve seen works where people implement neural networks, trained from coarse PDE data, and don’t give any consideration to what the truncation error of their PDE solver was in the first place. The intricacies of floating point arithmetic, appear to be the least of people’s worries.

I think there is bigger interest in unums, or even stochastic rounding than there is serious will to push vendors for better quadruple support.

That, or a decimal real, or whatever… I have used in the past a compiler that was proposing both the IEEE754 single and double precision reals, AND the IBM single and double precision reals.

Even in old days of f77, people will write real*8 when they were thinking more about storage, and “double precision” when precision was more on their mind.