diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,10 @@
 # Revision history for sv-core
 
+## 0.2.2 -- 2018-08-23
+
+* Add `rational` and `withTextReadable`.
+  `rational` can be more accurate than `double` but is slower.
+
 ## 0.2.1 -- 2018-08-10
 
 * Add column-name-based encoding. NameEncode, NameEncode', and
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
@@ -62,6 +62,7 @@
 , integer
 , float
 , double
+, rational
 , boolean
 , boolean'
 , ignore
@@ -95,6 +96,7 @@
 , withTrifecta
 , withAttoparsec
 , withParsec
+, withTextReader
 
 -- * Working with errors
 , onError
@@ -143,10 +145,13 @@
 import Data.Readable (Readable (fromBS))
 import Data.Semigroup (Semigroup ((<>)), sconcat)
 import Data.Semigroup.Foldable (asum1)
+import Data.Semigroupoid (Semigroupoid (o))
 import Data.Set (Set, fromList, member)
 import Data.String (IsString (fromString))
 import Data.Text (Text)
-import Data.Text.Encoding (decodeUtf8')
+import qualified Data.Text as T
+import Data.Text.Encoding (decodeUtf8', encodeUtf8)
+import qualified Data.Text.Read as TR (Reader, rational)
 import qualified Data.Text.Lazy as LT
 import Data.Vector (Vector, (!))
 import qualified Data.Vector as V
@@ -247,9 +252,25 @@
 float = named "float"
 
 -- | Decode a UTF-8 'ByteString' field as a 'Double'
+--
+-- This is much faster than 'rational' but can be less precise. If you're
+-- not sure which to use, use 'rational'.
+--
+-- See the documentation for 'Data.Text.Read.double' for more information.
 double :: Decode' ByteString Double
 double = named "double"
 
+-- | Decode a UTF-8 'ByteString' as any 'Floating' type (usually 'Double')
+--
+-- This is slower than 'double ' but more precise. If you're
+-- not sure which to use, use 'rational'.
+--
+-- See the documentation for 'Data.Text.Read.double' for more information.
+rational :: Floating a => Decode' ByteString a
+rational = rat `o` utf8
+  where
+    rat = mapErrors encodeUtf8 (withTextReader TR.rational)
+
 -- | Decode a field as a 'Bool'
 --
 -- This aims to be tolerant to different forms a boolean might take.
@@ -348,7 +369,7 @@
 
 -- | Use the 'Readable' instance to try to decode the given value.
 decodeRead :: Readable a => Decode' ByteString a
-decodeRead = decodeReadWithMsg (mappend "Couldn't parse ")
+decodeRead = decodeReadWithMsg (mappend "Couldn't decode ")
 
 -- | Use the 'Readable' instance to try to decode the given value,
 -- or fail with the given error message.
@@ -371,7 +392,7 @@
       n'' = n' name
       space = " "
   in  decodeReadWithMsg $ \bs ->
-        mconcat ["Couldn't parse \"", bs, "\" as a", n'', space, name]
+        mconcat ["Couldn't decode \"", bs, "\" as a", n'', space, name]
 
 -- | Map over the errors of a 'Decode'
 --
@@ -413,6 +434,21 @@
   in  mkParserFunction
     (validateEitherWith (BadDecode . UTF8.fromString . dropPos . show))
     (\p s -> P.parse p mempty s)
+
+-- | Build a 'Decode' from a @Data.Text@ 'Data.Text.Read.Reader'
+withTextReader :: TR.Reader a -> Decode' Text a
+withTextReader ir =
+  decodeEither $ \t -> case ir t of
+    Left s ->
+      let msg = "Couldn't decode \"" <> t <> "\": " <> T.pack s
+      in  Left (BadDecode msg)
+    Right (a,leftover) ->
+      if T.null leftover
+      then pure a
+      else Left (BadDecode (
+          "Leftover input during decoding: " <> leftover
+        ))
+
 
 mkParserFunction ::
   Tri.CharParsing p
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.2.1
+version:             0.2.2
 license:             BSD3
 license-file:        LICENCE
 author:              George Wilson
