What’s wrong with the following code?
module Acos (acos) where import Prelude hiding (acos) import Foreign.C.Types (CDouble(..)) import ccall "math.h acos" c_acos :: CDouble -> CDouble foreign acos :: Double -> Double acos = realToFrac . c_acos . realToFrac
If you use QuickCheck to test the equivalence of
Prelude.acos, you’ll quickly find a counterexample:
> Prelude.acos 1.1 NaN > Acos.acos 1.1 Infinity
You might think this is a difference in the semantics of Haskell acos vs. C acos, but the acos(3) manpage disproves that:
If x is outside the range [-1, 1], a domain error occurs, and a NaN is returned.
Moreover, you’ll notice the discrepancy only when compiling the Haskell program with
-O0. If you compile with
-O1 or higher, both versions will result in
NaN. So what’s going on here?
What turns the
NaN turned into the
realToFrac. It is defined as follows:
realToFrac :: (Real a, Fractional b) => a -> b realToFrac = fromRational . toRational
Rational, which is defined as a ratio of two Integers, has no way to represent special values such as
toRational (acos 1.1) results in a fraction with some ridiculously large numerator, which turns into
Infinity when converted back to
When you compile with
-O1 or higher, the following rewrite rules fire and avoid the round trip through
"realToFrac/a->CDouble" realToFrac = \x -> CDouble (realToFrac x) "realToFrac/CDouble->a" realToFrac = \(CDouble x) -> realToFrac x "realToFrac/Double->Double" realToFrac = id :: Double -> Double
Unfortunately, the Haskell 2010 Report doesn’t give you any reliable way to convert between
CDouble. According to the Report,
CDouble is an abstract newtype, about which all you know is the list of instances, including
Fractional. So if you want to stay portable,
realToFrac seems to be the only solution available.
However, if you only care about GHC and its base library (which pretty much everyone is using nowadays), then you can take advantage of the fact that the constructor of the
CDouble newtype is exported. You can use
Data.Coerce or apply the data constructor
So here’s a reliable, but not portable, version of the
Acos module above:
module Acos (acos) where import Prelude hiding (acos) import Foreign.C.Types (CDouble(..)) import Data.Coerce (coerce) import ccall "math.h acos" c_acos :: CDouble -> CDouble foreign acos :: Double -> Double acos = coerce c_acos