Skip to content

Commit 268cedc

Browse files
committed
Update tests for **_close,
1.0e-9 -> sqrt(epsilon(..))
1 parent 53874c9 commit 268cedc

File tree

4 files changed

+116
-24
lines changed

4 files changed

+116
-24
lines changed

doc/specs/stdlib_math.md

+17-20
Original file line numberDiff line numberDiff line change
@@ -393,13 +393,13 @@ end program demo_math_arange
393393

394394
#### Description
395395

396-
Returns a boolean scalar/array where two scalars/arrays are element-wise equal within a tolerance, behaves like `isclose` in Python stdlib.
396+
Returns a boolean scalar/array where two scalars/arrays are element-wise equal within a tolerance.
397397

398398
```fortran
399399
!> For `real` type
400400
is_close(a, b, rel_tol, abs_tol) = abs(a - b) <= max(rel_tol*(abs(a), abs(b)), abs_tol)
401401
402-
!> For `complex` type
402+
!> and for `complex` type
403403
is_close(a, b, rel_tol, abs_tol) = is_close(a%re, b%re, rel_tol, abs_tol) .and. &
404404
is_close(a%im, b%im, rel_tol, abs_tol)
405405
```
@@ -418,24 +418,26 @@ Elemental function.
418418

419419
#### Arguments
420420

421+
Note: All `real/complex` arguments must have same `kind`.
422+
If the value of `rel_tol/abs_tol` is negative (not recommended),
423+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `is_close`.
424+
421425
`a`: Shall be a `real/complex` scalar/array.
422426
This argument is `intent(in)`.
423427

424428
`b`: Shall be a `real/complex` scalar/array.
425429
This argument is `intent(in)`.
426430

427431
`rel_tol`: Shall be a `real` scalar/array.
428-
This argument is `intent(in)` and `optional`, which is `1.0e-9` by default.
432+
This argument is `intent(in)` and `optional`, which is `sqrt(epsilon(..))` by default.
429433

430434
`abs_tol`: Shall be a `real` scalar/array.
431435
This argument is `intent(in)` and `optional`, which is `0.0` by default.
432436

433437
`equal_nan`: Shall be a `logical` scalar/array.
434438
This argument is `intent(in)` and `optional`, which is `.false.` by default.
435-
436-
Note: All `real/complex` arguments must have same `kind`.
437-
If the value of `rel_tol/abs_tol` is negative (not recommended),
438-
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `is_close`.
439+
Whether to compare `NaN` values as equal. If `.true.`,
440+
`NaN` values in `a` will be considered equal to `NaN` values in `b`.
439441

440442
#### Result value
441443

@@ -457,9 +459,6 @@ program demo_math_is_close
457459
print *, is_close(2.0, 2.1, abs_tol=0.1) !! T
458460
print *, NAN, is_close(2.0, NAN), is_close(2.0, NAN, equal_nan=.true.) !! NAN, F, F
459461
print *, is_close(NAN, NAN), is_close(NAN, NAN, equal_nan=.true.) !! F, T
460-
461-
call check(all(is_close(x, [2.0, 2.0])), msg="all(is_close(x, [2.0, 2.0])) failed.", warn=.true.)
462-
!! all(is_close(x, [2.0, 2.0])) failed.
463462
464463
end program demo_math_is_close
465464
```
@@ -468,7 +467,7 @@ end program demo_math_is_close
468467

469468
#### Description
470469

471-
Returns a boolean scalar where two arrays are element-wise equal within a tolerance, behaves like `all(is_close(a, b [, rel_tol, abs_tol, equal_nan]))`.
470+
Returns a boolean scalar where two arrays are element-wise equal within a tolerance.
472471

473472
#### Syntax
474473

@@ -484,24 +483,26 @@ Pure function.
484483

485484
#### Arguments
486485

486+
Note: All `real/complex` arguments must have same `kind`.
487+
If the value of `rel_tol/abs_tol` is negative (not recommended),
488+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `all_close`.
489+
487490
`a`: Shall be a `real/complex` array.
488491
This argument is `intent(in)`.
489492

490493
`b`: Shall be a `real/complex` array.
491494
This argument is `intent(in)`.
492495

493496
`rel_tol`: Shall be a `real` scalar.
494-
This argument is `intent(in)` and `optional`, which is `1.0e-9` by default.
497+
This argument is `intent(in)` and `optional`, which is `sqrt(epsilon(..))` by default.
495498

496499
`abs_tol`: Shall be a `real` scalar.
497500
This argument is `intent(in)` and `optional`, which is `0.0` by default.
498501

499502
`equal_nan`: Shall be a `logical` scalar.
500503
This argument is `intent(in)` and `optional`, which is `.false.` by default.
501-
502-
Note: All `real/complex` arguments must have same `kind`.
503-
If the value of `rel_tol/abs_tol` is negative (not recommended),
504-
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `all_close`.
504+
Whether to compare `NaN` values as equal. If `.true.`,
505+
`NaN` values in `a` will be considered equal to `NaN` values in `b`.
505506

506507
#### Result value
507508

@@ -524,10 +525,6 @@ program demo_math_all_close
524525
print *, all_close(z+cmplx(1.0e-11, 1.0e-11), z) !! T
525526
print *, NAN, all_close([NAN], [NAN]), all_close([NAN], [NAN], equal_nan=.true.)
526527
!! NAN, F, T
527-
528-
call check(all_close(x, [2.0, 2.0], rel_tol=1.0e-6, abs_tol=1.0e-3), &
529-
msg="all_close(x, [2.0, 2.0]) failed.", warn=.true.)
530-
!! all_close(x, [2.0, 2.0]) failed.
531528
532529
end program demo_math_all_close
533530
```

src/stdlib_math.fypp

+2-2
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ module stdlib_math
297297
!> Version: experimental
298298
!>
299299
!> Returns a boolean scalar/array where two scalar/arrays are element-wise equal within a tolerance.
300-
!> ([Specification](../page/specs/stdlib_logic.html#is_close))
300+
!> ([Specification](../page/specs/stdlib_math.html#is_close))
301301
interface is_close
302302
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
303303
#:for k1, t1 in RC_KINDS_TYPES
@@ -312,7 +312,7 @@ module stdlib_math
312312
!> Version: experimental
313313
!>
314314
!> Returns a boolean scalar where two arrays are element-wise equal within a tolerance.
315-
!> ([Specification](../page/specs/stdlib_logic.html#all_close))
315+
!> ([Specification](../page/specs/stdlib_math.html#all_close))
316316
interface all_close
317317
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
318318
#:set RANKS = range(1, MAXRANK + 1)

src/stdlib_math_is_close.fypp

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ submodule(stdlib_math) stdlib_math_is_close
55
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
66
implicit none
77

8+
#:for k1 in REAL_KINDS
9+
real(${k1}$), parameter :: sqrt_eps_${k1}$ = sqrt(epsilon(1.0_${k1}$))
10+
#:endfor
11+
812
contains
913

1014
#! Determines whether the values of `a` and `b` are close.
@@ -21,7 +25,7 @@ contains
2125
if (ieee_is_nan(a) .or. ieee_is_nan(b)) then
2226
close = merge(.true., .false., equal_nan_ .and. ieee_is_nan(a) .and. ieee_is_nan(b))
2327
else
24-
close = abs(a - b) <= max( abs(optval(rel_tol, 1.0e-9_${k1}$)*max(abs(a), abs(b))), &
28+
close = abs(a - b) <= max( abs(optval(rel_tol, sqrt_eps_${k1}$)*max(abs(a), abs(b))), &
2529
abs(optval(abs_tol, 0.0_${k1}$)) )
2630
end if
2731

src/tests/math/test_stdlib_math.fypp

+92-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
module test_stdlib_math
66
use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test
7-
use stdlib_math, only: clip
7+
use stdlib_math, only: clip, is_close, all_close
88
use stdlib_kinds, only: int8, int16, int32, int64, sp, dp, xdp, qp
99
implicit none
1010

@@ -32,6 +32,14 @@ contains
3232
new_unittest("clip-real-double-bounds", test_clip_rdp_bounds), &
3333
new_unittest("clip-real-quad", test_clip_rqp), &
3434
new_unittest("clip-real-quad-bounds", test_clip_rqp_bounds) &
35+
36+
!> Tests for `is_close` and `all_close`
37+
#:for k1 in REAL_KINDS
38+
, new_unittest("is_close-real-${k1}$", test_is_close_real_${k1}$) &
39+
, new_unittest("is_close-cmplx-${k1}$", test_is_close_cmplx_${k1}$) &
40+
, new_unittest("all_close-real-${k1}$", test_all_close_real_${k1}$) &
41+
, new_unittest("all_close-cmplx-${k1}$", test_all_close_cmplx_${k1}$) &
42+
#:endfor
3543
]
3644

3745
end subroutine collect_stdlib_math
@@ -203,6 +211,89 @@ contains
203211
#:endif
204212

205213
end subroutine test_clip_rqp_bounds
214+
215+
#:for k1 in REAL_KINDS
216+
subroutine test_is_close_real_${k1}$(error)
217+
type(error_type), allocatable, intent(out) :: error
218+
real(${k1}$) :: x, NAN
219+
x = -3; NAN = sqrt(x)
220+
221+
call check(error, is_close(2.5_${k1}$, 2.5_${k1}$), .true.)
222+
if (allocated(error)) return
223+
call check(error, is_close(0.0_${k1}$, -0.0_${k1}$), .true.)
224+
if (allocated(error)) return
225+
call check(error, is_close(2.5_${k1}$, 1.2_${k1}$), .false.)
226+
if (allocated(error)) return
227+
call check(error, is_close(NAN, NAN), .false.)
228+
if (allocated(error)) return
229+
call check(error, is_close(NAN, 0.0_${k1}$), .false.)
230+
if (allocated(error)) return
231+
call check(error, is_close(NAN, NAN, equal_nan=.true.), .true.)
232+
233+
end subroutine test_is_close_real_${k1}$
234+
235+
subroutine test_is_close_cmplx_${k1}$(error)
236+
type(error_type), allocatable, intent(out) :: error
237+
real(${k1}$) :: x, NAN
238+
x = -3; NAN = sqrt(x)
239+
240+
call check(error, is_close((2.5_${k1}$, 1.5_${k1}$), (2.5_${k1}$, 1.5_${k1}$)), .true.)
241+
if (allocated(error)) return
242+
call check(error, is_close((2.5_${k1}$, 1.2_${k1}$), (2.5_${k1}$, 1.5_${k1}$)), .false.)
243+
if (allocated(error)) return
244+
call check(error, is_close(cmplx(NAN, NAN, ${k1}$), cmplx(NAN, NAN, ${k1}$)), .false.)
245+
if (allocated(error)) return
246+
call check(error, is_close(cmplx(NAN, NAN, ${k1}$), cmplx(NAN, 0.0_${k1}$, ${k1}$)), .false.)
247+
if (allocated(error)) return
248+
call check(error, is_close(cmplx(NAN, NAN, ${k1}$), cmplx(NAN, NAN, ${k1}$), equal_nan=.true.), .true.)
249+
if (allocated(error)) return
250+
call check(error, is_close(cmplx(NAN, 1.2_${k1}$, ${k1}$), cmplx(NAN, 1.2_${k1}$, ${k1}$), equal_nan=.true.), .true.)
251+
252+
end subroutine test_is_close_cmplx_${k1}$
253+
254+
subroutine test_all_close_real_${k1}$(error)
255+
type(error_type), allocatable, intent(out) :: error
256+
real(${k1}$) :: x(2, 2), eps, NAN
257+
x = 1; eps = -3; NAN = sqrt(eps)
258+
259+
eps = sqrt(epsilon(1.0_${k1}$))
260+
261+
call check(error, all_close(x, x), .true.)
262+
if (allocated(error)) return
263+
call check(error, all_close(x + x*eps + 1.0e-6, x), .false.)
264+
if (allocated(error)) return
265+
call check(error, all_close(x + NAN, x), .false.)
266+
if (allocated(error)) return
267+
call check(error, all_close(x + NAN, x, equal_nan=.true.), .false.)
268+
if (allocated(error)) return
269+
call check(error, all_close(x + NAN, x + NAN), .false.)
270+
if (allocated(error)) return
271+
call check(error, all_close(x + NAN, x + NAN, equal_nan=.true.), .true.)
272+
273+
end subroutine test_all_close_real_${k1}$
274+
275+
subroutine test_all_close_cmplx_${k1}$(error)
276+
type(error_type), allocatable, intent(out) :: error
277+
real(${k1}$) :: eps, NAN
278+
complex(${k1}$) :: x(2, 2)
279+
x = (1, 1); eps = -3; NAN = sqrt(eps)
280+
281+
eps = sqrt(epsilon(1.0_${k1}$))
282+
283+
call check(error, all_close(x, x), .true.)
284+
if (allocated(error)) return
285+
call check(error, all_close(x + x*eps + 1.0e-6, x), .false.)
286+
if (allocated(error)) return
287+
call check(error, all_close(x + cmplx(NAN, NAN, ${k1}$), x), .false.)
288+
if (allocated(error)) return
289+
call check(error, all_close(x + cmplx(NAN, NAN, ${k1}$), x, equal_nan=.true.), .false.)
290+
if (allocated(error)) return
291+
call check(error, all_close(x + cmplx(NAN, NAN, ${k1}$), x + cmplx(NAN, NAN, ${k1}$), equal_nan=.true.), .true.)
292+
if (allocated(error)) return
293+
call check(error, all_close(x + cmplx(NAN, NAN, ${k1}$), x + cmplx(NAN, NAN, ${k1}$)), .false.)
294+
295+
end subroutine test_all_close_cmplx_${k1}$
296+
#:endfor
206297

207298
end module test_stdlib_math
208299

0 commit comments

Comments
 (0)