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

在Haskell中, base库和Hackage软件包提供了将二进制IEEE-754浮点数据转换为FloatDoubletypes浮点数据的多种方法。 但是,这些方法的准确性,性能和可移植性还不清楚。

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

途径

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

FFI封送

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

 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 

在我的机器上这工作,但我只看到分配执行,只是为了完成强制。 另外,虽然这个解决scheme并不是唯一的,但是这里有一个隐含的假设,即IEEE-754实际上是内存中的表示。 伴随包装的testing给了它“在我的机器上工作”认可的印章,但是这不是理想的。

unsafeCoerce

与内存中的IEEE-754表示相同的隐含假设,下面的代码也得到了“在我的机器上工作”的封印:

 toFloat :: Word32 -> Float toFloat = unsafeCoerce 

这样做的好处是不像上面的方法那样进行明确的分配,但是文件中说“确保新旧types具有相同的内部表示是你的责任”。 这种隐含的假设仍然在做所有的工作,而且在处理被取消的types时更加费力。

unsafeCoerce#

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

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

这似乎工作,但似乎并不实际,因为它只限于GHC.Extstypes。 我们很高兴绕过这些举起来的types,但这就是所有可以说的。

encodeFloatdecodeFloat

这种方法具有绕过名称中unsafe任何东西的unsafe ,但似乎没有得到IEEE-754的正确。 之前的对于类似问题的回答提供了一个简洁的方法, ieee754-parser包在使用data-binary-ieee754之前,采用了更为通用的方法。

有一些代码不需要关于底层表示的隐含假设,但是这些解决scheme依赖于encodeFloatdecodeFloat ,这些显然充满了矛盾 。 我还没有find解决这些问题的方法。

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 sb cast x = newArray (0 :: Int, 0) x >>= castSTUArray >>= flip readArray 0 

因为这样做会导致GHC产生更紧密的核心,所以我将其强化为演员function。 内联后, wordToFloat被转换为runSTRep和三个primops ( newByteArray#writeWord32Array#readFloatArray# )的readFloatArray#

我不确定与FFI编组方法相比性能如何,但仅仅为了好玩,我比较了两种选项生成的核心。

在这方面做FFI编组是相当复杂一点的。 它调用unsafeDupablePerformIO和7个primops( noDuplicate#newAlignedPinnedByteArray#unsafeFreezeByteArray#byteArrayContents#writeWord32OffAddr#readFloatOffAddr#touch# )。

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

所有现代的CPU使用IEEE754浮点,这似乎不太可能在我们的有生之年改变。 所以不要担心代码做这个假设。

您肯定不能自由地使用unsafeCoerceunsafeCoerce#来在整数types和浮点types之间进行转换,因为这会导致编译失败和运行时崩溃。 有关详细信息,请参阅GHC错误2209 。

在解决对强制性强制要求的GHC缺陷4092是固定的,唯一安全可靠的方法是通过FFI。

我会使用FFI方法进行转换。 但是一定要在分配内存时使用alignment方式,以便获得浮点数和整数的加载/存储可接受的内存。 你也应该说一些关于float和word的大小是相同的,所以你可以检测是否有任何错误。

如果分配内存让你畏缩,你不应该使用Haskell。 🙂

我是data-binary-ieee754 。 它在某种程度上使用了三个选项中的每一个。

encodeFloatdecodeFloat在大多数情况下工作得很好,但是使用它们所需的附件代码增加了巨大的开销。 他们对NaNInfinity没有很好的反应,所以对于任何基于他们的演员,都需要一些GHC特定的假设。

unsafeCoerce是一个尝试更换,以获得更好的性能。 这真的很快,但是其他图书馆有重大问题的报道使我最终决定避免它。

FFI代码迄今为止是最可靠的,并且具有不俗的performance。 分配的开销并不像看起来那么糟糕,可能是由于GHC内存模型。 它实际上并不取决于浮点数的内部格式,而仅仅取决于Storable的行为。 只要Storable是IEEE-754,编译器就可以使用任何表示forms。 GHC在内部使用IEEE-754,我不再担心非GHC编译器,所以这是一个有争议的问题。

直到GHC开发人员认为适合给我们带来未经固定宽度的单词和相关的转换函数,FFI似乎是最好的select。