Converting a ByteString to a Double using FFI

Submitted by mrd on Tue, 12/18/2007 - 3:36pm.

ByteString, the ever-useful fast string library, has some nice functions for reading Ints which make reading in test data a breeze. However, there's no similar function for Doubles which has caused some recent troubles for me when dealing with floating point data. The C library has a function for this task: strtod. So I decided to take some older but similar FFI code and mold it into an interface to strtod.

{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign
import Foreign.C.Types
import Data.ByteString.Base

foreign import ccall unsafe "stdlib.h strtod"
c_strtod :: Ptr Word8 -> Ptr (Ptr Word8) -> IO CDouble

strtod :: ByteString -> Maybe (CDouble, ByteString)
strtod str = inlinePerformIO .
withForeignPtr x $ \ p ->
alloca (\ p_endptr -> do
let p_s = p `plusPtr` s
n <- c_strtod p_s p_endptr
endptr <- peek p_endptr
let diff = endptr `minusPtr` p_s
if diff == 0
then return Nothing
else return $ Just (n, PS x (s + diff) (l - diff)))
where (x, s, l) = toForeignPtr str

The type is chosen in particular to be convenient for usage with unfoldr.

The code probably looks more complicated than it is. Since ByteStrings are really stored as ForeignPtr Word8 (along with start-offset and length), it is easy to grab the raw form for usage by C functions. withForeignPtr lets you manipulate the raw Ptr Word8 within a function. Pointer arithmetic is performed using functions like plusPtr. The second parameter to strtod is actually an "out" parameter which sets a pointer to the spot it finished reading. I allocate space to store a pointer, namely a Ptr (Ptr Word8) and Haskell can figure out how much space it needs from the inferred type. I peek to retrieve the "returned" end-pointer. Then it's just a matter of putting back together the ByteString (PS for "Packed String") with the new offset and length.