Topic: No Intrinsic Procedure for real equals real?
Hi Jeff,
I have a general programming question regarding the precision of real variables. Particularly when testing two reals for equality (x == y). Due to the lack of precision, there are situations when x should equal y, but due to the error in precision they are not equal (1.0000000000000000 /= 0.9999999999999999). As far as I understand, there is not a FORTRAN intrinsic procedure for the equality of two real numbers other than x == y. Therefore, I've written a procedure to test for the equality of two real numbers that allows for the lack of precision in variables x and y. The routine is copied below. My question is as follows, (1) Is this the best way to test for equality?, (2) Is there a better approach that works well?, (3) Why does double precision x+factor == y1 (.true.) in my example? I welcome any thoughts on or improvements in this approach. Finally, Jeff, if you think my question is not appropriate for this forum, please remove this message.
Frank
Equality Program:
PROGRAM Test_EQ_FP8
!---------------------------------------------------------------
! NAME:
! Test_EQ_FP8
! PURPOSE:
! Program to test the routines in the EQ_FP8(x,y) routine.
! MODULES:
! Type_Kinds: Definitions for kinds of variable types.
! EQ_FP8: Performs equality comparison of real numbers.
!---------------------------------------------------------------
IMPLICIT NONE
!-------------------------------
!--- Floating Point Precision
!-------------------------------
! REAL( KIND )
!===============================
! Single (4 bytes)
! Double (8 bytes)
! Quad (16 bytes)
!-------------------------------
!--- Six decimal place precision
INTEGER, PARAMETER :: &
Single = SELECTED_REAL_KIND(6)
!--- Fourteen decimal place precision
INTEGER, PARAMETER :: &
Double = SELECTED_REAL_KIND(2*precision(1.0_Single))
!--- Sixteen decimal place precision
INTEGER, PARAMETER :: &
Quadr = SELECTED_REAL_KIND(2*precision(1.0_Double))
INTEGER, PARAMETER :: &
Quad = (((1 + SIGN(1,Quadr)) / 2) * Quadr) &
+ (((1 - SIGN(1,Quadr)) / 2) * Double)
INTEGER, PARAMETER :: &
Q = Quad, D = Double, S = Single
! ----------
! Parameters
CHARACTER( * ), PARAMETER :: ROUTINE = 'Test_EQ_FP8'
! -- The test numbers
INTEGER, PARAMETER :: N_NUMBERS = 7
REAL( Single ), PARAMETER, DIMENSION( N_NUMBERS ) :: SINGLE_NUMBER = &
(/ 1.000000000000000e-32_Single, &
1.234567890123456e-16_Single, 1.234567890123456e-08_Single, &
1.000000000000000e+00_Single, 1.234567890123456e+08_Single, &
1.234567890123456e+16_Single, 1.234567890123456e+32_Single /)
REAL( Double ), PARAMETER, DIMENSION( N_NUMBERS ) :: DOUBLE_NUMBER = &
(/ 1.000000000000000e-32_Double, &
1.234567890123456e-16_Double, 1.234567890123456e-08_Double, &
1.000000000000000e+00_Double, 1.234567890123456e+08_Double, &
1.234567890123456e+16_Double, 1.234567890123456e+32_Double /)
REAL( Quad ), PARAMETER, DIMENSION( N_NUMBERS ) :: QUAD_NUMBER = &
(/ 1.000000000000000e-32_Quad, &
1.234567890123456e-16_Quad, 1.234567890123456e-08_Quad, &
1.000000000000000e+00_Quad, 1.234567890123456e+08_Quad, &
1.234567890123456e+16_Quad, 1.234567890123456e+32_Quad /)
! ---------
! Variables
! ---------
CHARACTER(len=256) :: cend
REAL( Single ) :: x, y1, y2, y3, y4
REAL( Double ) :: xd, yd1, yd2, yd3, yd4
REAL( Quad ) :: xq, yq1, yq2, yq3, yq4
REAL( Double ) :: x0, y0
INTEGER :: i
!--- Added error
REAL( Double ), PARAMETER :: e = 1.0e-30_Double
!--- TEST EQUALITY OF X AND Y REALS
!---------------------------------------------------------------
DO i = 1, N_NUMBERS
WRITE( *, '(/5x, "NUMBER ", i2, " of ", i2 )' ) i, N_NUMBERS
!----------------------
! Single precision test
!----------------------
x = SINGLE_NUMBER(i)
y1 = NEAREST( x, 1.0_Single )
y2 = y1 - SPACING( x )
y3 = NEAREST( x, -1.0_Single )
y4 = y3 + SPACING( x )
x = x + e
!--- Test standalone single precision function routines
WRITE( *, '( /5x, "SINGLE PRECISION: x = ", es20.13, &
&/5x, "y1 = ", es20.13, 2x, ": NEAREST( x, 1.0 )", &
&/5x, "y2 = ", es20.13, 2x, ": y1 - SPACING( x )", &
&/5x, "y3 = ", es20.13, 2x, ": NEAREST( x,-1.0 )", &
&/5x, "y4 = ", es20.13, 2x, ": y3 + SPACING( x )" )' ) &
x, y1, y2, y3, y4
x0 = x; y0 = y1
WRITE( *, '(/5x,a,2x,L6)') 'EQ_FP8(x,y1): X=Y?', EQ_FP8( x0, y0 )
x0 = x; y0 = y2
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y2): X=Y?', EQ_FP8( x0, y0 )
x0 = x; y0 = y3
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y3): X=Y?', EQ_FP8( x0, y0 )
x0 = x; y0 = y4
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y4): X=Y?', EQ_FP8( x0, y0 )
!----------------------
! Double precision test
!----------------------
xd = DOUBLE_NUMBER(i)
yd1 = NEAREST( xd, 1.0_Double )
yd2 = yd1 - SPACING( xd )
yd3 = NEAREST( xd, -1.0_Double )
yd4 = yd3 + SPACING( xd )
xd = xd + e
!--- Test stand alone double precision function routines
WRITE( *, '( /5x, "DOUBLE PRECISION: x = ", es20.13, &
&/5x, "y1 = ", es20.13, 2x, ": NEAREST( x, 1.0 )", &
&/5x, "y2 = ", es20.13, 2x, ": y1 - SPACING( x )", &
&/5x, "y3 = ", es20.13, 2x, ": NEAREST( x,-1.0 )", &
&/5x, "y4 = ", es20.13, 2x, ": y3 + SPACING( x )" )' ) &
xd, yd1, yd2, yd3, yd4
x0 = xd; y0 = yd1
WRITE( *, '(/5x,a,2x,L6)') 'EQ_FP8(x,y1) X=Y?:', EQ_FP8( x0, y0 )
x0 = xd; y0 = yd2
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y2) X=Y?:', EQ_FP8( x0, y0 )
x0 = xd; y0 = yd3
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y3) X=Y?:', EQ_FP8( x0, y0 )
x0 = xd; y0 = yd4
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y4) X=Y?:', EQ_FP8( x0, y0 )
!-------------------------
! Quadruple precision test
!-------------------------
xq = QUAD_NUMBER(i)
yq1 = NEAREST( xq, 1.0_Quad )
yq2 = yq1 - SPACING( xq )
yq3 = NEAREST( xq, -1.0_Quad )
yq4 = yq3 + SPACING( xq )
xq = xq + e
!--- Test stand alone quadruple precision function routines
WRITE( *, '( /5x, "QUADRULE PRECISION: x = ", es20.13, &
&/5x, "y1 = ", es20.13, 2x, ": NEAREST( x, 1.0 )", &
&/5x, "y2 = ", es20.13, 2x, ": y1 - SPACING( x )", &
&/5x, "y3 = ", es20.13, 2x, ": NEAREST( x,-1.0 )", &
&/5x, "y4 = ", es20.13, 2x, ": y3 + SPACING( x )" )' ) &
xq, yq1, yq2, yq3, yq4
x0 = xq; y0 = yq1
WRITE( *, '(/5x,a,2x,L6)') 'EQ_FP8(x,y1) X=Y?:', EQ_FP8( x0, y0 )
x0 = xq; y0 = yq2
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y2) X=Y?:', EQ_FP8( x0, y0 )
x0 = xq; y0 = yq3
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y3) X=Y?:', EQ_FP8( x0, y0 )
x0 = xq; y0 = yq4
WRITE( *, '( 5x,a,2x,L6)') 'EQ_FP8(x,y4) X=Y?:', EQ_FP8( x0, y0 )
WRITE( *,'(5x,a)') REPEAT("-",50)
!----------------------
!--- Question?
!----------------------
WRITE( *, '(/5x,a/)') &
"Why is double precision number x+e=y1 true?"
END DO
WRITE( *, '( /10x, "Press <ENTER> to continue..." )' )
READ( *, '(a)' ) cend
!---------------------------------------------------------------
CONTAINS
!---------------------------------------------------------------
FUNCTION EQ_FP8( x,y ) Result( DrFP )
!***************************************************************
!--- EQ_FP8 version 1.0
!---
!--- Developed by F.W. Perrella, Ph.D. November 7, 2020
!--- EQ_FP8™ Copyright © 2020 F.W. PERRELLA, Ph.D.
!--- Silver Spring, Maryland U.S.A.
!--- All Rights Reserved.
!***************************************************************
!
! NAME:
! EQ_FP8
! PURPOSE:
! Program to determine whether two real numbers are equal.
! MODULES:
! Type_Kinds: Definition for double kind variable types.
! EQ_FP8: Routine to perform equality comparison
! of real numbers.
!---------------------------------------------------------------
!
! SOURCE listing of EQ_FP8.f90:
! u = DMIN1(DABS(x), DABS(y))
! v = DMAX1(DABS(x), DABS(y))
! Difx = NEAREST( u, v )
! Dify = SPACING( v )
! Difxy = NEAREST( v-u, v )
! IF (Difxy < TINY(u)) Difxy = TINY(u)
! IF ( Difxy < DABS(Difx + Dify - x) ) THEN
! DrFP = .TRUE.
! ELSE
! DrFP = .FALSE.
! ENDIF
!
! FORTRAN 90 INTRINSIC FUNCTIONS:
! SPACING(x) determines the absolute distance between the
! argument x and the nearest adjacent number of the same type.
!
! SPACING(x) = 2.D0**( EXPONENT(x) - DIGITS(x) )
!
! NEAREST( x,s ) determines the processor-representable number
! nearest to x in the direction indicated by the sign of s.
!
! NEAREST(x,y) = x + SIGN(1.D0,x)*2.D0**(EXPONENT(x)-DIGITS(x))
!
! CALLING SEQUENCE:
! Result = EQ_FP8( x,y )
!
! INPUT ARGUMENTS:
! x, y: Two real numbers to compare.
! TYPE: REAL( Double )
!
! ATTRIBUTES:
! INTENT( IN )
!
!
! FUNCTION RESULT:
! TYPE: Logical
! RESULT: A returned logical value indicating whether the
! x and y inputs are equal within the set precision.
! Set to .TRUE. if the real numbers are equal
! within the specified tolerance.
! Set to .FALSE. if the real numbers are not equal.
!---------------------------------------------------------------
IMPLICIT NONE
!--- Input variables
REAL(Kind=8), INTENT( IN ) :: x
REAL(Kind=8), INTENT( IN ) :: y
LOGICAL :: DrFP
!--- Double precision
INTEGER, PARAMETER :: Doubles = SELECTED_REAL_KIND(15,300)
!--- Local Variables
REAL(Doubles) :: u, v
REAL(Doubles) :: Difx
REAL(Doubles) :: Dify
REAL(Doubles) :: Difxy
INTRINSIC :: NEAREST, SPACING, DMIN1, DMAX1, DABS
!--- Minimum and maximum of x and y
u = DMIN1(DABS(x), DABS(y))
v = DMAX1(DABS(x), DABS(y))
!--- Determine Nearest, Spacing, and Nearest difference
Difx = NEAREST( u, v )
Dify = SPACING( v )
Difxy = NEAREST( v-u, v )
!--- Nearest difference cannot be less than Tiny
IF (Difxy < TINY(u)) Difxy = TINY(u)
!--- Evaluate if x is equal to y?
IF ( Difxy < DABS(Difx + Dify - x) ) THEN
DrFP = .TRUE.
ELSE
DrFP = .FALSE.
ENDIF
END FUNCTION EQ_FP8
!---------------------------------------------------------------
END PROGRAM Test_EQ_FP8
!---------------------------------------------------------------
!---------------------------------------------------------------