`nint(-0.1) = ?`

According to [https://j3-fortran.org/doc/year/10/10-007r1.pdf] on nint:

Experiments:

$ uname -a && lscpu | grep name
Linux 5.4.0-135-generic #152-Ubuntu SMP Wed Nov 23 20:19:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Model name:                      Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz

$ gfortran --version && gfortran test.f90 && ./a.out 
GNU Fortran (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

           0

$ ifort --version && ifort test.f90 && ./a.out 
ifort (IFORT) 2021.2.0 20210228

           0

$ nagfor -V && nagfor test.f90 && ./a.out 
NAG Fortran Compiler Release 7.0(Yurakucho) Build 7076
Product NPL6A70NA for x86-64 Linux
NAG Fortran Compiler Release 7.0(Yurakucho) Build 7076
 0

$ nvfortran --version && nvfortran test.f90 && ./a.out 
nvfortran 22.11-0 64-bit target on x86-64 Linux -tp haswell 

            0

However, according to [Types and kinds — Fortran Programming Language] on nint:

Which one is correct?

You mean why you do not get -0 instead of 0? Well, that depends on your compiler and often your compiler switches and the platform you are on. What does this program produce?

! floats
a=-0.0
b=0.0
! my favorite: compare -0.0 to 0.0 and take sqrt of negative 0
if(a==0.0)write(*,*)'sqrt(a)=',sqrt(a)
write(*,*)a.eq.b
write(*,*)a.lt.b
write(*,*)a.gt.b
write(*,'(b32.32)')a,b
write(*,*)a,b
write(*,*)sign(0.0,-0.0),sign(1.0,-0.0)

!integer
a=0.01
write(*,*)nint(a),int(a-0.5)

i=-0
j=0
write(*,'(b32.32)')i,j
write(*,*)i,j
write(*,*)sign(0,-0),sign(1,-0)
end

If you have more than one compiler try it with multiple compilers. Also, instead of nint(a), try anint(a).

Thank you @urbanjost for the explanation and example. I see. I forgot that 0 can have a sign, and I misread " If x is less than or equal to zero, nint(x) has the value int(a-0.5).", thinking that “nint(x) = nint(x - 0.5)”.

Support for negative zero can get problematic; but behaves relatively consistently on most machines nowadays, but compilers often have switches that control the details. For example,
if you have ifort try the program with

ifort -assume minus0 file.f90
ifort  file.f90

and you will see different output. It is not a good idea to do things like use -0.0 as a flag value
or depend on it much at all unless you know how it is used in your environment and you are not likely to be using other ones. When portability is required, depending on negative zero should be done with caution.

1 Like

Could you post the results so that people without ifort can see the difference?

With the above given @urbanjost’s code, one gets (MacOS 12.6, ifort 2021.7.1):

$ ifort file.f90 && ./a.out
 sqrt(a)=  0.0000000E+00
 T
 F
 F
10000000000000000000000000000000
00000000000000000000000000000000
  0.0000000E+00  0.0000000E+00
  0.0000000E+00   1.000000
           0           0
00000000000000000000000000000000
00000000000000000000000000000000
           0           0
           0           1

$ ifort -assume minus0 file.f90 && ./a.out
 sqrt(a)= -0.0000000E+00
 T
 F
 F
10000000000000000000000000000000
00000000000000000000000000000000
 -0.0000000E+00  0.0000000E+00
 -0.0000000E+00  -1.000000
           0           0
00000000000000000000000000000000
00000000000000000000000000000000
           0           0
           0           1

None of these differences involve nint(), right? It looks like the only differences are floating point values involving floating point signed zero.

nint gives always just zero,as there is no negative zero in integer types.
Interestingly, anint can return negative 0:

  integer :: n
  real :: a
  n = nint(-0.1)
  a = anint(-0.1)
  print *, n, a
end program

outputs:

   0  0.0000000E+00   ! ifort nint.f90
   0 -0.0000000E+00   ! ifort -assume minus0 nint.f90
   0  -0.00000000     ! gfortran nint.f90 (v. 12.2.0)

It seems a bit controversial. Standard descriptions of anint and nint differ only in the result type, otherwise stating that the result is the nearest integer. The nearest integer of -0.1 is just zero, no signs. Converting that to real should arguably give (plus) zero. There is no mention of preserving the sign cited by the OP from Types and kinds chapter of fortran-lang.org

Thanks for generating the output. There was a collection of files unfortunately lost that was accumulated as bugs were encountered (often when switching platforms or during development)
that frequently started off with a little code that you were asked to produce the answers for by hand before reading the manuscript. In an attempt to re-create it (if memory serves me right it was more complicated and had the equivalents of 0.0-epsilon(0.0) and 0+epsilon(0.0) in it somewhere) it went something like this for signed zeros:

! gfortran -fsign-zero
! gfortran -no-fsign-zero
! ifort -assume minus0
! ifort -noassume minus0
character(len=*),parameter :: g='(*(g0,1x))'
write(*,g)'float:'
a=-0.0
b=0.0
! my favorite: compare -0.0 to 0.0 and take sqrt of negative 0
if(a==0.0)write(*,*)'sqrt(a)=',sqrt(a)
write(*,g)a.eq.b
write(*,g)a.lt.b
write(*,g)a.gt.b
write(*,'(b32.32)')a,b
write(*,g)'a,b=',a,b
write(*,g)'sign(0.0,-0.0)=',sign(0.0,-0.0)
write(*,g)'sign(1.0,-0.0)=',sign(1.0,-0.0)
write(*,g)'anint([-0.0,0.0,-0.1,-0.5,0.5])=',anint([-0.0,0.0,-0.1,-0.5,0.5])
write(*,g)'nint([-0.0,0.0,-0.1,-0.5,0.5])=',nint([-0.0,0.0,-0.1,-0.5,0.5])
write(*,g)'integer:'
a=-0.1
write(*,g)'a=',a,'nint(a)=',nint(a),'int(a-0.5)=',int(a-0.5)
i=-0
j=0
write(*,'(b32.32)')i,j
write(*,g)i,j
write(*,g)'sign(0,-0)=',sign(0,-0)
write(*,g)'sign(1,-0)=',sign(1,-0)
end

it was probably F77-compatible, though. There was a change in what SIGN(-0.0) produced starting with f90 (I think); but roughly the idea was to decide what the output would be first, and
then try it. It would be a long journey down memory lane, but the output would vary in the past a lot more than it is likely to do now. Some of them were great reads; but alas they are lost to the sands of time.

Note in the comments you can see the switches for gfortran as well as ifort; two common compilers.

I know one “banned” practice discussed was using -0.0 as a flag for missing values, which caused a bug during transitioning to a new platform but I do not remember all the details ; except I always try to avoid depending on signed zeros since reading it. Maybe just an old habit I could drop(?); but it was not that long ago someone was asking what the junk at the start of a program was trying to do. It had a few lines similar to the above and if the result was not -1 it printed out something like “: $CODE depends on support of negative zero float values during convergence. Read SIGNZ in DUSTY repository”; and I knew what it meant. They added “-assume minus0” on the compile and all was well with the world.

PS: I remember a little more about the one bug. REAL values were read from a file and -1.0 represented missing measurements. A new platform did everything OK with the tests, but some runs with actual user data was getting incorrect results. Some of the user files were using -0 instead of -0.0 for the missing values; which previous values read as -1.0 but the new platform was returning 0.0 when the (list-directed) reads encountered “-0”. It was totally missed by the QA process where all the values representing missing measurements had decimal places.

Not quite true that “Standard descriptions of anint and nint differ only in the result type, otherwise stating that the result is the nearest integer”. The 2018 standard says that the result of anint is the nearest real whole number but that the result of nint is the nearest integer. That is a succinct way to specify that the Fortran rules for real numbers apply to anint, and -0.0 is the nearest real whole number to -0.1 in a processot that supports negative zero.

Well, there seems to be some inconsistency. The wording you have quoted is in the description of 16.9.12 ANINT (A [, KIND]):

1 Description. Nearest whole number.

But for the result, it is:

5 Result Value. The result is the integer nearest A, […]

I have indeed not noticed the difference in description.

In common English, a whole number is the same as an integer, usually defined as a number without a fractional part. In mathematics, whole numbers are the nonnegative subset of integers. Is the term whole number defined in the fortran standard?

No, it is not defined. The phrase occurs in 4 places, all related to aint/anint intrinsics. So nearest whole number can only be supposed to be in some opposition to nearest integer, relating the former to whole numbers represented in FP and the latter to integer type. Also negative zero can only be found in the context of enumeration of IEEE constants defined in IEEE_ARITHMETIC module.

Hmmm. I wonder whether the Fortran Committee forgot to update the definition of ANINT when IEEE arithmetic first appeared in the Fortran standard (2003). I like the descriptions of ANINT and NINT but the result value of ANINT could usefully be changed from

5 Result Value. The result is the integer nearest A, […]

to

5 Result Value. The result is the real whole number nearest A, […]

The plot thickens. With
print *,anint(-0.1)
ifort prints 0.0000000E+00 or -0.000000 depending on which options have been asked for. Should the standard say it is processor-dependent whether anint(a) returns negative zero when -0.5 < a <= -0.0?

I should have said which options were in use. Ifort gave -0.0 with the -standard-semantics option but 0.0 without it.

Consider AINT(), CEILING(), and FLOOR() as well.

On second thought, forget CEILING() and FLOOR(). I was thinking they returned REAL but they always return INTEGER.

I think the -0.0 agrees with the IEEE behavior too.

Hmmm. The description of AINT() seems to prescribe a behavior I am not seeing;

!16    16.9.11  AINT (A [, KIND])
!17  1 Description. Truncation toward 0 to a whole number.
!18  2 Class. Elemental function.
!19  3 Arguments.
!20    A         shall be of type real.
!21    KIND (optional) shall be a scalar integer constant expression.
!22  4 Result Characteristics. The result is of type real. If KIND is present, the kind type parameter is that speci   ed
!23    by the value of KIND; otherwise, the kind type parameter is that of A.
!24  5 Result Value. If |A| < 1, AINT (A) has the value 0; if |A|     1, AINT (A) has a value equal to the integer
!25    whose magnitude is the largest integer that does not exceed the magnitude of A and whose sign is the same as
!26    the sign of A.
!27  6 Examples. AINT (2.783) has the value 2.0. AINT (   2.783) has the value    2.0.
program demo_anint
use, intrinsic :: iso_fortran_env, only : real32, real64, real128
implicit none
real,allocatable :: arr(:)

   arr=[ -0.0, -0.1, -0.5, -1.0 ]
   print *, arr
   print *, anint(arr)
   print *, aint(arr)

end program demo_anint
GNU Fortran (Homebrew GCC 12.2.0) 12.2.0
$ gfortran xx.f90
$ ./a.out
  -0.00000000     -0.100000001     -0.500000000      -1.00000000    
  -0.00000000      -0.00000000      -1.00000000      -1.00000000    
  -0.00000000      -0.00000000      -0.00000000      -1.00000000    
ifort (IFORT) 2021.7.1 20221019
$ ifort xx.f90 -assume minus0
$ ./a.out
 -0.0000000E+00 -0.1000000     -0.5000000      -1.000000    
 -0.0000000E+00 -0.0000000E+00  -1.000000      -1.000000    
 -0.0000000E+00 -0.0000000E+00 -0.0000000E+00  -1.000000    

Am I reading that wrong? Two compilers did not generate what I expected for

real :: arr(4)=[ -0.0, -0.1, -0.5, -1.0 ]
   print *, arr
   print *, anint(arr)
   ! equivalent to anint when -0.5 < a < -0.0 ?
   print *, aint(arr+0.5*sign(1.0,arr)) 
end 
 -0.0000000E+00 -0.1000000     -0.5000000      -1.000000    
 -0.0000000E+00 -0.0000000E+00  -1.000000      -1.000000    
 -0.0000000E+00 -0.0000000E+00  -1.000000      -1.000000      

I thought that was saying AINT() would return an unsigned zero for A greater than -0.5 to -0.0, period.

I think the way signed zeros work in fortran is that both zeros have the same numerical value, but have different representations (i.e. different bit patterns. So if you compare the bit patterns, you will see a difference, but if you compare numerical values, they will test equal. If you go further and use the IEEE function comparisons rather than the fortran comparisons, then the behavior is further specified. I think the behavior shown above with the signed zeros is consistent with the default IEEE behavior, but I don’t think that is required by the fortran standard (meaning different compilers might make different choices for when the fortran comparisons agree with the IEEE behavior). Furthermore, for many of the IEEE functions themselves, their behavior can be modified by setting the various modes for how rounding occurs, which NaNs are produced, how signed zeros are handled, how Inf is treated, and how signals are propagated. So if you use the IEEE functions explicitly, there might be a combination of mode switches that gives what you want to see, even if it is not the default.