2 - Copyright (C) 2009 Nick Bowler.
4 - License BSD2: 2-clause BSD license. See LICENSE for full terms.
5 - This is free software: you are free to change and redistribute it.
6 - There is NO WARRANTY, to the extent permitted by law.
9 -- | Access to the floating point environment. Performing this access within
10 -- a Haskell program turns out to be extremely problematic, because floating
11 -- point operations are secretly impure. For example, the innocent-looking
14 -- @ (+) :: Double -> Double -> Double@
16 -- potentially both depends on and modifies the global floating point
19 -- This module avoids the referential transparency problems that occur as a
20 -- result of accessing the floating point environment by restricting when
21 -- computations which access it are evaluated. There is some minor discipline
22 -- required of the programmer: she must arrange her code so that pure floating
23 -- point expressions are not forced during a call to 'fenvExec'.
25 -- FEnv instances the numeric classes, so it should be possible to use
26 -- natural syntax. Note that the operations done on FEnv are stored so that
27 -- they can be performed later, thus one should be take care not to construct
28 -- huge thunks when using this interface. Floating point exceptions are not
29 -- handled yet, so anything in this documentation which refers to \"the
30 -- floating point environment\" actually means \"the current rounding mode\".
32 -- Multi-threaded programs are almost certainly /not/ supported at this time.
33 {-# LANGUAGE ForeignFunctionInterface, ExistentialQuantification #-}
34 {-# INCLUDE "cfloat.h" #-}
35 module Data
.Floating
.Environment
(
36 RoundingMode
(..), FEnv
,
37 fenvEval
, withRoundingMode
, fenvTrace
,
38 unsafeSetRoundingMode
, getRoundingMode
41 import Prelude
hiding (Float, Double, Floating
(..), RealFloat
(..))
43 import Data
.Floating
.Classes
44 import Control
.Exception
45 import Control
.Applicative
48 import System
.IO.Unsafe
53 foreign import ccall unsafe
"set_roundmode"
54 set_roundmode
:: CInt
-> IO CInt
55 foreign import ccall unsafe
"get_roundmode"
56 get_roundmode
:: IO CInt
58 data RoundingMode
= ToNearest | Upward | Downward | TowardZero
59 deriving (Show, Read, Enum
, Bounded
)
61 -- | Container for computations which will be run in a modified floating point
62 -- environment. The FEnv container records all operations for later evaluation
63 -- by 'fenvEval'. Do not use the 'Eq' or 'Show' instances, they are provided
64 -- only because Num requires them.
65 data FEnv a
= forall b
. FEnv
(b
-> a
) b
67 -- In the following instances, the two FEnv parts must be bashed together
68 -- exactly once every time the contained value is extracted. Care must be
69 -- taken to avoid memoization of this result. Interestingly, FEnv is not an
70 -- instance of Monad: While join (FEnv f x) = f x has the right type, it does
71 -- not satisfy this important property.
73 instance Functor FEnv
where
74 fmap f
(FEnv g x
) = FEnv
(f
. g
) x
76 instance Applicative FEnv
where
77 pure x
= seq x
(FEnv
id x
)
78 (FEnv f x
) <*> (FEnv g y
) = FEnv
(\(x
',y
') -> f x
' . g
$ y
') (x
, y
)
80 -- For hysterical raisins, we need to instance Eq and Show since they are
81 -- superclasses of Num.
82 instance Eq a
=> Eq
(FEnv a
) where
83 (==) = error "The Eq instance for FEnv is a lie."
84 instance Show a
=> Show (FEnv a
) where
85 show = const "<<FEnv>>"
87 instance Num a
=> Num
(FEnv a
) where
94 fromInteger = pure
. fromInteger
96 instance Fractional a
=> Fractional
(FEnv a
) where
99 fromRational = pure
. fromRational
101 instance Floating a
=> Floating
(FEnv a
) where
119 instance RealFloat a
=> RealFloat
(FEnv a
) where
121 copysign
= liftA2 copysign
122 nextafter
= liftA2 nextafter
136 lgamma
= liftA lgamma
137 tgamma
= liftA tgamma
139 classify
= error "classify is not supported on FEnv."
140 fquotRem
= error "fquotRem is not supported on FEnv."
142 -- | Sets the floating point rounding mode. This function is considered unsafe
143 -- because it affects unevaluated thunks, breaking referential transparency.
144 unsafeSetRoundingMode
:: RoundingMode
-> IO ()
145 unsafeSetRoundingMode mode
= do
146 rc
<- set_roundmode
(fromIntegral (fromEnum mode
))
147 unless (rc
== 0) $ fail "Error setting rounding mode"
149 -- | Gets the current floating point rounding mode.
150 getRoundingMode
:: IO RoundingMode
153 unless (rc
>= 0) $ fail "Error getting rounding mode"
154 return . toEnum . fromIntegral $ rc
156 -- | Evaluate an FEnv using a specific rounding mode. Rounding mode selections
157 -- nest: subcomputations might use another mode. The default rounding mode is
159 withRoundingMode
:: RoundingMode
-> FEnv a
-> FEnv a
160 withRoundingMode mode
(FEnv f x
) = FEnv unsafePerformIO
$ do
161 oldMode
<- getRoundingMode
162 unsafeSetRoundingMode mode
164 unsafeSetRoundingMode oldMode
167 -- | This function is to help with debugging the floating point environment
168 -- handling. @fenvTrace msg x@ constructs an FEnv value containing @x@ that
169 -- prints @msg@ (using 'Debug.Trace.trace') whenever the value is extracted.
170 fenvTrace
:: String -> a
-> FEnv a
171 fenvTrace s
= fmap (trace s
) . pure
173 -- | Runs all the computations which are recorded in an FEnv container. The
174 -- floating point environment is preserved across this call.
175 fenvEval
:: FEnv a
-> IO a
176 fenvEval
(FEnv f x
) = evaluate
$ f x