在Fortran中使用参数(常量)变量时出现NaN值的情况

14

是否可以使用NaN设置参数变量,并将其用于特定模块。我想将其用于初始化其他变量。因此,如果它们未更新,我将面临运行时错误,而不是使用某些随机数进行模拟。
我正在使用GFortran。


5
对于gfortran,我倾向于只考虑使用-finit-real=nan选项。 - francescalus
2
在ifort中使用-init=arrays -init=snan - knia
4个回答

16

除了 Vladimir F's answer提到的内容,我要补充一点,gfortran 5.0(但不包括早期版本)支持IEEE内置模块。

与其这样做,

real x
x=0
x=0/x

可以使用

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)

这使您可以在NaN值之间灵活选择。您也不需要担心任何无效标志的信号。[但请注意,要求ieee_signaling_nan可能并不真正给您那个。]
请注意,ieee_value()不能直接用于初始化:对它的引用不是常量表达式。对于这种用途,采用此方法获取位模式并应用其他答案的方法。
您还需要确保每种数据类型都支持相应的特性。

2
是的,我还没有深入研究这个问题,因为我必须保持与gfortran 4.8的兼容性。不幸的是,在常量表达式中确实不允许使用“ieee_value”,因此仍然需要使用transfer()。更多讨论请参见https://groups.google.com/forum/#!msg/comp.lang.fortran/tYC3UgAyNrY/6TzOLbkP6tsJ。 - Vladimir F Героям слава
1
似乎如果我想要在调试和获取运行时错误时使用NaN, 我必须使用信号NaN而不是静态的(参考)。 - Sorush
1
@Sorush 是的。不能保证ieee_signaling_nan会产生这种情况(虽然建议这样做)。我不知道gfortran返回什么-如果您可以测试,请告诉我。在某种意义上,一个安静的NaN仍然很有价值:你知道出了问题。对于调试来说不太方便。 - francescalus

13

这是可能的。首先,您需要找出哪个位模式表示可能的NaN值之一。您可以将位模式存储在整数中:

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

它会给出:-2251799813685248

然后,您可以使用以下方式初始化变量:

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

同样地,对于32位变量,您将获得整数-4194304,使您可以执行。
real(real32), parameter :: nan32 =  transfer(-4194304_int32, 1._real32)

如果您使用IEEE-754兼容的浮点表示(当您关心NaN时几乎肯定会使用),您还可以使用该标准中的定义。有许多可能的比特模式意味着不是数字。它们在指数中的所有位都相等于1,并且在尾数中的某些位相等于1。可以使用转换器,例如https://www.h-schmidt.net/FloatConverter/IEEE754.html 如果需要区分信号和静默NaN,则静默NaN的尾数中第一位(最高有效位)等于1,而信号NaN的第一位等于零。但是如https://faculty.cc.gatech.edu/~hyesoon/spr09/ieee754.pdf所指出的:“主要因政治原因而存在的SNaNs很少被使用”。上面引用的转换器没有显示这种差异。
例如:
  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

返回

 qnan: T
 snan: T

在默认设置下,Intel Fortran中的NaN信号是启用的。在GCC(gfortran)中,默认情况下禁用了NaN信号,并且可以通过-fsignaling-nans启用,但似乎并没有帮助。

其他位,包括第一个符号位,通常被忽略。


许多编译器都有一个选项可以为所有实数变量自动完成此操作。如francescalus所示,在gfortran中为-finit-real=nan。手动执行此操作可以使您更加精细地控制。
免责声明:在切换到不同的平台时要小心。字节序和其他问题可能会起作用,尽管我认为这实际上应该没有问题。我假设了符合IEEE标准的CPU。

可以参考francescalus的答案,该答案使用了标准函数。不幸的是,它不适用于参数常量,但仍然有用。


我同意你的建议@Vladimir F,因为这样在运行时可以将变量的值更改为NaN或反之。但缺点是NaN的机器相关位模式。 - Sorush

1
francescalus所指出并在此讨论中提到的,ieee_value的结果不能分配给parameter。另一种方法是使用一个protected模块变量代替parameter。但是,这样需要在程序开始时调用模型初始化函数。请注意保留HTML标记。
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

1
如果您使用的GFortran没有内在的IEEE但具有内在的iso_c_binding(例如在Windows上构建R所需的那种),则以下内容可行并等同于C和R NaN(在R上通过is.nan):
real(kind = c_double), parameter :: ONE = 1_c_double    
real(kind = c_double), parameter :: NAN = TRANSFER(z'7FF0000000000001', ONE)

有趣的是,real(kind = c_double), parameter :: NAN = TRANSFER(z'7FF0000000000001', 1_c_double) 未通过我的 is.nan 检查。


2
你知道1_c_double是一个整数吗?意识到这一点后,你可能就不会再觉得失败有多有趣了。你的第一行可以只是real(kind = c_double), parameter :: ONE = 1,它会做同样的事情。请参见我的答案以获取实数的正确语法。 - Vladimir F Героям слава
2
最终,您可以将其缩短为 real(c_double), parameter :: NAN = TRANSFER(z'7FF0000000000001', 1._c_double)real(c_double), parameter :: NAN = TRANSFER(9218868437227405313_c_int64_t, 1._c_double) - Vladimir F Героям слава
@VladimirF,谢谢。 - Avraham
BOZ 字面量不仅仅只允许在数据语句和一些特定的位操作内置函数中使用吗? - Rodrigo Rodrigues
@RodrigoRodrigues 请查看(https://gcc.gnu.org/onlinedocs/gfortran/BOZ-literal-constants.html)。 "直到Fortran 95,BOZ文字常量只能用于DATA语句中初始化整数变量。自Fortran 2003以来,BOZ文字常量也可以作为REAL、DBLE、INT和CMPLX的参数;结果与将整数BOZ文字通过TRANSFER转换为实数、双精度、整数或复数一样。作为GNU Fortran扩展,内置过程FLOAT、DFLOAT、COMPLEX和DCMPLX也被视为相同。" - Avraham
2015标准中的约束C7110(R764):“boz-literal-constant只能作为DATA语句中的data-stmt-constant出现,或者在子句16.9中明确允许作为内部过程的实际参数。”但是TRANSFER内部过程没有提到它们,不像类型转换和位操作内部过程。 - Rodrigo Rodrigues

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接