# How (not) to convert CDouble to Double

Published on

What’s wrong with the following code?

```
module Acos (acos) where
import Prelude hiding (acos)
import Foreign.C.Types (CDouble(..))
foreign import ccall "math.h acos" c_acos :: CDouble -> CDouble
acos :: Double -> Double
acos = realToFrac . c_acos . realToFrac
```

If you use QuickCheck to test the equivalence of `Acos.acos`

and `Prelude.acos`

, you’ll quickly find a counterexample:

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 `Infinity`

is `realToFrac`

. It is defined as follows:

Unlike `Double`

, `Rational`

, which is defined as a ratio of two Integers, has no way to represent special values such as `NaN`

. Instead, `toRational (acos 1.1)`

results in a fraction with some ridiculously large numerator, which turns into `Infinity`

when converted back to `Double`

.

When you compile with `-O1`

or higher, the following rewrite rules fire and avoid the round trip through `Rational`

:

```
"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 `Double`

and `CDouble`

. According to the Report, `CDouble`

is an abstract newtype, about which all you know is the list of instances, including `Real`

and `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 `coerce`

from `Data.Coerce`

or apply the data constructor `CDouble`

directly.

So here’s a reliable, but not portable, version of the `Acos`

module above: