subroutine gr4_extrema (nxy,z,bval,eval,zmin,zmax,nmin,nmax)
  use gildas_def
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is renamed elsewhere)
  ! Find extrema of a (Real*4) array.
  ! +/- Infinity are considered as valid extrema is this routine
  ! See GR4_MINMAX for a version which ignores Infinity
  !
  !     Ignore NaNs and all "blanked" values.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: nxy     ! Number of values
  real(kind=4),              intent(in)    :: z(nxy)  ! Values
  real(kind=4),              intent(in)    :: bval    ! Blanking
  real(kind=4),              intent(in)    :: eval    ! Tolerance
  real(kind=4),              intent(inout) :: zmin    ! Unchanged if no valid value
  real(kind=4),              intent(inout) :: zmax    ! Unchanged if no valid value
  integer(kind=size_length), intent(out)   :: nmin    ! Set to 0 if no valid value
  integer(kind=size_length), intent(out)   :: nmax    ! Set to 0 if no valid value
  ! Local
  character(len=*), parameter :: rname='EXTREMA'
  integer(kind=size_length) :: i,j
  !
  j = nxy+1
  nmin = 0
  nmax = 0
  if (eval.lt.0.0) then
    do i=1,nxy
      if (z(i).eq.z(i)) then
        zmin = z(i)
        zmax = z(i)
        nmin = i
        nmax = i
        j = i
        exit
      endif
    enddo
    !
    do i=j,nxy
      if (z(i).eq.z(i)) then
        if (z(i).lt.zmin) then
          zmin = z(i)
          nmin = i
        elseif (z(i).gt.zmax) then
          zmax = z(i)
          nmax = i
        endif
      endif
    enddo
  else
    do i=1,nxy
      if (z(i).eq.z(i)) then
        !
        !     Tests are not equivalent if BVAL is a NaN!
        !     IF (ABS(Z(I)-BVAL).GT.EVAL) THEN
        if (.not.(abs(z(i)-bval).le.eval)) then
          zmin = z(i)
          zmax = z(i)
          nmin = i
          nmax = i
          j = i
          exit
        endif
      endif
    enddo
    !
    do i=j,nxy
      if (z(i).eq.z(i)) then
        if (.not.(abs(z(i)-bval).le.eval)) then
          if (z(i).lt.zmin) then
            zmin = z(i)
            nmin = i
          elseif (z(i).gt.zmax) then
            zmax = z(i)
            nmax = i
          endif
        endif
      endif
    enddo
  endif
end subroutine gr4_extrema
!
subroutine gr8_extrema (nxy,z,bval,eval,zmin,zmax,nmin,nmax)
  use gildas_def
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is renamed elsewhere)
  ! Find extrema of a (Real*8) array
  ! +/- Infinity are considered as valid extrema is this routine
  ! See GR8_MINMAX for a version which ignores Infinity
  !!
  !     Ignore NaNs and all "blanked" values.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: nxy     ! Number of values
  real(kind=8),              intent(in)    :: z(nxy)  ! Values
  real(kind=4),              intent(in)    :: bval    ! Blanking
  real(kind=4),              intent(in)    :: eval    ! Tolerance
  real(kind=4),              intent(inout) :: zmin    ! Unchanged if no valid value
  real(kind=4),              intent(inout) :: zmax    ! Unchanged if no valid value
  integer(kind=size_length), intent(out)   :: nmin    ! Set to 0 if no valid value
  integer(kind=size_length), intent(out)   :: nmax    ! Set to 0 if no valid value
  ! Local
  character(len=*), parameter :: rname='EXTREMA'
  integer(kind=size_length) :: i,j
  !
  j = nxy+1
  nmin = 0
  nmax = 0
  if (eval.lt.0.0) then
    do i=1,nxy
      if (z(i).eq.z(i)) then
        zmin = z(i)
        zmax = z(i)
        nmin = i
        nmax = i
        j = i
        exit
      endif
    enddo
    !
    do i=j,nxy
      if (z(i).eq.z(i)) then
        if (z(i).lt.zmin) then
          zmin = z(i)
          nmin = i
        elseif (z(i).gt.zmax) then
          zmax = z(i)
          nmax = i
        endif
      endif
    enddo
  else
    do i=1,nxy
      if (z(i).eq.z(i)) then
        !
        !     Tests are not equivalent if BVAL is a NaN!
        !     IF (ABS(Z(I)-BVAL).GT.EVAL) THEN
        if (.not.(abs(z(i)-bval).le.eval)) then
          zmin = z(i)
          zmax = z(i)
          nmin = i
          nmax = i
          j = i
          exit
        endif
      endif
    enddo
    !
    do i=j,nxy
      if (z(i).eq.z(i)) then
        if (.not.(abs(z(i)-bval).le.eval)) then
          if (z(i).lt.zmin) then
            zmin = z(i)
            nmin = i
          elseif (z(i).gt.zmax) then
            zmax = z(i)
            nmax = i
          endif
        endif
      endif
    enddo
  endif
end subroutine gr8_extrema
!
subroutine gr4_minmax(nxy,z,bval,eval,zmin,zmax,nmin,nmax)
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr4_minmax
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is used elsewhere)
  ! Protect against NaN and INFs even without blanking
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: nxy     !
  real(kind=4),              intent(in)  :: z(nxy)  !
  real(kind=4),              intent(in)  :: bval    !
  real(kind=4),              intent(in)  :: eval    !
  real(kind=4),              intent(out) :: zmin    !
  real(kind=4),              intent(out) :: zmax    !
  integer(kind=size_length), intent(out) :: nmin    !
  integer(kind=size_length), intent(out) :: nmax    !
  ! Local
  character(len=*), parameter :: rname='MINMAX'
  integer(kind=size_length) :: i,j,cnt_nan,cnt_inf,cnt_bla
  character(len=message_length) :: chain
  !
  ! Default values if no valid data found
  zmin = bval
  zmax = bval
  nmin = 1
  nmax = 1
  !
  cnt_nan=0
  cnt_inf=0
  cnt_bla=0
  do j=1,nxy
    if (sic_fini4(z(j)))then
      if(abs(z(j)-bval).gt.eval) then
        zmin = z(j)
        zmax = z(j)
        nmin = j
        nmax = j
        goto 10
      else
        cnt_bla=cnt_bla+1
      endif
    elseif (.not.(z(j).eq.z(j))) then
      cnt_nan=cnt_nan+1
    else
      cnt_inf=cnt_inf+1
    endif
  enddo
  !
10 continue
  do i=j+1,nxy
    if (sic_fini4(z(i)))then
      if (abs(z(i)-bval).gt.eval) then
        if (z(i).lt.zmin) then
          zmin = z(i)
          nmin = i
        elseif (z(i).gt.zmax) then
          zmax = z(i)
          nmax = i
        endif
      else
        cnt_bla=cnt_bla+1
      endif
    elseif (.not.(z(i).eq.z(i))) then
      cnt_nan=cnt_nan+1
    else
      cnt_inf=cnt_inf+1
    endif
  enddo
  if (cnt_nan+cnt_inf+cnt_bla.eq.0) return
  !
  ! Debug (these messages are too much verbose for a daily use)
  if (cnt_nan.gt.0) then
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_nan,' NaNs'
    call gmath_message(seve%d,rname,chain)
  endif
  if (cnt_inf.gt.0) then
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_inf,' INFs'
    call gmath_message(seve%d,rname,chain)
  endif
  if (cnt_bla.gt.0) then
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_bla,' blanks'
    call gmath_message(seve%d,rname,chain)
  endif
  !
  if (cnt_nan+cnt_inf+cnt_bla.eq.nxy)  &
    call gmath_message(seve%w,rname,'No valid data found')
  !
end subroutine gr4_minmax
!
subroutine gr8_minmax(nxy,z,bval,eval,zmin,zmax,nmin,nmax)
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr8_minmax
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is used elsewhere)
  ! Protect against NaN and INFs even without blanking
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: nxy     !
  real(kind=8),              intent(in)  :: z(nxy)  !
  real(kind=8),              intent(in)  :: bval    !
  real(kind=8),              intent(in)  :: eval    !
  real(kind=8),              intent(out) :: zmin    !
  real(kind=8),              intent(out) :: zmax    !
  integer(kind=size_length), intent(out) :: nmin    !
  integer(kind=size_length), intent(out) :: nmax    !
  ! Local
  character(len=*), parameter :: rname='MINMAX'
  integer(kind=size_length) :: i,j,cnt_nan,cnt_inf,cnt_bla
  character(len=message_length) :: chain
  !
  ! Default values if no valid data found
  zmin = bval
  zmax = bval
  nmin = 1
  nmax = 1
  !
  cnt_nan=0
  cnt_inf=0
  cnt_bla=0
  do j=1,nxy
    if (sic_fini8(z(j)))then
      if (abs(z(j)-bval).gt.eval) then
        zmin = z(j)
        zmax = z(j)
        nmin = j
        nmax = j
        goto 10
      else
        cnt_bla=cnt_bla+1
      endif
    elseif (.not.(z(j).eq.z(j))) then
      cnt_nan=cnt_nan+1
    else
      cnt_inf=cnt_inf+1
    endif
  enddo
  !
10 continue
  do i=j,nxy
    if (sic_fini8(z(i)))then
      if (abs(z(i)-bval).gt.eval) then
        if (z(i).lt.zmin) then
          zmin = z(i)
          nmin = i
        elseif (z(i).gt.zmax) then
          zmax = z(i)
          nmax = i
        endif
      else
        cnt_bla=cnt_bla+1
      endif
    elseif (.not.(z(i).eq.z(i))) then
      cnt_nan=cnt_nan+1
    else
      cnt_inf=cnt_inf+1
    endif
  enddo
  if (cnt_nan+cnt_inf+cnt_bla.eq.0) return
  !
  ! Debug (these messages are too much verbose for a daily use)
  if (cnt_nan.gt.0) then
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_nan,' NaNs'
    call gmath_message(seve%d,rname,chain)
  endif
  if (cnt_inf.gt.0) then
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_inf,' INFs'
    call gmath_message(seve%d,rname,chain)
  endif
  if (cnt_bla.gt.0) then
    ! Debug (message is too much verbose for a daily use)
    write(chain,'(A,1X,I0,A)')  'Array has ',cnt_bla,' blanks'
    call gmath_message(seve%d,rname,chain)
  endif
  !
  if (cnt_nan+cnt_inf+cnt_bla.eq.nxy)  &
    call gmath_message(seve%w,rname,'No valid data found')
  !
end subroutine gr8_minmax
!
subroutine gr4_load(in,out,n,bval,eval,m)
  use gildas_def
  !---------------------------------------------------------------------
  ! @ private
  ! Copy input array into output array, but remove blanked values if
  ! any. Return the number of elements kept accordingly. Real*4 version.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: n       ! Number of elements to copy
  real(kind=4),              intent(in)  :: in(n)   ! Input array
  real(kind=4),              intent(out) :: out(n)  ! Output array
  real(kind=4),              intent(in)  :: bval    ! Blanking value
  real(kind=4),              intent(in)  :: eval    ! Error value for blanking
  integer(kind=size_length), intent(out) :: m       ! Number elements kept
  ! Local
  integer(kind=size_length) :: i
  !
  if (eval.lt.0.) then
    m = 0
    do i=1,n
      if (in(i).ne.in(i)) cycle
      m = m+1
      out(m) = in(i)
    enddo
  else
    m = 0
    do i=1,n
      if (in(i).ne.in(i)) cycle
      if (abs(in(i)-bval).le.eval) cycle
      m = m+1
      out(m) = in(i)
    enddo
  endif
end subroutine gr4_load
!
subroutine gr8_load(in,out,n,bval,eval,m)
  use gildas_def
  !---------------------------------------------------------------------
  ! @ private
  ! Copy input array into output array, but remove blanked values if
  ! any. Return the number of elements kept accordingly. Real*8 version.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: n       !
  real(kind=8),              intent(in)  :: in(n)   !
  real(kind=8),              intent(out) :: out(n)  !
  real(kind=8),              intent(in)  :: bval    !
  real(kind=8),              intent(in)  :: eval    !
  integer(kind=size_length), intent(out) :: m       !
  ! Local
  integer(kind=size_length) :: i
  !
  if (eval.lt.0.d0) then
    m = 0
    do i=1,n
      if (in(i).ne.in(i)) cycle
      m = m+1
      out(m) = in(i)
    enddo
  else
    m = 0
    do i=1,n
      if (in(i).ne.in(i)) cycle
      if (abs(in(i)-bval).le.eval) cycle
      m = m+1
      out(m) = in(i)
    enddo
  endif
end subroutine gr8_load
!
subroutine gr4_median(data,ndata,bval,eval,median,error)
  use gildas_def
  use gmath_interfaces, except_this=>gr4_median
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: ndata        ! Array size
  real(kind=4),              intent(in)    :: data(ndata)  ! Data
  real(kind=4),              intent(in)    :: bval         ! Blanking value
  real(kind=4),              intent(in)    :: eval         ! Tolerance
  real(kind=4),              intent(inout) :: median       ! Median of input array
  logical,                   intent(inout) :: error        ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='MEDIAN'
  integer(kind=4) :: ier
  integer(kind=size_length) :: nwork
  real(kind=4), allocatable :: work(:)
  integer(kind=4), allocatable :: indices(:)
  !
  if (ndata.le.0) then
    call gmath_message(seve%e,rname,'Null or negative array size')
    error = .true.
    return
  endif
  !
  allocate(work(ndata),stat=ier)
  if (ier.ne.0) then
    call gmath_message(seve%e,rname,'Could not allocate working arrays')
    error = .true.
    goto 10
  endif
  !
  ! Copy the data (in every case we want a copy of the input data since
  ! gr4_trie modifies its input data array, which is annoying in most of
  ! our usecases), and remove the blanked values by the way.
  call gr4_load(data,work,ndata,bval,eval,nwork)
  if (nwork.eq.0) then
    call gmath_message(seve%w,rname,'No valid data found')
    ! Let output median value unmodified on return
    goto 10
  endif
  !
  allocate(indices(nwork),stat=ier)
  if (ier.ne.0) then
    call gmath_message(seve%e,rname,'Could not allocate working arrays')
    error = .true.
    goto 10
  endif
  !
  call gr4_trie(work,indices,int(nwork,kind=4),error)
  if (error) goto 10
  !
  if (mod(nwork,2).eq.0) then
    median = (work(nwork/2)+work((nwork/2)+1))/2.0
  else
    median = work((nwork+1)/2)
  endif
  !
10 continue
  if (allocated(work)) deallocate(work)
  if (allocated(indices)) deallocate(indices)
  !
end subroutine gr4_median
!
subroutine gr8_median(data,ndata,bval,eval,median,error)
  use gildas_def
  use gmath_interfaces, except_this=>gr8_median
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is used elsewhere)
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: ndata        ! Array size
  real(kind=8),              intent(in)    :: data(ndata)  ! Data
  real(kind=8),              intent(in)    :: bval         ! Blanking value
  real(kind=8),              intent(in)    :: eval         ! Tolerance
  real(kind=8),              intent(inout) :: median       ! Median of input array
  logical,                   intent(inout) :: error        ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='MEDIAN'
  integer(kind=4) :: ier
  integer(kind=size_length) :: nwork
  real(kind=8), allocatable :: work(:)
  integer(kind=4), allocatable :: indices(:)
  !
  if (ndata.le.0) then
    call gmath_message(seve%e,rname,'Null or negative array size')
    error = .true.
    return
  endif
  !
  allocate(work(ndata),stat=ier)
  if (ier.ne.0) then
    call gmath_message(seve%e,rname,'Could not allocate working arrays')
    error = .true.
    goto 10
  endif
  !
  ! Copy the data (in every case we want a copy of the input data since
  ! gr8_trie modifies its input data array, which is annoying in most of
  ! our usecases), and remove the blanked values by the way.
  call gr8_load(data,work,ndata,bval,eval,nwork)
  if (nwork.eq.0) then
    call gmath_message(seve%w,rname,'No valid data found')
    ! Let output median value unmodified on return
    goto 10
  endif
  !
  allocate(indices(nwork),stat=ier)
  if (ier.ne.0) then
    call gmath_message(seve%e,rname,'Could not allocate working arrays')
    error = .true.
    goto 10
  endif
  !
  call gr8_trie(work,indices,int(nwork,kind=4),error)
  if (error) goto 10
  !
  if (mod(nwork,2).eq.0) then
    median = (work(nwork/2)+work((nwork/2)+1))/2.d0
  else
    median = work((nwork+1)/2)
  endif
  !
10 continue
  if (allocated(work)) deallocate(work)
  if (allocated(indices)) deallocate(indices)
  !
end subroutine gr8_median
!
subroutine gr4_dicho(np,x,xval,ival)
  use gildas_def
  !---------------------------------------------------------------------
  ! @ public-mandatory (because symbol is used elsewhere)
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: np     ! Number of input points
  real(kind=4),              intent(in)  :: x(np)  ! Input ordered Values
  real(kind=4),              intent(in)  :: xval   ! The value we search for
  integer(kind=size_length), intent(out) :: ival   ! Position in the array
  ! Local
  integer(kind=size_length) :: iinf,isup,imid
  !
  if (x(1).gt.xval) then
    ival = 0
    return
  elseif (x(1).eq.xval) then
    ival = 1
    return
  elseif (x(np).lt.xval) then
    ival = np
    return
  endif
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)
    imid = (isup + iinf)/2
    if (x(imid).lt.xval) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ival = isup
  !
end subroutine gr4_dicho
!
subroutine gr8_dicho(np,x,xval,ceil,ival,error)
  use gildas_def
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: np     ! Number of input points
  real(kind=8),              intent(in)    :: x(np)  ! Input ordered Values
  real(kind=8),              intent(in)    :: xval   ! The value we search for
  logical,                   intent(in)    :: ceil   ! Ceiling or floor mode?
  integer(kind=size_length), intent(out)   :: ival   ! Position in the array
  logical,                   intent(inout) :: error  ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='DICHO'
  integer(kind=size_length) :: iinf,isup,imid
  character(len=message_length) :: mess
  !
  if (x(1).gt.xval .or. x(np).lt.xval) then
    write(mess,'(A,F0.8,A,F0.8,A,F0.8,A)')  &
      'Input value (',xval,') out of array range (',x(1),' to ',x(np),')'
    call gmath_message(seve%e,rname,mess)
    error = .true.
    return
  endif
  !
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)  ! As long as we are considering more than 2 values in range
    imid = floor((isup + iinf)/2.)
    if (x(imid).lt.xval) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ! From now on the range has been reduced to the 2 consecutive values
  ! x(iinf) and x(isup) with isup = iinf+1
  !
  ! The specification inequalities (see subroutine header) say that exact
  ! match is returned if available:
  if (xval.eq.x(isup)) then
    ival = isup
  elseif (xval.eq.x(iinf)) then
    ival = iinf
    !
  ! If no exact match, the requested value is in between. Returned value
  ! depends on ceiling or floor mode
  elseif (ceil) then  ! Ceiling mode
    ival = isup
  else                ! Floor mode
    ival = iinf
  endif
  !
end subroutine gr8_dicho
!
subroutine gi4_dicho(np,x,xval,ceil,ival,error)
  use gildas_def
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: np     ! Number of input points
  integer(kind=4),           intent(in)    :: x(np)  ! Input ordered Values
  integer(kind=4),           intent(in)    :: xval   ! The value we search for
  logical,                   intent(in)    :: ceil   ! Ceiling or floor mode?
  integer(kind=size_length), intent(out)   :: ival   ! Position in the array
  logical,                   intent(inout) :: error  ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='DICHO'
  integer(kind=size_length) :: iinf,isup,imid
  character(len=message_length) :: mess
  !
  if (x(1).gt.xval .or. x(np).lt.xval) then
    write(mess,'(A,I0,A,I0,A,I0,A)')  &
      'Input value (',xval,') out of array range (',x(1),' to ',x(np),')'
    call gmath_message(seve%e,rname,mess)
    error = .true.
    return
  endif
  !
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)  ! As long as we are considering more than 2 values in range
    imid = floor((isup + iinf)/2.)
    if (x(imid).lt.xval) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ! From now on the range has been reduced to the 2 consecutive values
  ! x(iinf) and x(isup) with isup = iinf+1
  !
  ! The specification inequalities (see subroutine header) say that exact
  ! match is returned if available:
  if (xval.eq.x(isup)) then
    ival = isup
  elseif (xval.eq.x(iinf)) then
    ival = iinf
    !
    ! If no exact match, the requested value is in between. Returned value
    ! depends on ceiling or floor mode
  elseif (ceil) then  ! Ceiling mode
    ival = isup
  else                ! Floor mode
    ival = iinf
  endif
  !
end subroutine gi4_dicho
!
subroutine gi8_dicho(np,x,xval,ceil,ival,error)
  use gildas_def
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)    :: np     ! Number of input points
  integer(kind=8),           intent(in)    :: x(np)  ! Input ordered Values
  integer(kind=8),           intent(in)    :: xval   ! The value we search for
  logical,                   intent(in)    :: ceil   ! Ceiling or floor mode?
  integer(kind=size_length), intent(out)   :: ival   ! Position in the array
  logical,                   intent(inout) :: error  ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='DICHO'
  integer(kind=size_length) :: iinf,isup,imid
  character(len=message_length) :: mess
  !
  if (x(1).gt.xval .or. x(np).lt.xval) then
    write(mess,'(A,I0,A,I0,A,I0,A)')  &
      'Input value (',xval,') out of array range (',x(1),' to ',x(np),')'
    call gmath_message(seve%e,rname,mess)
    error = .true.
    return
  endif
  !
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)  ! As long as we are considering more than 2 values in range
    imid = floor((isup + iinf)/2.)
    if (x(imid).lt.xval) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ! From now on the range has been reduced to the 2 consecutive values
  ! x(iinf) and x(isup) with isup = iinf+1
  !
  ! The specification inequalities (see subroutine header) say that exact
  ! match is returned if available:
  if (xval.eq.x(isup)) then
    ival = isup
  elseif (xval.eq.x(iinf)) then
    ival = iinf
    !
  ! If no exact match, the requested value is in between. Returned value
  ! depends on ceiling or floor mode
  elseif (ceil) then  ! Ceiling mode
    ival = isup
  else                ! Floor mode
    ival = iinf
  endif
  !
end subroutine gi8_dicho
!
subroutine gi4_dicho_with_user_ltgt(np,ceil,ival,ult,ugt,error)
  use gildas_def
  use gmath_interfaces, except_this=>gi4_dicho_with_user_ltgt
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public-generic gi0_dicho_with_user_ltgt
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=4), intent(in)    :: np     ! Number of elements in array
  logical,         intent(in)    :: ceil   ! Ceiling mode?
  integer(kind=4), intent(out)   :: ival   ! Deduced position in the array
  logical,         external      :: ult    ! User lower than
  logical,         external      :: ugt    ! User greater than
  logical,         intent(inout) :: error  ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='DICHO'
  integer(kind=4) :: iinf,isup,imid
  !
  if (ugt(1) .or. ult(np)) then
    call gmath_message(seve%e,rname,'Input value is out of range')
    error = .true.
    return
  endif
  !
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)  ! As long as we are considering more than 2 values in range
    imid = floor((isup + iinf)/2.)
    if (ult(imid)) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ! From now on the range has been reduced to the 2 consecutive values
  ! x(iinf) and x(isup) with isup = iinf+1
  !
  if (ult(iinf).and.ugt(isup)) then
    ! We are in between (boundaries excluded), output depends on ceiling or
    ! floor mode
    if (ceil) then
      ival = isup
    else
      ival = iinf
    endif
    !
  else
    ! Not in between: we match one of the 2 boundaries
    if (ult(iinf)) then
      ival = isup
    else
      ival = iinf
    endif
  endif
  !
end subroutine gi4_dicho_with_user_ltgt
!
subroutine gi8_dicho_with_user_ltgt(np,ceil,ival,ult,ugt,error)
  use gildas_def
  use gmath_interfaces, except_this=>gi8_dicho_with_user_ltgt
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public-generic gi0_dicho_with_user_ltgt
  !  Find ival such as
  !    X(ival-1) < xval <= X(ival)     (ceiling mode)
  !  or
  !    X(ival) <= xval < X(ival+1)     (floor mode)
  ! for input data ordered. Use a dichotomic search for that.
  !---------------------------------------------------------------------
  integer(kind=8), intent(in)    :: np     ! Number of elements in array
  logical,         intent(in)    :: ceil   ! Ceiling mode?
  integer(kind=8), intent(out)   :: ival   ! Deduced position in the array
  logical,         external      :: ult    ! User lower than
  logical,         external      :: ugt    ! User greater than
  logical,         intent(inout) :: error  ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='DICHO'
  integer(kind=8) :: iinf,isup,imid
  !
  if (ugt(1_8) .or. ult(np)) then
    call gmath_message(seve%e,rname,'Input value is out of range')
    error = .true.
    return
  endif
  !
  iinf = 1
  isup = np
  !
  do while (isup.gt.iinf+1)  ! As long as we are considering more than 2 values in range
    imid = floor((isup + iinf)/2.)
    if (ult(imid)) then
      iinf = imid
    else
      isup = imid
    endif
  enddo
  ! From now on the range has been reduced to the 2 consecutive values
  ! x(iinf) and x(isup) with isup = iinf+1
  !
  if (ult(iinf).and.ugt(isup)) then
    ! We are in between (boundaries excluded), output depends on ceiling or
    ! floor mode
    if (ceil) then
      ival = isup
    else
      ival = iinf
    endif
    !
  else
    ! Not in between: we match one of the 2 boundaries
    if (ult(iinf)) then
      ival = isup
    else
      ival = iinf
    endif
  endif
  !
end subroutine gi8_dicho_with_user_ltgt
!
subroutine gr4_mean(x,nelem,bval,eval,out)
  !$ use omp_lib
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr4_mean
  !---------------------------------------------------------------------
  ! @ public
  !   Protected against NaNs and Using Blanking Values. If Blanking
  ! enabled, will return blanking value if no valid result. If Blanking
  ! is not enabled, will return NaN if no valid result.
  !   R*4 version.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: nelem     ! Number of data values
  real(kind=4),              intent(in)  :: x(nelem)  ! Data values to compute
  real(kind=4),              intent(in)  :: bval      ! Blanking value
  real(kind=4),              intent(in)  :: eval      ! Blanking tolerance
  real(kind=4),              intent(out) :: out       ! Output scalar value
  ! Local
  integer(kind=size_length) :: ielem,nvalid
  real(kind=4) :: nan,lastchanceretval
  ! We need an intermediate real*8 array to avoid precision loss
  ! in case of a very large number of values...
  real(kind=8) :: dout
  !
  if (eval.ge.0.) then
    lastchanceretval = bval
  else
    call gag_notanum(nan)
    lastchanceretval = nan
  endif
  out = lastchanceretval
  if (nelem.lt.1) return
  !
  ! Now we can compute
  dout = 0.
  nvalid = 0
  !$OMP PARALLEL IF(.not.OMP_IN_PARALLEL()) &
  !$OMP DEFAULT(NONE) &
  !$OMP SHARED(nelem,x,bval,eval,nvalid,dout) &
  !$OMP PRIVATE(ielem)
  !$OMP DO REDUCTION(+:dout,nvalid)
  do ielem=1,nelem
    if (x(ielem).eq.x(ielem)) then
      if (eval.lt.0.) then
        dout = dout + x(ielem)
        nvalid = nvalid+1
      else
        if (abs(x(ielem)-bval).gt.eval) then
          dout = dout + x(ielem)
          nvalid = nvalid+1
        endif
      endif
    endif
  enddo
  !$OMP END DO
  !$OMP END PARALLEL
  if (nvalid.gt.0) then
    out = dout/real(nvalid)
  else
    out = lastchanceretval
  endif
  !
end subroutine gr4_mean
!
subroutine gr8_mean(x,nelem,bval,eval,out)
  !$ use omp_lib
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr8_mean
  !---------------------------------------------------------------------
  ! @ public
  !   Protected against NaNs and Using Blanking Values. If Blanking
  ! enabled, will return blanking value if no valid result. If Blanking
  ! is not enabled, will return NaN if no valid result.
  !   R*8 version.
  !---------------------------------------------------------------------
  integer(kind=size_length), intent(in)  :: nelem     ! Number of data values
  real(kind=8),              intent(in)  :: x(nelem)  ! Data values to compute
  real(kind=8),              intent(in)  :: bval      ! Blanking value
  real(kind=8),              intent(in)  :: eval      ! Blanking tolerance
  real(kind=8),              intent(out) :: out       ! Output scalar value
  ! Local
  integer(kind=size_length) :: ielem,nvalid
  real(kind=8) :: nan,lastchanceretval
  !
  if (eval.ge.0.d0) then
    lastchanceretval = bval
  else
    call gag_notanum(nan)
    lastchanceretval = nan
  endif
  out = lastchanceretval
  if (nelem.lt.1) return
  !
  ! Now we can compute
  out = 0.d0
  nvalid = 0
  !$OMP PARALLEL IF(.not.OMP_IN_PARALLEL()) &
  !$OMP DEFAULT(NONE) &
  !$OMP SHARED(nelem,x,bval,eval,nvalid,out) &
  !$OMP PRIVATE(ielem)
  !$OMP DO REDUCTION(+:out,nvalid)
  do ielem=1,nelem
    if (x(ielem).eq.x(ielem)) then
      if (eval.lt.0.d0) then
        out = out + x(ielem)
        nvalid = nvalid+1
      else
        if (abs(x(ielem)-bval).gt.eval) then
          out = out + x(ielem)
          nvalid = nvalid+1
        endif
      endif
    endif
  enddo
  !$OMP END DO
  !$OMP END PARALLEL
  !
  if (nvalid.gt.0) then
    out = out/nvalid
  else
    out = lastchanceretval
  endif
  !
end subroutine gr8_mean
!
subroutine gr4_quantile(data,ndata,bval,eval,quant,nquant,error)
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr4_quantile
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  ! Compute the k-quantiles (i.e. the k-1 values which split the data in
  ! k subsets of equal or almost-equal sizes) of the input data array in
  ! quant(nquant).
  ! For convenience the 0-th (minimum value) and the k-th (maximum
  ! value) quantiles of the data are also returned. Which means:
  ! - the median (2-quantile) needs nquant=3
  ! - quartiles               need  nquant=5
  ! and so on.
  ! - nquant = 2 will return only the min-max values
  ! - nquant = 1 will return only the min value
  !---------------------------------------------------------------------
  integer(kind=size_length),  intent(in)    :: ndata          ! Array size
  real(kind=4),               intent(in)    :: data(ndata)    ! Data
  real(kind=4),               intent(in)    :: bval           ! Blanking value
  real(kind=4),               intent(in)    :: eval           ! Tolerance
  integer(kind=index_length), intent(in)    :: nquant         ! Number of quantiles
  real(kind=4),               intent(inout) :: quant(nquant)  ! Quantiles of input array
  logical,                    intent(inout) :: error          ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='QUANTILE'
  integer(kind=4) :: ier,iquant
  integer(kind=size_length) :: nwork,fwork,cwork
  real(kind=4), allocatable :: work(:)
  integer(kind=4), allocatable :: indices(:)
  real(kind=8) :: rwork
  !
  ! Sanity check
  if (nquant.le.0) then
    call gmath_message(seve%e,rname,'Null or negative quantile count')
    error = .true.
    return
  endif
  if (ndata.le.0) then
    call gmath_message(seve%e,rname,'Null or negative array size')
    error = .true.
    return
  endif
  !
  allocate(work(ndata),stat=ier)
  if (failed_allocate(rname,'work',ier,error))  return
  !
  ! Copy the data (in every case we want a copy of the input data since
  ! gr4_trie modifies its input data array, which is annoying in most of
  ! our usecases), and remove the blanked values by the way.
  call gr4_load(data,work,ndata,bval,eval,nwork)
  if (nwork.eq.0) then
    call gmath_message(seve%w,rname,'No valid data found')
    ! Let output quantile values unmodified on return
    return
  endif
  !
  allocate(indices(nwork),stat=ier)
  if (failed_allocate(rname,'indices',ier,error))  return
  !
  call gr4_trie(work,indices,int(nwork,kind=4),error)
  if (error)  return
  !
  ! Fill the quantile. Be careful for the stupid cases where nquant>=ndata.
  ! Also works for boundaries:
  !   quant(1) = work(1)
  !   quant(nquant) = work(nwork)
  if (nquant.eq.1) then
    quant(1) = work(1)
  else
    do iquant=1,nquant
      rwork = 1+(nwork-1)*real(iquant-1,kind=8)/real(nquant-1,kind=8)
      fwork = floor(rwork,kind=size_length)
      cwork = ceiling(rwork,kind=size_length)
      if (fwork.eq.cwork) then
        quant(iquant) = work(fwork)
      else
        quant(iquant) = work(fwork)*(cwork-rwork) + &
                        work(cwork)*(rwork-fwork)
      endif
    enddo
  endif
end subroutine gr4_quantile
!
subroutine gr8_quantile(data,ndata,bval,eval,quant,nquant,error)
  use gildas_def
  use gmath_dependencies_interfaces
  use gmath_interfaces, except_this=>gr8_quantile
  use gmath_messaging
  !---------------------------------------------------------------------
  ! @ public
  ! Compute the k-quantiles (i.e. the k-1 values which split the data in
  ! k subsets of equal or almost-equal sizes) of the input data array in
  ! quant(nquant).
  ! For convenience the 0-th (minimum value) and the k-th (maximum
  ! value) quantiles of the data are also returned. Which means:
  ! - the median (2-quantile) needs nquant=3
  ! - quartiles               need  nquant=5
  ! and so on.
  ! - nquant = 2 will return only the min-max values
  ! - nquant = 1 will return only the min value
  !---------------------------------------------------------------------
  integer(kind=size_length),  intent(in)    :: ndata          ! Array size
  real(kind=8),               intent(in)    :: data(ndata)    ! Data
  real(kind=8),               intent(in)    :: bval           ! Blanking value
  real(kind=8),               intent(in)    :: eval           ! Tolerance
  integer(kind=index_length), intent(in)    :: nquant         ! Number of quantiles
  real(kind=8),               intent(inout) :: quant(nquant)  ! Quantiles of input array
  logical,                    intent(inout) :: error          ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='QUANTILE'
  integer(kind=4) :: ier,iquant
  integer(kind=size_length) :: nwork,fwork,cwork
  real(kind=8), allocatable :: work(:)
  integer(kind=4), allocatable :: indices(:)
  real(kind=8) :: rwork
  !
  ! Sanity check
  if (nquant.le.0) then
    call gmath_message(seve%e,rname,'Null or negative quantile count')
    error = .true.
    return
  endif
  if (ndata.le.0) then
    call gmath_message(seve%e,rname,'Null or negative array size')
    error = .true.
    return
  endif
  !
  allocate(work(ndata),stat=ier)
  if (failed_allocate(rname,'work',ier,error))  return
  !
  ! Copy the data (in every case we want a copy of the input data since
  ! gr8_trie modifies its input data array, which is annoying in most of
  ! our usecases), and remove the blanked values by the way.
  call gr8_load(data,work,ndata,bval,eval,nwork)
  if (nwork.eq.0) then
    call gmath_message(seve%w,rname,'No valid data found')
    ! Let output quantile values unmodified on return
    return
  endif
  !
  allocate(indices(nwork),stat=ier)
  if (failed_allocate(rname,'indices',ier,error))  return
  !
  call gr8_trie(work,indices,int(nwork,kind=4),error)
  if (error)  return
  !
  ! Fill the quantile. Be careful for the stupid cases where nquant>=ndata.
  ! Also works for boundaries:
  !   quant(1) = work(1)
  !   quant(nquant) = work(nwork)
  if (nquant.eq.1) then
    quant(1) = work(1)
  else
    do iquant=1,nquant
      rwork = 1+(nwork-1)*real(iquant-1,kind=8)/real(nquant-1,kind=8)
      fwork = floor(rwork,kind=size_length)
      cwork = ceiling(rwork,kind=size_length)
      if (fwork.eq.cwork) then
        quant(iquant) = work(fwork)
      else
        quant(iquant) = work(fwork)*(cwork-rwork) + &
                        work(cwork)*(rwork-fwork)
      endif
    enddo
  endif
end subroutine gr8_quantile
