stdlib icon indicating copy to clipboard operation
stdlib copied to clipboard

A function to compare two floating-point numbers

Open masuday opened this issue 4 years ago • 6 comments

Inspied by Julia, I would like to have a function to compare two floating-point numbers with a tolerance, useful for testing.

elemental function is_approximated_1(x) result(nearly_equal)
    real(dp),intent(in) :: x
    logical :: nearly_equal
    nearly_equal = abs(x) < d_tol
end function is_approximated_1

elemental function is_approximated_2(x,y) result(nearly_equal)
    real(dp),intent(in) :: x,y
    logical :: nearly_equal
    nearly_equal = abs(x-y) < d_tol
end function is_approximated_2

elemental function is_approximated_2_tol(x,y,tol) result(nearly_equal)
    real(dp),intent(in) :: x,y,tol
    logical :: nearly_equal
    nearly_equal = abs(x-y) < tol
end function is_approximated_2

Defining a general name:

interface is_approximated
   module procedure is_approximated_1, is_approximated_2, is_approximated_2_tol
end interface is_approximated

Usage:

real(real64) :: x(5),y(5)

print *,any(is_approximated(x,y))

masuday avatar Feb 06 '20 04:02 masuday

I would propose the following API:

is_approximated( x [, tol])
is_approximated( x, y, [, tol])

where tol is optional for both functions. A default tol can be determined with optval. So, something like:

elemental function is_approximated_2_tol_dp(x,y,tol) result(nearly_equal)
    real(dp),intent(in) :: x,y
    real(dp), intent(in), optional :: tol
    logical :: nearly_equal
    nearly_equal = abs(x-y) < optval(tol, d_tol)
end function is_approximated_2_tol_dp

I would also think that we can drop is_approaximated(x [, tol]) because the user would do explicitely what the second API does implicitely.

This function could be used in the tests of stdlib. A bitwise check with (almost) the same API could also be implemented. See #142 for more discussion.

Finally I may envisage some rebuttals from the community regarding this function because it is a one-line function.

jvdp1 avatar Feb 06 '20 10:02 jvdp1

I agree with your suggestion. A simple API is good enough.

I like this function because this comparison always occurs in any tests. A function with a clear purpose makes the code readable. For more precise comparisons, a bit-wise approach is preferred.

masuday avatar Feb 06 '20 16:02 masuday

I'd suggest to go with something more general, which allows also relative error. I like quite a lot Numpy's isclose() function which enables to specify both, absolute and relative error. So the interface could be like

elemental function is_close(x, y, reltol, abstol) result(isclosel)
    real(dp),intent(in) :: x, y
    real(dp), intent(in), optional :: reltol, abstol
    logical :: isclose
    isclose = abs(x - y) < reltol * abs(y) + atol
end function is_close

Also, the name is_close() is much shorter than is_approximated().

aradi avatar Feb 06 '20 16:02 aradi

@aradi Julia also uses a similar function. Personally, for a quick comparison, I am using the following function (as suggested by Tamas_Papp).

abs(x - y) < reltol * (1+abs(x)+abs(y))

masuday avatar Feb 06 '20 16:02 masuday

I suggest is_within.

wclodius2 avatar Jun 20 '20 17:06 wclodius2

In #488 @bilderbuchi mentions the Python math.isclose as another possible implementation. The choices behind the Python version (available with Python >= 3.5) are discussed in a Python Enhancement Proposal PEP 485 which also considers the numpy and Boost C++ implementations.

Tagging @zoziha, @milancurcic

ivan-pi avatar Sep 27 '21 13:09 ivan-pi