Catch integer exceptions in Fortran - fortran

Is there a way to catch integer exceptions with gfortran or ifort like there is for catching floating point exceptions?
Consider this simple program to calculate the factorial:
program factorial
use, intrinsic :: iso_fortran_env
implicit none
integer(8) :: fac
real(REAL64) :: facR
integer,parameter :: maxOrder = 30
integer :: i
fac = 1 ; facR = 1.e0_REAL64
do i=2,maxOrder
fac=fac*i ; facR=facR*real(i,REAL64)
write(*,*) i, fac, facR
enddo ! i
end program
At some point there will be an overflow - for integer(8) as shown here, it will occur at around 21. But without the calculation using floats as a reference I couldn't tell for sure...

There is nothing in the Fortran standard that deals with integer overflow. As it stands you can't even rely on integers wrapping round when a computation exceeds the maximum value representable in the chosen kind. So, while a test such as
huge(2_8)+1 < 0_8
is likely to work with most current compilers (at least the ones I have used recently) it's not guaranteed.
I am sure that neither Intel Fortran nor gfortran provide compiler-generated run-time checks for integer overflow either. I'm not sure about other compilers but I'll be (pleasantly) surprised to learn that any of them do.
I think, therefore, that you have to proceed with your current approach.

gfortran will catch integer overflow with -ftrapv flag, see man gcc:
-ftrapv
This option generates traps for signed overflow on addition, subtraction, multiplication operations.
ifort does not seem to have that capability.

Related

Arithmetic overflow in Fortran

I am working with REAL numbers like 4*10**11, and the compiler complains that the numbers are too big, it says
Error: Arithmetic overflow at (1)
I tried to use REAL*8 and REAL*16, but this does not solve the problem
How can I solve this problem?
A MWE could be this:
INTEGER nioni
REAL fg,t,num,c1,minimo,massimo,en0,alpha,func
fg=0.047214
t=0.00000015
num=3688100.
c1=5.30
minimo=0.469185*fg*t
massimo=7.4*10**11*fg*t
DO nioni=1, 10
en0=(0.1+(20.-0.1)*rand())
func=num*(1./(en0+c1)**c2)*en0**c3*fg*t
alpha=minimo+(massimo-minimo)*rand()
DO WHILE(alpha.gt.func)
en0=(0.1+(20.-0.1)*rand())
func=num*(1./(en0+c1)**c2)*en0**c3*fg*t
alpha=minimo+(massimo-minimo)*rand()
WRITE(*,*) en0
END DO
END DO

How to convert from one Fortran integer kind to a smaller one?

I need to convert a parameter from integer(kind=8) to integer(kind=4) in Fortran, is there any (simple) way of doing this?
This parameter is an input number, and if this number is greater than 2^31-1 (the limit of a 4-byte integer), the program will always ask for a smaller number (so it can "fit" inside those 4 bytes), so I think that this shouldn't be a problem.
To create integer of any kind use
result = int(source, kind=result_kind)
so you can do
result = int(source, 4)
source can be any number, including an integer of any kind.
Note that kind=8 does not mean 8 bytes and kind=4 does not mean 4 bytes. There are compilers which do not have kinds 4 and 8 at all. These numbers are not portable. Do not use them. See Fortran: integer*4 vs integer(4) vs integer(kind=4) for more details.
As Vladimir F's answer notes, the intrinsic function int returns an integer value of desired kind int(i,kind=kind).
When an expression of a certain kind is required (such as in a procedure argument list) this is quite useful:
call sub_with_int_i1_arg(INT(int_i2, kind=i1))
However, intrinsic assignment to an integer already provides conversion if required:
integer, parameter :: kind1=7, kind2=23
integer(kind1) :: i
integer(kind2) :: j = 85
! The intrinsic assignment
i = j
! is equivalent to
i = INT(j,KIND(i))
! which here is also
i = INT(j,kind1)
end
The intrinsic huge may be useful in determining whether the range of i is large enough:
if (ABS(j).le.HUGE(i)) then
i = j
else
error stop "Oh noes :("
end if
As Steve Lionel commented about the draft, Fortran 2018 introduced the intrinsic function out_of_range which also tests such cases:
if (.not.OUT_OF_RANGE(j,i)) then
i = j
else
error stop "Oh noes :("
end if
However, even in early 2022 it's not wise to rely on implementation of this function.

Quadruple precision for abs and sign

I am trying to change the precision of the abs and sign with gfortran (gcc version 5.3.1 and x86_64-linux-gnu).
I know I can change to dabs and dsign to have double precision but what about quad precision, is it possible?
For sqrt(x) for instance, I simply change to x**(0.5q0) with the arg defined as a real(16). I read that some compilers do not have the intrinsic routines implemented in quad or extended precision, see e.g. here.
If gfortran has been compiled with support for libquadmath (on Linux it typically is), then it supports this right away. All you need to do is declare your variables and literals quadruple precision, and use the generic versions abs and sign.
However, using real(16) is not portable (and outdated). Better use something standardized such as selected_real_kind(p=30).
Here is a short example to illustrate this:
program test
integer, parameter :: qp = selected_real_kind(p=30)
real(qp) :: a
a = -2._qp
print *, abs(a)
print *, sqrt(-a)
end program
The output is:
2.00000000000000000000000000000000000
1.41421356237309504880168872420969818
Compare this to the result of Wolfram alpha:
1.414213562373095048801688724209698078569671875376948073176
Or...
https://software.intel.com/en-us/node/525335
PROGRAM!SUBROUTINE!FUNCTION
USE ISO_C_BINDING
IMPLICIT NONE
REAL(KIND=C_LONG_DOUBLE) :: A
!or maybe...
REAL(C_LONG_DOUBLE) :: B
...

detection of the ILP mode of BLAS

the blas mathematical library is distributed either in the i32lp64 mode (that is, with integer*4 integers) or in the ilp64 mode (with integer*8 or 8-bytes integers).
The question is how to distinguish between these two BLAS modes (i32lp64 vs ilp64) in a short Fortran routine, and without giving the segfault crash.
Well,
when you link this program against the ilp64 blas library, we get program crash. In the case of i32lp64 there will be no crash.
And this is the distinction between ilp64 and i32lp64 blas; not very elegant, but doable.
program test
integer*8, parameter :: inc = +1_8 + 2_8**33_8
real*8 :: a(3),d
integer :: ii
a(1)=1.0d0; a(2)=1.0d0;a(3)=1.0d0
d=ddot(3,a,inc,a,inc)
print *,"inc=",inc
print *,"d=",d
end program

fortran 64 bit hex BOZ

in C++ this is accepted:
uint64_t mask = 0x7FC0000FF80001FFLL;
but in fortran
integer(kind=8), parameter :: mask = Z'7FC0000FF80001FF'
does not work with gfortan.
I think both of them are 64bit values? or not?
gfortran complains:
arithmetic overflow from converting INTEGER(16) to INTEGER(8)
EDIT:
So, sorry for the confusion, here is some more extended problem description.
I will do some bit shifting in Fortran and have some sample code in c++.
There in the sample c++ code the masks are defines like:
typedef uint64_t mask;
static const mask dilate_2 = (mask)0x7FC0000FF80001FFLL ;
static const mask dilate_1 = (mask)0x01C0E070381C0E07LL ;
static const mask dilate_0 = (mask)0x9249249249249249LL ;
From my poor c++ understanding, I think that the hex values are 64bit
integer values (they have LL in the ending).
Now in Fortran my problem first was, that the definition with
integer(kind=8), parameter ...
did not work, as Vladimir said, because
integer(kind=8), ...
might be no 64bit integer.
Than I tested Alexanders solution, which works for the first and the
second (dilate_2, dilate_1) constant.
Also Vladimirs solution works for these two.
Now for dilate_0 none of these solutions work. I would suppose that Vladimirs solution will cast 0x9249249249249249LL (what is actually
a greater integer than allowed in INT64) into a INT64
if I do:
integer(INT64), parameter :: dilate_0 = int(Z'9249249249249249', &
kind=kind(dilate_0)
But this also don't work and gfortran give me an error:
Error: Arithmetic overflow converting INTEGER(16) to INTEGER(8) at (1).
So my actual question is how to implement this constant in Fortran?
As Vladimir posted in his comment integer(kind=8) is not portable (and not of kind 16 as the compiler complains).
As a remedy I suggest to use the intrinsic module ISO_Fortran_env (Fortran 2003) which has many predefined constants for the compiler used. Using INT64 from this module solves your problem and results in portable code:
program test
use,intrinsic :: ISO_Fortran_env, only: INT64
integer(INT64), parameter :: mask = Z'7FC0000FF80001FF'
print *,mask
end program
Z'9249249249249249' is not representable as a an INT64 (which is equivalent to an INTEGER(kind=8) in gfortran) because
BOZ constants are signed numbers (the same as every other integer constant in Fortran)
This number is larger than 2**63-1, the largest representable number for an INT64
Gfortran therefore selects the smallest integer type which fits, which is INTEGER(KIND=16).
We then have parameter staement where an INTEGER(KIND=8) parameter should be assigned a value outside its range. This is what the compiler complains about. It would complain the same way about
INTEGER(KIND=4), PARAMETER :: N = 37094947285
If you want to get around this, you can use the -fno-range-check option to gfortran. Information about -fno-range-check is already included in the gfortran error message (the part you didn't show).
I would do this to stay standard conforming
integer(whatever), parameter :: mask = int(Z'7FC0000FF80001FF', &
kind=kind(mask))
where whatever is some kind constant of the required value. It could be int64.
The above will not work if the constant corresponds to a negative number. One then has to make a trick like:
integer(int32), parameter :: mask = transfer(int(Z'A0000000',int64),1_int32)
or
integer(int32), parameter :: mask = transfer(Z'A0000000',1_int32)
but I am not sure whether the last one is strictly standard conforming.