I have a subroutine ran_init ( length )
, in which one IF
statement works incorrectly.
The code below is a highly simplified version of my original code:
MODULE ran_state
USE nrtype
IMPLICIT NONE
INTEGER, PARAMETER :: K4B = SELECTED_INT_KIND ( 9 )
INTEGER ( K4B ), PARAMETER :: hg = HUGE ( 1_K4B ), hgm = -hg, hgng = hgm - 1
INTEGER ( K4B ), SAVE :: lenran = 0
CONTAINS
SUBROUTINE ran_init ( length )
USE nrtype; USE nrutil, ONLY: nrerror
IMPLICIT NONE
INTEGER ( K4B ), INTENT ( IN ) :: length
INTEGER ( K4B ) :: hgt
IF ( length < lenran ) RETURN
hgt = hg
PRINT *, hgt, hgt + 1, hgng, hgt + 1 - hgng
IF ( hgt + 1 .NE. hgng ) CALL nrerror ( 'ran_init: arith assump 3 fails' )
END SUBROUTINE
END MODULE ran_state
This code returns the result:
2147483647 -2147483648 -2147483648 0
nrerror: ran_init: arith assump 3 fails
STOP program terminated by nrerror
It is seen that values hgt + 1
and hgng
are equal to one another, but at the IF
statement these values are interpreted as unequal.
Why this can happen?
UPD №1
Some technical details:
- I use
gfortran
as a compiler - the command
gfortran --version
returns
GNU Fortran (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
- I compile my programs with flags
-O2 -Wall -Wextra -fbacktrace -fcheck=all -g -o
with a level of optimization does not affect on results.
- my OS is Ubuntu 16.04LTS 64-bit
UPD №2
I don’t know WHY, but if one create a new variable, say, hgtp
of type INTEGER ( K4B )
and replace lines
hgt = hg
PRINT *, hgt, hgt + 1, hgng, hgt + 1 - hgng
IF ( hgt + 1 .NE. hgng ) CALL nrerror ( 'ran_init: arith assump 3 fails' )
with lines
hgt = hg
hgtp = hgt + 1
PRINT *, hgt, hgt + 1, hgng, hgt + 1 - hgng
IF ( hgtp .NE. hgng ) CALL nrerror ( 'ran_init: arith assump 3 fails' )
the IF-statement begins to interpret the condition hgtp .NE. hgng
as true.
2
Answers
With ifort (intel fortran), default flags:
With gfortran, default flags:
Something odd about gfortran?
You’re running into integer overflow, which is an undefined operation in Fortran. Meaning that the Fortran processor (compiler and runtime) is allowed to do anything it likes with it, like doing some more or less random calculation, abort the compilation with an error message, abort the program at runtime with an error message, or launch the rockets to start WWIII.
What it means that you cannot rely on some particular behavior if integer overflow happens. Generally compilers will optimize the code to be as fast as possible, assuming no overflow occurs (because then it’s undefined behavior and it’s the responsibility of the programmer to ensure that the program doesn’t get into such a state).
Further, there’s two kinds overflow here:
hgt + 1
is kind of obvious, sincehgt
is equal toHUGE()
.hgm - 1
is also an overflow. It’s not an overflow on hardware that uses two’s complement representation of integers (which is, practically speaking, all hardware out there these days outside maybe some museum piece), however the Fortran model numbers are symmetrical so in the Fortran standard it’s an overflow, and again, all bets are off.(The above assuming you’re running on a system where the default integer has the same kind as
K4B
which should be most systems out there these days unless you’re using some-fdefault-integer-8
or such options)