packages feed

haskell-mpfr-0.1: src/Numeric/Rounded.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GHCForeignImportPrim #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE UnliftedFFITypes #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Numeric.Rounded
-- Copyright   :  (C) 2012-2014 Edward Kmett, Daniel Peebles
-- License     :  LGPL
-- Maintainer  :  Edward Kmett <ekmett@gmail.com>
-- Stability   :  experimental
-- Portability :  non-portable
--
----------------------------------------------------------------------------
module Numeric.Rounded
    (
    -- * floating point numbers with a specified rounding mode and precision
      Rounded(..)
    , fromInt
    , fromDouble
    -- * Precision
    , Precision(precision)
    , Bytes
    , reifyPrecision
    -- * Rounding
    , Rounding(rounding)
    , RoundingMode(..)
    , reifyRounding
    -- * Useful Constants
    , kLog2
    , kEuler
    , kCatalan
    -- * Combinators that are oblivious to precision
    , (.+.)
    , (.-.)
    , (.*.)
    , abs'
    , negate'
    , decodeFloat'
    , succUlp
    , predUlp
    ) where

import Data.Proxy
import Data.Bits
import GHC.Integer.GMP.Internals
import GHC.Integer.GMP.Prim
import GHC.Prim
import GHC.Types
import GHC.Real
import GHC.Int
import Numeric.Rounded.Precision
import Control.Parallel()
import Numeric.Rounded.Rounding
import System.IO.Unsafe()
import Numeric

prec# :: forall proxy p. Precision p => proxy p -> Int#
prec# p = case precision p of
  I# i# -> i#

mode# :: forall proxy r. Rounding r => proxy r -> Int#
mode# = case fromEnum (rounding (Proxy :: Proxy r)) of
  I# i# -> \_ -> i#

type CSignPrec#  = Int#
type CPrecision# = Int#
type CExp#       = Int#
type CRounding#  = Int#

prec_bit :: Int
prec_bit
  | b63 == 0  = b31
  | otherwise = b63
  where b63 = bit 63
        b31 = bit 31

type role Rounded phantom nominal

-- | A properly rounded floating-point number with a given rounding mode and precision.
--
-- you can 'Data.Coerce.coerce' to change rounding modes, but not precision.
data Rounded (r :: RoundingMode) p = Rounded
  { roundedSignPrec :: CSignPrec# -- Sign# << 64/32 | Precision#
  , roundedExp      :: CExp#
  , roundedLimbs    :: ByteArray#
  }

-- We could use this in a rewrite rule for fast conversions to Double...
-- foreign import prim "mpfr_cmm_get_d"       mpfr_cmm_get_d :: CRounding# -> CSignPrec# -> CExp# -> ByteArray# -> Double#

instance (Rounding r, Precision p) => Show (Rounded r p) where
  showsPrec _ = showFloat

-- | N.B.: similar to Unary, assumes that output precision is same as precsion of _second_ operand
type Binary
  = CRounding# ->
    CSignPrec# -> CExp# -> ByteArray# ->
    CSignPrec# -> CExp# -> ByteArray# ->
    (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_add" mpfrAdd# :: Binary
foreign import prim "mpfr_cmm_sub" mpfrSub# :: Binary
foreign import prim "mpfr_cmm_mul" mpfrMul# :: Binary
foreign import prim "mpfr_cmm_div" mpfrDiv# :: Binary
foreign import prim "mpfr_cmm_min" mpfrMin# :: Binary
foreign import prim "mpfr_cmm_max" mpfrMax# :: Binary

type Comparison
  = CSignPrec# -> CExp# -> ByteArray# ->
    CSignPrec# -> CExp# -> ByteArray# ->
    Int#

foreign import prim "mpfr_cmm_equal_p"         mpfrEqual#        :: Comparison
foreign import prim "mpfr_cmm_lessgreater_p"   mpfrNotEqual#     :: Comparison
foreign import prim "mpfr_cmm_less_p"          mpfrLess#         :: Comparison
foreign import prim "mpfr_cmm_greater_p"       mpfrGreater#      :: Comparison
foreign import prim "mpfr_cmm_lessequal_p"     mpfrLessEqual#    :: Comparison
foreign import prim "mpfr_cmm_greaterequal_p"  mpfrGreaterEqual# :: Comparison

cmp :: (CSignPrec# -> CExp# -> ByteArray# -> CSignPrec# -> CExp# -> ByteArray# -> Int#) -> Rounded r p -> Rounded r p -> Bool
cmp f (Rounded s e l) (Rounded s' e' l') = I# (f s e l s' e' l') /= 0

binary :: Rounding r => Binary -> Rounded r p -> Rounded r p -> Rounded r p
binary f (Rounded s e l) (Rounded s' e' l') = r where
  r = case f (mode# (proxyRounding r)) s e l s' e' l' of
    (# s'', e'', l'' #) -> Rounded s'' e'' l''

instance Eq (Rounded r p) where
  (==) = cmp mpfrEqual#
  (/=) = cmp mpfrNotEqual#

foreign import prim "mpfr_cmm_cmp" mpfrCmp# :: CSignPrec# -> CExp# -> ByteArray# -> CSignPrec# -> CExp# -> ByteArray# -> Int#

instance Rounding r => Ord (Rounded r p) where
  compare (Rounded s e l) (Rounded s' e' l') = compare (fromIntegral (I# (mpfrCmp# s e l s' e' l'))) (0 :: Int32)
  (<=) = cmp mpfrLessEqual#
  (>=) = cmp mpfrGreaterEqual#
  (<) = cmp mpfrLess#
  (>) = cmp mpfrGreater#
  -- we shed the Rounding r dependency if we drop these, but give wrong answers on negative 0
  min = binary mpfrMin#
  max = binary mpfrMax#

foreign import prim "mpfr_cmm_sgn" mpfrSgn# :: CSignPrec# -> CExp# -> ByteArray# -> Int#

infixl 6 .+., .-.
infixl 7 .*.

(.+.) :: Rounding r => Rounded r p -> Rounded r p -> Rounded r p
(.+.) = binary mpfrAdd#

(.-.) :: Rounding r => Rounded r p -> Rounded r p -> Rounded r p
(.-.) = binary mpfrSub#

(.*.) :: Rounding r => Rounded r p -> Rounded r p -> Rounded r p
(.*.) = binary mpfrMul#

abs' :: Rounded r p -> Rounded r p
abs' (Rounded s e l) = case I# s .&. complement prec_bit of
  I# s' -> Rounded s' e l

negate' :: Rounding r => Rounded r p -> Rounded r p
negate' = unary mpfrNeg#

instance (Rounding r, Precision p) => Num (Rounded r p) where
  (+) = binary mpfrAdd#
  (-) = binary mpfrSub#
  (*) = binary mpfrMul#
  negate = unary mpfrNeg#
  fromInteger (S# i) = case mpfrFromInt# (mode# (Proxy::Proxy r)) (prec# (Proxy::Proxy p)) i of
    (# s, e, l #) -> Rounded s e l
  fromInteger (J# i xs) = case mpfrFromInteger# (mode# (Proxy::Proxy r)) (prec# (Proxy::Proxy p)) i xs of
    (# s, e, l #) -> Rounded s e l
  abs (Rounded s e l) = case I# s .&. complement prec_bit of
    I# s' -> Rounded s' e l
  signum (Rounded s e l) = case compare (fromIntegral sgn) (0 :: Int32) of
    LT -> -1
    EQ -> 0
    GT -> 1
    where sgn = I# (mpfrSgn# s e l)

foreign import prim "mpfr_cmm_init_si" mpfrFromInt#
  :: CRounding# -> CPrecision# -> Int# -> (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_init_z" mpfrFromInteger#
  :: CRounding# -> CPrecision# -> Int# -> ByteArray# -> (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_init_q" mpfrFromRational#
  :: CRounding# -> CPrecision#
  -> Int# -> ByteArray#
  -> Int# -> ByteArray#
  -> (# CSignPrec#, CExp#, ByteArray# #)

instance (Rounding r, Precision p) => Fractional (Rounded r p) where
  fromRational x = case x of
    S# n# :% S# d# -> case int2Integer# n# of
      (# ns#, nl# #) -> case int2Integer# d# of
        (# ds#, dl# #) -> conv ns# nl# ds# dl#
    J# ns# nl# :% S# d# -> case int2Integer# d# of
      (# ds#, dl# #) -> conv ns# nl# ds# dl#
    S# n# :% J# ds# dl# -> case int2Integer# n# of
      (# ns#, nl# #) -> conv ns# nl# ds# dl#
    J# ns# nl# :% J# ds# dl# -> conv ns# nl# ds# dl#
    where
    conv :: Int# -> ByteArray# -> Int# -> ByteArray# -> Rounded r p
    conv ns# nl# ds# dl# =
      case mpfrFromRational# (mode# (Proxy::Proxy r)) (prec# (Proxy::Proxy p)) ns# nl# ds# dl# of
        (# s, e, l #) -> Rounded s e l

  (/) = binary mpfrDiv#

proxyRounding :: Rounded r p -> Proxy r
proxyRounding _ = Proxy

proxyPrecision :: Rounded r p -> Proxy p
proxyPrecision _ = Proxy

-- | Construct a properly rounded floating point number from an 'Int'.

-- TODO: shouldn't this take a rounding, too? It's conceivable that an Int might exceed the requested
-- precision, which would require rounding.
fromInt :: (Rounding r, Precision p) => Int -> Rounded r p
fromInt (I# i) = r where
  r = case mpfrFromInt# (mode# (proxyRounding r)) (prec# (proxyPrecision r)) i of
    (# s, e, l #) -> Rounded s e l

foreign import prim "mpfr_cmm_init_d" mfpr_cmm_init_d
  :: CRounding# -> CPrecision# -> Double# -> (# CSignPrec#, CExp#, ByteArray# #)

-- | Construct a rounded floating point number directly from a 'Double'.
fromDouble :: (Rounding r, Precision p) => Double -> Rounded r p
fromDouble (D# d) = r where
  r = case mfpr_cmm_init_d (mode# (proxyRounding r)) (prec# (proxyPrecision r)) d of
    (# s, e, l #) -> Rounded s e l

-- N.B.: This (and the corresponding CMM) assumes that you want same precision as the
-- operand. Is this what we want? All the standard Haskell typeclasses are homogeneous
-- in the type, and since the precision is recorded in the type, this seems like a safe
-- assumption, but perhaps someone might want to ask for a higher precision for output?
type Unary
  = CRounding# ->
    CSignPrec# -> CExp# -> ByteArray# ->
    (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_neg"   mpfrNeg#     :: Unary
foreign import prim "mpfr_cmm_log"   mpfrLog#     :: Unary
foreign import prim "mpfr_cmm_exp"   mpfrExp#     :: Unary
foreign import prim "mpfr_cmm_sqrt"  mpfrSqrt#    :: Unary
foreign import prim "mpfr_cmm_sin"   mpfrSin#     :: Unary
foreign import prim "mpfr_cmm_cos"   mpfrCos#     :: Unary
foreign import prim "mpfr_cmm_tan"   mpfrTan#     :: Unary
foreign import prim "mpfr_cmm_asin"  mpfrArcSin#  :: Unary
foreign import prim "mpfr_cmm_acos"  mpfrArcCos#  :: Unary
foreign import prim "mpfr_cmm_atan"  mpfrArcTan#  :: Unary
foreign import prim "mpfr_cmm_sinh"  mpfrSinh#    :: Unary
foreign import prim "mpfr_cmm_cosh"  mpfrCosh#    :: Unary
foreign import prim "mpfr_cmm_tanh"  mpfrTanh#    :: Unary
foreign import prim "mpfr_cmm_asinh" mpfrArcSinh# :: Unary
foreign import prim "mpfr_cmm_acosh" mpfrArcCosh# :: Unary
foreign import prim "mpfr_cmm_atanh" mpfrArcTanh# :: Unary

unary :: Rounding r => Unary -> Rounded r p -> Rounded r p
unary f (Rounded s e l) = r where
  r = case f (mode# (proxyRounding r)) s e l of
    (# s', e', l' #) -> Rounded s' e' l'
{-# INLINE unary #-}

type Inplace = CSignPrec# -> CExp# -> ByteArray# -> (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_nextabove" mpfrNextAbove# :: Inplace
foreign import prim "mpfr_cmm_nextbelow" mpfrNextBelow# :: Inplace

inplace :: Inplace -> Rounded r p -> Rounded r p
inplace f (Rounded s e l) = case f s e l of
  (# s', e', l' #) -> Rounded s' e' l'
{-# INLINE inplace #-}

succUlp, predUlp :: Rounded r p -> Rounded r p
succUlp = inplace mpfrNextAbove#
predUlp = inplace mpfrNextBelow#

type Constant = CRounding# -> CPrecision# -> (# CSignPrec#, CExp#, ByteArray# #)

foreign import prim "mpfr_cmm_const_pi"      mpfrConstPi#      :: Constant

constant :: (Rounding r, Precision p) => Constant -> Rounded r p
constant k = r where
  r = case k (mode# (proxyRounding r)) (prec# (proxyPrecision r)) of
      (# s, e, l #) -> Rounded s e l
{-# INLINE constant #-}

instance (Rounding r, Precision p) => Floating (Rounded r p) where
  pi    = constant mpfrConstPi#
  exp   = unary mpfrExp#
  sqrt  = unary mpfrSqrt#
  log   = unary mpfrLog#
  sin   = unary mpfrSin#
  tan   = unary mpfrTan#
  cos   = unary mpfrCos#
  asin  = unary mpfrArcSin#
  atan  = unary mpfrArcTan#
  acos  = unary mpfrArcCos#
  sinh  = unary mpfrSinh#
  tanh  = unary mpfrTanh#
  cosh  = unary mpfrCosh#
  asinh = unary mpfrArcSinh#
  atanh = unary mpfrArcTanh#
  acosh = unary mpfrArcCosh#

toRational' :: Rounded r p -> Rational
toRational' r
   | e > 0     = fromIntegral (s `shiftL` e)
   | otherwise = s % (1 `shiftL` negate e)
   where (s, e) = decodeFloat' r

instance (Rounding r, Precision p) => Real (Rounded r p) where
  toRational = toRational'

instance (Rounding r, Precision p) => RealFrac (Rounded r p) where
  properFraction r = (i, fromRational f) where
    (i, f) = properFraction (toRational r)

foreign import prim "mpfr_cmm_get_z_2exp" mpfrDecode#
  :: CSignPrec# -> CExp# -> ByteArray# -> (# CExp#, Int#, ByteArray# #)

foreign import prim "mpfr_cmm_init_z_2exp" mpfrEncode#
  :: CRounding# -> CPrecision# -> CExp# -> Int# -> ByteArray# -> (# CSignPrec#, CExp#, ByteArray# #)

type Test = CSignPrec# -> CExp# -> ByteArray# -> Int#

tst :: (CSignPrec# -> CExp# -> ByteArray# -> Int#) -> Rounded r p -> Bool
tst f (Rounded s e l) = I# (f s e l) /= 0
{-# INLINE tst #-}

foreign import prim "mpfr_cmm_nan_p"  mpfrIsNaN#  :: Test
foreign import prim "mpfr_cmm_inf_p"  mpfrIsInf#  :: Test
foreign import prim "mpfr_cmm_zero_p" mpfrIsZero# :: Test
foreign import prim "mpfr_cmm_atan2"  mpfrArcTan2# :: Binary

decodeFloat' :: Rounded r p -> (Integer, Int)
decodeFloat' (Rounded sp e l) = case mpfrDecode# sp e l of (# i, s, d #) -> (J# s d, I# i)

instance (Rounding r, Precision p) => RealFloat (Rounded r p) where
  floatRadix  _ = 2
  floatDigits _r = I# (prec# (Proxy::Proxy p))

  -- FIXME: this should do for now, but the real ones can change...
  floatRange _ = (fromIntegral (minBound :: Int32), fromIntegral (maxBound :: Int32))

  decodeFloat = decodeFloat'

  -- FIXME: encodeFloat appears broken, but I haven't figured out how yet
  encodeFloat (S# i)   (I# e) = r where
    r = case int2Integer# i of
          (# s, d #) -> case mpfrEncode# (mode# (proxyRounding r)) (prec# (proxyPrecision r)) e s d of
            (# s', e', l #) -> Rounded s' e' l
  encodeFloat (J# s d) (I# e) = r where
    r = case mpfrEncode# (mode# (proxyRounding r)) (prec# (proxyPrecision r)) e s d of
          (# s', e', l #) -> Rounded s' e' l
  isNaN = tst mpfrIsNaN#
  isInfinite = tst mpfrIsInf#
  isDenormalized _ = False
  isNegativeZero r@(Rounded s _ _) = tst mpfrIsZero# r && I# s .&. prec_bit /= 0
  isIEEE _ = True -- is this a lie? it mostly behaves like an IEEE float, despite being much bigger
  atan2 = binary mpfrArcTan2#

foreign import prim "mpfr_cmm_const_log2"    mpfrConstLog2#    :: Constant
foreign import prim "mpfr_cmm_const_euler"   mpfrConstEuler#   :: Constant
foreign import prim "mpfr_cmm_const_catalan" mpfrConstCatalan# :: Constant

-- | Natural logarithm of 2
kLog2 :: (Rounding r, Precision p) => Rounded r p
kLog2 = constant mpfrConstLog2#

-- | 0.577...
kEuler :: (Rounding r, Precision p) => Rounded r p
kEuler = constant mpfrConstEuler#

-- | 0.915...
kCatalan :: (Rounding r, Precision p) => Rounded r p
kCatalan = constant mpfrConstCatalan#