diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Revision history for small-bytearray-builder
 
+## 0.2.1.0 -- 2019-09-05
+
+* Stop exporting data constructor in `Data.ByteArray.Builder`.
+  This is technically a breaking change, but it was only
+  exported by accident. So, with this release, we will technically
+  violate PVP, and the previous release will be deprecated on hackage.
+* Add more functions for encoding unsigned words: `word16PaddedLowerHex`,
+  `word16LowerHex`, `word16UpperHex`, `word8LowerHex`.
+* Unroll loop for `word8Dec`.
+
 ## 0.2.0.0 -- 2019-09-04
 
 * Use `natural-arithmetic` to make manipulation of bounds possible.
diff --git a/small-bytearray-builder.cabal b/small-bytearray-builder.cabal
--- a/small-bytearray-builder.cabal
+++ b/small-bytearray-builder.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: small-bytearray-builder
-version: 0.2.0.0
+version: 0.2.1.0
 synopsis: Serialize to a small byte arrays
 description:
   This is similar to the builder facilities provided by
diff --git a/src/Data/ByteArray/Builder.hs b/src/Data/ByteArray/Builder.hs
--- a/src/Data/ByteArray/Builder.hs
+++ b/src/Data/ByteArray/Builder.hs
@@ -8,7 +8,7 @@
 
 module Data.ByteArray.Builder
   ( -- * Bounded Primitives
-    Builder(..)
+    Builder
   , construct
   , fromBounded
     -- * Evaluation
@@ -38,10 +38,19 @@
   , int16Dec
   , int8Dec
   , intDec
+    -- * Unsigned Words
+    -- ** 64-bit
   , word64PaddedUpperHex
+    -- ** 32-bit
   , word32PaddedUpperHex
+    -- ** 16-bit
   , word16PaddedUpperHex
+  , word16PaddedLowerHex
+  , word16LowerHex
+  , word16UpperHex
+    -- ** 8-bit
   , word8PaddedUpperHex
+  , word8LowerHex
   , ascii
   , char
     -- ** Machine-Readable
@@ -351,6 +360,34 @@
 word16PaddedUpperHex :: Word16 -> Builder
 word16PaddedUpperHex w =
   fromBounded Nat.constant (Bounded.word16PaddedUpperHex w)
+
+-- | Encode a 16-bit unsigned integer as hexadecimal, zero-padding
+-- the encoding to 4 digits. This uses lowercase for the alphabetical
+-- digits. For example, this encodes the number 1022 as @03fe@.
+word16PaddedLowerHex :: Word16 -> Builder
+word16PaddedLowerHex w =
+  fromBounded Nat.constant (Bounded.word16PaddedLowerHex w)
+
+-- | Encode a 16-bit unsigned integer as hexadecimal without leading
+-- zeroes. This uses lowercase for the alphabetical digits. For
+-- example, this encodes the number 1022 as @3fe@.
+word16LowerHex :: Word16 -> Builder
+word16LowerHex w =
+  fromBounded Nat.constant (Bounded.word16LowerHex w)
+
+-- | Encode a 16-bit unsigned integer as hexadecimal without leading
+-- zeroes. This uses uppercase for the alphabetical digits. For
+-- example, this encodes the number 1022 as @3FE@.
+word16UpperHex :: Word16 -> Builder
+word16UpperHex w =
+  fromBounded Nat.constant (Bounded.word16UpperHex w)
+
+-- | Encode a 16-bit unsigned integer as hexadecimal without leading
+-- zeroes. This uses lowercase for the alphabetical digits. For
+-- example, this encodes the number 1022 as @3FE@.
+word8LowerHex :: Word8 -> Builder
+word8LowerHex w =
+  fromBounded Nat.constant (Bounded.word8LowerHex w)
 
 -- | Encode a 8-bit unsigned integer as hexadecimal, zero-padding
 -- the encoding to 2 digits. This uses uppercase for the alphabetical
diff --git a/src/Data/ByteArray/Builder/Bounded.hs b/src/Data/ByteArray/Builder/Bounded.hs
--- a/src/Data/ByteArray/Builder/Bounded.hs
+++ b/src/Data/ByteArray/Builder/Bounded.hs
@@ -10,7 +10,8 @@
 {-# language TypeOperators #-}
 {-# language UnboxedTuples #-}
 
--- | The functions in this module are explict in the amount of bytes they require.
+-- | The functions in this module are explict about the maximum number
+-- of bytes they require.
 module Data.ByteArray.Builder.Bounded
   ( -- * Builder
     Builder
@@ -35,10 +36,19 @@
   , int16Dec
   , int8Dec
   , intDec
+    -- * Unsigned Words
+    -- ** 64-bit
   , word64PaddedUpperHex
+    -- ** 32-bit
   , word32PaddedUpperHex
+    -- ** 16-bit
+  , word16PaddedLowerHex
   , word16PaddedUpperHex
+  , word16LowerHex
+  , word16UpperHex
+    -- ** 8-bit
   , word8PaddedUpperHex
+  , word8LowerHex
   , ascii
   , char
     -- ** Machine-Readable
@@ -67,6 +77,7 @@
 
 import qualified Arithmetic.Types as Arithmetic
 import qualified Arithmetic.Nat as Nat
+import qualified Arithmetic.Lte as Lte
 import qualified Data.ByteArray.Builder.Bounded.Unsafe as Unsafe
 import qualified Data.Primitive as PM
 
@@ -161,7 +172,14 @@
 -- | Requires up to 3 bytes. Encodes an unsigned 8-bit integer as decimal.
 -- This encoding never starts with a zero unless the argument was zero.
 word8Dec :: Word8 -> Builder 3
-word8Dec (W8# w) = wordCommonDec# w
+word8Dec (W8# w) =
+  -- We unroll the loop when encoding Word8s. This speeds things
+  -- up IPv4 encoding by about 10% in the @ip@ library. We can
+  -- encode Word8s at twice this speed by using a lookup table.
+  -- However, I (Andrew Martin) am concerned that although lookup
+  -- table perform very well in microbenchmarks, they can thrash
+  -- L1 cache in real applications.
+  word8Dec# w
 
 -- | Requires up to 19 bytes. Encodes an unsigned machine-sized integer
 -- as decimal. This encoding never starts with a zero unless the argument
@@ -204,6 +222,22 @@
 intDec :: Int -> Builder 20
 intDec (I# w) = intCommonDec# w
 
+word8Dec# :: Word# -> Builder 3
+{-# noinline word8Dec# #-}
+word8Dec# w# = Unsafe.construct $ \arr off0 -> do
+  let !(I# off0# ) = off0
+      !(!x,!ones) = quotRem w 10
+      !(hundreds@(W# hundreds# ),tens@(W# tens# )) = quotRem x 10
+  writeByteArray arr off0 (fromIntegral (hundreds + 0x30) :: Word8)
+  let !hasHundreds = gtWord# hundreds# 0##
+      !off1@(I# off1# ) = I# (off0# +# hasHundreds)
+  writeByteArray arr off1 (fromIntegral (tens + 0x30) :: Word8)
+  let !off2 = I# (off1# +# (orI# hasHundreds (gtWord# tens# 0## )))
+  writeByteArray arr off2 (fromIntegral (ones + 0x30) :: Word8)
+  pure (off2 + 1)
+  where
+  w = W# w#
+
 -- Requires a number of bytes that is bounded by the size of
 -- the word. This is only used internally.
 wordCommonDec# :: Word# -> Builder n
@@ -259,6 +293,17 @@
   loSolved = w + 48
   hiSolved = w + 55
 
+toHexLower :: Word -> Word8
+toHexLower w' = fromIntegral
+    $ (complement theMask .&. loSolved)
+  .|. (theMask .&. hiSolved)
+  where
+  w = w' .&. 0xF
+  -- This is all ones if the value was >= 10
+  theMask = (1 .&. unsafeShiftR (w - 10) 63) - 1
+  loSolved = w + 48
+  hiSolved = w + 87
+
 -- | Requires exactly 16 bytes. Encodes a 64-bit unsigned integer as
 -- hexadecimal, zero-padding the encoding to 16 digits. This uses
 -- uppercase for the alphabetical digits. For example, this encodes the
@@ -275,9 +320,45 @@
 -- | Requires exactly 4 bytes. Encodes a 16-bit unsigned integer as
 -- hexadecimal, zero-padding the encoding to 4 digits. This uses
 -- uppercase for the alphabetical digits.
+--
+-- >>> word16PaddedUpperHex 0xab0
+-- 0AB0
 word16PaddedUpperHex :: Word16 -> Builder 4
 word16PaddedUpperHex (W16# w) = word16PaddedUpperHex# w
 
+-- | Requires exactly 4 bytes. Encodes a 16-bit unsigned integer as
+-- hexadecimal, zero-padding the encoding to 4 digits. This uses
+-- lowercase for the alphabetical digits.
+--
+-- >>> word16PaddedLowerHex 0xab0
+-- 0ab0
+word16PaddedLowerHex :: Word16 -> Builder 4
+word16PaddedLowerHex (W16# w) = word16PaddedLowerHex# w
+
+-- | Requires at most 4 bytes. Encodes a 16-bit unsigned integer as
+-- hexadecimal. No leading zeroes are displayed. Letters are presented
+-- in lowercase. If the number is zero, a single zero digit is used.
+--
+-- >>> word16LowerHex 0xab0
+-- ab0
+word16LowerHex :: Word16 -> Builder 4
+word16LowerHex (W16# w) = word16LowerHex# w
+
+-- | Requires at most 4 bytes. Encodes a 16-bit unsigned integer as
+-- hexadecimal. No leading zeroes are displayed. Letters are presented
+-- in uppercase. If the number is zero, a single zero digit is used.
+--
+-- >>> word16UpperHex 0xab0
+-- AB0
+word16UpperHex :: Word16 -> Builder 4
+word16UpperHex (W16# w) = word16UpperHex# w
+
+-- | Requires at most 2 bytes. Encodes a 8-bit unsigned integer as
+-- hexadecimal. No leading zeroes are displayed. If the number is zero,
+-- a single zero digit is used.
+word8LowerHex :: Word8 -> Builder 2
+word8LowerHex (W8# w) = word8LowerHex# w
+
 -- | Requires exactly 2 bytes. Encodes a 8-bit unsigned integer as
 -- hexadecimal, zero-padding the encoding to 2 digits. This uses
 -- uppercase for the alphabetical digits.
@@ -337,13 +418,92 @@
   where
   w = W# w#
 
+word16PaddedLowerHex# :: Word# -> Builder 4
+word16PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexLower (unsafeShiftR w 12))
+  writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 8))
+  writeByteArray arr (off + 2) (toHexLower (unsafeShiftR w 4))
+  writeByteArray arr (off + 3) (toHexLower (unsafeShiftR w 0))
+  pure (off + 4)
+  where
+  w = W# w#
+
+word12PaddedLowerHex# :: Word# -> Builder 3
+word12PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexLower (unsafeShiftR w 8))
+  writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 4))
+  writeByteArray arr (off + 2) (toHexLower (unsafeShiftR w 0))
+  pure (off + 3)
+  where
+  w = W# w#
+
+word12PaddedUpperHex# :: Word# -> Builder 3
+word12PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexUpper (unsafeShiftR w 8))
+  writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 4))
+  writeByteArray arr (off + 2) (toHexUpper (unsafeShiftR w 0))
+  pure (off + 3)
+  where
+  w = W# w#
+
 -- Definitely want this to inline. It's maybe a dozen instructions total.
 word8PaddedUpperHex# :: Word# -> Builder 2
-{-# inline word8PaddedUpperHex #-}
+{-# inline word8PaddedUpperHex# #-}
 word8PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do
   writeByteArray arr off (toHexUpper (unsafeShiftR w 4))
   writeByteArray arr (off + 1) (toHexUpper (unsafeShiftR w 0))
   pure (off + 2)
+  where
+  w = W# w#
+
+word8PaddedLowerHex# :: Word# -> Builder 2
+{-# inline word8PaddedLowerHex# #-}
+word8PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexLower (unsafeShiftR w 4))
+  writeByteArray arr (off + 1) (toHexLower (unsafeShiftR w 0))
+  pure (off + 2)
+  where
+  w = W# w#
+
+word4PaddedLowerHex# :: Word# -> Builder 1
+{-# inline word4PaddedLowerHex# #-}
+word4PaddedLowerHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexLower w)
+  pure (off + 1)
+  where
+  w = W# w#
+
+word4PaddedUpperHex# :: Word# -> Builder 1
+{-# inline word4PaddedUpperHex# #-}
+word4PaddedUpperHex# w# = Unsafe.construct $ \arr off -> do
+  writeByteArray arr off (toHexUpper w)
+  pure (off + 1)
+  where
+  w = W# w#
+
+word16UpperHex# :: Word# -> Builder 4
+word16UpperHex# w#
+  | w <= 0xF = weaken Lte.constant (word4PaddedUpperHex# w#)
+  | w <= 0xFF = weaken Lte.constant (word8PaddedUpperHex# w#)
+  | w <= 0xFFF = weaken Lte.constant (word12PaddedUpperHex# w#)
+  | otherwise = word16PaddedUpperHex# w#
+  where
+  w = W# w#
+
+word16LowerHex# :: Word# -> Builder 4
+word16LowerHex# w#
+  | w <= 0xF = weaken Lte.constant (word4PaddedLowerHex# w#)
+  | w <= 0xFF = weaken Lte.constant (word8PaddedLowerHex# w#)
+  | w <= 0xFFF = weaken Lte.constant (word12PaddedLowerHex# w#)
+  | otherwise = word16PaddedLowerHex# w#
+  where
+  w = W# w#
+
+-- Precondition: argument less than 256
+word8LowerHex# :: Word# -> Builder 2
+word8LowerHex# w#
+  | w <= 0xF = weaken Lte.constant (word4PaddedLowerHex# w#)
+  | otherwise = weaken Lte.constant (word8PaddedLowerHex# w#)
   where
   w = W# w#
 
diff --git a/src/Data/ByteArray/Builder/Bounded/Unsafe.hs b/src/Data/ByteArray/Builder/Bounded/Unsafe.hs
--- a/src/Data/ByteArray/Builder/Bounded/Unsafe.hs
+++ b/src/Data/ByteArray/Builder/Bounded/Unsafe.hs
@@ -27,8 +27,11 @@
 -- when executed.
 newtype Builder :: Nat -> Type where
    Builder ::
-        (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #))
+        (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)) 
+        -- ^ This function takes a buffer, an offset, and a number of remaining bytes.
+        --   It returns the new offset.
      -> Builder n
+   
 
 -- | Constructor for 'Builder' that works on a function with lifted
 -- arguments instead of unlifted ones. This is just as unsafe as the
diff --git a/src/Data/ByteArray/Builder/Unsafe.hs b/src/Data/ByteArray/Builder/Unsafe.hs
--- a/src/Data/ByteArray/Builder/Unsafe.hs
+++ b/src/Data/ByteArray/Builder/Unsafe.hs
@@ -31,10 +31,11 @@
 
 -- | An unmaterialized sequence of bytes that may be pasted
 -- into a mutable byte array.
-newtype Builder = Builder
-  -- This functions takes an offset and a number of remaining bytes
-  -- and returns the new offset.
-  (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))
+newtype Builder
+  = Builder (forall s. MutableByteArray# s -> Int# -> Int# -> State# s -> (# State# s, Int# #))
+    -- ^ This function takes a buffer, an offset, and a number of remaining bytes.
+    --   It returns the new offset (should be greater than the old offset), or if
+    --   there was not enough space left in buffer, it returns -1.
 
 instance IsString Builder where
   {-# inline fromString #-}
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -53,6 +53,10 @@
         run 1 (word64PaddedUpperHex w)
         ===
         pack (showWord64PaddedUpperHex w)
+    , TQC.testProperty "word8Dec" $ \w ->
+        run 1 (word8Dec w)
+        ===
+        pack (show w)
     , TQC.testProperty "pasteArrayST" $ \(xs :: [Word64]) ->
         (runArray word64Dec (V.fromList xs))
         ===
