【问题标题】:Logarithm of 0 causes error even in an unreachable if condition即使在无法到达的 if 条件下,0 的对数也会导致错误
【发布时间】:2016-11-07 00:39:06
【问题描述】:

我在 Fortran 90 中实现了以下代码,我希望使用某些参数的对数来计算参数 tau,前提是这些参数在适当的范围内。

MODULE nrtype
    INTEGER, PARAMETER :: SP=KIND(1.0)
    INTEGER, PARAMETER :: DP=KIND(1.0d0)
    INTEGER, PARAMETER :: I4B=SELECTED_INT_KIND(9)
    INTEGER, PARAMETER :: I2B=SELECTED_INT_KIND(4)
    INTEGER, PARAMETER :: I1B=SELECTED_INT_KIND(2)
    INTEGER, PARAMETER :: SPC=KIND((1.0,1.0))
    INTEGER, PARAMETER :: DPC=KIND((1.0D0,1.0D0))
    INTEGER, PARAMETER :: LGT=KIND(.TRUE.)
END MODULE

MODULE parameters
USE nrtype
    REAL(DP), PARAMETER :: beta=.98_dp
    REAL(DP), PARAMETER :: maxtol=1.0e-6_dp
    REAL(DP), PARAMETER :: theta=1.0_dp
    REAL(DP), PARAMETER :: delta=0.0_dp
END MODULE

PROGRAM mainp
    USE parameters
    USE nrtype

    IMPLICIT NONE

    INTEGER(I4B) :: tau
    REAL(DP) :: taustar

    IF (theta > 1.0_dp .AND. (delta > 0.0_dp .AND. delta < 1.0_dp)) THEN
        taustar=LOG(maxtol/(beta*(1.0_dp-delta)*(theta-1.0_dp)))/LOG(beta*delta)
        tau=CEILING(taustar,REAL(DP))
    ENDIF

    IF (theta > 1.0_dp .AND. delta==0.0_dp) THEN
        tau=1
    ELSEIF (theta == 1.0_DP .OR. delta==1.0_DP) THEN
        tau=0
    ENDIF

END PROGRAM main

但是,在带有 OSX El Capitan 的 MacBook Air 2013 中使用 gfortran 6.1 进行编译时,出现以下错误:

program.f90:30:69:

    taustar=LOG(maxtol/(beta*(1.0_dp-delta)*(theta-1.0_dp)))/LOG(beta*delta)
                                                                 1
Error: Argument of LOG at (1) cannot be less than or equal to zero

我在代码上犯了什么错误?看起来好像没有识别 IF 语句。

【问题讨论】:

  • 对所有 Fortran 问题使用标签 fortran
  • 您将自己限制在 Fortran 90(近 3 个十年前)有什么特别的原因吗?现代 Fortran 具有内在模块 ieee_arithmetic 来提高此类问题的稳健性。

标签: if-statement fortran fortran90 gfortran


【解决方案1】:

当 delta 为 0 常量时,gfortran 认为表达式 LOG(beta*delta) 无效。它位于代码的不可访问部分中并不重要。这是无效的。你不能把它放在 if 条件或其他任何地方。

解决方案可能是不将delta 声明为parameter 或使用一些预处理器。其他编译器可能会接受它(我试过 ifort)。我不确定标准。

【讨论】:

  • 非常感谢,很抱歉之前没有添加fortran标签。
  • 你是怎么得出这个结论的?我只是认为这是一个编译器错误。除了类型之外,当调用过程时,对 LOG 的实际参数值的限制也适用——这需要执行包含过程引用的语句,而这不应该发生。该示例还有其他问题应该会阻止它编译。
  • 根据我使用该编译器的经验。有可能是允许的,但编译器不接受它。这就是为什么stackoverflow.com/questions/31971836/… 中的所有麻烦(解决方法)
【解决方案2】:

(虽然这有点像长评论...)Mac OSX10.9 上的gfortran-6.1 在使用-fdump-fortran-original-fdump-fortran-optimized 编译时给出以下信息:

  code:
  IF .false.
    ASSIGN mainp:taustar (/ log[(((/ 9.9999999999999995e-7_8
                            (parens (* (* 9.7999999999999998e-1_8 
                            (parens 1.0000000000000000_8))
                            (parens 0_8))))))] log[((0_8))])
    ASSIGN mainp:tau ceiling[((mainp:taustar) (8.00000000))]
  ENDIF
  IF .false.
    ASSIGN mainp:tau 1  ELSE
    IF .true.
      ASSIGN mainp:tau 0
    ENDIF
  ENDIF

所有-O0-O2-O3-O5 的结果都相同。

ifort-14.0 编译程序时没有抱怨无法访问的log 行(同时抱怨tau=CEILING(...))。 theta == 1.0_DP .OR. delta==1.0_DP对应的第三个IF块被执行。

Oracle fortran 12.4 有点有趣,因为它给出了这样的警告:

taustar=LOG(maxtol/(beta*(1.0_dp-delta)*(theta-1.0_dp)))/LOG(beta*delta)
                  ^                                     ^        ^                                        
"test.f90", Line = 31, Column = 27: WARNING: A divisor of zero was detected in an expression.                                                        
"test.f90", Line = 31, Column = 65: WARNING: Evaluation of this constant expression produced a NaN or other abnormal value.
"test.f90", Line = 31, Column = 74: WARNING: The argument is not in the valid range for this intrinsic.

但第三个 IF 块又被执行了。

另外,如果我将delta 声明为一个常用变量(不使用PARAMETER 属性),gfortran 还会在删除CEILING(...) 后执行第三个 IF 块(此行似乎有问题,原因不同)。


一种解决方法可能是将常量分配给局部变量一次并将它们传递给LOG(...)/LOG(...)(在某种意义上,欺骗gfortran!):

REAL(DP) :: arg1, arg2, tmp

IF (theta > 1.0_dp .AND. (delta > 0.0_dp .AND. delta < 1.0_dp)) THEN
    tmp = beta * (1.0_dp - delta) * (theta - 1.0_dp)
    arg1 = maxtol / tmp
    arg2 = beta * delta
    taustar = LOG( arg1 ) / LOG( arg2 )
ENDIF

thetadelta 具有上述极端情况值并在其他情况下给出正确结果时,这似乎有效。在前一种情况下,gfortran -fdump-fortran-original 的输出变成这样:

code:
IF .false.
    ASSIGN mainp:tmp 0_8
    ASSIGN mainp:arg1 (/ 9.9999999999999995e-7_8 mainp:tmp)
    ASSIGN mainp:arg2 0_8
    ASSIGN mainp:taustar (/ __log_r8[[((mainp:arg1))]] __log_r8[[((mainp:arg2))]])
ENDIF

【讨论】:

  • 嗨,非常感谢你,我做了你提到的最后一件事,(将常量分配给局部变量)。
【解决方案3】:

这个错误是在编译时抛出的,而不是在运行时抛出的。

由于 beta 和 delta 是常量,编译器会采取捷径并在程序运行之前评估日志。因为它是一个零日志,所以它应该抛出一个错误。

我不确定您要做什么。编译器是对的,这将始终是零日志。您确定要将 delta 设为常数吗?

【讨论】:

  • 请参阅下面 IanH 的评论以获得反对意见。我不确定什么是正确的答案。
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 2020-07-29
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多