Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kind specification implicit from context #201

Open
zerothi opened this issue Mar 24, 2021 · 16 comments
Open

Kind specification implicit from context #201

zerothi opened this issue Mar 24, 2021 · 16 comments
Labels
Clause 8 Standard Clause 8: Attribute declarations and specifications

Comments

@zerothi
Copy link

zerothi commented Mar 24, 2021

Problem

Currently real are by definition floating point values. So when assigning constants to a double precision you'll get conversion losses

program test
  real(8), parameter :: const = 1.4435435345345
  real(8), parameter :: const2 = 1./3.
  real(8), parameter :: const3 = 1./4.

  print *, const
  print *, const2
  print *, const3

end program test

the output will be:

   1.4435435533523560     
  0.33333334326744080     
  0.25000000000000000 

to much surprise of some users.

Current way

Users are forced to explicitly denote the important constants as proper kinds. I.e. simple integers or reals that are well defined may not be affected. The solution to the above would be:

 real(8), parameter :: const = 1.4435435345345_8
  real(8), parameter :: const2 = 1./3._8
  real(8), parameter :: const3 = 1./4.

note not all are needed.
This alters the output to:

   1.4435435345345000     
  0.33333333333333331     
  0.25000000000000000     

Suggestion

  1. When doing math that is stored in a certain floating or integer kind specification (LHS), it would be natural to bump up every constant written on the RHS to the equivalent kind specification.
  2. When doing any math that gets printed every constant written should be bumped up to the highest kind specification in the expression

One needs to distinguish the two situations.

Consider this:

program test
 real(8), parameter :: const = 1./3.
 print *, 1./3. * 4._8

in both the above cases 1. and 3. should automatically be bumped to the kind=8 specification as noted in the parameter declaration and by use of one kind=8

Another benefit is that complicated inline math would be much easier to write without worrying about kind-specifications.

Note that all of the same considerations of the above should apply to integer and complex data types.

Breaking code

I hardly think this would break codes in undesirable ways. In fact, what it would do is ensure correct handling in many codes since the kind specification is not necessary in many cases any more. So results may change, but I would argue that there are no cases where one wants to do less precision math (on purpose) and store in higher precision variables.

@awehrfritz
Copy link

This is indeed one of the rather annoying and nasty surprises of Fortran's floating point handling. So, I for one would really appreciate a feature like this.

I think the most irritating issue with the current behaviour is that

real(8), parameter :: const2 = 1./3.

yields

  0.33333334326744080

At first I would have thought that the value should just be truncated and zero-padded, but I do realise that this would be as wrong as the current value.

A quite nice summary of these issues can be found here:
https://www.fortran90.org/src/best-practices.html#floating-point-numbers
https://www.fortran90.org/src/gotchas.html#floating-point-numbers-gotcha

Currently, the only reliable way I found to deal with this is via compiler flags, especially if one deals with codes that contain bits and pieces from various sources (as is the case with a lot of Fortran codes especially).

@zerothi
Copy link
Author

zerothi commented Mar 24, 2021

Currently, the only reliable way I found to deal with this is via compiler flags, especially if one deals with codes that contain bits and pieces from various sources (as is the case with a lot of Fortran codes especially).

Yes. That could be one existing approach. However, this would only work if your code is using a consistent precision all around. I.e. mixed precision codes would be difficult to handle through compiler flags, or it could have some performance issues at least. Having this built-in the standard would clarify and make a lot of things easier.

@certik
Copy link
Member

certik commented Mar 24, 2021

One question is to get this into the standard, where I can see objections about "backwards incompatibility". However, this issue I think can be completely fixed by a compiler that would warn or refuse to compile your first example. I just created an issue for this in LFortran:

https://gitlab.com/lfortran/lfortran/-/issues/305

In general I would like LFortran to have a mode that is "pedantic" and does not allow such code. Similar with "implicit none" if you forget to specify it, it will refuse to compile your code. That way it will force you to write correct code (that will correctly work with other compilers today), without needing to change the standard.

I believe that is the easiest way to get these issues fixed in practice.

@zerothi
Copy link
Author

zerothi commented Mar 25, 2021

One question is to get this into the standard, where I can see objections about "backwards incompatibility"....

I sort of had this feeling that perhaps there could be some backwards incompatibility. However, I think that the backwards incompatibility is a "random" fluctuation of numbers (as shown in the prior example). You never know which numbers gets appended in double precision. Here the clarity is implicit.

However, this issue I think can be completely fixed by a compiler that would warn or refuse to compile your first example. I just created an issue for this in LFortran:

Yeah, compiler flags for checking this would also be awesome -fpedantic-kind or something similar. have just opened up a "bug" @ gfortran see here

...
I believe that is the easiest way to get these issues fixed in practice.

Yeah, you are probably right, but my feeling is that nobody does this by intent. And if so they can easily be explicit. I think this would save more code than it breaks, if it actually will break anything. :)

@zerothi
Copy link
Author

zerothi commented Mar 25, 2021

For gfortran -Wconversion and -Wconversion-extra could be used.

@FortranFan
Copy link
Member

KIND inference implicitly from context can cause significant problems in terms of backward compatibility.

The issue is really is with the KIND of REAL (and INTEGER) literal constants and arguments and results of intrinsic subprograms.

A proposal such as #78 will be a good approach for Fortran.

@zerothi
Copy link
Author

zerothi commented Mar 25, 2021

KIND inference implicitly from context can cause significant problems in terms of backward compatibility.

Could you give an example where this would cause significant problems? I have tried to think of one that is really a problem, but I can't come up with anything but "unintended" assignment as mentioned above.

@wclodius2
Copy link
Contributor

Sometimes legacy codes have strict restrictions on backwards compatibility, i.e. results may have to be reproducible to the last bit. That will not be fulfilled if the precision of the expression in the following example changes

REAL(DP), PARAMETER :: EXAMPLE = 1./3.

@certik certik added the Clause 8 Standard Clause 8: Attribute declarations and specifications label Apr 23, 2022
@zerothi
Copy link
Author

zerothi commented Apr 17, 2024

Sometimes legacy codes have strict restrictions on backwards compatibility, i.e. results may have to be reproducible to the last bit. That will not be fulfilled if the precision of the expression in the following example changes

REAL(DP), PARAMETER :: EXAMPLE = 1./3.

But here, that is not explicit on what the code wants? If it truly wants bit-set reproduceability, they should do 1._sp / 3._sp for clarity. Compiling that code with -fdefault-real-8 would also break it.
I would consider the above a bug.

@zerothi
Copy link
Author

zerothi commented Apr 17, 2024

Is this something you want a PR describing?

@PierUgit
Copy link
Contributor

PierUgit commented Jun 3, 2024

I think such a proposal would have more cons than pros. The current rule is consistent over the whole standard, i.e. the evaluation of an expression does not depend on the context where it appears. Easy to remember.

If
REAL(DP), PARAMETER :: EXAMPLE = 1./3.
was interpreted as
REAL(DP), PARAMETER :: EXAMPLE = 1._dp/3._dp

then many people would think that

real :: x(n)
real(dp) :: y(n)
y(:) = x(:) / 3.0

should also be interpreted as

y(:) = x(:) / 3.0_dp

and will omit the _dp, thinking it's implied.

@certik
Copy link
Member

certik commented Jun 3, 2024

@PierUgit I think probably the best bet is to have good compiler warnings. I personally always append ._sp or ._dp and then it's always clear, no surprises. A compiler warning can be a lot more specific, only warn (and thus require ._dp) if a value is changed to what the user might expect (e.g., unless I am mistaken, I think 3.0 is the same as 3.0_dp so no warning technically needed, but 3.1 is not the same as 3.1_dp, so warning needed). As you correctly pointed out, we might need such warnings with this proposal also. In which case perhaps just keeping things as they are, with good warnings, might be enough.

@zerothi
Copy link
Author

zerothi commented Jun 4, 2024

I still don't think that the corner cases should hold back something that is consistently done wrong. And this is a major issue for fortran programs (and new programmers!).
And I don't see any connection between your all constant vs. mixed variables with explicit kind specification. They are very different, since that is intentionally mixing different kinds.

While your example is real, I think the majority of programs tend to do all operations in 1 precision.
One can in the standard define the conversion factors as implicit for the highest precision variable in the expression. In your case 3 would be bumped to double.

Another approach would be to say that all constants are doubles (like c).

@PierUgit
Copy link
Contributor

PierUgit commented Jun 4, 2024

I still don't think that the corner cases should hold back something that is consistently done wrong. And this is a major issue for fortran programs (and new programmers!).

On the contrary, when considering changing a decades-old behavior one has to carefully browse all the cases that could be affected, including the corner cases. And this is particularly important for the legacy codes (but not only).

Note also that I disagree on the "major issue": yes this is a gotcha for new Fortran programmers, but once you have been caught you learn and you don't do the same mistake again.

I can see another case where an automatic promotion (one of your proposals) would be a problem:

real :: x(n), y(n)
real(dp) :: s
s = 0d0
do i = 1, n
   s = s + cos( x(i) * y(i) / 3.0 )
end do

Here I want a double precision accumulator but I don't necessarily want the whole cos(...) expression to be promoted to double precision, because of performance reasons (vector registers handle twice as less dp variables, and a cos() is longer to compute in double precision. And specifying 3.0_sp is not a solution for existing codes.

Apart from the suggestion of @certik to enable compiler warnings when detecting for literal constants that may have insufficient precision, the only reasonnable solution I can see would be making mandatory the use of _kindvalue for all litteral constants if something like

implicit none(litteral_kind)

was present

@zerothi
Copy link
Author

zerothi commented Jun 4, 2024

Note also that I disagree on the "major issue": yes this is a gotcha for new Fortran programmers, but once you have been caught you learn and you don't do the same mistake again.

My experience is not this, quite often I find problems in submitted code that is because people just forget, even though they are capable, and knowledgeable about these issues.

I can see another case where an automatic promotion (one of your proposals) would be a problem:

real :: x(n), y(n)
real(dp) :: s
s = 0d0
do i = 1, n
   s = s + cos( x(i) * y(i) / 3.0 )
end do

Here I want a double precision accumulator but I don't necessarily want the whole cos(...) expression to be promoted to double precision, because of performance reasons (vector registers handle twice as less dp variables, and a cos() is longer to compute in double precision. And specifying 3.0_sp is not a solution for existing codes.

Apart from the suggestion of @certik to enable compiler warnings when detecting for literal constants that may have insufficient precision, the only reasonnable solution I can see would be making mandatory the use of _kindvalue for all litteral constants if something like

implicit none(untyped_litteral)

was present

I agree that a compiler warning is a good thing to do. Sometimes breaking changes needs to be done to make life easier... ;)

@PierUgit
Copy link
Contributor

PierUgit commented Jun 4, 2024

Note that Fortran 202Y will actually bring a solution here, by allowing to specify in the code what are the default kinds. See this proposal that has been approved at the june 2023 meeting: https://j3-fortran.org/doc/year/23/23-199r1.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Clause 8 Standard Clause 8: Attribute declarations and specifications
Projects
None yet
Development

No branches or pull requests

6 participants