Permalink
Browse files

Add a ByteString module, and benchmarks.

Interestingly, the ByteString code is *slower* than Text, by a
wide margin (2x or so). I assume this is due to the overhead of
using malloc for ByteString, versus pointer-bump allocation for
the MutableByteArray# used by Text.

Still a big win over plain old show, and over bytestring-show (which
is once again a surprise, being *slower* than plain old show!).

--HG--
rename : Data/Double/Conversion.hs => Data/Double/Conversion/ByteString.hs
rename : Data/Double/Conversion.hs => Data/Double/Conversion/Text.hs
  • Loading branch information...
1 parent d07a327 commit 129a9878436afca86ce1f48713dfb46ec0d489cf @bos committed Jun 29, 2011
@@ -0,0 +1,71 @@
+-- |
+-- Module : Data.Double.Conversion.ByteString
+-- Copyright : (c) 2011 MailRank, Inc.
+--
+-- License : BSD-style
+-- Maintainer : bos@mailrank.com
+-- Stability : experimental
+-- Portability : GHC
+--
+-- Fast, efficient support for converting between double precision
+-- floating point values and text.
+
+module Data.Double.Conversion.ByteString
+ (
+ toExponential
+ , toFixed
+ , toPrecision
+ , toShortest
+ ) where
+
+import Control.Monad (when)
+import Foreign.ForeignPtr (withForeignPtr)
+import Data.Double.Conversion.FFI
+import Data.Word (Word8)
+import Data.ByteString.Internal (ByteString(..), mallocByteString)
+import Foreign.C.Types (CDouble, CInt)
+import Foreign.Ptr (Ptr)
+import System.IO.Unsafe (unsafePerformIO)
+
+-- | Compute a representation in exponential format with the requested
+-- number of digits after the decimal point. The last emitted digit is
+-- rounded. If -1 digits are requested, then the shortest exponential
+-- representation is computed.
+toExponential :: Int -> Double -> ByteString
+toExponential ndigits = convert "toExponential" len $ \val mba ->
+ c_ToExponential val mba (fromIntegral ndigits)
+ where len = c_ToExponentialLength
+ {-# NOINLINE len #-}
+
+-- | Compute a decimal representation with a fixed number of digits
+-- after the decimal point. The last emitted digit is rounded.
+toFixed :: Int -> Double -> ByteString
+toFixed ndigits = convert "toFixed" len $ \val mba ->
+ c_ToFixed val mba (fromIntegral ndigits)
+ where len = c_ToFixedLength
+ {-# NOINLINE len #-}
+
+-- | Compute the shortest string of digits that correctly represent
+-- the input number.
+toShortest :: Double -> ByteString
+toShortest = convert "toShortest" len c_ToShortest
+ where len = c_ToShortestLength
+ {-# NOINLINE len #-}
+
+-- | Compute @precision@ leading digits of the given value either in
+-- exponential or decimal format. The last computed digit is rounded.
+toPrecision :: Int -> Double -> ByteString
+toPrecision ndigits = convert "toPrecision" len $ \val mba ->
+ c_ToPrecision val mba (fromIntegral ndigits)
+ where len = c_ToPrecisionLength
+ {-# NOINLINE len #-}
+
+convert :: String -> CInt -> (CDouble -> Ptr Word8 -> IO CInt)
+ -> Double -> ByteString
+convert func len act val = unsafePerformIO $ do
+ fp <- mallocByteString (fromIntegral len)
+ size <- withForeignPtr fp $ act (realToFrac val)
+ when (size == -1) .
+ fail $ "Data.Double.Conversion.ByteString." ++ func ++
+ ": conversion failed (invalid precision requested)"
+ return $ PS fp 0 (fromIntegral size)
@@ -22,9 +22,15 @@ module Data.Double.Conversion.FFI
, c_ToFixedLength
, c_ToPrecisionLength
, c_ToShortestLength
+ , c_ToExponential
+ , c_ToFixed
+ , c_ToPrecision
+ , c_ToShortest
) where
+import Data.Word (Word8)
import Foreign.C.Types (CDouble, CInt)
+import Foreign.Ptr (Ptr)
import GHC.Prim (MutableByteArray#)
foreign import ccall unsafe "hs-double-conversion.h _hs_ToShortestLength"
@@ -33,20 +39,32 @@ foreign import ccall unsafe "hs-double-conversion.h _hs_ToShortestLength"
foreign import ccall unsafe "hs-double-conversion.h _hs_Text_ToShortest"
c_Text_ToShortest :: CDouble -> MutableByteArray# s -> IO CInt
+foreign import ccall unsafe "hs-double-conversion.h _hs_ToShortest"
+ c_ToShortest :: CDouble -> Ptr Word8 -> IO CInt
+
foreign import ccall unsafe "hs-double-conversion.h _hs_ToFixedLength"
c_ToFixedLength :: CInt
foreign import ccall unsafe "hs-double-conversion.h _hs_Text_ToFixed"
c_Text_ToFixed :: CDouble -> MutableByteArray# s -> CInt -> IO CInt
+foreign import ccall unsafe "hs-double-conversion.h _hs_ToFixed"
+ c_ToFixed :: CDouble -> Ptr Word8 -> CInt -> IO CInt
+
foreign import ccall unsafe "hs-double-conversion.h _hs_ToExponentialLength"
c_ToExponentialLength :: CInt
foreign import ccall unsafe "hs-double-conversion.h _hs_Text_ToExponential"
c_Text_ToExponential :: CDouble -> MutableByteArray# s -> CInt -> IO CInt
+foreign import ccall unsafe "hs-double-conversion.h _hs_ToExponential"
+ c_ToExponential :: CDouble -> Ptr Word8 -> CInt -> IO CInt
+
foreign import ccall unsafe "hs-double-conversion.h _hs_ToPrecisionLength"
c_ToPrecisionLength :: CInt
foreign import ccall unsafe "hs-double-conversion.h _hs_Text_ToPrecision"
c_Text_ToPrecision :: CDouble -> MutableByteArray# s -> CInt -> IO CInt
+
+foreign import ccall unsafe "hs-double-conversion.h _hs_ToPrecision"
+ c_ToPrecision :: CDouble -> Ptr Word8 -> CInt -> IO CInt
@@ -1,7 +1,7 @@
{-# LANGUAGE MagicHash, Rank2Types #-}
-- |
--- Module : Data.Double.Conversion
+-- Module : Data.Double.Conversion.Text
-- Copyright : (c) 2011 MailRank, Inc.
--
-- License : BSD-style
@@ -12,7 +12,7 @@
-- Fast, efficient support for converting between double precision
-- floating point values and text.
-module Data.Double.Conversion
+module Data.Double.Conversion.Text
(
toExponential
, toFixed
@@ -70,7 +70,7 @@ convert func len act val = runST go
buf <- A.new (fromIntegral len)
size <- unsafeIOToST $ act (realToFrac val) (A.maBA buf)
when (size == -1) .
- fail $ "Data.Double.Conversion." ++ func ++
+ fail $ "Data.Double.Conversion.Text." ++ func ++
": conversion failed (invalid precision requested)"
frozen <- A.unsafeFreeze buf
return $ Text frozen 0 (fromIntegral size)
View
@@ -1,20 +1,28 @@
{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
import Criterion.Main
-import Data.Double.Conversion
+import qualified Data.Double.Conversion.ByteString as B
+import qualified Data.Double.Conversion.Text as T
import Foreign.C.Types (CInt, CDouble)
import qualified Data.Text as T
-
-showText :: Double -> T.Text
-showText d = T.pack (show d)
+import qualified Text.Show.ByteString as BS
main = defaultMain [
bgroup "haskell" [
- bench "show" $ whnf showText pi
- , bench "toShortest" $ whnf toShortest pi
- , bench "toExponential" $ whnf (toExponential 3) pi
- , bench "toPrecision" $ whnf (toExponential 8) pi
- , bench "toFixed" $ whnf (toFixed 8) pi
+ bench "show" $ nf show (pi::Double)
+ , bench "bytestring-show" $ whnf BS.show (pi::Double)
+ , bgroup "text" [
+ bench "toShortest" $ whnf T.toShortest pi
+ , bench "toExponential" $ whnf (T.toExponential 3) pi
+ , bench "toPrecision" $ whnf (T.toExponential 8) pi
+ , bench "toFixed" $ whnf (T.toFixed 8) pi
+ ]
+ , bgroup "bytestring" [
+ bench "toShortest" $ whnf B.toShortest pi
+ , bench "toExponential" $ whnf (B.toExponential 3) pi
+ , bench "toPrecision" $ whnf (B.toExponential 8) pi
+ , bench "toFixed" $ whnf (B.toFixed 8) pi
+ ]
]
, bgroup "sprintf" [
bench "exact" $ whnf sprintf_exact pi
@@ -10,6 +10,7 @@ executable bm
build-depends:
base,
+ bytestring-show,
criterion >= 0.5.0.10,
double-conversion,
text >= 0.11.0.8
@@ -48,6 +48,13 @@ static int copy(uint16_t *buf, const StringBuilder& builder, const char *cbuf)
return pos;
}
+static int copy(uint16_t *buf, const char *cbuf, const int len)
+{
+ for (int i = 0; i < len; i++)
+ buf[i] = cbuf[i];
+ return len;
+}
+
static inline const DoubleToStringConverter& defaultConverter(void)
{
const int flags = DoubleToStringConverter::UNIQUE_ZERO;
@@ -61,53 +68,61 @@ static inline const DoubleToStringConverter& defaultConverter(void)
}
extern "C"
+int _hs_ToShortest(double value, char *buf)
+{
+ StringBuilder builder(buf, kToShortestLength);
+ return defaultConverter().ToShortest(value, &builder)
+ ? builder.position() : -1;
+}
+
+extern "C"
int _hs_Text_ToShortest(double value, uint16_t *buf)
{
char cbuf[kToShortestLength];
- StringBuilder builder(cbuf, kToShortestLength);
- bool ok = defaultConverter().ToShortest(value, &builder);
-
- if (!ok)
- return -1;
+ return copy(buf, cbuf, _hs_ToShortest(value, cbuf));
+}
- return copy(buf, builder, cbuf);
+extern "C"
+int _hs_ToFixed(double value, char *buf, const int ndigits)
+{
+ StringBuilder builder(buf, kToFixedLength);
+ return defaultConverter().ToFixed(value, ndigits, &builder)
+ ? builder.position() : -1;
}
extern "C"
int _hs_Text_ToFixed(double value, uint16_t *buf, const int ndigits)
{
char cbuf[kToFixedLength];
- StringBuilder builder(cbuf, kToFixedLength);
- bool ok = defaultConverter().ToFixed(value, ndigits, &builder);
+ return copy(buf, cbuf, _hs_ToFixed(value, cbuf, ndigits));
+}
- if (!ok)
- return -1;
-
- return copy(buf, builder, cbuf);
+extern "C"
+int _hs_ToExponential(double value, char *buf, const int ndigits)
+{
+ StringBuilder builder(buf, kToExponentialLength);
+ return defaultConverter().ToExponential(value, ndigits, &builder)
+ ? builder.position() : -1;
}
extern "C"
int _hs_Text_ToExponential(double value, uint16_t *buf, const int ndigits)
{
char cbuf[kToExponentialLength];
- StringBuilder builder(cbuf, kToExponentialLength);
- bool ok = defaultConverter().ToExponential(value, ndigits, &builder);
+ return copy(buf, cbuf, _hs_ToExponential(value, cbuf, ndigits));
+}
- if (!ok)
- return -1;
-
- return copy(buf, builder, cbuf);
+extern "C"
+int _hs_ToPrecision(double value, char *buf, const int precision)
+{
+ StringBuilder builder(buf, kToPrecisionLength);
+ return defaultConverter().ToPrecision(value, precision, &builder)
+ ? builder.position() : -1;
}
extern "C"
int _hs_Text_ToPrecision(double value, uint16_t *buf, const int precision)
{
char cbuf[kToPrecisionLength];
- StringBuilder builder(cbuf, kToPrecisionLength);
- bool ok = defaultConverter().ToPrecision(value, precision, &builder);
-
- if (!ok)
- return -1;
-
- return copy(buf, builder, cbuf);
+ return copy(buf, cbuf, _hs_ToPrecision(value, cbuf, precision));
}
View
@@ -1,5 +1,5 @@
name: double-conversion
-version: 0.1.1.0
+version: 0.2.0.0
license: BSD3
license-file: LICENSE
homepage: https://github.com/mailrank/double-conversion
@@ -59,13 +59,15 @@ library
include
exposed-modules:
- Data.Double.Conversion
+ Data.Double.Conversion.ByteString
+ Data.Double.Conversion.Text
other-modules:
Data.Double.Conversion.FFI
build-depends:
base == 4.*,
+ bytestring,
ghc-prim,
text >= 0.11.0.8
@@ -10,12 +10,16 @@ extern "C"
int _hs_ToShortestLength(void);
int _hs_Text_ToShortest(double value, uint16_t *buf);
+int _hs_ToShortest(double value, char *buf);
int _hs_ToFixedLength(void);
int _hs_Text_ToFixed(double value, uint16_t *buf, int ndigits);
+int _hs_ToFixed(double value, char *buf, int ndigits);
int _hs_ToExponentialLength(void);
int _hs_Text_ToExponential(double value, uint16_t *buf, int ndigits);
+int _hs_ToExponential(double value, char *buf, int ndigits);
int _hs_ToPrecisionLength(void);
int _hs_Text_ToPrecision(double value, uint16_t *buf, int ndigits);
+int _hs_ToPrecision(double value, char *buf, int ndigits);
#ifdef __cplusplus
}

0 comments on commit 129a987

Please sign in to comment.