Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having parameter (constant) variable with NaN value in FORTRAN

Is it possible to set a parameter variable with NaN? and have that in a particular module. I want to use it for initialization of some other variables. Therefore, I'll be faced with a run-time error, if they are not updated, rather than simulations running with some random numbers.
I am using GFortran.

like image 487
Sorush Avatar asked Aug 12 '15 17:08

Sorush


People also ask

How do you define NaN in Fortran?

Not a Number (NaN) is represented by the largest value that the exponent can assume (all ones), and a nonzero fraction. Normalized REAL and DOUBLE PRECISION numbers have an implicit leading bit that provides one more bit of precision than is stored in memory.


4 Answers

To add to Vladimir F's answer I'll mention that gfortran 5.0 (but not earlier) supports the IEEE intrinsic modules.

Instead of

real x
x=0
x=0/x

one can use

use, intrinsic :: iso_fortran_env
use, intrinsic :: ieee_arithmetic
integer(int32) i
real(real32) x

x = ieee_value(x, ieee_quiet_nan)
i = transfer(x,i)

This gives you a little flexibility over which of the NaN values you get. You also needn't worry about any signalling invalid flag. [But note that asking for ieee_signaling_nan may not really give you that.]

Note that ieee_value() can't be used directly in initialization: a reference to it isn't a constant expression. For such use, take this approach to get the bit pattern and apply the method of the other answer.

You'll also need to ensure that there is the support for the features for each datatype.

like image 109
francescalus Avatar answered Oct 22 '22 12:10

francescalus


It is possible. You first have to find out which bit pattern represents one of the possible NaN values. You can store the bit pattern in an integer:

 use, intrinsic :: iso_fortran_env
 real(real64) x
 integer(int64) i
 x = 0
 x = 0/x
 print *, x
 print *, transfer(x, i)
end

It gives: -2251799813685248

Then you can initialize your variables using

real(real64), parameter :: nan64 =  transfer(-2251799813685248_int64, 1._real64)

Similarly for 32 bit variables you get the integer -4194304, so that you can do

real(real32), parameter :: nan32 =  transfer(-4194304_int32, 1._real32)

If you are using IEEE-754 compatible floating point representation (almost certain, when you care about NaNs), you can also use the definitions from that standard. There are many possible bit patterns that mean a not-a-number. They have all bits in the exponent equal to 1 and some bit in the mantissa equal to 1. One can use convertors such as https://www.h-schmidt.net/FloatConverter/IEEE754.html

If you need to distinguish signaling and quiet NaNs, the quiet NaNs have the first bit (most significant) in the mantissa equal to one and the signaling NaNs have the first bit equal to zero. But as https://faculty.cc.gatech.edu/~hyesoon/spr09/ieee754.pdf notes: "SNaNs, which exist mainly for political reasons and are rarely used". The convertor referenced above does not show this difference.

For example:

  use, intrinsic :: iso_fortran_env
  use ieee_arithmetic
  real(real32), parameter :: qnan =  transfer(int(B'01111111110000000000000000000000',int32), 1._real32)
  real(real32), parameter :: snan =  transfer(int(B'01111111101000000000000000000000',int32), 1._real32)
 
  if  (IEEE_SUPPORT_DATATYPE(qnan)) then
     print *, "qnan:", (ieee_class(qnan)==ieee_quiet_nan)
  end if
     
  if  (IEEE_SUPPORT_DATATYPE(snan)) then
     print *, "snan:", (ieee_class(snan)==ieee_signaling_nan)
  end if
end

returns

 qnan: T
 snan: T

in Intel Fortran in default settings. In GCC (gfortran) signaling NaNs are disabled by default. and can be enabled by -fsignaling-nans, but it does not seem to help anyway.

Other bits, including the first sign bit, are usually ignored.


Many compilers have an option to do that for you for all real variables. As francescalus shows, in gfortran it is -finit-real=nan. Doing that manually gives you a finer control.

Disclaimer: Be careful when switching to a different platform. Endianness and other issues could play a role, even though I think it could be actually OK. I assumed an IEEE conforming CPU.


See, francescalus's answer for an alternative which uses a standard function. Unfortunately, it is not applicable for parameter constants, but is useful.

like image 29
Vladimir F Героям слава Avatar answered Oct 22 '22 12:10

Vladimir F Героям слава


If you are stuck with a GFortran that does not have the intrinsic IEEE but does have the intrinsic iso_c_binding (like the one needed to build R on Windows), the following works and is equivalent to the C and R NaN (passes is.nan on R):

real(kind = c_double), parameter :: ONE = 1_c_double    
real(kind = c_double), parameter :: NAN = TRANSFER(z'7FF0000000000001', ONE)

Interestingly, real(kind = c_double), parameter :: NAN = TRANSFER(z'7FF0000000000001', 1_c_double) fails my check for is.nan.

like image 26
Avraham Avatar answered Oct 22 '22 11:10

Avraham


As pointed out by francescalus and in this discussion, the result of ieee_value cannot be assigned to a parameter. Another approach is using a protected module variable instead of a parameter. However, then one has to call a model initialization function at the start of the program.

module nan_module

  implicit none
  real, protected :: nan_real
  contains

  subroutine init_nan_module
    use, intrinsic :: ieee_arithmetic
    implicit none
    nan_real = ieee_value(nan_real, ieee_quiet_nan)
  end subroutine

end module nan_module

program test

  use nan_module, only: init_nan_module, nan_real

  implicit none
    real :: x
    call init_nan_module()
    x = nan_real
    write(*,*) x, 'isnan', isnan(x)

end program test
like image 24
acac Avatar answered Oct 22 '22 12:10

acac