问题
在Haskell中, base
库和Hackage包提供了几种将二进制IEEE-754浮点数据转换为提升的 Float
和 Double
类型的方法 . 但是,这些方法的准确性,性能和可移植性尚不清楚 .
对于旨在(反)序列化跨平台二进制格式的GHC目标库,处理IEEE-754浮点数据的最佳方法是什么?
方法
这些是我在现有库和在线资源中遇到的方法 .
FFI Marshaling
这是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实际上是内存中的表示 . 包装附带的测试给它“在我的机器上工作”的批准印章,但这并不理想 .
unsafeCoerce
使用内存中IEEE-754表示的相同隐含假设,以下代码也获得了“在我的机器上工作”的封条:
toFloat :: Word32 -> Float
toFloat = unsafeCoerce
这样做的好处是不像上面的方法那样执行显式分配,但documentation表示"it is your responsibility to ensure that the old and new types have identical internal representations" . 这个隐含的假设仍然在做所有的工作,在处理提升的类型时更加费劲 .
unsafeCoerce#
扩展可能被视为“便携”的限制:
toFloat :: Word -> Float
toFloat (W# w) = F# (unsafeCoerce# w)
这似乎有效,但不限于GHC.Exts类型 . 关于所有可以说的话,它都是's nice to bypass the lifted types, but that' .
encodeFloat和decodeFloat
这种方法具有绕过名称中 unsafe
的任何东西的良好属性,但似乎没有使IEEE-754非常正确 . 类似问题的previous SO answer提供了一种简洁的方法,ieee754-parser包在被弃用之前使用了更为通用的方法,而有利于 data-binary-ieee754
.
对于不需要隐含关于底层表示的假设的代码有很大的吸引力,但这些解决方案依赖于 encodeFloat
和 decodeFloat
,这显然是fraught with inconsistencies . 我还没有找到解决这些问题的方法 .
4 回答
Simon Marlow在_831777中提到了另一种方法(也与Bryan O _831778的答案有关)
我在我的一些库中使用了这个选项,以避免FFI编组方法所需的
unsafePerformIO
.我内联了强制转换函数,因为这样做会导致GHC产生更紧密的核心 . 内联后,
wordToFloat
被转换为对runSTRep的调用和三个primops(newByteArray#
,writeWord32Array#
,readFloatArray#
) .与FFI编组方法相比,我不确定性能是什么样的,但为了好玩,我比较了核心generated by both options .
在这方面,做FFI编组操作要复杂得多 . 它调用unsafeDupablePerformIO和7个primops(
noDuplicate#
,newAlignedPinnedByteArray#
,unsafeFreezeByteArray#
,byteArrayContents#
,writeWord32OffAddr#
,readFloatOffAddr#
,touch#
) .我刚刚开始学习如何分析核心,也许有经验的人可以评论这些操作的成本?
所有现代CPU都使用IEEE754作为浮点,这似乎不太可能在我们的生命周期内改变 . 所以不要担心代码做出这个假设 .
您绝对不能自由地使用
unsafeCoerce
或unsafeCoerce#
来在积分和浮点类型之间进行转换,因为这会导致编译失败和运行时崩溃 . 有关详细信息,请参阅GHC bug 2209 .直到GHC bug 4092,解决了对强制执行的需求,是唯一安全可靠的方法是通过FFI .
我是
data-binary-ieee754
的作者 . 它在某些时候使用了三个选项中的每一个 .encodeFloat
和decodeFloat
在大多数情况下运行良好,但使用它们所需的附件代码会增加巨大的开销 . 它们对NaN
或Infinity
反应不佳,因此任何基于它们的演员都需要一些GHC特定的假设 .unsafeCoerce
是一次尝试替换,以获得更好的性能 . 这真的很快,但其他图书馆有重大问题的报道让我最终决定避免它 .到目前为止,FFI代码是最可靠的,并且具有良好的性能 . 分配的开销并不像看起来那么糟糕,可能是由于GHC内存模型 . 它实际上并不依赖于浮点数的内部格式,而只取决于
Storable
实例的行为 . 只要Storable
是IEEE-754,编译器就可以使用它想要的任何表示 . GHC无论如何都在内部使用IEEE-754,我也没有任何意义点 .直到GHC开发人员认为适合赋予我们未固定的固定宽度字,并具有相关的转换功能,FFI似乎是最好的选择 .
我使用FFI方法进行转换 . 但是一定要在分配内存时使用对齐,这样就可以获得浮点数和整数的加载/存储都可以接受的内存 . 你还应该放一些关于float和word的大小相同的断言,这样你就可以检测出是否有什么问题 .
如果分配内存让你感到畏缩,你不应该使用Haskell . :)