    {- |
        Copyright  : Copyright (C) 2006-2018 Bjorn Buckwalter
        License    : BSD3

        Maintainer : bjorn@buckwalter.se
        Stability  : Stable

    Defines convenience functions for inspecting and manipulating quantities with 'RealFloat'
    floating-point representations.

    The dimensionally-typed versions of functions from Patrick Perry's @ieee754@ package
    copy that package's API as closely as possible, by permission. In turn they are based on
    the @tango@ math library for the D language.

    -}

    {-# LANGUAGE ScopedTypeVariables #-}

    module Numeric.Units.Dimensional.Float
    (
      -- * Lifted Predicates from 'RealFloat'
      isDenormalized, isInfinite, isNaN, isNegativeZero
      -- * Convenience Functions
    , isFiniteNumber, scaleFloat
      -- * Lifted Functions from "Numeric.IEEE"
      -- ** Values
    , infinity, minNormal, maxFinite, epsilon, nan
      -- ** Arithmetic
    , predIEEE, succIEEE, bisectIEEE, copySign
      -- ** NaN with Payload
    , nanWithPayload, nanPayload, F.maxNaNPayload
      -- ** Comparisons
    , identicalIEEE, minNum, maxNum, minNaN, maxNaN
    )
    where

    import Control.Applicative
    import Data.Word (Word64)
    import Prelude (RealFloat)
    import qualified Prelude as P
    import Numeric.IEEE (IEEE)
    import qualified Numeric.IEEE as F
    import Numeric.Units.Dimensional.Internal (liftQ, liftQ2)
    import Numeric.Units.Dimensional.Prelude hiding (RealFloat(..))
    import Numeric.Units.Dimensional.Coercion

    -- $setup
    -- >>> :set -XExtendedDefaultRules
    -- >>> :set -XNegativeLiterals

    -- | 'True' if the representation of the argument is too small to be represented in normalized format.
    isDenormalized :: RealFloat a => Quantity d a -> Bool
    isDenormalized = P.isDenormalized . unQuantity

    -- | 'True' if the representation of the argument is a number and is not infinite.
    --
    -- >>> isFiniteNumber (_1 / _0)
    -- False
    --
    -- >>> isFiniteNumber (_0 / _0)
    -- False
    --
    -- >>> isFiniteNumber (_3 / _2)
    -- True
    isFiniteNumber :: RealFloat a => Quantity d a -> Bool
    isFiniteNumber = not . liftA2 (||) isNaN isInfinite

    -- | 'True' if the representation of the argument is an IEEE infinity or negative infinity.
    --
    -- >>> isInfinite (_1 / _0)
    -- True
    --
    -- >>> isInfinite (42 *~ micro farad)
    -- False
    isInfinite :: RealFloat a => Quantity d a -> Bool
    isInfinite = P.isInfinite . unQuantity

    -- | 'True' if the representation of the argument is an IEEE "not-a-number" (NaN) value.
    --
    -- >>> isNaN _3
    -- False
    --
    -- >>> isNaN (_1 / _0)
    -- False
    --
    -- >>> isNaN (asin _4)
    -- True
    isNaN :: RealFloat a => Quantity d a -> Bool
    isNaN = P.isNaN . unQuantity

    -- | 'True' if the representation of the argument is an IEEE negative zero.
    --
    -- >>> isNegativeZero _0
    -- False
    --
    -- >>> isNegativeZero $ (-1e-200 *~ one) * (1e-200 *~ one)
    -- True
    isNegativeZero :: RealFloat a => Quantity d a -> Bool
    isNegativeZero = P.isNegativeZero . unQuantity

    -- | Multiplies a floating-point quantity by an integer power of the radix of the representation type.
    --
    -- Use 'P.floatRadix' to determine the radix.
    --
    -- >>> let x = 3 *~ meter
    -- >>> scaleFloat 3 x
    -- 24.0 m
    scaleFloat :: RealFloat a => Int -> Quantity d a -> Quantity d a
    scaleFloat x = Quantity . P.scaleFloat x . unQuantity

    -- | An infinite floating-point quantity.
    infinity :: IEEE a => Quantity d a
    infinity = Quantity F.infinity

    -- | The smallest representable positive quantity whose representation is normalized.
    minNormal :: IEEE a => Quantity d a
    minNormal = Quantity F.minNormal

    -- | The largest representable finite floating-point quantity.
    maxFinite :: IEEE a => Quantity d a
    maxFinite = Quantity F.maxFinite

    -- | The smallest positive value @x@ such that @_1 + x@ is representable.
    epsilon :: IEEE a => Dimensionless a
    epsilon = Quantity F.epsilon

    -- | @copySign x y@ returns the quantity @x@ with its sign changed to match that of @y@.
    copySign :: IEEE a => Quantity d a -> Quantity d a -> Quantity d a
    copySign = liftQ2 F.copySign

    -- | Return 'True' if two floating-point quantities are /exactly/ (bitwise) equal.
    identicalIEEE :: IEEE a => Quantity d a -> Quantity d a -> Bool
    identicalIEEE (Quantity x) (Quantity y) = F.identicalIEEE x y

    -- | Return the next largest representable floating-point quantity (@Infinity@ and @NaN@ are unchanged).
    succIEEE :: IEEE a => Quantity d a -> Quantity d a
    succIEEE = liftQ F.succIEEE

    -- | Return the next smallest representable floating-point quantity (@Infinity@ and @NaN@ are unchanged).
    predIEEE :: IEEE a => Quantity d a -> Quantity d a
    predIEEE = liftQ F.predIEEE

    -- | Given two floating-point quantities with the same sign, return the quantity whose representation is halfway
    -- between their representations on the IEEE number line. If the signs of the values differ or either is @NaN@,
    -- the value is undefined.
    bisectIEEE :: IEEE a => Quantity d a -> Quantity d a -> Quantity d a
    bisectIEEE (Quantity x) (Quantity y) = Quantity $ F.bisectIEEE x y

    -- | Default @NaN@ quantity.
    nan :: IEEE a => Quantity d a
    nan = Quantity F.nan

    -- | Quiet @NaN@ quantity with a positive integer payload.
    -- Payload must be less than 'maxNaNPayload' of the representation type.
    --
    -- Beware that while some platforms allow using 0 as a payload, this behavior is not portable.
    nanWithPayload :: IEEE a => Word64 -> Quantity d a
    nanWithPayload = Quantity . F.nanWithPayload

    -- | The payload stored in a @NaN@ quantity. Undefined if the argument is not @NaN@.
    nanPayload :: IEEE a => Quantity d a -> Word64
    nanPayload = F.nanPayload . unQuantity

    -- | Return the minimum of two quantities; if one value is @NaN@, return the other. Prefer the first if both values are @NaN@.
    minNum :: RealFloat a => Quantity d a -> Quantity d a -> Quantity d a
    minNum = liftQ2 F.minNum

    -- | Return the maximum of two quantities; if one value is @NaN@, return the other. Prefer the first if both values are @NaN@.
    maxNum :: RealFloat a => Quantity d a -> Quantity d a -> Quantity d a
    maxNum = liftQ2 F.maxNum

    -- | Return the minimum of two quantities; if one value is @NaN@, return it. Prefer the first if both values are @NaN@.
    minNaN :: RealFloat a => Quantity d a -> Quantity d a -> Quantity d a
    minNaN = liftQ2 F.minNaN

    -- | Return the maximum of two quantities; if one value is @NaN@, return it. Prefer the first if both values are @NaN@.
    maxNaN :: RealFloat a => Quantity d a -> Quantity d a -> Quantity d a
    maxNaN = liftQ2 F.maxNaN