问题
在 Haskell 中,base
库和 Hackage 包提供了多种将二进制 IEEE-754 float 据与提升的 Float
和 Double< 相互转换的方法。/
类型。然而,这些方法的准确性、性能和可移植性尚不清楚。
对于旨在跨平台(反)序列化二进制格式的 GHC 目标库,处理 IEEE-754 float 据的最佳方法是什么?
方法
这些是我在现有图书馆和在线资源中遇到的方法。
FFI 编码
这是 data-binary-ieee754
使用的方法包裹。由于 Float
、Double
、Word32
和 Word64
都是 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
类型。绕过提升的类型很好,但只能说这么多了。
encodeFloat
和 decodeFloat
这种方法具有绕过名称中带有 unsafe
的任何内容的良好特性,但似乎不太正确地获得 IEEE-754。一个previous SO answer对类似问题提供了一种简洁的方法,并且 ieee754-parser
包在被弃用之前使用了更通用的方法,转而使用 data-binary-ieee754
。
拥有不需要对底层表示进行隐式假设的代码很有吸引力,但这些解决方案依赖于 encodeFloat
和 decodeFloat
,这显然是 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/