4

I'm trying to parse a binary file format in Haskell (Apple's binary property list format), and one of the things required by the format is to treat sequences of bytes as either (a) unsigned 1-, 2-, or 4-byte integers; (b) signed 8-byte integers; (c) 32-bit floats; and (d) 64-bit doubles. Converting sequences of bytes to unsigned integers is easy, and even dealing with signed integers wouldn't be terrible. But for signed integers, and especially for Floats and Doubles, I don't really want to implement the logic myself. I've been able to find functions int2Float# :: Int# -> Float# and int2Double# :: Int# -> Double# in GHC.Prim, but these don't seem ideal (I don't particularly want to be working with unboxed types). My hope is that there's some way to cast from either a [Word8] or Word32s/Word64s. Are there any functions of type Word32 -> Float, Word64 -> Double, Word64 -> Int64, or similar?

Antal Spector-Zabusky
  • 36,191
  • 7
  • 77
  • 140

2 Answers2

5

If you aren't aware, fromIntegral converts integrals perfectly well. Also, the binary package and associated data-binary-ieee754 package are very applicable to your problem.

λ> :set -XOverloadedStrings
λ> import           Data.Binary.Get       (runGet)
λ> import qualified Data.Binary.IEEE754   as I
λ> runGet I.getFloat32le "\STX\SOH\SOH\SOH"
2.369428e-38
λ> runGet I.getFloat32le "\STX\SOH\SOH\SOHtrailing characters are ignored"
2.369428e-38
λ> runGet I.getFloat32le "\STX\SOH\SOH" -- remember to use `catch`:
*** Exception: Data.Binary.Get.runGet at position 0: not enough bytes
CallStack (from HasCallStack):
  error, called at libraries/binary/src/Data/Binary/Get.hs:351:5 in binary-0.8.5.1:Data.Binary.Get
unhammer
  • 4,306
  • 2
  • 39
  • 52
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • Looks like `Data.Binary.IEEE754` effectively does `unsafePerformIO $ peek . castPtr . poke`, which is about as safe as `unsafeCoerce`. Still useful, though – it means that `-0` and `NaN` actually round-trip correctly. – ephemient Jan 10 '11 at 04:20
  • 3
    And it doesn't require beginners to know about / directly use `unsafeCoerce`. – Thomas M. DuBuisson Jan 10 '11 at 04:30
  • I didn't realize `fromIntegral` would work with the fixed-width types—I should have tested that. – Antal Spector-Zabusky Jan 10 '11 at 05:40
  • Inspired by [this question](http://stackoverflow.com/questions/6976684/converting-ieee-754-floating-point-in-haskell-word32-64-to-and-from-haskell-floa), I have released a package on Hackage that implements reinterpretation casts, and tries to stay updated with the fastest implementation known: http://hackage.haskell.org/package/reinterpret-cast – nh2 Apr 30 '14 at 14:58
1

Unsafe.Coerce.unsafeCoerce can convert between types, like C++'s reinterpret_cast<>. Use with caution.

Otherwise, you can implement your own IEEE-754 decoding using RealFloat.

bitsAsIEEE754 :: (Bits a, Integral a, RealFloat b) => a -> b
bitsAsIEEE754 word =
    assert (floatRadix float == 2) $
    assert (bitSize word == 1 + es + ms) $
    assert (1 `shiftL` es == maxE - minE + 3) $
    float
  where
    ms = floatDigits float - 1
    (minE, maxE) = floatRange float
    es = length $ takeWhile (< maxE - minE + 2) $ iterate (* 2) 1
    sgn = if testBit word (ms + es) then negate else id
    e = fromIntegral $ word `shiftR` ms .&. (1 `shiftL` es - 1)
    nor = if e == 0 then id else flip setBit ms
    m = sgn . toInteger . nor $ word .&. (1 `shiftL` ms - 1)
    float = encodeFloat m $ max minE (e + minE - 1) - ms - 1

At least with my GHC, it doesn't seem possible to create -0 and NaN using encodeFloat, but everything else should work.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 1
    `unsafeCoerce` is not correct for floating <-> integral conversion, see [this question](http://stackoverflow.com/questions/6976684/converting-ieee-754-floating-point-in-haskell-word32-64-to-and-from-haskell-floa) and [here](https://ghc.haskell.org/trac/ghc/ticket/2209). – nh2 Apr 30 '14 at 00:12