haskell - 将 Haskell Word32/64 中的 IEEE 754 浮点与 Haskell Float/Double 相互转换

标签 haskell floating-point ghc ieee-754

问题

在 Haskell 中,base 库和 Hackage 包提供了多种将二进制 IEEE-754 float 据与提升的 FloatDouble< 相互转换的方法。/ 类型。然而,这些方法的准确性、性能和可移植性尚不清楚。

对于旨在跨平台(反)序列化二进制格式的 GHC 目标库,处理 IEEE-754 float 据的最佳方法是什么?

方法

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

FFI 编码

这是 data-binary-ieee754 使用的方法包裹。由于 FloatDoubleWord32Word64 都是 Storable 的实例,因此可以poke将源类型的值放入外部缓冲区,然后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 实际上是内存中的表示形式。包装附带的测试给了它“在我的机器上运行”的批准印章,但这并不理想。

不安全强制

使用内存中 IEEE-754 表示的相同隐式假设,以下代码也获得“在我的机器上运行”印章:

toFloat :: Word32 -> Float
toFloat = unsafeCoerce

这样做的好处是不像上面的方法那样执行显式分配,但是documentation说“你有责任确保新旧类型具有相同的内部表示”。这个隐含的假设仍然在完成所有工作,并且在处理提升类型时更加费力。

unsafeCoerce#

扩展“可移植”的限制:

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

这似乎可行,但似乎根本不实用,因为它仅限于 GHC.Exts类型。绕过提升的类型很好,但只能说这么多了。

encodeFloatdecodeFloat

这种方法具有绕过名称中带有 unsafe 的任何内容的良好特性,但似乎不太正确地获得 IEEE-754。一个previous SO answer对类似问题提供了一种简洁的方法,并且 ieee754-parser包在被弃用之前使用了更通用的方法,转而使用 data-binary-ieee754

拥有不需要对底层表示进行隐式假设的代码很有吸引力,但这些解决方案依赖于 encodeFloatdecodeFloat,这显然是 fraught with inconsistencies 。我还没有找到解决这些问题的方法。

最佳答案

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

You can achieve the desired effect using castSTUArray, incidentally (this is the way we do it in 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 被转换为对 runSTRep 的调用。和三个primops (newByteArray#writeWord32Array#readFloatArray#)。

我不确定与 FFI 编码方法相比性能如何,但只是为了好玩我比较了核心 generated by both options .

在这方面,进行 FFI 编码要复杂一些。它调用unsafeDupablePerformIO和 7 个 primops(noDuplicate#newAlignedPinnedByteArray#unsafeFreezeByteArray#byteArrayContents#writeWord32OffAddr#, readFloatOffAddr#, touch#)。

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

关于haskell - 将 Haskell Word32/64 中的 IEEE 754 浮点与 Haskell Float/Double 相互转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6976684/

相关文章:

haskell - cabal的 "Warning: Falling back to topdown solver for GHC < 7."是什么意思?

haskell - Haskell 标准库类型类的替代实现

java - JAX-WS 2.0 - 从 xsd :double to xsd:decimal 迁移

haskell : understanding "No instance for" error messages in ghci

c - float 给出非常大的答案(C)

floating-point - 为什么 float 不正确?

Haskell 空间溢出

haskell - 两次函数调用但只显示一条跟踪

haskell - 为列表创建 TypeClass 实例

haskell - 在 Haskell 中绑定(bind)部分应用的函数