I wrote a function to convert a 64-bit Double to ByteString (architecture / type security is not really a problem), suppose Double is a 64-bit Word). Although the function below works well, I am wondering if there is a faster way to convert Double to ByteString. In the code below, there is one unpacking of Word64 into a Word8 list, followed by the opposite (to make it a small final format), and then packaging in ByteString. Code below:
{-# LANGUAGE MagicHash #-} import GHC.Prim import GHC.Types import GHC.Word import Data.Bits (shiftR) import Data.ByteString (pack, unpack) import Data.ByteString.Internal (ByteString) import Text.Printf (printf) encodeDouble :: Double -> ByteString encodeDouble (D# x) = pack $ reverse $ unpack64 $ W64# (unsafeCoerce# x) unpack64 :: Word64 -> [Word8] unpack64 x = map (fromIntegral.(shiftR x)) [56,48..0] -- function to convert list of bytestring into hex digits - for debugging bprint :: ByteString -> String bprint x = ("0x" ++ ) $ foldl (++) "" $ fmap (printf "%02x") $ unpack x main = putStrLn $ bprint $ encodeDouble 7234.4
Sample GHCi output on Mac x86:
*Main> bprint $ encodeDouble 7234.4 "0x666666666642bc40"
While the code works well, I plan to use it to encode a Double values ββbatch in a ByteString before sending it through IPC. So, I will be grateful that you will do it faster, if any.
It seems to me that the double should be unpacked in Word8, and then packaged in ByteString. Thus, there may be a general algorithm, since it cannot be significantly improved. But using the more efficient unpack / pack function would probably make a difference if it were.
EDIT1: I discovered another complication on Mac (GHC 7.0.3) - the code above will not compile in GHC due to this error - I have tested in GHCi so far:
$ ghc -O --make t.hs [1 of 1] Compiling Main ( t.hs, to ) /var/folders/_q/33htc59519b3xq7y6xv100z40000gp/T/ghc6976_0/ghc6976_0.s:285:0: suffix or operands invalid for `movsd' /var/folders/_q/33htc59519b3xq7y6xv100z40000gp/T/ghc6976_0/ghc6976_0.s:304:0: suffix or operands invalid for `movsd'
So it looks like I should go back to FFI (cereal package / data -binary-ieee754) until this error is fixed, or until I find a workaround. Looks like a GHC Ticket 4092 link. Please correct me if this is a new error or other error. So far I can not compile it :(
EDIT2: Updating the code to use unsafeCoerce fixes the compilation problem. Code below with criterion criteria:
{-# LANGUAGE MagicHash #-} import GHC.Prim import GHC.Types import GHC.Word import Data.Bits (shiftR) import Data.ByteString (pack, unpack) import Data.ByteString.Internal (ByteString) import Text.Printf (printf) import Unsafe.Coerce import Criterion.Main --encodeDouble :: Double -> ByteString encodeDouble x = pack $ reverse $ unpack64 $ unsafeCoerce x unpack64 :: Word64 -> [Word8] unpack64 x = map (fromIntegral.(shiftR x)) [56,48..0] main = defaultMain [ bgroup "encodeDouble" [ bench "78901.234" $ whnf encodeDouble 78901.234 , bench "789.01" $ whnf encodeDouble 789.01 ] ]
Criterion output (truncated):
estimating cost of a clock call... mean is 46.09080 ns (36 iterations) benchmarking encodeDouble/78901.234 mean: 218.8732 ns, lb 218.4946 ns, ub 219.3389 ns, ci 0.950 std dev: 2.134809 ns, lb 1.757455 ns, ub 2.568828 ns, ci 0.950 benchmarking encodeDouble/789.01 mean: 219.5382 ns, lb 219.0744 ns, ub 220.1296 ns, ci 0.950 std dev: 2.675674 ns, lb 2.197591 ns, ub 3.451464 ns, ci 0.950
Upon further analysis, most of the bottleneck seems to be in unpack64. Forcing takes ~ 6 ns. unpack64 takes ~ 195 ns. Unpacking the word64 as a list of words8 is pretty expensive here.