module QUADRUPLE_PRECISION
!
! Quadruple precision package for the F90 subset - F
! N.B. This has only been tested using the Windows 95 version of F.
!      On other computers you will probably need to change the value of
!      CONSTANT below.
!
! N.B. This is quadruple precision implemented in SOFTWARE, hence it is SLOW.

! N.B. Overlaying of some operators and intrinsic functions is supported.
!      For instance if I is an integer, and Q is quad.-precision, then
!      Q * I is allowed in a program, but at the moment Q / I is not.
!      If I, R, D, Q represent respectively integer, real, double precision
!      and quadruple, then there are 7 combinations for multiplication:
!      Q*I, Q*R, Q*D, Q*Q, I*Q, R*Q, D*Q.   When you consider the number of
!      operators and intrinsic functions, this requires a very large number
!      of functions to support them all.   Check with the interfaces below
!      to see if the combination you want is supported.   If it is not, then
!      you can:
!      1. Write your own function to do it and add it as a MODULE PROCEDURE,
!      2. Reverse the order, e.g. Q+I instead of I+Q, or
!      3. Use Q / quad( real(I,DP), 0.0_dp ) instead of Q/I, i.e. convert the
!         integer to quad.-precision.

! The basic algorithms come from:
! Linnainmma, S.  Software for doubled-precision floating-point computations,
!    ACM Trans, Math. Software, vol. 7, pp.272-283, 1981.

! Programmer : Alan Miller
! e-mail: amiller@bigpond.net.au   URL:  www.ozemail.com.au/~milleraj

! Latest revision - 18 September 2002

! 4 Aug 97.  Added new algorithm for exponential.  Takes half the time of the
!            previous Taylor series, but errors 2.5 times larger.   The old
!            exponential is included here under the name exp_taylor for anyone
!            who needs the extra accuracy.  To use it instead of the new
!            algorithm, change the module procedure name in INTERFACE exp from
!            longexp to exp_taylor.
! 5 Aug 97.  Found way to reduce cancellation errors in yesterday's algorithm
!            for the exponential.   Removed exp_taylor.
! 18 Aug 97. Added table of quadruple-precision constants.
! 8 Sept 97. Added str_quad which reads a character string and converts it to
!            quadruple-precision form.
! 15 Oct 97. Added quad_str which takes a quadruple-precision value and outputs
!            a character string containing its decimal value to 30 significant
!            digits.
!            Added overlays to the ** operator for quadruple-precision values.
! 15 Jan 98. Added ATAN2.
! 19 Jan 98. Added <, <=, ==, >= and >.
!  2 Feb 98. Converted to F-compatibility
! 16 Feb 98. Added complex operations
! 5 Oct 99.  log10e corrected.
! 15 Oct 99. Corrected quad_pow_int.
! 18 Oct 99. Rewrote quad_pow_int to use the binary power method.
! 11 Nov 99. Added overlaying of assignments, e.g. quad = int, etc.
! 21 Jan 00. Added interface for EPSILON.
! 18 Sep 02. Added reference to Linnainmaa in comments at beginning.

implicit none

public :: LONGMUL, MULT_QUAD_INT, MULT_INT_QUAD, MULT_QUAD_DP, MULT_DP_QUAD,  &
          MULT_QUAD_REAL, MULT_REAL_QUAD, LONGDIV, DIV_QUAD_DP, DIV_QUAD_REAL, &
          DIV_QUAD_INT, DIV_INT_QUAD, DIV_REAL_QUAD,  DIV_DP_QUAD,  &
          LONGADD, QUAD_ADD_INT, QUAD_ADD_REAL, QUAD_ADD_DP,  &
          INT_ADD_QUAD, REAL_ADD_QUAD, DP_ADD_QUAD, LONGSUB, QUAD_SUB_INT,  &
          QUAD_SUB_REAL, QUAD_SUB_DP, INT_SUB_QUAD, REAL_SUB_QUAD,  &
          DP_SUB_QUAD, NEGATE_QUAD, QUAD_EQ_INT, QUAD_EQ_REAL, QUAD_EQ_DP,  &
          INT_EQ_QUAD, REAL_EQ_QUAD, DP_EQ_QUAD, QUAD_POW_INT, QUAD_POW_REAL, &
          QUAD_POW_DP, QUAD_POW_QUAD, QUAD_LT, QUAD_LE, QUAD_EQ, QUAD_GE,     &
          QUAD_GT, QSCALE, QABS, LONGSQRT, LONGLOG, LONGEXP, LONGSIN, LONGCOS, &
          LONGTAN, LONGASIN, LONGACOS, LONGATAN, QATAN2, EXACTMUL2, LONGMODR, &
          QUAD_SUM, QUAD_DOT_PRODUCT, Q_MATMUL12, Q_MATMUL21, Q_MATMUL22,  &
          STRING_QUAD, LONGCST, QUAD_STRING, Q_EPSILON
private :: QC_ADD, QC_SUB, NEGATE_QC, QC_MULT, QC_DIV, QC_ABS, QC_CMPLX, &
           QC_CONJG, QC_AIMAG, QC_SQRT, QC_EXP, QC_LOG

public :: scale, abs, sqrt, log, exp, sin, cos, tan, asin, acos, atan, atan2, &
          sum, dot_product, matmul, epsilon
public :: cmplx, conjg, aimag

public :: operator (*), operator (/), operator (+), operator (-),    &
          operator (**), operator (<), operator (<=), operator (==), &
          operator (>=), operator (>), assignment (=)

intrinsic scale, abs, sqrt, log, exp, sin, cos, tan, asin, acos, atan, atan2, &
          sum, dot_product, matmul, epsilon
intrinsic cmplx, conjg, aimag

integer, parameter, public    :: DP = selected_real_kind(12, 100)
integer, parameter, private   :: NBITS = digits(1.0_DP)
real (kind=DP), parameter, private :: CONSTANT = 2.0_DP**(NBITS+11-NBITS/2) + 1.0_DP
                        !                                       ^^
                        ! N.B. IEEE format for double precision uses 53 bits
                        ! for the mantissa.   PC floating-point processors
                        ! use an extra 11 bits - hence that 11.
! For other computers, try:
! REAL (kind=DP), parameter, private :: CONSTANT = 2._DP**(NBITS-NBITS/2) + 1._dp


real (kind=DP), parameter, private :: ZERO = 0.0_DP, ONE = 1.0_DP

type, public :: QUAD
  real (kind=DP) :: HI, LO
end type QUAD

type, public  :: QC
  type (QUAD) :: QR, QI                ! quad_real, quad_imaginary
end type QC

type, public :: FUNC_RESULT           ! Structure for the output from LONGMODR
  type (QUAD)  :: remainder
  integer      :: nmult
end type FUNC_RESULT

interface operator (*)
  module procedure LONGMUL
  module procedure MULT_QUAD_INT
  module procedure MULT_INT_QUAD
  module procedure MULT_QUAD_DP
  module procedure MULT_DP_QUAD
  module procedure MULT_QUAD_REAL
  module procedure MULT_REAL_QUAD
  module procedure QC_MULT
end interface

interface operator (/)
  module procedure LONGDIV
  module procedure DIV_QUAD_DP
  module procedure DIV_QUAD_REAL
  module procedure DIV_QUAD_INT
  module procedure DIV_INT_QUAD
  module procedure DIV_REAL_QUAD
  module procedure DIV_DP_QUAD
  module procedure QC_DIV
end interface

interface operator (+)
  module procedure LONGADD
  module procedure QUAD_ADD_INT
  module procedure QUAD_ADD_REAL
  module procedure QUAD_ADD_DP
  module procedure INT_ADD_QUAD
  module procedure REAL_ADD_QUAD
  module procedure DP_ADD_QUAD
  module procedure QC_ADD
end interface

interface operator (-)
  module procedure LONGSUB
  module procedure QUAD_SUB_INT
  module procedure QUAD_SUB_REAL
  module procedure QUAD_SUB_DP
  module procedure INT_SUB_QUAD
  module procedure REAL_SUB_QUAD
  module procedure DP_SUB_QUAD
  module procedure NEGATE_QUAD
  module procedure QC_SUB
  module procedure NEGATE_QC
end interface

interface assignment (=)
  module procedure QUAD_EQ_INT
  module procedure QUAD_EQ_REAL
  module procedure QUAD_EQ_DP
  module procedure INT_EQ_QUAD
  module procedure REAL_EQ_QUAD
  module procedure DP_EQ_QUAD
end interface

interface operator (**)
  module procedure QUAD_POW_INT
  module procedure QUAD_POW_REAL
  module procedure QUAD_POW_DP
  module procedure QUAD_POW_QUAD
end interface

interface operator (<)
  module procedure QUAD_LT
end interface

interface operator (<=)
  module procedure QUAD_LE
end interface

interface operator (==)
  module procedure QUAD_EQ
end interface

interface operator (>=)
  module procedure QUAD_GE
end interface

interface operator (>)
  module procedure QUAD_GT
end interface

interface scale
  module procedure QSCALE
end interface

interface abs
  module procedure QABS
  module procedure QC_ABS
end interface

interface cmplx
  module procedure QC_CMPLX
end interface

interface conjg
  module procedure QC_CONJG
end interface

interface aimag
  module procedure QC_AIMAG
end interface

interface sqrt
  module procedure LONGSQRT
  module procedure QC_SQRT
end interface

interface log
  module procedure LONGLOG
  module procedure QC_LOG
end interface

interface exp
  module procedure LONGEXP
  module procedure QC_EXP
end interface

interface sin
  module procedure LONGSIN
end interface

interface cos
  module procedure LONGCOS
end interface

interface tan
  module procedure LONGTAN
end interface

interface asin
  module procedure LONGASIN
end interface

interface acos
  module procedure LONGACOS
end interface

interface atan
  module procedure LONGATAN
end interface

interface atan2
  module procedure QATAN2
end interface

interface sum
  module procedure QUAD_SUM
end interface

interface dot_product
  module procedure QUAD_DOT_PRODUCT
end interface

interface matmul
  module procedure Q_MATMUL12
  module procedure Q_MATMUL21
  module procedure Q_MATMUL22
end interface

interface epsilon
  module procedure Q_EPSILON
end interface


type (QUAD), parameter, public ::  &
          PI = QUAD( 0.3141592653589793e+01_DP,  0.1224646799147353e-15_DP ), &
       PIBY2 = QUAD( 0.1570796326794897e+01_DP, -0.3828568698926950e-15_DP ), &
       PIBY3 = QUAD( 0.1047197551196598e+01_DP, -0.3292527815701405e-15_DP ), &
       PIBY4 = QUAD( 0.7853981633974484e+00_DP, -0.8040613248383182e-16_DP ), &
       PIBY6 = QUAD( 0.5235987755982990e+00_DP, -0.1646263907850702e-15_DP ), &
       TWOPI = QUAD( 0.6283185307179586e+01_DP,  0.2449293598294707e-15_DP ), &
       LN_PI = QUAD( 0.1144729885849400e+01_DP,  0.2323105560877391e-15_DP ), &
      SQRTPI = QUAD( 0.1772453850905516e+01_DP, -0.7666586499825800e-16_DP ), &
    FACT_PT5 = QUAD( 0.8862269254527582e+00_DP, -0.1493552349616447e-15_DP ), &
     SQRT2PI = QUAD( 0.2506628274631001e+01_DP, -0.6273750096546544e-15_DP ), &
   LNSQRT2PI = QUAD( 0.9189385332046728e+00_DP, -0.3878294158067242e-16_DP ), &
   ONE_ON2PI = QUAD( 0.1591549430918953e+00_DP,  0.4567181289366658e-16_DP ), &
 TWO_ON_RTPI = QUAD( 0.1128379167095513e+01_DP, -0.4287537502368968e-15_DP ), &
     DEG2RAD = QUAD( 0.1745329251994330e-01_DP, -0.3174581724866598e-17_DP ), &
     RAD2DEG = QUAD( 0.5729577951308232e+02_DP, -0.1987849567057628e-14_DP ), &
         LN2 = QUAD( 0.6931471805599454e+00_DP, -0.8783183432405266e-16_DP ), &
        LN10 = QUAD( 0.2302585092994046e+01_DP, -0.2170756223382249e-15_DP ), &
       LOG2E = QUAD( 0.1442695040888964e+01_DP, -0.6457785410341630e-15_DP ), &
      LOG10E = QUAD( 0.4342944819032519e+00_DP, -0.5552037773430574e-16_DP ), &
     LOG2_10 = QUAD( 0.3321928094887362e+01_DP,  0.1661617516973592e-15_DP ), &
     LOG10_2 = QUAD( 0.3010299956639812e+00_DP, -0.2803728127785171e-17_DP ), &
       EULER = QUAD( 0.5772156649015330e+00_DP, -0.1159652176149463e-15_DP ), &
           E = QUAD( 0.2718281828459045e+01_DP,  0.1445646891729250e-15_DP ), &
       SQRT2 = QUAD( 0.1414213562373095e+01_DP,  0.1253716717905022e-15_DP ), &
       SQRT3 = QUAD( 0.1732050807568877e+01_DP,  0.3223954471431004e-15_DP ), &
      SQRT10 = QUAD( 0.3162277660168380e+01_DP, -0.6348773795572286e-15_DP )

contains


function EXACTMUL2(A, C) result(AC)
!  Procedure exactmul2, translated from Pascal, from:
!  Linnainmaa, Seppo (1981).   Software for doubled-precision floating-point
!  computations.   ACM Trans. on Math. Software (TOMS), 7, 272-283.

real (kind=DP), intent(in) :: A, C
type (QUAD)                :: AC

!     Local variables
real (kind=DP) :: A1, A2, C1, C2, T

T = CONSTANT * A
A1 = (A - T) + T             ! Lahey's optimization removes the brackets
                             ! and sets a1 = a which defeats the whole point.
A2 = A - A1
T = CONSTANT * C
C1 = (C - T) + T
C2 = C - C1
AC%HI = A * C
AC%LO = (((A1 * C1 - AC%HI) + A1 * C2) + C1 * A2) + C2 * A2

return
end function EXACTMUL2



function LONGMUL(A, C) result(AC)
!  Procedure longmul, translated from Pascal, from:
!  Linnainmaa, Seppo (1981).   Software for doubled-precision floating-point
!  computations.   ACM Trans. on Math. Software (TOMS), 7, 272-283.

type (QUAD), intent(in) :: A, C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: ZZ
type (QUAD)    :: Z

Z = EXACTMUL2(A%HI, C%HI)
ZZ = ((A%HI + A%LO) * C%LO + A%LO * C%HI) + Z%LO
AC%HI = Z%HI + ZZ
AC%LO = (Z%HI - AC%HI) + ZZ

return
end function LONGMUL



function MULT_QUAD_INT(A, I) result(C)
!     Multiply quadruple-precision number (a) by an integer (i).

type (QUAD), intent(in) :: A
integer, intent(in)     :: I
type (QUAD)             :: C

if (I == 0) then
  C = QUAD(ZERO, ZERO)
else if (I == 1) then
  C = A
else if (I == -1) then
  C = -A
else
  C = EXACTMUL2(A%HI, real(I,DP)) + EXACTMUL2(A%LO, real(I,DP))
end if

return
end function MULT_QUAD_INT



function MULT_INT_QUAD(I, A) result(C)
!     Multiply quadruple-precision number (a) by an integer (i).

integer, intent(in)     :: I
type (QUAD), intent(in) :: A
type (QUAD)             :: C

if (I == 0) then
  C = QUAD(ZERO, ZERO)
else if (I == 1) then
  C = A
else if (I == -1) then
  C = -A
else
  C = EXACTMUL2(A%HI, real(I,DP)) + EXACTMUL2(A%LO, real(I,DP))
end if

return
end function MULT_INT_QUAD



function MULT_QUAD_DP(A, B) result(C)
!  Multiply a quadruple-precision number (a) by a double-precision number (b).

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: B
type (QUAD)                :: C

!     Local variables
type (QUAD)             :: Z
real (kind=DP)          :: ZZ

Z = EXACTMUL2(A%HI, B)
ZZ = A%LO * B + Z%LO
C%HI = Z%HI + ZZ
C%LO = (Z%HI - C%HI) + ZZ

return
end function MULT_QUAD_DP



function MULT_QUAD_REAL(A, B) result(C)
!  Multiply a quadruple-precision number (a) by a real number (b).

type (QUAD), intent(in) :: A
real, intent(in)        :: B
type (QUAD)             :: C

!     Local variables
type (QUAD)             :: Z
real (kind=DP)          :: ZZ

Z = EXACTMUL2(A%HI, real(B,DP))
ZZ = A%LO * B + Z%LO
C%HI = Z%HI + ZZ
C%LO = (Z%HI - C%HI) + ZZ

return
end function MULT_QUAD_REAL



function MULT_DP_QUAD(B, A) result(C)
!  Multiply a quadruple-precision number (a) by a double-precision number (b).

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: B
type (QUAD)                :: C

!     Local variables
type (QUAD)             :: Z
real (kind=DP)          :: ZZ

Z = EXACTMUL2(A%HI, B)
ZZ = A%LO * B + Z%LO
C%HI = Z%HI + ZZ
C%LO = (Z%HI - C%HI) + ZZ

return
end function MULT_DP_QUAD



function MULT_REAL_QUAD(A, B) result(C)
!  Multiply a quadruple-precision number (a) by a double-precision number (b).

real, intent(in)        :: A
type (QUAD), intent(in) :: B
type (QUAD)             :: C

!     Local variables
type (QUAD)             :: Z
real (kind=DP)          :: ZZ

Z = EXACTMUL2(B%HI, real(A,DP))
ZZ = B%LO * A + Z%LO
C%HI = Z%HI + ZZ
C%LO = (Z%HI - C%HI) + ZZ

return
end function MULT_REAL_QUAD



function LONGDIV(A, C) result(AC)
!  Procedure longdiv, translated from Pascal, from:
!  Linnainmaa, Seppo (1981).   Software for doubled-precision floating-point
!  computations.   ACM Trans. on Math. Software (TOMS), 7, 272-283.

type (QUAD), intent(in) :: A, C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = A%HI / C%HI
Q = EXACTMUL2(C%HI, Z)
ZZ = ((((A%HI - Q%HI) - Q%LO) + A%LO) - Z*C%LO) / (C%HI + C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function LONGDIV



function DIV_QUAD_DP(A, B) result(C)
!     Divide a quadruple-precision number (a) by a double-precision number (b)

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: B
type (QUAD)                :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = A%HI / B
Q = EXACTMUL2(B, Z)
ZZ = (((A%HI - Q%HI) - Q%LO) + A%LO) / B
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_QUAD_DP



function DIV_QUAD_REAL(A, B) result(C)
!     Divide a quadruple-precision number (a) by a real number (b)

type (QUAD), intent(in)  :: A
real, intent(in)         :: B
type (QUAD)              :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = A%HI / B
Q = EXACTMUL2(real(B,kind=DP), Z)
ZZ = (((A%HI - Q%HI) - Q%LO) + A%LO) / B
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_QUAD_REAL



function DIV_QUAD_INT(A, B) result(C)
!     Divide a quadruple-precision number (a) by an integer number (b)

type (QUAD), intent(in)  :: A
integer, intent(in)      :: B
type (QUAD)              :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = A%HI / B
Q = EXACTMUL2(real(B,kind=DP), Z)
ZZ = (((A%HI - Q%HI) - Q%LO) + A%LO) / B
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_QUAD_INT



function DIV_INT_QUAD(A, B) result(C)
!     Divide a quadruple-precision number (a) by an integer number (b)

integer, intent(in)      :: A
type (QUAD), intent(in)  :: B
type (QUAD)              :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = real(A,kind=DP) / B%HI
Q = EXACTMUL2(B%HI, Z)
ZZ = (((A - Q%HI) - Q%LO) - Z*B%LO) / (B%HI + B%LO)
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_INT_QUAD



function DIV_DP_QUAD(A, B) result(C)
!     Divide a quadruple-precision number (a) by an integer number (b)

real (kind=DP), intent(in)  :: A
type (QUAD), intent(in)     :: B
type (QUAD)                 :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = A / B%HI
Q = EXACTMUL2(B%HI, Z)
ZZ = (((A - Q%HI) - Q%LO) - Z*B%LO) / (B%HI + B%LO)
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_DP_QUAD



function DIV_REAL_QUAD(A, B) result(C)
!     Divide a quadruple-precision number (a) by an integer number (b)

real, intent(in)         :: A
type (QUAD), intent(in)  :: B
type (QUAD)              :: C

!     Local variables
real (kind=DP) :: Z, ZZ
type (QUAD)    :: Q

Z = real(A,kind=DP) / B%HI
Q = EXACTMUL2(B%HI, Z)
ZZ = (((A - Q%HI) - Q%LO) - Z*B%LO) / (B%HI + B%LO)
C%HI = Z + ZZ
C%LO = (Z - C%HI) + ZZ

return
end function DIV_REAL_QUAD



function LONGADD(A, C) result(AC)
!  Procedure longadd, translated from Pascal, from:
!  Linnainmaa, Seppo (1981).   Software for doubled-precision floating-point
!  computations.   ACM Trans. on Math. Software (TOMS), 7, 272-283.

type (QUAD), intent(in) :: A, C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI + C%HI
Q = A%HI - Z
ZZ = (((Q + C%HI) + (A%HI - (Q + Z))) + A%LO) + C%LO
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function LONGADD



function QUAD_ADD_INT(A, C) result(AC)

type (QUAD), intent(in) :: A
integer, intent(in)     :: C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI + C
Q = A%HI - Z
ZZ = (((Q + C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_ADD_INT



function QUAD_ADD_REAL(A, C) result(AC)

type (QUAD), intent(in) :: A
real, intent(in)        :: C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI + C
Q = A%HI - Z
ZZ = (((Q + C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_ADD_REAL



function QUAD_ADD_DP(A, C) result(AC)

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: C
type (QUAD)                :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI + C
Q = A%HI - Z
ZZ = (((Q + C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_ADD_DP



function INT_ADD_QUAD(A, C) result(AC)

integer, intent(in)      :: A
type (QUAD), intent(in)  :: C
type (QUAD)              :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = C%HI + A
Q = C%HI - Z
ZZ = (((Q + A) + (C%HI - (Q + Z))) + C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function INT_ADD_QUAD



function REAL_ADD_QUAD(A, C) result(AC)

real, intent(in)         :: A
type (QUAD), intent(in)  :: C
type (QUAD)              :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = C%HI + A
Q = C%HI - Z
ZZ = (((Q + A) + (C%HI - (Q + Z))) + C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function REAL_ADD_QUAD



function DP_ADD_QUAD(A, C) result(AC)

real (kind=DP), intent(in)  :: A
type (QUAD), intent(in)     :: C
type (QUAD)                 :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = C%HI + A
Q = C%HI - Z
ZZ = (((Q + A) + (C%HI - (Q + Z))) + C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function DP_ADD_QUAD



function LONGSUB(A, C) result(AC)
!  Adapted from longadd by changing signs of c.
!  Linnainmaa, Seppo (1981).   Software for doubled-precision floating-point
!  computations.   ACM Trans. on Math. Software (TOMS), 7, 272-283.

type (QUAD), intent(in) :: A, C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI - C%HI
Q = A%HI - Z
ZZ = (((Q - C%HI) + (A%HI - (Q + Z))) + A%LO) - C%LO
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function LONGSUB



function QUAD_SUB_INT(A, C) result(AC)

type (QUAD), intent(in) :: A
integer, intent(in)     :: C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI - C
Q = A%HI - Z
ZZ = (((Q - C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_SUB_INT



function QUAD_SUB_REAL(A, C) result(AC)

type (QUAD), intent(in) :: A
real, intent(in)        :: C
type (QUAD)             :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI - C
Q = A%HI - Z
ZZ = (((Q - C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_SUB_REAL



function QUAD_SUB_DP(A, C) result(AC)

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: C
type (QUAD)                :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A%HI - C
Q = A%HI - Z
ZZ = (((Q - C) + (A%HI - (Q + Z))) + A%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function QUAD_SUB_DP



function INT_SUB_QUAD(A, C) result(AC)

integer, intent(in)      :: A
type (QUAD), intent(in)  :: C
type (QUAD)              :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = real(A,kind=DP) - C%HI
Q = real(A,kind=DP) - Z
ZZ = (((Q - C%HI) + (real(A,kind=DP) - (Q + Z))) - C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function INT_SUB_QUAD



function REAL_SUB_QUAD(A, C) result(AC)

real, intent(in)         :: A
type (QUAD), intent(in)  :: C
type (QUAD)              :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A - C%HI
Q = A - Z
ZZ = (((Q - C%HI) + (A - (Q + Z))) - C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function REAL_SUB_QUAD



function DP_SUB_QUAD(A, C) result(AC)

real (kind=DP), intent(in)  :: A
type (QUAD), intent(in)     :: C
type (QUAD)                 :: AC

!     Local variables
real (kind=DP) :: Z, Q, ZZ

Z = A - C%HI
Q = A - Z
ZZ = (((Q - C%HI) + (A - (Q + Z))) - C%LO)
AC%HI = Z + ZZ
AC%LO = (Z - AC%HI) + ZZ

return
end function DP_SUB_QUAD



function NEGATE_QUAD(A) result(B)
!     Change the sign of a quadruple-precision number.
!     In many cases, a & b will occupy the same locations.

type (QUAD), intent(in) :: A
type (QUAD)             :: B

B%HI = -A%HI
B%LO = -A%LO

return
end function NEGATE_QUAD



subroutine QUAD_EQ_INT(A, I)
!     Assignment

type (QUAD), intent(out)  :: A
integer, intent(in)       :: I

A%HI = I
A%LO = 0

return
end subroutine QUAD_EQ_INT



subroutine QUAD_EQ_REAL(A, R)
!     Assignment

type (QUAD), intent(out)  :: A
real, intent(in)          :: R

A%HI = R
A%LO = ZERO

return
end subroutine QUAD_EQ_REAL



subroutine QUAD_EQ_DP(A, D)
!     Assignment

type (QUAD), intent(out)    :: A
real (kind=DP), intent(in)  :: D

A%HI = D
A%LO = ZERO

return
end subroutine QUAD_EQ_DP



subroutine INT_EQ_QUAD(I, A)
!     Assignment

integer, intent(out)     :: I
type (QUAD), intent(in)  :: A

I = A%HI

return
end subroutine INT_EQ_QUAD



subroutine REAL_EQ_QUAD(R, A)
!     Assignment

real, intent(out)        :: R
type (QUAD), intent(in)  :: A

R = A%HI

return
end subroutine REAL_EQ_QUAD



subroutine DP_EQ_QUAD(D, A)
!     Assignment

real (kind=DP), intent(out)  :: D
type (QUAD), intent(in)      :: A

D = A%HI

return
end subroutine DP_EQ_QUAD



function QUAD_LT(X, Y) result(IS_IT)
!     Comparison of 2 logical numbers

type (QUAD), intent(in) :: X, Y
logical                 :: IS_IT

! Local variable
type (QUAD) :: DIFF

DIFF = X - Y
IS_IT = (DIFF%HI < ZERO)

return
end function QUAD_LT



function QUAD_LE(X, Y) result(IS_IT)
!     Comparison of 2 logical numbers

type (QUAD), intent(in) :: X, Y
logical                 :: IS_IT

! Local variable
type (QUAD) :: DIFF

DIFF = X - Y
IS_IT = .not. (DIFF%HI > ZERO)

return
end function QUAD_LE



function QUAD_EQ(X, Y) result(IS_IT)
!     Comparison of 2 logical numbers

type (QUAD), intent(in) :: X, Y
logical                 :: IS_IT

! Local variable
type (QUAD) :: DIFF

DIFF = X - Y
IS_IT = (DIFF%HI == ZERO)

return
end function QUAD_EQ



function QUAD_GE(X, Y) result(IS_IT)
!     Comparison of 2 logical numbers

type (QUAD), intent(in) :: X, Y
logical                 :: IS_IT

! Local variable
type (QUAD) :: DIFF

DIFF = X - Y
IS_IT = .not. (DIFF%HI < ZERO)

return
end function QUAD_GE



function QUAD_GT(X, Y) result(IS_IT)
!     Comparison of 2 logical numbers

type (QUAD), intent(in) :: X, Y
logical                 :: IS_IT

! Local variable
type (QUAD) :: DIFF

DIFF = X - Y
IS_IT = (DIFF%HI > ZERO)

return
end function QUAD_GT



function QUAD_POW_INT(A, I) result(B)
!     Raise a quadruple=precision number (a) to a power.

type (QUAD), intent(in) :: A
integer, intent(in)     :: I
type (QUAD)             :: B

! Local variables

integer     :: IA, J, EMAX
type (QUAD) :: POWER
logical     :: FIRST

select case (I)
  case (0)
    B = QUAD(ONE, ZERO)
  case (-1023:-1, 1:1023)
    IA = abs(I)
    FIRST = .true.
    POWER = A
    EMAX = maxexponent(ONE)
    do J = 0, 9
      if (btest(IA, J)) then
        if (FIRST) then
          B = POWER
          FIRST = .false.
        else
          if (exponent(POWER%HI) + exponent(B%HI) > EMAX) then
            write(unit=*, fmt=*) "** Exponential overflow in routine QUAD_POW_INT **"
            return
          end if
          B = B * POWER
        end if
        IA = ibclr(IA, J)
        if (IA == 0) then
          exit
        end if
      end if
      if (exponent(POWER%HI) > EMAX/2) then
        write(unit=*, fmt=*) "** Exponential overflow in routine QUAD_POW_INT **"
        return
      end if
      POWER = POWER * POWER
    end do
    if (I < 0) then
      B = QUAD(ONE, ZERO) / B
    end if
  case default
    if (A%HI < ZERO) then
      if (I * log(-A%HI) > log( huge(ONE) )) then
        write(unit=*, fmt=*) "** Exponential overflow in routine QUAD_POW_INT **"
        return
      else if (I * log(-A%HI) < log( tiny(ONE) )) then
        B = QUAD(ZERO, ZERO)
      else
        IA = abs(I)
        if (btest(IA,0)) then
          B = - exp( log(-A)*I )         ! i is odd (test least sign. bit)
        else
          B = exp( log(-A)*I )           ! i is even
        end if
      end if
    else if (A%HI > ZERO) then
      if (I * log(A%HI) > log( huge(ONE) )) then
        write(unit=*, fmt=*) "** Exponential overflow in routine QUAD_POW_INT **"
        return
      else if (I * log(A%HI) < log( tiny(ONE) )) then
        B = QUAD(ZERO, ZERO)
      else
        B = exp( log(A)*I )
      end if
    else
      B = QUAD(ZERO, ZERO)
    end if
end select

return
end function QUAD_POW_INT



function QUAD_POW_REAL(A, R) result(B)
!     Raise a quadruple=precision number (a) to a power.

type (QUAD), intent(in) :: A
real, intent(in)        :: R
type (QUAD)             :: B

if (A%HI < ZERO) then
  write(unit=*, fmt=*)   &
  " *** Error: attempt to raise negative quad. prec. number to a real power ***"
  B = QUAD(ZERO, ZERO)
else if (A%HI > ZERO) then
  B = exp( log(A)*R )
else
  B = QUAD(ZERO, ZERO)
end if

return
end function QUAD_POW_REAL



function QUAD_POW_DP(A, D) result(B)
!     Raise a quadruple=precision number (a) to a power.

type (QUAD), intent(in)    :: A
real (kind=DP), intent(in) :: D
type (QUAD)                :: B

if (A%HI < ZERO) then
  write(unit=*, fmt=*)   &
  " *** Error: attempt to raise negative quad. prec. number to a real power ***"
  B = QUAD(ZERO, ZERO)
else if (A%HI > ZERO) then
  B = exp( log(A)*D )
else
  B = QUAD(ZERO, ZERO)
end if

return
end function QUAD_POW_DP



function QUAD_POW_QUAD(A, Q) result(B)
!     Raise a quadruple=precision number (a) to a power.

type (QUAD), intent(in) :: A, Q
type (QUAD)             :: B

if (A%HI < ZERO) then
  write(unit=*, fmt=*)   &
  " *** Error: attempt to raise negative quad. prec. number to a real power ***"
  B = QUAD(ZERO, ZERO)
else if (A%HI > ZERO) then
  B = exp( log(A)*Q )
else
  B = QUAD(ZERO, ZERO)
end if

return
end function QUAD_POW_QUAD



function QSCALE(A, I) result(B)
!     Multiply a by 2^i

type (QUAD), intent(in) :: A
integer, intent(in)     :: I
type (QUAD)             :: B

B%HI = scale(A%HI, I)
B%LO = scale(A%LO, I)

return
end function QSCALE



function QABS(A) result(B)
!     Absolute value of a quadruple-precision number

type (QUAD), intent(in) :: A
type (QUAD)             :: B

if (A%HI < ZERO) then
  B%HI = - A%HI
  B%LO = - A%LO
else
  B%HI = A%HI
  B%LO = A%LO
end if

return
end function QABS



function LONGSQRT(A) result(B)
! This is modified from procedure sqrt2 of:
!    Dekker, T.J. (1971). 'A floating-point technique for extending the
!    available precision', Numer. Math., 18, 224-242.

type (QUAD), intent(in) :: A
type (QUAD)             :: B

!     Local variables
real (kind=DP) :: T, RES
type (QUAD)    :: TT

! Check that ahi >= 0.

if (A%HI < 0.0_DP) then
  write(unit=*, fmt=*) " *** Negative argument for longsqrt ***"
  return
else if (A%HI == ZERO) then
  B%HI = ZERO
  B%LO = ZERO
  return
end if

! First approximation is  t = sqrt(a).

T = sqrt(A%HI)
TT = EXACTMUL2(T, T)
RES = T + (((A%HI - TT%HI) - TT%LO) + A%LO) * 0.5_DP / T
B%LO = (T - RES) + (((A%HI - TT%HI) - TT%LO) + A%LO) * 0.5_DP / T
B%HI = RES

return
end function LONGSQRT



function LONGLOG(X) result(Y)
!  Quadruple-precision logarithm to base e
!  Halley's algorithm using double-precision logarithm as starting value.
!  Solves:         y
!          f(y) = e  - x = 0

type (QUAD), intent(in) :: X
type (QUAD)             :: Y

!     Local variables
type (QUAD) :: EXPY, F

Y%HI = log(X%HI)
Y%LO = ZERO
EXPY = exp(Y)
F = EXPY - X
F = scale(F, 1)
Y = Y - F / (EXPY + X)

return
end function LONGLOG



function LONGEXP(X) result(Y)
!  Calculate a quadruple-precision exponential
!  Method:
!   x    x.log2(e)    nint[x.log2(e)] + frac[x.log2(e)]
!  e  = 2          = 2
!
!                     iy    fy
!                  = 2   . 2
!  Then
!   fy    y.ln(2)
!  2   = e
!
!  Now y.ln(2) will be less than 0.3466 in absolute value.
!  This is halved and a Pade approximation is used to approximate e^x over
!  the region (-0.1733, +0.1733).   This approximation is then squared.

!  WARNING: No overflow checks!

type (QUAD), intent(in) :: X
type (QUAD)             :: Y

! Local variables
type (QUAD)          :: TEMP, YSQ, SUM1, SUM2
integer              :: IY

Y = X / LN2
IY = nint(real(Y%HI))
Y = (Y - real(IY,DP)) * LN2
Y = scale(Y, -1)

! The Pade series is:
!     p0 + p1.y + p2.y^2 + p3.y^3 + ... + p9.y^9
!     ------------------------------------------
!     p0 - p1.y + p2.y^2 - p3.y^3 + ... - p9.y^9
!
! sum1 is the sum of the odd powers, sum2 is the sum of the even powers

YSQ = Y * Y
SUM1 = Y * ((((YSQ + 3960.0)*YSQ + 2162160.0_DP)*YSQ + 302702400.0_DP)*YSQ +   &
               8821612800.0_DP)
SUM2 = (((90.0*YSQ + 110880.0)*YSQ + 30270240.0_DP)*YSQ + 2075673600.0_DP)*YSQ +  &
       17643225600.0_DP

!                     sum2 + sum1         2.sum1
! Now approximation = ----------- = 1 + ----------- = 1 + 2.temp
!                     sum2 - sum1       sum2 - sum1
!
! Then (1 + 2.temp)^2 = 4.temp.(1 + temp) + 1

TEMP = SUM1 / (SUM2 - SUM1)
Y = TEMP * (TEMP + ONE)
Y = scale(Y, 2)
Y = Y + ONE
Y = scale(Y, IY)

return
end function LONGEXP



function LONGMODR(A, B) result(REM)

! Extended arithmetic calculation of the 'rounded' modulus:
!  a = n.b + rem
! where all quantities are in quadruple-precision, except the integer
! number of multiples, n.   The absolute value of the remainder (rem)
! is not greater than b/2.
! The result is exact.   rem may occupy the same location as either input.

! Programmer: Alan Miller

! Latest revision - 11 September 1986
! Fortran version - 4 December 1996

type (QUAD), intent(in)  :: A, B
type (FUNC_RESULT)       :: REM

! Local variables

type (QUAD) :: TEMP

! Check that b%hi .ne. 0

if (B%HI == ZERO) then
  write(unit=*, fmt=*) " *** Error in longmodr - 3rd argument zero ***"
  return
end if

! Calculate NMULT.

TEMP = A / B
REM % NMULT = nint(real(TEMP % HI))

! Calculate remainder preserving full accuracy.

TEMP = EXACTMUL2(real(REM % NMULT,DP), B % HI)
REM % REMAINDER % HI = A % HI
REM % REMAINDER % LO = ZERO
TEMP = REM % REMAINDER - TEMP
REM % REMAINDER % HI = A % LO
REM % REMAINDER % LO = ZERO
TEMP = REM % REMAINDER + TEMP
REM % REMAINDER = EXACTMUL2(real(REM % NMULT,DP), B % LO)
REM % REMAINDER = TEMP - REM % REMAINDER

return
end function LONGMODR



! Extended accuracy arithmetic sine, cosine & tangent (about 31 decimals).
! Calculates  b = sin, cos or tan (a), where all quantities are in
! quadruple-precision, using table look-up and a Taylor series expansion.
! The result may occupy the same locations as the input value.
! Much of the code is common to all three functions, and this is in a
! subroutine longcst.


function LONGSIN(A) result(B)

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables

logical :: SINE, COSINE, TANGENT

! Set logical variables for sine function.

SINE = .true.
COSINE = .false.
TANGENT = .false.
B = LONGCST(A, SINE, COSINE, TANGENT)

return
end function LONGSIN



function LONGCOS(A) result(B)

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables

logical :: SINE, COSINE, TANGENT

! Set logical variables for sine function.

SINE = .false.
COSINE = .true.
TANGENT = .false.
B = LONGCST(A, SINE, COSINE, TANGENT)

return
end function LONGCOS



function LONGTAN(A) result(B)

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables

logical :: SINE, COSINE, TANGENT

! Set logical variables for sine function.

SINE = .false.
COSINE = .false.
TANGENT = .true.
B = LONGCST(A, SINE, COSINE, TANGENT)

return
end function LONGTAN



function LONGCST(A, SINE, COSINE, TANGENT) result(B)

type (QUAD), intent(in)  :: A
logical, intent(in)      :: SINE, COSINE, TANGENT
type (QUAD)              :: B

! Local variables

logical                   :: POS
type (QUAD)               :: D, TERM, TEMP, ANGLE, PIBY40, SUM1, SUM2, SSIN
integer                   :: NPI, IPT, I
type (FUNC_RESULT)        :: REM
real (kind=DP), parameter :: TOL15 = 1.0e-15_DP, TOL30 = 1.0e-30_DP

! sin(i.pi/40), i = 0(1)20
real (kind=DP), parameter, dimension(2, 0:20) :: TABLE = reshape( (/   &
               0.0000000000000000e+00_DP,  0.0000000000000000e+00_DP,  &
               0.7845909572784494e-01_DP,  0.1464397249532491e-17_DP,  &
               0.1564344650402309e+00_DP, -0.2770509565052586e-16_DP,  &
               0.2334453638559054e+00_DP,  0.2058612230858154e-16_DP,  &
               0.3090169943749475e+00_DP, -0.8267172724967036e-16_DP,  &
               0.3826834323650898e+00_DP, -0.1005077269646159e-16_DP,  &
               0.4539904997395468e+00_DP, -0.1292033036231312e-16_DP,  &
               0.5224985647159488e+00_DP,  0.6606794454708078e-16_DP,  &
               0.5877852522924732e+00_DP, -0.1189570533007057e-15_DP,  &
               0.6494480483301838e+00_DP, -0.1134903961116171e-15_DP,  &
               0.7071067811865476e+00_DP, -0.4833646656726458e-16_DP,  &
               0.7604059656000310e+00_DP, -0.1036987135483477e-15_DP,  &
               0.8090169943749476e+00_DP, -0.1381828784809282e-15_DP,  &
               0.8526401643540922e+00_DP,  0.4331886637554353e-16_DP,  &
               0.8910065241883680e+00_DP, -0.1474714419679880e-15_DP,  &
               0.9238795325112868e+00_DP, -0.9337725537817898e-16_DP,  &
               0.9510565162951536e+00_DP, -0.7008780156242836e-16_DP,  &
               0.9723699203976766e+00_DP,  0.4478912629332321e-16_DP,  &
               0.9876883405951378e+00_DP, -0.4416018005989794e-16_DP,  &
               0.9969173337331280e+00_DP,  0.1235153006196267e-16_DP,  &
               0.1000000000000000e+01_DP,  0.0000000000000000e+00_DP  /),  &
               (/ 2, 21 /) )

! pi/40

PIBY40%HI = 0.7853981633974484e-01_DP
PIBY40%LO = -0.1081617080994607e-16_DP

! Reduce angle to range (-pi/2, +pi/2) by subtracting an integer multiple of pi.

REM = LONGMODR(A, PI)
ANGLE = REM % REMAINDER
NPI = REM % NMULT

! Find nearest multiple of pi/40 to angle.

REM = LONGMODR(ANGLE, PIBY40)
D = REM % REMAINDER
IPT = REM % NMULT

! Sum 1 = 1 - d**2/2! + d**4/4! - d**6/6! + ...
! Sum 2 = d - d**3/3! + d**5/5! - d**7/7! + ...

SUM1%HI = ZERO
SUM1%LO = ZERO
SUM2%HI = ZERO
SUM2%LO = ZERO
POS = .false.
TERM = D
I = 2
do
  if (abs(TERM%HI) > TOL15) then
    TERM = TERM * D                                ! Use quad. precision
    if (I == 2 .or. I == 4 .or. I == 8) then
      TERM%HI = TERM%HI / I
      TERM%LO = TERM%LO / I
    else
      TERM = TERM / real(I,DP)
    end if
    if (POS) then
      SUM1 = SUM1 + TERM
    else
      SUM1 = SUM1 - TERM
    end if
  else
    TERM%HI = TERM%HI * D%HI / real(I,DP)             ! Double prec. adequate
    if (POS) then
      SUM1%LO = SUM1%LO + TERM%HI
    else
      SUM1%LO = SUM1%LO - TERM%HI
    end if
  end if

  ! Repeat for sum2

  I = I + 1
  if (abs(TERM%HI) > TOL15) then
    TERM = TERM * D / real(I,DP)                      ! Use quad. precision
    if (POS) then
      SUM2 = SUM2 + TERM
    else
      SUM2 = SUM2 - TERM
    end if
  else
    TERM%HI = TERM%HI * D%HI / real(I,DP)             ! Double prec. adequate
    if (POS) then
      SUM2%LO = SUM2%LO + TERM%HI
    else
      SUM2%LO = SUM2%LO - TERM%HI
    end if
  end if

  I = I + 1
  POS = .not. POS
  if (abs(TERM%HI) < TOL30) then
    exit
  end if
end do

SUM1 = SUM1 + 1.0_DP                              ! Now add the 1st terms
SUM2 = SUM2 + D                                   ! for max. accuracy

! Construct sine, cosine or tangent.
! Sine first.    sin(angle + d) = sin(angle).cos(d) + cos(angle).sin(d)

if (SINE .or. TANGENT) then
  if (IPT >= 0) then
    TEMP%HI = TABLE(1, IPT)
    TEMP%LO = TABLE(2, IPT)
  else
    TEMP%HI = - TABLE(1, -IPT)
    TEMP%LO = - TABLE(2, -IPT)
  end if
  B = SUM1 * TEMP
  if (IPT >= 0) then
    TEMP%HI = TABLE(1, 20-IPT)
    TEMP%LO = TABLE(2, 20-IPT)
  else
    TEMP%HI = TABLE(1, 20+IPT)
    TEMP%LO = TABLE(2, 20+IPT)
  end if
  B = B + SUM2 * TEMP
  if (NPI /= 2*(NPI/2)) then
    B = -B
  end if
  if (TANGENT) then
    SSIN = B
  end if
end if

! Cosine or tangent.

if (COSINE .or. TANGENT) then
  if (IPT >= 0) then
    TEMP%HI = TABLE(1, IPT)
    TEMP%LO = TABLE(2, IPT)
  else
    TEMP%HI = - TABLE(1, -IPT)
    TEMP%LO = - TABLE(2, -IPT)
  end if
  B = SUM2 * TEMP
  if (IPT >= 0) then
    TEMP%HI = TABLE(1, 20-IPT)
    TEMP%LO = TABLE(2, 20-IPT)
  else
    TEMP%HI = TABLE(1, 20+IPT)
    TEMP%LO = TABLE(2, 20+IPT)
  end if
  B = SUM1 * TEMP - B
  if (NPI /= 2*(NPI/2)) then
    B = -B
  end if
end if

! Tangent.

if (TANGENT) then

! Check that bhi .ne. 0

  if (B%HI == ZERO) then
    write(unit=*, fmt=*) " *** Infinite tangent - routine longcst ***"
    B%HI = huge(ONE)
    B%LO = ZERO
    return
  end if
  B = SSIN / B
end if

return
end function LONGCST



function LONGASIN(A) result(B)

! Quadratic-precision arc sine (about 31 decimals).
! Newton-Raphson iteration to solve:  f(b) = sin(b) - a = 0.
! The result (b) may occupy the same location as the input values (a).

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables
type (QUAD)  :: Y

! Check that -1 <= a%hi <= +1.

if (A%HI < -1.0_DP .or. A%HI > ONE) then
  write(unit=*, fmt=*) " *** Argument outside range for longasin ***"
  return
end if

! First approximation is  y = asin(a).
! Quadruple-precision result is  y - [sin(y) - a]/cos(y).

Y%HI = asin(A%HI)
Y%LO = ZERO
B = Y + (A - sin(Y)) / cos(Y%HI)

return
end function LONGASIN



function LONGACOS(A) result(B)

! Quadratic-precision arc cosine (about 31 decimals).
! Newton-Raphson iteration to solve: f(b) = cos(b) - a = 0.
! The result (b) may occupy the same location as the input values (a).

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables
type (QUAD)  :: Y

! Check that -1 <= a%hi <= +1.

if (A%HI < -1.0_DP .or. A%HI > ONE) then
  write(unit=*, fmt=*) " *** Argument outside range for longacos ***"
  return
end if

! First approximation is  y = acos(a).
! Quadruple-precision result is  y + [cos(y) - a]/sin(y).

Y%HI = acos(A%HI)
Y%LO = ZERO
B = Y + (cos(Y) - A) / sin(Y%HI)

return
end function LONGACOS



function LONGATAN(A) result(B)

! Quadratic-precision arc tangent (about 31 decimals).
! Newton-Raphson iteration to solve: f(b) = tan(b) - a = 0.
! The result (b) may occupy the same location as the input values (a).

type (QUAD), intent(in) :: A
type (QUAD)             :: B

! Local variables
type (QUAD)  :: Y

! First approximation is  y = atan(a).
! Quadruple-precision result is  y - [tan(y) - a] * cos(y)**2.

Y%HI = atan(A%HI)
Y%LO = ZERO
B = Y - (tan(Y) - A) * (cos(Y%HI))**2

return
end function LONGATAN



function QATAN2(Y, X) result(B)

! Quadratic-precision arc tangent (about 31 decimals).
! As for arc tangent (y/x) except that the result is in the range
!       -pi < ATAN2 <= pi.
! The signs of x and y determine the quadrant.

type (QUAD), intent(in) :: Y, X
type (QUAD)             :: B

! Local variables
type (QUAD)  :: Z

! First approximation is  z = atan2(y, x).
! Quadruple-precision result is  z - [tan(z) - (y/x)] * cos(z)**2.

Z%HI = atan2(Y%HI, X%HI)
Z%LO = ZERO
if (X%HI == ZERO) then
  B = Z
else
  B = Z - (tan(Z) - Y/X) * (cos(Z%HI))**2
end if

return
end function QATAN2



function QUAD_SUM(A) result(S)

! Quadruple-precision SUM

type (QUAD), intent(in), dimension(:)  :: A
type (QUAD)                            :: S

! Local variables

integer  :: I

S = QUAD(ZERO, ZERO)
do I = 1, size(A)
  S = S + A(I)
end do

return
end function QUAD_SUM



function QUAD_DOT_PRODUCT(A, B) result(AB)

! Quadruple-precision DOT_PRODUCT

type (QUAD), intent(in), dimension(:) :: A, B
type (QUAD)                           :: AB

! Local variables

integer :: I, N

AB = QUAD(ZERO, ZERO)
N = size(A)
if (N /= size(B)) then
  write(unit=*, fmt=*) " ** Error invoking DOT_PRODUCT - different argument sizes **"
  write(unit=*, fmt="(a, i10, a, i10)") " Size of 1st argument = ", N,   &
                               "   Size of 2nd argument = ", size(B)
  return
end if

do I = 1, N
  AB = AB + A(I)*B(I)
end do

return
end function QUAD_DOT_PRODUCT



function Q_MATMUL12(A, B) result(AB)

! Quadruple-precision MATMUL
! Rank of A = 1, rank of B = 2

type (QUAD), intent(in), dimension(:)   :: A
type (QUAD), intent(in), dimension(:,:) :: B
type (QUAD), dimension( size(B,2) )     :: AB

! Local variables

integer :: J, NA, NB1, NB2

! Check dimensions

NA = size(A)
NB1 = size(B, 1)
NB2 = size(B, 2)
if (NA /= NB1) then
  write(unit=*, fmt=*) " ** Incompatible dimensions for quad-prec. MATMUL"
  return
end if

do J = 1, NB2
  AB(J) = QUAD_DOT_PRODUCT( A, B(:,J) )
end do

return
end function Q_MATMUL12



function Q_MATMUL21(A, B) result(AB)

! Quadruple-precision MATMUL
! Rank of A = 2, rank of B = 1

type (QUAD), intent(in), dimension(:,:) :: A
type (QUAD), intent(in), dimension(:)   :: B
type (QUAD), dimension( size(A,1) )     :: AB

! Local variables

integer :: I, NA1, NA2, NB

! Check dimensions

NA1 = size(A, 1)
NA2 = size(A, 2)
NB = size(B)

if (NA2 /= NB) then
  write(unit=*, fmt=*) " ** Incompatible dimensions for quad-prec. MATMUL"
  return
end if

do I = 1, NA1
  AB(I) = QUAD_DOT_PRODUCT( A(I,:), B )
end do

return
end function Q_MATMUL21



function Q_MATMUL22(A, B) result(AB)

! Quadruple-precision MATMUL
! Rank of A = 2, rank of B = 2

type (QUAD), intent(in), dimension(:,:)     :: A
type (QUAD), intent(in), dimension(:,:)     :: B
type (QUAD), dimension(size(A,1),size(B,2)) :: AB

! Local variables

integer :: I, J, NA1, NA2, NB1, NB2

! Check dimensions

NA1 = size(A, 1)
NA2 = size(A, 2)
NB1 = size(B, 1)
NB2 = size(B, 2)

if (NA2 /= NB1) then
  write(unit=*, fmt=*) " ** Incompatible dimensions for quad-prec. MATMUL"
  return
end if

do I = 1, NA1
  do J = 1, NB2
    AB(I,J) = QUAD_DOT_PRODUCT( A(I,:), B(:,J) )
  end do
end do

return
end function Q_MATMUL22



subroutine STRING_QUAD(STRING, VALUE, IER)
! Convert a character string to a quadruple-precision quantity.
! Error indicator ier = 0 if value OK
!                     = 1 if string has > 50 characters and no decimal point
!                            in about the first 45
!                     = 2 if a non-numeric character is read in the mantissa
!                            part

character (len=*), intent(in) :: STRING
type (QUAD), intent(out)      :: VALUE
integer, intent(out)          :: IER

! Local variables
character (len=50)            :: STR
integer                       :: LENGTH, POS, DECPT, STATUS, POWER10, I, I1, I2
real (kind=DP)                :: SGN, TEMP
character (len= 5)            :: EXPNT

STR = adjustl(STRING)
LENGTH = len_trim( adjustl(STRING) )
if (LENGTH > 50) then
                             ! Truncate string to 50 characters preserving
                             ! the exponent, if present.
  POS = scan(STRING(LENGTH-4:LENGTH), "DdEe")
  if (POS > 0) then
    STR(45+POS:50) = STRING(LENGTH-5+POS:LENGTH)
  end if
                             ! Check that the string contains a '.'
  if (index(STR, ".") == 0) then
    IER = 1
    VALUE = QUAD(ZERO, ZERO)
    return
  end if
end if

IER = 0

! Determine sign of mantissa

SGN = ONE
if (STR(1:1) == "-") then
  SGN = -1.0_DP
  STR = STR(2:)
else if (STR(1:1) == "+") then
  STR = STR(2:)
end if

! Separate the exponent, if there is one

LENGTH = len_trim(STR)
POS = scan(STR(LENGTH-4:LENGTH), "DdEe")
if (POS > 0) then
  EXPNT = STR(LENGTH-5+POS:)
  STR(LENGTH-5+POS:) = "     "
else
  EXPNT = " "
end if

! decpt = position of decimal point

DECPT = index(STR, ".")
if (DECPT > 0) then
  STR = STR(:DECPT-1) // STR(DECPT+1:)
end if

! Read str in blocks of up to 9 digits at a time, as a large integer.

I1 = 1
LENGTH = len_trim(STR)
VALUE = QUAD(ZERO, ZERO)
do
  I2 = min(I1+8, LENGTH)
  read(unit=STR(I1:I2), fmt="(f9.0)", iostat=STATUS) TEMP
  if (STATUS /= 0) then
    IER = 2
    return
  end if
  if (I1 > 1) then
    VALUE = VALUE * (10.0_DP ** (I2+1-I1)) + TEMP
  else
    VALUE = QUAD(TEMP, ZERO)
  end if
  I1 = I2 + 1
  if (I1 > LENGTH) then
    exit
  end if
end do

if (SGN < ZERO) then
  VALUE = -VALUE
end if

! Multiply by appropriate power of 10 for position of the decimal point,
! and the exponent.

if (EXPNT == " ") then
  POWER10 = 0
else
  I = len_trim(EXPNT)
  read(unit=EXPNT(2:I), fmt="(i4)") POWER10
end if
if (DECPT > 0) then
  POWER10 = POWER10 - (LENGTH + 1 - DECPT)
end if

! As 1.D+15 is represented exactly in double-precision IEEE arithmetic,
! multiply by multiples of 1.D+15 or 1.D-15.

if (POWER10 == 0) then
  return
end if
if (POWER10 < 0) then
  do I = 1, -POWER10/15
    VALUE = VALUE / 1.0e+15_DP
  end do
  I = modulo(-POWER10, 15)
  if (I /= 0) then
    VALUE = VALUE / (10.0_DP ** I)
  end if
else
  do I = 1, POWER10/15
    VALUE = VALUE * 1.0e+15_DP
  end do
  I = modulo(POWER10, 15)
  if (I /= 0) then
    VALUE = VALUE * (10.0_DP ** I)
  end if
end if

return
end subroutine STRING_QUAD


subroutine QUAD_STRING(VALUE, STRING, IER)
! Convert a quadruple-precision quantity to a decimal character string.
! Error indicator ier = 0 if conversion OK
!                     = 1 if the length of the string < 36 characters.

type (QUAD), intent(in)        :: VALUE
character (len=*), intent(out) :: STRING
integer, intent(out)           :: IER

! Local variables
character (len= 1)   :: SGN
character (len=17)   :: STR1, STR2
type (QUAD)          :: VAL
integer              :: DEC_EXPNT, I
real (kind=DP)       :: TMP

if (len(STRING) < 36) then
  IER = 1
  STRING = "***"
  return
end if
IER = 0

! Check if value = ZERO.
if (VALUE%HI == ZERO) then
  STRING = " 0.00"
  return
end if

if (VALUE%HI < ZERO) then
  SGN = "-"
  VAL = - VALUE
else
  SGN = " "
  VAL = VALUE
end if

! Use LOG10 to set the exponent.
DEC_EXPNT = floor( log10(VAL%HI) )

! Get the first 15 decimal digits
if (DEC_EXPNT /= 14) then
  VAL = VAL * exp( log(QUAD(10.0_DP, ZERO)) * real(14 - DEC_EXPNT) )
end if
write(unit=STR1, fmt="(f16.0)") VAL%HI

! Calculate the remainder
read(unit=STR1, fmt="(f16.0)") TMP
VAL = VAL - TMP

! If val is -ve, subtract 1 from the last digit of str1, and add 1 to val.
if (VAL%HI < -0.5e-16) then
  TMP = TMP - ONE
  write(unit=STR1, fmt="(f16.0)") TMP
  VAL = VAL + ONE
end if
VAL = VAL * 1.0e15_DP

! write the second 15 digits
write(unit=STR2, fmt="(f16.0)") VAL%HI

! If str2 consists of asterisks, add 1 in the last digit to str1.
! Set str2 to zeroes.
if (STR2(2:2) == "*") then
  TMP = TMP + ONE
  write(unit=STR1, fmt="(f17.0)") TMP
  if (STR1(1:1) /= " ") then
    DEC_EXPNT = DEC_EXPNT + 1
  else
    STR1 = STR1(2:17)
  end if
  STR2 = "000000000000000."
end if

! Replace leading blanks with zeroes
do I = 1, 15
  if (STR2(I:I) /= " ") then
    exit
  end if
  STR2(I:I) = "0"
end do

! Combine str1 & str2, removing decimal points & adding exponent.
I = index(STR1, ".")
STR1(I:I) = " "
STR2(16:16) = " "
STRING = "." // trim(adjustl(STR1)) // trim(adjustl(STR2)) // "E"
write(unit=STR1, fmt="(i4.2)") DEC_EXPNT + 1
STRING = trim(STRING) // adjustl(STR1)

! Restore the sign.
if (SGN == "-") then
  STRING = "-" // adjustl(STRING)
else
  STRING = adjustl(STRING)
end if

return
end subroutine QUAD_STRING



function Q_EPSILON(X) result(EPS)
! Returns the machine accuracy.
! eps is the smallest value such that x.(1 + eps) > x for all x.

type (QUAD), intent(in)  :: X
type (QUAD)              :: EPS

EPS = QUAD(0.6163e-32_DP, 0.0_DP)

return
end function Q_EPSILON




! Quad. prec. complex functions & routines

function QC_ADD(X, Y) result(Z)
! Quadruple-precision complex addition

type (QC), intent(in) :: X, Y
type (QC)             :: Z

Z % QR = X % QR + Y % QR
Z % QI = X % QI + Y % QI

return
end function QC_ADD



function QC_SUB(X, Y) result(Z)
! Quadruple-precision complex subtraction

type (QC), intent(in) :: X, Y
type (QC)             :: Z

Z%QR = X%QR - Y%QR
Z%QI = X%QI - Y%QI

return
end function QC_SUB



function NEGATE_QC(X) result(Z)
! Quadruple-precision complex negate sign

type (QC), intent(in) :: X
type (QC)             :: Z

Z%QR = - X%QR
Z%QI = - X%QI

return
end function NEGATE_QC



function QC_MULT(X, Y) result(Z)
! Quadruple-precision complex multiplication

type (QC), intent(in) :: X, Y
type (QC)             :: Z

Z%QR = X%QR * Y%QR - X%QI * Y%QI
Z%QI = X%QI * Y%QR + X%QR * Y%QI

return
end function QC_MULT



function QC_DIV(X, Y) result(Z)
! Quadruple-precision complex division

type (QC), intent(in) :: X, Y
type (QC)             :: Z

type (QUAD) :: T, D

if ( abs(Y%QR%HI) > abs(Y%QI%HI) ) then
  T = Y%QI / Y%QR
  D = Y%QR + T * Y%QI
  Z%QR = (X%QR + T * X%QI) / D
  Z%QI = (X%QI - T * X%QR) / D
else
  T = Y%QR / Y%QI
  D = Y%QI + T * Y%QR
  Z%QR = (T * X%QR + X%QI) / D
  Z%QI = (T * X%QI - X%QR) / D
end if

return
end function QC_DIV



function QC_CMPLX(XR, XI) result(Z)
! Convert pair of quad.precision numbers to qc.

type (QUAD), intent(in) :: XR, XI
type (QC)               :: Z

Z%QR = XR
Z%QI = XI

return
end function QC_CMPLX



function QC_AIMAG(X) result(Z)
! Convert pair of quad.precision numbers to qc.

type (QC), intent(in)   :: X
type (QUAD)             :: Z

Z = X%QI

return
end function QC_AIMAG



function QC_CONJG(X) result(Z)
! Quadruple-precision complex square root

type (QC), intent(in) :: X
type (QC)             :: Z

Z%QR = X%QR
Z%QI = - X%QI

return
end function QC_CONJG



function QC_SQRT(X) result(Z)
! Quadruple-precision complex square root

type (QC), intent(in) :: X
type (QC)             :: Z

! Local variables
type (QUAD)                :: R
real (kind=DP), parameter  :: VSMALL = tiny(1.0_DP)

R = sqrt(X%QR **2 + X%QI **2)
if (R%HI < VSMALL) then
  Z%QR = QUAD( 0.0_DP, 0.0_DP )
  Z%QI = QUAD( 0.0_DP, 0.0_DP )
  return
end if

Z%QR = sqrt( scale( R + abs(X%QR), -1 ) )
Z%QI = abs( scale( X%QI, -1 ) ) / Z%QR
if (X%QR%HI < 0.0_DP) then
  R = Z%QR
  Z%QR = Z%QI
  Z%QI = R
end if
if (X%QI%HI < 0.0_DP) then
  Z%QI = - Z%QI
end if

return
end function QC_SQRT



function QC_ABS(X) result(Z)
! Quadruple-precision complex absolute (modulus)
! SQRT( x%qr **2 + x%qi **2 ) avoiding overflow
! Using either:   X^2 + Y^2 = X^2.(1 + (Y/X)^2)
!           or:             = Y^2.(1 + (X/Y)^2)

type (QC), intent(in) :: X
type (QUAD)           :: Z

type (QUAD) :: A

if ( abs(X%QR%HI) > abs(X%QI%HI) ) then
  A = X%QI / X%QR
  Z = abs( X%QR ) * sqrt( QUAD(1.0_DP, 0.0_DP) + A**2 )
else if ( X%QI%HI == 0.0_DP ) then
  Z = QUAD( 0.0_DP, 0.0_DP )
else
  A = X%QR / X%QI
  Z = abs( X%QI ) * sqrt( QUAD(1.0_DP, 0.0_DP) + A**2 )
end if

return
end function QC_ABS



function QC_EXP(X) result(Z)
! Quadruple-precision complex exponential

type (QC), intent(in) :: X
type (QC)             :: Z

type (QUAD)           :: EXPX

EXPX = exp(X%QR)
Z%QR = EXPX * cos(X%QI)
Z%QI = EXPX * sin(X%QI)

return
end function QC_EXP



function QC_LOG(X) result(Z)
! Quadruple-precision complex logarithm

type (QC), intent(in) :: X
type (QC)             :: Z

Z%QR = log( abs(X) )
Z%QI = atan2( X%QI, X%QR )

return
end function QC_LOG

end module QUADRUPLE_PRECISION
