Converting IEEE 754 floating point in Haskell Word32/64 to and from Haskell Float/Double

半腔热情 提交于 2019-11-28 20:06:39

Simon Marlow mentions another approach in GHC bug 2209 (also linked to from Bryan O'Sullivan's answer)

You can achieve the desired effect using castSTUArray, incidentally (this is the way we do it in GHC).

I've used this option in some of my libraries in order to avoid the unsafePerformIO required for the FFI marshalling method.

{-# LANGUAGE FlexibleContexts #-}

import Data.Word (Word32, Word64)
import Data.Array.ST (newArray, castSTUArray, readArray, MArray, STUArray)
import GHC.ST (runST, ST)

wordToFloat :: Word32 -> Float
wordToFloat x = runST (cast x)

floatToWord :: Float -> Word32
floatToWord x = runST (cast x)

wordToDouble :: Word64 -> Double
wordToDouble x = runST (cast x)

doubleToWord :: Double -> Word64
doubleToWord x = runST (cast x)

{-# INLINE cast #-}
cast :: (MArray (STUArray s) a (ST s),
         MArray (STUArray s) b (ST s)) => a -> ST s b
cast x = newArray (0 :: Int, 0) x >>= castSTUArray >>= flip readArray 0

I inlined the cast function because doing so causes GHC to generate much tighter core. After inlining, wordToFloat is translated to a call to runSTRep and three primops (newByteArray#, writeWord32Array#, readFloatArray#).

I'm not sure what performance is like compared to the FFI marshalling method, but just for fun I compared the core generated by both options.

Doing FFI marshalling is a fair bit more complicated in this regard. It calls unsafeDupablePerformIO and 7 primops (noDuplicate#, newAlignedPinnedByteArray#, unsafeFreezeByteArray#, byteArrayContents#, writeWord32OffAddr#, readFloatOffAddr#, touch#).

I've only just started learning how to analyse core, perhaps someone with more experience can comment on the cost of these operations?

Bryan O'Sullivan

All modern CPUs use IEEE754 for floating point, and this seems very unlikely to change within our lifetime. So don't worry about code making that assumption.

You are very definitely not free to use unsafeCoerce or unsafeCoerce# to convert between integral and floating point types, as this can cause both compilation failures and runtime crashes. See GHC bug 2209 for details.

Until GHC bug 4092, which addresses the need for int↔fp coercions, is fixed, the only safe and reliable approach is via the FFI.

I'm the author of data-binary-ieee754. It has at some point used each of the three options.

encodeFloat and decodeFloat work well enough for most cases, but the accessory code required to use them adds tremendous overhead. They do not react well to NaN or Infinity, so some GHC-specific assumptions are required for any casts based upon them.

unsafeCoerce was an attempted replacement, to get better performance. It was really fast, but reports of other libraries having significant problems made me eventually decide to avoid it.

The FFI code has so far been the most reliable, and has decent performance. The overhead of allocation isn't as bad as it seems, likely due to the GHC memory model. And it actually doesn't depend on the internal format of floats, merely on the behavior of the Storable instance. The compiler can use whatever representation it wants as long as Storable is IEEE-754. GHC uses IEEE-754 internally anyway, and I don't worry much about non-GHC compilers any more, so it's a moot point.

Until the GHC developers see fit to bestow upon us unlifted fixed-width words, with associated conversion functions, FFI seems the best option.

I'd use the FFI method for conversion. But be sure to use the alignment when allocating memory so you get memory that is acceptable for load/store for both the floating point number and the integer. You should also put in some assert about the sizes of the float and word being the same so you can detect if anything goes wrong.

If allocating memory makes you cringe you should not be using Haskell. :)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!