How to write IEEE doubles to a binary file in Haskell

Haskell is a great language, but it’s got a steep learning curve. Most of the Haskell examples and tutorials present trivial programs and, as when learning any new language, the best way to get your feet wet is to try to solve an everyday problem. Unfortunately, if your everyday language is imperative (which it probably is) then Haskell is going to leave you without many of the basic things you take for granted when you get things done.

In my case, I needed to run a computation involving numerical integration using IEEE double precision floating point values, and output the resulting table of values to a file for visualization. Implementing this computation in Haskell was great fun. However, when it came time to store the output I discovered that Haskell’s IO libraries are very text-oriented. Binary IO functionality was not to be found.

I googled around, as one does, and came up empty-handed. There is a module called Data.Binary, but its purpose is serialization rather than raw IO, so the binary files it produces include header data. The Data.Array.IO module supports doubles, but only supports IO on bytes, and does not properly maintain the array length when casting from double. Finally, there’s this ridiculous mailing list thread that actually presents code to pack and unpack IEEE values from raw bytes. I boggled at the notion of a language that forces the programmer to implement their own IEEE 754 packer.

Fortunately, I was looking in the wrong place. It turns out that Haskell categorizes double IO functionality as “foreign”. The Haskell Foreign Function Interface defines sufficient functionality to pack doubles into a buffer and output them to a file. I guess that makes sense if you consider the operating system (or the floating point processor itself) to be something apart from the Haskell system.

So here’s some code. It writes the numbers 1 through 8 to a file named foo.dat. The System.IO import provides the file handle and the withBinaryFile function to open and close it. Foreign.Ptr defines the concept of a pointer to an array of doubles. Foreign.Storable provides peek and poke functions to manipulate that array. Foreign.Marshal.Array brings malloc to the party.

import System.IO
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Array

writebin h = do a <- mallocArray 8 :: IO (Ptr Double)
                pokeElemOff a 0 1.0
                pokeElemOff a 1 2.0
                pokeElemOff a 2 3.0
                pokeElemOff a 3 4.0
                pokeElemOff a 4 5.0
                pokeElemOff a 5 6.0
                pokeElemOff a 6 7.0
                pokeElemOff a 7 8.0
                hPutBuf h a 64

main = withBinaryFile "foo.dat" WriteMode writebin

And that's all I need. I hope you can stop googling now and get something done.

One Comment

  1. Erik:

    There is also the package data-binary-ieee754 on hackage, which does exactly this (and more).

Leave a comment