{- |
    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 :: Quantity d a -> Bool
isDenormalized = a -> Bool
forall a. RealFloat a => a -> Bool
P.isDenormalized (a -> Bool) -> (Quantity d a -> a) -> Quantity d a -> Bool
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
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 :: Quantity d a -> Bool
isFiniteNumber = Bool -> Bool
not (Bool -> Bool) -> (Quantity d a -> Bool) -> Quantity d a -> Bool
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (Bool -> Bool -> Bool)
-> (Quantity d a -> Bool)
-> (Quantity d a -> Bool)
-> Quantity d a
-> Bool
forall (f :: Type -> Type) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 Bool -> Bool -> Bool
(||) Quantity d a -> Bool
forall a (d :: Dimension). RealFloat a => Quantity d a -> Bool
isNaN Quantity d a -> Bool
forall a (d :: Dimension). RealFloat a => Quantity d a -> Bool
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 :: Quantity d a -> Bool
isInfinite = a -> Bool
forall a. RealFloat a => a -> Bool
P.isInfinite (a -> Bool) -> (Quantity d a -> a) -> Quantity d a -> Bool
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
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 :: Quantity d a -> Bool
isNaN = a -> Bool
forall a. RealFloat a => a -> Bool
P.isNaN (a -> Bool) -> (Quantity d a -> a) -> Quantity d a -> Bool
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
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 :: Quantity d a -> Bool
isNegativeZero = a -> Bool
forall a. RealFloat a => a -> Bool
P.isNegativeZero (a -> Bool) -> (Quantity d a -> a) -> Quantity d a -> Bool
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
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 :: Int -> Quantity d a -> Quantity d a
scaleFloat Int
x = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity (a -> Quantity d a)
-> (Quantity d a -> a) -> Quantity d a -> Quantity d a
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Int -> a -> a
forall a. RealFloat a => Int -> a -> a
P.scaleFloat Int
x (a -> a) -> (Quantity d a -> a) -> Quantity d a -> a
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
unQuantity

-- | An infinite floating-point quantity.
infinity :: IEEE a => Quantity d a
infinity :: Quantity d a
infinity = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity a
forall a. IEEE a => a
F.infinity

-- | The smallest representable positive quantity whose representation is normalized.
minNormal :: IEEE a => Quantity d a
minNormal :: Quantity d a
minNormal = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity a
forall a. IEEE a => a
F.minNormal

-- | The largest representable finite floating-point quantity.
maxFinite :: IEEE a => Quantity d a
maxFinite :: Quantity d a
maxFinite = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity a
forall a. IEEE a => a
F.maxFinite

-- | The smallest positive value @x@ such that @_1 + x@ is representable.
epsilon :: IEEE a => Dimensionless a
epsilon :: Dimensionless a
epsilon = a -> Dimensionless a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity a
forall a. IEEE a => a
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 :: Quantity d a -> Quantity d a -> Quantity d a
copySign = (a -> a -> a) -> Quantity d a -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension) (s3 :: ExactPi') (d3 :: Dimension).
(a -> a -> a)
-> SQuantity s1 d1 a -> SQuantity s2 d2 a -> SQuantity s3 d3 a
liftQ2 a -> a -> a
forall a. IEEE a => a -> a -> a
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 d a -> Quantity d a -> Bool
identicalIEEE (Quantity x) (Quantity y) = a -> a -> Bool
forall a. IEEE a => a -> a -> Bool
F.identicalIEEE a
x a
y

-- | Return the next largest representable floating-point quantity (@Infinity@ and @NaN@ are unchanged).
succIEEE :: IEEE a => Quantity d a -> Quantity d a
succIEEE :: Quantity d a -> Quantity d a
succIEEE = (a -> a) -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension).
(a -> a) -> SQuantity s1 d1 a -> SQuantity s2 d2 a
liftQ a -> a
forall a. IEEE a => a -> a
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 :: Quantity d a -> Quantity d a
predIEEE = (a -> a) -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension).
(a -> a) -> SQuantity s1 d1 a -> SQuantity s2 d2 a
liftQ a -> a
forall a. IEEE a => a -> a
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 d a -> Quantity d a -> Quantity d a
bisectIEEE (Quantity x) (Quantity y) = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity (a -> Quantity d a) -> a -> Quantity d a
forall a b. (a -> b) -> a -> b
$ a -> a -> a
forall a. IEEE a => a -> a -> a
F.bisectIEEE a
x a
y

-- | Default @NaN@ quantity.
nan :: IEEE a => Quantity d a
nan :: Quantity d a
nan = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity a
forall a. IEEE a => a
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 :: Word64 -> Quantity d a
nanWithPayload = a -> Quantity d a
forall (s :: ExactPi') (d :: Dimension) a.
a -> Dimensional ('DQuantity s) d a
Quantity (a -> Quantity d a) -> (Word64 -> a) -> Word64 -> Quantity d a
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Word64 -> a
forall a. IEEE a => Word64 -> a
F.nanWithPayload

-- | The payload stored in a @NaN@ quantity. Undefined if the argument is not @NaN@.
nanPayload :: IEEE a => Quantity d a -> Word64
nanPayload :: Quantity d a -> Word64
nanPayload = a -> Word64
forall a. IEEE a => a -> Word64
F.nanPayload (a -> Word64) -> (Quantity d a -> a) -> Quantity d a -> Word64
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Quantity d a -> a
forall (s :: ExactPi') (d :: Dimension) a. SQuantity s d a -> a
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 :: Quantity d a -> Quantity d a -> Quantity d a
minNum = (a -> a -> a) -> Quantity d a -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension) (s3 :: ExactPi') (d3 :: Dimension).
(a -> a -> a)
-> SQuantity s1 d1 a -> SQuantity s2 d2 a -> SQuantity s3 d3 a
liftQ2 a -> a -> a
forall a. RealFloat a => a -> a -> a
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 :: Quantity d a -> Quantity d a -> Quantity d a
maxNum = (a -> a -> a) -> Quantity d a -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension) (s3 :: ExactPi') (d3 :: Dimension).
(a -> a -> a)
-> SQuantity s1 d1 a -> SQuantity s2 d2 a -> SQuantity s3 d3 a
liftQ2 a -> a -> a
forall a. RealFloat a => a -> a -> a
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 :: Quantity d a -> Quantity d a -> Quantity d a
minNaN = (a -> a -> a) -> Quantity d a -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension) (s3 :: ExactPi') (d3 :: Dimension).
(a -> a -> a)
-> SQuantity s1 d1 a -> SQuantity s2 d2 a -> SQuantity s3 d3 a
liftQ2 a -> a -> a
forall a. RealFloat a => a -> a -> a
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 :: Quantity d a -> Quantity d a -> Quantity d a
maxNaN = (a -> a -> a) -> Quantity d a -> Quantity d a -> Quantity d a
forall a (s1 :: ExactPi') (d1 :: Dimension) (s2 :: ExactPi')
       (d2 :: Dimension) (s3 :: ExactPi') (d3 :: Dimension).
(a -> a -> a)
-> SQuantity s1 d1 a -> SQuantity s2 d2 a -> SQuantity s3 d3 a
liftQ2 a -> a -> a
forall a. RealFloat a => a -> a -> a
F.maxNaN