diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,14 @@
 # Revision history for sv-core
 
+## 0.4 -- 2019-01-14
+
+* Use attoparsec for decoding in `double`, which is faster and more accurate
+* Deprecate `rational` decoder. `double` is now better in both speed and
+  accuracy.
+* Add `doubleFast`, a fast and correct way to render doubles. The only
+  downside is that it does not satisfy a round-trip property with any
+  decoder.
+
 ## 0.3.1 -- 2019-01-04
 
 * Add `read` and `read'` decoders
diff --git a/src/Data/Sv/Decode/Core.hs b/src/Data/Sv/Decode/Core.hs
--- a/src/Data/Sv/Decode/Core.hs
+++ b/src/Data/Sv/Decode/Core.hs
@@ -134,6 +134,7 @@
 import Control.Monad.State (state)
 import Control.Monad.Writer.Strict (runWriter)
 import qualified Data.Attoparsec.ByteString as A
+import qualified Data.Attoparsec.ByteString.Char8 as AC8
 import Data.Bifunctor (first, second)
 import Data.ByteString (ByteString)
 import qualified Data.ByteString.UTF8 as UTF8
@@ -159,6 +160,7 @@
 import qualified Data.Text.Lazy as LT
 import Data.Vector (Vector, (!))
 import qualified Data.Vector as V
+import GHC.Float (double2Float)
 import Text.Parsec (Parsec)
 import qualified Text.Parsec as P (parse)
 import Text.Read (readMaybe)
@@ -254,22 +256,18 @@
 
 -- | Decode a UTF-8 'ByteString' field as a 'Float'
 float :: Decode' ByteString Float
-float = named "float"
+float = double2Float <$> double
 
 -- | Decode a UTF-8 'ByteString' field as a 'Double'
 --
--- This is currently the fastest but least precise way to decode doubles.
--- 'rational' is more precise but slower. 'read' is the most precise, but
--- slower still.
---
--- If you aren't sure which to use, use 'read'.
+-- This is currently the fastest and most precise way to decode doubles.
 double :: Decode' ByteString Double
-double = named "double"
+double = withAttoparsec AC8.double <!> (
+    contents >>== \s -> badDecode $ "Couldn't decode \"" <> s <> "\" as a double"
+  )
 
+{-# DEPRECATED rational "use double or float instead" #-}
 -- | Decode a UTF-8 'ByteString' as any 'Floating' type (usually 'Double')
---
--- This is slower than 'double' but more precise. It is not as precise as
--- 'read'.
 rational :: Floating a => Decode' ByteString a
 rational = rat `o` utf8
   where
diff --git a/src/Data/Sv/Encode/Core.hs b/src/Data/Sv/Encode/Core.hs
--- a/src/Data/Sv/Encode/Core.hs
+++ b/src/Data/Sv/Encode/Core.hs
@@ -96,6 +96,7 @@
 , integer
 , float
 , double
+, doubleFast
 , boolTrueFalse
 , booltruefalse
 , boolyesno
@@ -137,6 +138,7 @@
 import qualified Data.ByteString as Strict
 import qualified Data.ByteString.Builder as BS
 import qualified Data.ByteString.Lazy as LBS
+import qualified Data.Double.Conversion.ByteString as DC
 import Data.Foldable (fold)
 import Data.Functor.Contravariant (Contravariant (contramap))
 import Data.Functor.Contravariant.Compose (ComposeFC (ComposeFC, getComposeFC))
@@ -349,8 +351,16 @@
 float = unsafeBuilder BS.floatDec
 
 -- | Encode a 'Double'
+--
+-- This version satisfies the roundtrip property. If that doesn't matter to you,
+-- use the faster version 'doubleFast'
 double :: Encode Double
 double = unsafeBuilder BS.doubleDec
+
+-- | Encode a 'Double' really quickly. This version uses the @double-conversion@
+-- package.
+doubleFast :: Encode Double
+doubleFast = contramap DC.toShortest unsafeByteString
 
 -- | Encode a 'String'
 string :: Encode String
diff --git a/sv-core.cabal b/sv-core.cabal
--- a/sv-core.cabal
+++ b/sv-core.cabal
@@ -1,5 +1,5 @@
 name:                sv-core
-version:             0.3.1
+version:             0.4
 license:             BSD3
 license-file:        LICENCE
 author:              George Wilson
@@ -55,6 +55,7 @@
                        , containers >= 0.4 && < 0.7
                        , contravariant >= 1.2 && < 1.6
                        , deepseq >= 1.1 && < 1.5
+                       , double-conversion >= 2 && < 2.1
                        , lens >= 4 && < 4.18
                        , mtl >= 2.0.1 && < 2.3
                        , parsec >= 3.1 && < 3.2
