Skip to content

Commit bdb47b7

Browse files
committed
median: return NaN when real array contain NaN
1 parent 4d328dc commit bdb47b7

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

doc/specs/stdlib_stats.md

+2
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,14 @@ Generic subroutine
229229
### Return value
230230

231231
If `array` is of type `real`, the result is of type `real` with the same kind as `array`.
232+
If `array` is of type `real` and contains IEEE `NaN`, the result is IEEE `NaN`.
232233
If `array` is of type `integer`, the result is of type `real(dp)`.
233234

234235
If `dim` is absent, a scalar with the median of all elements in `array` is returned. Otherwise, an array of rank `n-1`, where `n` equals the rank of `array`, and a shape similar to that of `array` with dimension `dim` dropped is returned.
235236

236237
If `mask` is specified, the result is the median of all elements of `array` corresponding to `true` elements of `mask`. If every element of `mask` is `false`, the result is IEEE `NaN`.
237238

239+
238240
### Example
239241

240242
```fortran

src/stdlib_stats_median.fypp

+43-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
submodule (stdlib_stats) stdlib_stats_median
66

7-
use, intrinsic:: ieee_arithmetic, only: ieee_value, ieee_quiet_nan
7+
use, intrinsic:: ieee_arithmetic, only: ieee_value, ieee_quiet_nan, ieee_is_nan
88
use stdlib_error, only: error_stop
99
use stdlib_optval, only: optval
1010
! Use "ord_sort" rather than "sort" because the former can be much faster for arrays
@@ -31,6 +31,13 @@ contains
3131
return
3232
end if
3333

34+
#:if t1[0] == 'r'
35+
if (any(ieee_is_nan(x))) then
36+
res = ieee_value(1._${o1}$, ieee_quiet_nan)
37+
return
38+
end if
39+
#:endif
40+
3441
n = size(x, kind=int64)
3542
c = floor( (n + 1) / 2._${o1}$, kind=int64 )
3643

@@ -87,6 +94,19 @@ contains
8794
do j${fj}$ = 1, size(x, ${fj}$)
8895
#:endfor
8996
x_tmp(:) = x${select_subvector('j', rank, fi)}$
97+
98+
#:if t1[0] == 'r'
99+
if (any(ieee_is_nan(x_tmp))) then
100+
res${reduce_subvector('j', rank, fi)}$ = &
101+
ieee_value(1._${o1}$, ieee_quiet_nan)
102+
#:if fi == 1
103+
return
104+
#:else
105+
cycle
106+
#:endif
107+
end if
108+
#:endif
109+
90110
call sort(x_tmp)
91111

92112
if (mod(n, 2) == 0) then
@@ -127,12 +147,19 @@ contains
127147
call error_stop("ERROR (median): shapes of x and mask are different")
128148
end if
129149

150+
#:if t1[0] == 'r'
151+
if (any(ieee_is_nan(x))) then
152+
res = ieee_value(1._${o1}$, ieee_quiet_nan)
153+
return
154+
end if
155+
#:endif
156+
130157
x_tmp = pack(x, mask)
131158

132159
call sort(x_tmp)
133160

134-
n = size(x_tmp, kind=int64 )
135-
c = floor( (n + 1) / 2._${o1}$, kind=int64 )
161+
n = size(x_tmp, kind=int64)
162+
c = floor( (n + 1) / 2._${o1}$, kind=int64)
136163

137164
if (n == 0) then
138165
res = ieee_value(1._${o1}$, ieee_quiet_nan)
@@ -180,6 +207,19 @@ contains
180207
#:endfor
181208
x_tmp = pack(x${select_subvector('j', rank, fi)}$, &
182209
mask${select_subvector('j', rank, fi)}$)
210+
211+
#:if t1[0] == 'r'
212+
if (any(ieee_is_nan(x_tmp))) then
213+
res${reduce_subvector('j', rank, fi)}$ = &
214+
ieee_value(1._${o1}$, ieee_quiet_nan)
215+
#:if rank == 1
216+
return
217+
#:else
218+
cycle
219+
#:endif
220+
end if
221+
#:endif
222+
183223
call sort(x_tmp)
184224

185225
n = size(x_tmp, kind=int64)

src/tests/stats/test_median.fypp

+37-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ program test_median
55
use stdlib_kinds, only: sp, dp, qp, &
66
int8, int16, int32, int64
77
use stdlib_stats, only: median, mean
8-
use, intrinsic :: ieee_arithmetic, only : ieee_is_nan
8+
use, intrinsic :: ieee_arithmetic, only : ieee_is_nan, ieee_value, ieee_quiet_nan
99
implicit none
1010
real(sp), parameter :: sptol = 1000 * epsilon(1._sp)
1111
real(dp), parameter :: dptol = 2000 * epsilon(1._dp)
@@ -108,6 +108,7 @@ end subroutine
108108
#:for k1,t1 in REAL_KINDS_TYPES
109109
subroutine test_median_${k1}$()
110110
${t1}$, allocatable :: d0(:), d1(:), d2(:,:), d3(:,:,:)
111+
${t1}$, allocatable :: tmp1(:)
111112

112113
allocate(d0(0))
113114
!check just to be sure that the setup of d0 is correct
@@ -190,6 +191,41 @@ subroutine test_median_${k1}$()
190191
call check( sum(abs(median(d2, 2) - [7._${k1}$, 1._${k1}$, 0._${k1}$])) < ${k1}$tol, &
191192
'${k1}$ median(d2, 2), odd')
192193

194+
!check IEEE NaN values in array
195+
d1(1) = ieee_value(1._${k1}$, ieee_quiet_nan)
196+
d2(1, 1) = ieee_value(1._${k1}$, ieee_quiet_nan)
197+
d3(1, 1, 1) = ieee_value(1._${k1}$, ieee_quiet_nan)
198+
199+
!median_all
200+
call check( ieee_is_nan(median(d1)), '${k1}$ median(d1), should be NaN')
201+
call check( ieee_is_nan(median(d2)), '${k1}$ median(d2), should be NaN')
202+
call check( ieee_is_nan(median(d3)), '${k1}$ median(d3), should be NaN')
203+
204+
!median
205+
call check( any(ieee_is_nan(median(d2, 1))), '${k1}$ median(d2, 1) should contain at least 1 NaN' )
206+
call check( any(ieee_is_nan(median(d2, 2))), '${k1}$ median(d2, 2) should contain at least 1 NaN' )
207+
208+
call check( any(ieee_is_nan(median(d3, 1))), '${k1}$ median(d3, 1) should contain at least 1 NaN' )
209+
call check( any(ieee_is_nan(median(d3, 2))), '${k1}$ median(d3, 2) should contain at least 1 NaN' )
210+
call check( any(ieee_is_nan(median(d3, 3))), '${k1}$ median(d3, 3) should contain at least 1 NaN' )
211+
212+
!median_mask_all
213+
call check( ieee_is_nan(median(d1, d1 > 0)), '${k1}$ median(d1, d1 > 0) should be NaN' )
214+
call check( ieee_is_nan(median(d2, d2 > 0)), '${k1}$ median(d2, d2 > 0) should be NaN' )
215+
call check( ieee_is_nan(median(d3, d3 > 0)), '${k1}$ median(d3, d3 > 0) should be NaN' )
216+
217+
!median mask
218+
call check( ieee_is_nan(median(d1, 1, d1 .ne. 0)), '${k1}$ median(d1, 1, d1.ne.0) should return NaN')
219+
220+
tmp1 = median(d2, 1, d2 .ne. 0)
221+
call check( any(ieee_is_nan(tmp1)), '${k1}$ median(d2, 1, d2 .ne. 0 ) should contain at least 1 NaN')
222+
call check( tmp1(2) == -4._${k1}$, '${k1}$ tmp1(2) == -4')
223+
call check( any(ieee_is_nan(median(d2, 2, d2 .ne. 0))), &
224+
'${k1}$ median(d2, 2, d2 .ne. 0 ) should contain at least 1 NaN')
225+
226+
call check( any(ieee_is_nan(median(d3, 1, d3 .ne. 0))), &
227+
'${k1}$ median(d3, 1, d3 .ne. 0 ) should contain at least 1 NaN')
228+
193229
end subroutine
194230
#:endfor
195231

0 commit comments

Comments
 (0)