将Haskell Word32 / 64中的IEEE 754浮点转换为Haskell Float / Double

将Haskell Word32 / 64中的IEEE 754浮点转换为Haskell Float / Double

在Haskell中, base库和Hackage包提供了将FloatDouble类型转换为二进制IEEE-754浮点数据的几种方法。 然而,这些方法的准确性,性能和可移植性尚不清楚。

对于旨在(de)跨平台串行化二进制格式的GHC目标库,处理IEEE-754浮点数据的最佳方法是什么?

途径

这些是我在现有库和在线资源中遇到的方法。

FFI封送

这是data-binary-ieee754包使用的方法。 由于FloatDoubleWord32Word64Storable每个实例,可以将源类型的值Word64外部缓冲区,然后peek目标类型的值:

toFloat :: (F.Storable word, F.Storable float) => word -> float
toFloat word = F.unsafePerformIO $ F.alloca $ \buf -> do
    F.poke (F.castPtr buf) word
    F.peek buf

在我的机器上这个工作,但我很沮丧地看到分配是为了完成强制执行。 此外,虽然这个解决方案不是唯一的,但是在这里有一个隐含的假设,IEEE-754实际上是内存中的表示。 包装中的测试给出了“我机器上的作品”的批准,但这不是理想的。

unsafeCoerce

使用内存中IEEE-754表示的同样隐含的假设,以下代码也获得“在我机器上的作品”密封:

toFloat :: Word32 -> Float
toFloat = unsafeCoerce

这样做不会像上述方法那样执行明确的分配,但文档说“您有责任确保新旧类型具有相同的内部表示”。 这个隐含的假设仍然在做所有的工作,在处理解除类型时更加艰巨。

unsafeCoerce#

拉伸可能被认为是“便携式”的极限:

toFloat :: Word -> Float
toFloat (W# w) = F# (unsafeCoerce# w)

这似乎是有效的,但似乎并不可行,因为它仅限于GHC.Exts类型。 绕过提升的类型是很好的,但这是所有可以说的。

encodeFloatdecodeFloat

这种方法有一个很好的属性绕过任何unsafe的名字,但似乎没有得到IEEE-754相当正确。 类似问题的以前的SO答案提供了一种简洁的方法,而ieee754-parser包在被弃用为有利于data-binary-ieee754之前使用了更一般的方法。

有一些吸引力的代码不需要隐含的基础表示假设,但是这些解决方案依赖于encodeFloatdecodeFloat ,这显然充满了不一致之处 。 我还没有找到解决这些问题的方法。

采纳答案:

Simon Marlow在GHC bug 2209中提到了另一种方法(也与Bryan O'Sullivan的回答相关)

您可以使用castSTUArray实现所需的效果(顺便说一下,这是我们在GHC中执行的方式)。

我在我的一些库中使用了这个选项,以避免FFI编组方法所需的unsafePerformIO

{-# 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

我强调了演员功能,因为这样做会导致GHC产生更紧密的核心。 wordToFloat联之后, wordToFloat被转换为对runSTRep和三个primops ( newByteArray#writeWord32Array#readFloatArray# )的readFloatArray#

我不知道与FFI编组方法相比,性能如何,但只是为了好玩,我比较了这两个选项生成的核心。

在这方面,进行FFI编组是比较复杂的。 它调用unsafeDupablePerformIO和7 primops( noDuplicate#newAlignedPinnedByteArray#unsafeFreezeByteArray#byteArrayContents#writeWord32OffAddr#readFloatOffAddr#touch# )。

我只是开始学习如何分析核心,也许有更多经验的人可以评论这些操作的成本?

参考更多解答:将Haskell Word32 / 64中的IEEE 754浮点转换为Haskell Float / Double,转载请保留将Haskell Word32 / 64中的IEEE 754浮点转换为Haskell Float / Double

更多:haskell