views:

350

answers:

4

I want to convert a Haskell Float to a String that contains the 32-bit hexadecimal representation of the float in standard IEEE format. I can't seem to find a package that will do this for me. Does anybody know of one?

I've noticed that GHC.Float offers a function to decompose a Float into its signed base and exponent (decodeFloat), but this provides a 14- and 8-digit hex number for the base and exponent, respectively, which takes up much more than 32 bits. This doesn't seem to help.

If there's an easier way to do this that I'm not seeing, please let me know.

+3  A: 

How about the float-ieee package on Hackage? http://hackage.haskell.org/package/data-binary-ieee754

Will print a 32 bit ieee754 string value representing the float passed in.

import Data.Binary.Put
import Data.Binary.IEEE754
import qualified Data.ByteString.Lazy.Char8 as S

main = do
    let s = runPut $ putFloat32be pi
    S.putStrLn s
Don Stewart
This prints "Pi" as "@I". Is this a different hex representation?
Jeremy
It isn't hex. Its just a bytestring. You need a different library/function to print the bytestring value in hex.
sclv
+1  A: 

I think you accidentally decoded a Double instead of a Float. That's why it doesn't seem to fit.

newacct
+4  A: 

The float-ieee package is pure Haskell-98, but very CPU intensive. If you are going to need to do this many times, and don't mind being GHC specific, then you use code like this, which extracts the IEEE representation of a Double as a Word64:

import GHC.Prim
import GHC.Types
import GHC.Word

encodeIEEEDouble :: Double -> Word64
encodeIEEEDouble (D# x) = W64# (unsafeCoerce# x)

decodeIEEEDouble :: Word64 -> Double
decodeIEEEDouble (W64# x) = D# (unsafeCoerce# x)

You can code something similar for Float and Word32.

MtnViewMark
Technically, this also assumes that `Double` s are represented in hardware in IEEE format, but that's probably the case for every platform that GHC runs on. You will also need to be aware of endianness issues regarding the machine representation of `Word64`.
Reid Barton
A: 

There are a few different ways you can do it, depending on your taste. Using a library like Don mentioned is probably the best option, otherwise you can try something along the lines of these:

doubleToBytes :: Double -> [Int]
doubleToBytes d
   = runST (do
        arr <- newArray_ ((0::Int),7)
        writeArray arr 0 d
        arr <- castDoubleToWord8Array arr
        i0 <- readArray arr 0
        i1 <- readArray arr 1
        i2 <- readArray arr 2
        i3 <- readArray arr 3
        i4 <- readArray arr 4
        i5 <- readArray arr 5
        i6 <- readArray arr 6
        i7 <- readArray arr 7
        return (map fromIntegral [i0,i1,i2,i3,i4,i5,i6,i7])
     )

-- | Store to array and read out individual bytes of array
dToStr :: Double -> String
dToStr d
  = let bs     = doubleToBytes d
        hex d' = case showHex d' "" of
                     []    -> error "dToStr: too few hex digits for float"
                     [x]   -> ['0',x]
                     [x,y] -> [x,y]
                     _     -> error "dToStr: too many hex digits for float"

        str  = map toUpper $ concat . fixEndian . (map hex) $ bs
    in  "0x" ++ str

-- | Create pointer to Double and cast pointer to Word64, then read out
dToStr2 :: Double -> IO String
dToStr2 f = do
    fptr <- newStablePtr f
    let pptr = castStablePtrToPtr fptr
    let wptr = (castPtrToStablePtr pptr)::(StablePtr Word64)
    w <- deRefStablePtr wptr
    let s = showHex w ""
    return ("0x" ++ (map toUpper s))

-- | Use GHC specific primitive operations
dToStr3 :: Double -> String
dToStr3 (D# f) = "0x" ++ (map toUpper $ showHex w "")
    where w = W64# (unsafeCoerce# f)

Three different ways. Last is GHC specific. Other two may work with other Haskell compilers but are relying a little on the underlying implementation so hard to guarantee.

David Terei