diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+# 0.0.0.0 (2022-11-29)
+
+* Initial release
diff --git a/integer-generators/Integer/Gen.hs b/integer-generators/Integer/Gen.hs
new file mode 100644
--- /dev/null
+++ b/integer-generators/Integer/Gen.hs
@@ -0,0 +1,122 @@
+module Integer.Gen
+  (
+    GenIntegral (integral),
+    GenFinite (finite),
+    astronomical,
+  )
+  where
+
+import Control.Applicative (pure, (<*>))
+import Data.Function (id, ($))
+import Data.Functor (fmap)
+import Data.Int (Int)
+import Data.Word (Word)
+import Integer (BoundedBelow (..), Integer, Natural, Positive, Sign (..),
+                Signed (..))
+import Text.Show (Show)
+
+import qualified Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Num (Integral (..), Num (..), (+), (^))
+
+---
+
+class (Num.Integral a, Show a) => GenIntegral a
+  where
+    -- | Generators for 'Integer', 'Natural', 'Positive',
+    -- or 'Signed' selected from one of three methods:
+    --
+    -- * small numbers (magnitude less than ten)
+    -- * large numbers (well in excess of 64-bit)
+    -- * numbers at or around a bound of 'Int' or 'Word'
+    integral :: Hedgehog.Gen a
+
+instance GenIntegral Integer  where integral = integer
+instance GenIntegral Natural  where integral = boundedBelow
+instance GenIntegral Positive where integral = boundedBelow
+instance GenIntegral Signed   where integral = signed
+
+---
+
+class (Num.Integral a, Bounded.Bounded a, Show a) => GenFinite a
+  where
+    finite :: Hedgehog.Gen a
+
+instance GenFinite Int where finite = defaultFinite
+
+instance GenFinite Word where finite = defaultFinite
+
+defaultFinite :: (Num.Integral a, Bounded.Bounded a) => Hedgehog.Gen a
+defaultFinite = Gen.choice
+    [ Gen.integral $ Range.linear Bounded.minBound Bounded.maxBound
+    , Gen.integral $ Range.linear Bounded.maxBound Bounded.minBound
+    ]
+
+---
+
+smol :: Num.Integral a => a
+smol = 10
+
+astronomical :: Num.Integral a => a
+astronomical = 2 Num.^ (99 :: Integer)
+
+bigRange :: Num.Integral a => Range.Range a
+bigRange = Range.exponential smol astronomical
+
+---
+
+integer :: Hedgehog.Gen Integer
+integer = Gen.choice [smolInteger, nearFiniteBoundInteger, bigInteger]
+
+smolInteger :: Hedgehog.Gen Integer
+smolInteger = Gen.integral $ Range.linearFrom 0 (Num.negate smol) smol
+
+bigInteger :: Hedgehog.Gen Integer
+bigInteger = Gen.element [id, Num.negate] <*> Gen.integral bigRange
+
+nearFiniteBoundInteger :: Hedgehog.Gen Integer
+nearFiniteBoundInteger = Gen.element [id, Num.negate] <*> nearPositiveFiniteBound
+
+---
+
+boundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
+boundedBelow = Gen.choice [smolBoundedBelow, nearPositiveFiniteBound, bigBoundedBelow]
+
+smolBoundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
+smolBoundedBelow = fmap Num.fromInteger $ Gen.integral $ Range.linear (Num.toInteger $ minBound @a) smol
+
+bigBoundedBelow :: forall a. (BoundedBelow a, Num.Integral a) => Hedgehog.Gen a
+bigBoundedBelow = fmap Num.fromInteger $ Gen.integral bigRange
+
+nearPositiveFiniteBound :: forall a. Num.Integral a => Hedgehog.Gen a
+nearPositiveFiniteBound = fmap Num.fromInteger $
+    pure (Num.+)
+    <*> Gen.element
+      [ Num.toInteger (Bounded.maxBound :: Int)
+      , Num.toInteger (Bounded.maxBound :: Word)
+      ]
+    <*> smolInteger
+
+---
+
+signed :: Hedgehog.Gen Signed
+signed = Gen.choice [smolSigned, nearFiniteBoundSigned, bigSigned]
+
+smolSigned :: Hedgehog.Gen Signed
+smolSigned = Gen.frequency
+    [ (,) 1 $ pure Zero
+    , (,) 9 $ pure NonZero <*> sign <*> smolBoundedBelow
+    ]
+
+bigSigned :: Hedgehog.Gen Signed
+bigSigned = pure NonZero <*> sign <*> bigBoundedBelow
+
+nearFiniteBoundSigned :: Hedgehog.Gen Signed
+nearFiniteBoundSigned = pure NonZero <*> sign <*> nearPositiveFiniteBound
+
+---
+
+sign :: Hedgehog.Gen Sign
+sign = Gen.element [PlusSign, MinusSign]
diff --git a/integer-types.cabal b/integer-types.cabal
new file mode 100644
--- /dev/null
+++ b/integer-types.cabal
@@ -0,0 +1,109 @@
+cabal-version: 3.0
+
+name: integer-types
+version: 0.0.0.0
+
+category: Numeric
+synopsis: Integer, Natural, and Positive
+
+description:
+    The base package contains two unbounded integral types:
+    Integer (-∞, ∞) and Natural (0, ∞). This package expands that
+    integral repertoire with the addition of Positive (1, ∞).
+
+author: Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+homepage: https://github.com/typeclasses/integer-types
+bug-reports: https://github.com/typeclasses/integer-types/issues
+
+license: Apache-2.0
+license-file: license.txt
+
+extra-doc-files: readme.md changelog.md
+
+common base
+    default-language: GHC2021
+    ghc-options: -Wall
+    default-extensions:
+        MultiParamTypeClasses
+        NoGeneralizedNewtypeDeriving
+        NoImplicitPrelude
+        PatternSynonyms
+        ViewPatterns
+    build-depends:
+        base ^>= 4.16 || ^>= 4.17
+      , deepseq ^>= 1.4.6
+
+common test
+    import: base
+    default-extensions:
+        AllowAmbiguousTypes
+    build-depends:
+        exceptions
+      , integer-types
+
+common test-with-hedgehog
+    import: test
+    build-depends:
+        hedgehog ^>= 1.1 || ^>= 1.2
+
+common test-with-hspec
+    import: test
+    build-depends:
+        hspec
+
+library
+    import: base
+    hs-source-dirs: integer-types
+    exposed-modules:
+        Integer
+        Integer.BoundedBelow
+        Integer.Conversion
+        Integer.Finite
+        Integer.Integer
+        Integer.Natural
+        Integer.Positive
+        Integer.Sign
+        Integer.Signed
+        Integer.Subtraction
+    other-modules:
+        Integer.Positive.Unsafe
+
+library integer-generators
+    import: test-with-hedgehog
+    hs-source-dirs: integer-generators
+    exposed-modules: Integer.Gen
+
+test-suite test-integer-arithmetic
+    import: test-with-hedgehog
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test-integer-arithmetic
+    build-depends: integer-generators
+
+test-suite test-integer-conversions
+    import: test-with-hedgehog
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test-integer-conversions
+    build-depends: integer-generators
+
+test-suite test-integer-enum
+    import: test-with-hspec
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test-integer-enum
+
+test-suite test-integer-deepseq
+    import: test-with-hspec
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test-integer-deepseq
+
+test-suite test-integer-finite
+    import: test-with-hedgehog
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test-integer-finite
+    build-depends: integer-generators
diff --git a/integer-types/Integer.hs b/integer-types/Integer.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer.hs
@@ -0,0 +1,24 @@
+{-# language Safe #-}
+
+module Integer
+  (
+    {- ** Types -} Integer, Natural, Positive,
+        Signed (Zero, NonZero, Minus, Plus), Sign (MinusSign, PlusSign),
+    {- ** Subtraction -} Subtraction (subtractInteger, subtractSigned), Subtraction' (subtract),
+    {- ** Conversion -} IntegerNarrow (narrow), IntegerConvert (convert), IntegerEquiv, yolo, ConvertWithFinite (toInt, fromInt, toWord, fromWord), Finite (..),
+    {- ** Lower bound -} BoundedBelow (minBound),
+  )
+  where
+
+import Integer.BoundedBelow (BoundedBelow (minBound))
+import Integer.Conversion (IntegerConvert (convert), IntegerEquiv,
+                           IntegerNarrow (narrow), yolo)
+import Integer.Finite (ConvertWithFinite (fromInt, fromWord, toInt, toWord),
+                       Finite (..))
+import Integer.Integer (Integer)
+import Integer.Natural (Natural)
+import Integer.Positive (Positive)
+import Integer.Sign (Sign (MinusSign, PlusSign))
+import Integer.Signed (Signed (Minus, NonZero, Plus, Zero))
+import Integer.Subtraction (Subtraction (subtractInteger, subtractSigned),
+                            Subtraction' (subtract))
diff --git a/integer-types/Integer/BoundedBelow.hs b/integer-types/Integer/BoundedBelow.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/BoundedBelow.hs
@@ -0,0 +1,11 @@
+module Integer.BoundedBelow where
+
+import Numeric.Natural (Natural)
+
+class BoundedBelow a where
+    minBound :: a
+
+instance BoundedBelow Natural where
+    minBound = 0
+
+
diff --git a/integer-types/Integer/Conversion.hs b/integer-types/Integer/Conversion.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Conversion.hs
@@ -0,0 +1,93 @@
+{-# language Safe #-}
+
+module Integer.Conversion
+  (
+    IntegerNarrow (narrow),
+    IntegerConvert (convert),
+    IntegerEquiv,
+    yolo,
+  )
+  where
+
+import Data.Function (id, (.))
+import Data.Maybe (Maybe (..))
+import Integer.Integer (Integer)
+import Integer.Natural (Natural)
+import Integer.Positive (Positive)
+import Integer.Signed (Signed)
+
+import qualified Integer.Integer as Integer
+import qualified Integer.Natural as Natural
+import qualified Integer.Positive as Positive
+import qualified Integer.Signed as Signed
+import qualified Prelude as Num (Integral (..), Num (..))
+
+class IntegerNarrow a b => IntegerConvert a b where
+    convert :: a -> b
+
+class IntegerNarrow a b where
+    narrow :: a -> Maybe b
+
+class (IntegerConvert a b, IntegerConvert b a) => IntegerEquiv a b
+
+
+---  Isomorphisms  ---
+
+instance IntegerEquiv   Integer  Integer
+instance IntegerConvert Integer  Integer  where convert = id
+instance IntegerNarrow  Integer  Integer  where narrow = Just
+
+instance IntegerEquiv   Natural  Natural
+instance IntegerConvert Natural  Natural  where convert = id
+instance IntegerNarrow  Natural  Natural  where narrow  = Just
+
+instance IntegerEquiv   Positive Positive
+instance IntegerConvert Positive Positive where convert = id
+instance IntegerNarrow  Positive Positive where narrow  = Just
+
+instance IntegerEquiv   Signed   Signed
+instance IntegerConvert Signed   Signed   where convert = id
+instance IntegerNarrow  Signed   Signed   where narrow  = Just
+
+instance IntegerEquiv   Integer  Signed
+instance IntegerConvert Integer  Signed   where convert = Integer.toSigned
+instance IntegerNarrow  Integer  Signed   where narrow  = Just . convert
+
+instance IntegerEquiv   Signed   Integer
+instance IntegerConvert Signed   Integer  where convert = Signed.toInteger
+instance IntegerNarrow  Signed   Integer  where narrow  = Just . convert
+
+
+---  Prisms  ---
+
+instance IntegerNarrow  Integer  Natural  where narrow  = Integer.toNatural
+instance IntegerNarrow  Natural  Integer  where narrow  = Just . convert
+instance IntegerConvert Natural  Integer  where convert = Natural.toInteger
+
+instance IntegerNarrow  Signed   Natural  where narrow  = Signed.toNatural
+instance IntegerNarrow  Natural  Signed   where narrow  = Just . convert
+instance IntegerConvert Natural  Signed   where convert = Natural.toSigned
+
+instance IntegerNarrow  Integer  Positive where narrow  = Integer.toPositive
+instance IntegerNarrow  Positive Integer  where narrow  = Just . convert
+instance IntegerConvert Positive Integer  where convert = Positive.toInteger
+
+instance IntegerNarrow  Natural  Positive where narrow  = Natural.toPositive
+instance IntegerNarrow  Positive Natural  where narrow  = Just . convert
+instance IntegerConvert Positive Natural  where convert = Positive.toNatural
+
+instance IntegerNarrow  Signed   Positive where narrow  = Signed.toPositive
+instance IntegerNarrow  Positive Signed   where narrow  = Just . convert
+instance IntegerConvert Positive Signed   where convert = Positive.toSigned
+
+
+---  lol  ---
+
+-- | Partial conversion between 'Num.Integral' types via 'Integer'
+--
+-- @
+-- yolo = 'Num.fromInteger' . 'Num.toInteger'
+-- @
+--
+yolo :: (Num.Integral a, Num.Num b) => a -> b
+yolo = Num.fromInteger . Num.toInteger
diff --git a/integer-types/Integer/Finite.hs b/integer-types/Integer/Finite.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Finite.hs
@@ -0,0 +1,59 @@
+module Integer.Finite where
+
+import Data.Function ((.))
+import Data.Int (Int)
+import Data.Maybe (Maybe)
+import Data.Word (Word)
+import Integer.Integer (Integer)
+import Integer.Natural (Natural)
+import Integer.Positive (Positive)
+import Integer.Signed (Signed)
+import Prelude (Bounded, Integral)
+
+import qualified Data.Maybe as Maybe
+import qualified Integer.Integer as Integer
+import qualified Integer.Natural as Natural
+import qualified Integer.Positive as Positive
+import qualified Integer.Signed as Signed
+
+class ConvertWithFinite a where
+    toWord :: a -> Maybe Word
+    fromWord :: Word -> Maybe a
+    toInt :: a -> Maybe Int
+    fromInt :: Int -> Maybe a
+
+instance ConvertWithFinite Natural where
+    toWord = Natural.toWord
+    fromWord = Maybe.Just . Natural.fromWord
+    toInt = Natural.toInt
+    fromInt = Natural.fromInt
+
+instance ConvertWithFinite Positive where
+    toWord = Positive.toWord
+    fromWord = Positive.fromWord
+    toInt = Positive.toInt
+    fromInt = Positive.fromInt
+
+instance ConvertWithFinite Integer where
+    toWord = Integer.toWord
+    fromWord = Maybe.Just . Integer.fromWord
+    toInt = Integer.toInt
+    fromInt = Maybe.Just . Integer.fromInt
+
+instance ConvertWithFinite Signed where
+    toWord = Signed.toWord
+    fromWord = Maybe.Just . Signed.fromWord
+    toInt = Signed.toInt
+    fromInt = Maybe.Just . Signed.fromInt
+
+class (Bounded b, Integral b) => Finite b where
+    toFinite :: ConvertWithFinite a => a -> Maybe b
+    fromFinite :: ConvertWithFinite a => b -> Maybe a
+
+instance Finite Int where
+    toFinite = toInt
+    fromFinite = fromInt
+
+instance Finite Word where
+    toFinite = toWord
+    fromFinite = fromWord
diff --git a/integer-types/Integer/Integer.hs b/integer-types/Integer/Integer.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Integer.hs
@@ -0,0 +1,65 @@
+{-# language Safe #-}
+
+module Integer.Integer
+  (
+    {- * Type -} Integer,
+    {- * Conversion -}
+    {- ** Positive -} toPositive, fromPositive,
+    {- ** Natural -} toNatural, fromNatural,
+    {- ** Signed -} toSigned, fromSigned,
+    {- ** Int -} toInt, fromInt,
+    {- ** Word -} toWord, fromWord,
+  )
+  where
+
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Word (Word)
+import Integer.Positive (Positive)
+import Integer.Signed (Signed (..))
+import Numeric.Natural (Natural)
+import Prelude (Integer)
+
+import qualified Data.Bool as Bool
+import qualified Data.Ord as Ord
+import qualified Integer.Natural as Natural
+import qualified Integer.Positive as Positive
+import qualified Integer.Signed as Signed
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Num (Integral (..), Num (..))
+
+toPositive :: Integer -> Maybe Positive
+toPositive = Positive.fromInteger
+
+fromPositive :: Positive -> Integer
+fromPositive = Positive.toInteger
+
+toNatural :: Integer -> Maybe Natural
+toNatural = Natural.fromInteger
+
+fromNatural :: Natural -> Integer
+fromNatural = Natural.toInteger
+
+toSigned :: Integer -> Signed
+toSigned = Signed.fromInteger
+
+fromSigned :: Signed -> Integer
+fromSigned = Signed.toInteger
+
+toInt :: Integer -> Maybe Int
+toInt x = if ok then Just (Num.fromInteger x) else Nothing
+  where
+    ok = x Ord.>= Num.toInteger (Bounded.minBound :: Int) Bool.&&
+         x Ord.<= Num.toInteger (Bounded.maxBound :: Int)
+
+fromInt :: Int -> Integer
+fromInt = Num.toInteger
+
+toWord :: Integer -> Maybe Word
+toWord x = if ok then Just (Num.fromInteger x) else Nothing
+  where
+    ok = x Ord.>= Num.toInteger (Bounded.minBound :: Word) Bool.&&
+         x Ord.<= Num.toInteger (Bounded.maxBound :: Word)
+
+fromWord :: Word -> Integer
+fromWord = Num.toInteger
diff --git a/integer-types/Integer/Natural.hs b/integer-types/Integer/Natural.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Natural.hs
@@ -0,0 +1,86 @@
+{-# language Trustworthy #-}
+
+module Integer.Natural
+  (
+    {- * Type -} Natural,
+    {- * Subtraction -} subtract,
+    {- * Conversion -}
+    {- ** Positive -} toPositive, fromPositive,
+    {- ** Integer -} toInteger, fromInteger,
+    {- ** Signed -} toSigned, fromSigned,
+    {- ** Int -} toInt, fromInt,
+    {- ** Word -} toWord, fromWord,
+    {- * One (1) -} one, addOne, subtractOne,
+  )
+  where
+
+import Data.Function (($))
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Word (Word)
+import Integer.Signed (Signed (..))
+import Numeric.Natural (Natural)
+import Prelude (Integer)
+
+import qualified Data.Ord as Ord
+import qualified Integer.Positive as Positive
+import qualified Integer.Positive.Unsafe as Positive.Unsafe
+import qualified Integer.Signed as Signed
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Num (Integral (..), Num (..))
+
+toPositive :: Natural -> Maybe Positive.Unsafe.Positive
+toPositive = Positive.fromNatural
+
+fromPositive :: Positive.Unsafe.Positive -> Natural
+fromPositive = Positive.toNatural
+
+fromInteger :: Integer -> Maybe Natural
+fromInteger x = if x Ord.>= 0 then Just (Num.fromInteger x) else Nothing
+
+toInteger :: Natural -> Integer
+toInteger = Num.toInteger
+
+toSigned :: Natural -> Signed
+toSigned = Signed.fromNatural
+
+fromSigned :: Signed -> Maybe Natural
+fromSigned = Signed.toNatural
+
+toInt :: Natural -> Maybe Int
+toInt x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.<= Num.toInteger (Bounded.maxBound :: Int)
+    x' = Num.toInteger x
+
+fromInt :: Int -> Maybe Natural
+fromInt x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x Ord.>= 0
+    x' = Num.toInteger x
+
+toWord :: Natural -> Maybe Word
+toWord x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.<= Num.toInteger (Bounded.maxBound :: Word)
+    x' = Num.toInteger x
+
+fromWord :: Word -> Natural
+fromWord x = Num.fromInteger (Num.toInteger x)
+
+subtract :: Natural -> Natural -> Signed
+subtract a b = case Ord.compare a b of
+    Ord.EQ -> Zero
+    Ord.GT -> Plus  $ Positive.Unsafe.fromNatural $ (Num.-) a b
+    Ord.LT -> Minus $ Positive.Unsafe.fromNatural $ (Num.-) b a
+
+one :: Natural
+one = 1
+
+addOne :: Integer -> Integer
+addOne = (Num.+ 1)
+
+subtractOne :: Natural -> Maybe Signed
+subtractOne x = case x of
+    0 -> Nothing
+    p -> Just (subtract p 1)
diff --git a/integer-types/Integer/Positive.hs b/integer-types/Integer/Positive.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Positive.hs
@@ -0,0 +1,75 @@
+{-# language Trustworthy #-}
+
+module Integer.Positive
+  (
+    {- * Type -} Positive,
+    {- * Subtraction -} subtract,
+    {- * Conversion -}
+    {- ** Natural -} toNatural, fromNatural,
+    {- ** Integer -} toInteger, fromInteger,
+    {- ** Signed -} toSigned, fromSigned,
+    {- ** Int -} toInt, fromInt,
+    {- ** Word -} toWord, fromWord,
+    {- * One (1) -} one, addOne, subtractOne,
+  )
+  where
+
+import Data.Function (($))
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Word (Word)
+import Integer.Positive.Unsafe (Positive, addOne, one, toInteger, toNatural)
+import Integer.Signed (Signed (..))
+import Numeric.Natural (Natural)
+import Prelude (Integer)
+
+import qualified Data.Ord as Ord
+import qualified Integer.Positive.Unsafe as Unsafe
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Num (Integral (..), Num (..))
+
+fromInteger :: Integer -> Maybe Positive
+fromInteger x = if x Ord.> 0 then Just (Unsafe.fromInteger x) else Nothing
+
+fromNatural :: Natural -> Maybe Positive
+fromNatural x = case x of 0 -> Nothing; _ -> Just (Unsafe.fromNatural x)
+
+toInt :: Positive -> Maybe Int
+toInt x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.<= Num.toInteger (Bounded.maxBound :: Int)
+    x' = Num.toInteger x
+
+fromInt :: Int -> Maybe Positive
+fromInt x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.>= 1
+    x' = Num.toInteger x
+
+toWord :: Positive -> Maybe Word
+toWord x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.<= Num.toInteger (Bounded.maxBound :: Word)
+    x' = Num.toInteger x
+
+fromWord :: Word -> Maybe Positive
+fromWord x = if ok then Just (Num.fromInteger x') else Nothing
+  where
+    ok = x' Ord.>= 1
+    x' = Num.toInteger x
+
+subtract :: Positive -> Positive -> Signed
+subtract a b = case Ord.compare a b of
+    Ord.EQ -> Zero
+    Ord.GT -> Plus  $ Unsafe.subtract a b
+    Ord.LT -> Minus $ Unsafe.subtract b a
+
+subtractOne :: Positive -> Natural
+subtractOne x = toNatural x Num.- 1
+
+toSigned :: Positive -> Signed
+toSigned = Plus
+
+fromSigned :: Signed -> Maybe Positive
+fromSigned (Plus x) = Just x
+fromSigned _        = Nothing
diff --git a/integer-types/Integer/Positive/Unsafe.hs b/integer-types/Integer/Positive/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Positive/Unsafe.hs
@@ -0,0 +1,160 @@
+{-# language Unsafe #-}
+
+{- | This module is unsafe not merely in the sense that it contains partial
+functions, but moreover than it is capable of constructing the invalid
+'Positive' value @'FromNatural' 0@ representing zero, which is not positive.
+When a function has "checked" in its name, this indicates that it is partial but
+will never construct an invalid 'Positive'. -}
+
+module Integer.Positive.Unsafe
+  (
+    {- * Type -} Positive (FromNatural),
+    {- * Conversion -}
+    {- ** Natural -} toNatural, fromNatural, fromNaturalChecked,
+    {- ** Integer -} toInteger, fromInteger, fromIntegerChecked,
+    {- ** Int -} toInt, fromInt, fromIntChecked,
+    {- * Arithmetic -} subtract, subtractChecked,
+    {- * One (1) -} one, addOne, subtractOne, subtractOneChecked,
+  )
+  where
+
+import Data.Function (const, id, ($), (.))
+import Integer.BoundedBelow (BoundedBelow)
+import Numeric.Natural (Natural)
+import Prelude (Enum, Eq, Int, Integer, Integral, Num, Ord, Real, Show)
+
+import qualified Control.DeepSeq as DeepSeq
+import qualified Control.Exception as Exception
+import qualified Data.Bits as Bits
+import qualified Data.List as List
+import qualified Data.Maybe as Maybe
+import qualified Data.Ord as Ord
+import qualified Integer.BoundedBelow as BoundedBelow
+import qualified Prelude as Enum (Enum (..))
+import qualified Prelude as Num (Integral (..), Num (..), Real (..),
+                                 fromIntegral)
+import qualified Text.Show as Show
+
+newtype Positive = FromNatural{ toNatural :: Natural } deriving (Eq, Ord)
+
+instance DeepSeq.NFData Positive where rnf (FromNatural x) = DeepSeq.rnf x
+
+fromNatural :: Natural -> Positive
+fromNatural = FromNatural
+
+fromNaturalChecked :: Natural -> Positive
+fromNaturalChecked x = case x of 0 -> Exception.throw Exception.Underflow; _ -> fromNatural x
+
+toInteger :: Positive -> Integer
+toInteger = Num.toInteger . toNatural
+
+fromInteger :: Integer -> Positive
+fromInteger = fromNatural . Num.fromInteger
+
+fromIntegerChecked :: Integer -> Positive
+fromIntegerChecked x = if x Ord.>= 1 then fromInteger x else Exception.throw Exception.Underflow
+
+add :: Positive -> Positive -> Positive
+add a b = fromNatural (toNatural a Num.+ toNatural b)
+
+subtract :: Positive -> Positive -> Positive
+subtract a b = fromNatural (toNatural a Num.- toNatural b)
+
+subtractChecked :: Positive -> Positive -> Positive
+subtractChecked a b = if a Ord.> b then subtract a b else Exception.throw Exception.Underflow
+
+multiply :: Positive -> Positive -> Positive
+multiply a b = fromNatural (toNatural a Num.* toNatural b)
+
+one :: Positive
+one = fromNatural 1
+
+addOne :: Positive -> Positive
+addOne = fromNatural . (Num.+ 1) . toNatural
+
+subtractOne :: Positive -> Positive
+subtractOne = fromNatural . (Num.- 1) . toNatural
+
+subtractOneChecked :: Positive -> Positive
+subtractOneChecked x = case x of { 1 -> Exception.throw Exception.Underflow; _ -> subtractOne x }
+
+toInt :: Positive -> Int
+toInt = Num.fromIntegral . toNatural
+
+toIntChecked :: Positive -> Int
+toIntChecked = Maybe.fromMaybe (Exception.throw Exception.Overflow) . Bits.toIntegralSized . toNatural
+
+fromInt :: Int -> Positive
+fromInt = fromNatural . Num.fromIntegral
+
+fromIntChecked :: Int -> Positive
+fromIntChecked x = case Num.signum x of { 1 -> fromInt x; _ -> Exception.throw Exception.Underflow }
+
+enumFrom :: Positive -> [Positive]
+enumFrom = List.map fromNatural . Enum.enumFrom . toNatural
+
+enumFromTo :: Positive -> Positive -> [Positive]
+enumFromTo a b = List.map fromNatural $ Enum.enumFromTo (toNatural a) (toNatural b)
+
+enumFromThen :: Positive -> Positive -> [Positive]
+enumFromThen a b = if a Ord.< b then ascending else descending
+  where
+    ascending = List.map fromNatural $ Enum.enumFromThen (toNatural a) (toNatural b)
+    descending = List.map fromInteger $ List.takeWhile (Ord.>= 1) $
+        Enum.enumFromThen (toInteger a) (toInteger b)
+
+enumFromThenTo :: Positive -> Positive -> Positive -> [Positive]
+enumFromThenTo a b c = if a Ord.< b then ascending else descending
+  where
+    ascending = List.map fromNatural $ Enum.enumFromThenTo (toNatural a) (toNatural b) (toNatural c)
+    descending = List.map fromInteger $ List.takeWhile (Ord.>= 1) $
+        Enum.enumFromThenTo (toInteger a) (toInteger b) (toInteger c)
+
+type Div a = a -> a -> (a, a)
+
+divisionOp :: Div Natural -> Div Positive
+divisionOp o a b =
+    let (q, r) = o (toNatural a) (toNatural b)
+    in (fromNaturalChecked q, fromNaturalChecked r)
+
+instance BoundedBelow Positive
+  where
+    minBound = 1
+
+instance Num Positive
+  where
+    abs = id
+    negate = const (Exception.throw Exception.Underflow)
+    signum = const (fromNatural 1)
+    fromInteger = fromIntegerChecked
+    (+) = add
+    (*) = multiply
+    (-) = subtractChecked
+
+instance Enum Positive
+  where
+    succ = addOne
+    pred = subtractOneChecked
+
+    fromEnum = toIntChecked
+    toEnum = fromIntChecked
+
+    enumFrom = enumFrom
+    enumFromTo = enumFromTo
+    enumFromThen = enumFromThen
+    enumFromThenTo = enumFromThenTo
+
+instance Real Positive
+  where
+    toRational = Num.toRational . toInteger
+
+instance Integral Positive
+  where
+    toInteger = toInteger
+    quotRem = divisionOp Num.quotRem
+    divMod = divisionOp Num.divMod
+
+instance Show Positive
+  where
+    show = Show.show . toNatural
+    showsPrec i = Show.showsPrec i . toNatural
diff --git a/integer-types/Integer/Sign.hs b/integer-types/Integer/Sign.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Sign.hs
@@ -0,0 +1,24 @@
+{-# language Safe #-}
+
+module Integer.Sign
+  (
+    {- * Type -} Sign (..),
+    {- * Operations -} negate, multiply,
+  )
+  where
+
+import Prelude (Eq, Ord, Show, seq, (==))
+
+import qualified Control.DeepSeq as DeepSeq
+
+data Sign = MinusSign | PlusSign
+    deriving (Eq, Ord, Show)
+
+instance DeepSeq.NFData Sign where rnf x = seq x ()
+
+negate :: Sign -> Sign
+negate PlusSign  = MinusSign
+negate MinusSign = PlusSign
+
+multiply :: Sign -> Sign -> Sign
+multiply a b = if a == b then PlusSign else MinusSign
diff --git a/integer-types/Integer/Signed.hs b/integer-types/Integer/Signed.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Signed.hs
@@ -0,0 +1,213 @@
+{-# language Trustworthy #-}
+
+module Integer.Signed
+  (
+    {- * Type -} Signed (Zero, NonZero, Plus, Minus, NotPlus, NotMinus),
+    {- * Conversion -}
+    {- ** Integer -} toInteger, fromInteger,
+    {- ** Natural -} toNatural, fromNatural,
+    {- ** Positive -} toPositive, fromPositive,
+    {- ** Int -} toInt, fromInt,
+    {- ** Word -} toWord, fromWord,
+  )
+  where
+
+import Data.Function (($), (.))
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Word (Word)
+import Integer.Positive.Unsafe (Positive)
+import Integer.Sign (Sign (..))
+import Numeric.Natural (Natural)
+import Prelude (Enum, Eq, Integer, Integral, Num, Ord, Real, Show, seq)
+
+import qualified Control.DeepSeq as DeepSeq
+import qualified Data.List as List
+import qualified Data.Ord as Ord
+import qualified Integer.Positive.Unsafe as Positive.Unsafe
+import qualified Integer.Sign as Sign
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Enum (Enum (..))
+import qualified Prelude as Num (Integral (..), Num (..), Real (..))
+import qualified Text.Show as Show
+
+data Signed = Zero | NonZero Sign Positive
+    deriving (Eq)
+
+instance Ord Signed where
+    compare Zero Zero = Ord.EQ
+
+    compare Zero (Minus _) = Ord.GT
+    compare Zero (Plus _ ) = Ord.LT
+    compare (Minus _) Zero = Ord.LT
+    compare (Plus  _) Zero = Ord.GT
+
+    compare (Plus  _) (Minus _) = Ord.GT
+    compare (Minus _) (Plus  _) = Ord.LT
+    compare (Plus  a) (Plus  b) = Ord.compare a b
+    compare (Minus a) (Minus b) = Ord.compare b a
+
+instance DeepSeq.NFData Signed where
+    rnf Zero          = ()
+    rnf (NonZero a b) = a `seq` b `seq` ()
+
+pattern Minus :: Positive -> Signed
+pattern Minus x = NonZero MinusSign x
+pattern Plus :: Positive -> Signed
+
+pattern Plus x = NonZero PlusSign x
+
+-- | A 'Signed' that is either zero or positive
+pattern NotMinus :: Natural -> Signed
+pattern NotMinus x <- (toNatural -> Just x)
+  where NotMinus = fromNatural
+
+-- | A 'Signed' that is either zero or negative;
+-- the 'Natural' gives the magnitude of the negative
+pattern NotPlus :: Natural -> Signed
+pattern NotPlus x <- ((toNatural . negate) -> Just x)
+  where NotPlus = negate . fromNatural
+
+{-# complete Zero, Minus, Plus #-}
+{-# complete Plus, NotPlus #-}
+{-# complete Minus, NotMinus #-}
+
+fromPositive :: Positive -> Signed
+fromPositive = Plus
+
+toPositive :: Signed -> Maybe Positive
+toPositive (Plus x) = Just x
+toPositive _        = Nothing
+
+fromNatural :: Natural -> Signed
+fromNatural 0 = Zero
+fromNatural x = Plus $ Positive.Unsafe.fromNatural x
+
+toNatural :: Signed -> Maybe Natural
+toNatural (Minus _) = Nothing
+toNatural Zero      = Just 0
+toNatural (Plus x)  = Just (Positive.Unsafe.toNatural x)
+
+add :: Signed -> Signed -> Signed
+add Zero x = x
+add x Zero = x
+add (NonZero sa a) (NonZero sb b) = case (sa, sb) of
+    (PlusSign, PlusSign)   -> Plus  $ a Num.+ b
+    (MinusSign, MinusSign) -> Minus $ a Num.+ b
+
+    (MinusSign, PlusSign) -> case Ord.compare a b of
+        Ord.EQ -> Zero
+        Ord.LT -> Plus  $ Positive.Unsafe.subtract b a
+        Ord.GT -> Minus $ Positive.Unsafe.subtract a b
+
+    (PlusSign, MinusSign) -> case Ord.compare a b of
+        Ord.EQ -> Zero
+        Ord.LT -> Minus $ Positive.Unsafe.subtract b a
+        Ord.GT -> Plus  $ Positive.Unsafe.subtract a b
+
+negate :: Signed -> Signed
+negate Zero          = Zero
+negate (NonZero s x) = NonZero (Sign.negate s) x
+
+multiply :: Signed -> Signed -> Signed
+multiply Zero _ = Zero
+multiply _ Zero = Zero
+multiply (NonZero sa a) (NonZero sb b) =
+    NonZero (Sign.multiply sa sb) (a Num.* b)
+
+abs :: Signed -> Signed
+abs Zero = Zero
+abs x@(NonZero s p) = case s of
+    PlusSign  -> x
+    MinusSign -> NonZero PlusSign p
+
+signum :: Signed -> Signed
+signum Zero          = Zero
+signum (NonZero s _) = NonZero s Positive.Unsafe.one
+
+fromInteger :: Integer -> Signed
+fromInteger x = case Ord.compare x 0 of
+    Ord.EQ -> Zero
+    Ord.LT -> Minus $ Positive.Unsafe.fromInteger $ Num.abs x
+    Ord.GT -> Plus  $ Positive.Unsafe.fromInteger x
+
+toInteger :: Signed -> Integer
+toInteger Zero      = 0
+toInteger (Plus x)  = Positive.Unsafe.toInteger x
+toInteger (Minus x) = Num.negate $ Positive.Unsafe.toInteger x
+
+toInt :: Signed -> Maybe Int
+toInt x = case x of
+    Zero -> Just 0
+    Plus p -> if ok then Just (Num.fromInteger i) else Nothing
+      where
+        ok = i Ord.<= Num.toInteger (Bounded.maxBound :: Int)
+        i = Positive.Unsafe.toInteger p
+    Minus p -> if ok then Just (Num.fromInteger i) else Nothing
+      where
+        ok = i Ord.>= Num.toInteger (Bounded.minBound :: Int)
+        i = Num.negate (Positive.Unsafe.toInteger p)
+
+fromInt :: Int -> Signed
+fromInt x = case Ord.compare x 0 of
+    Ord.EQ -> Zero
+    Ord.GT -> Plus $ Positive.Unsafe.fromInt x
+    Ord.LT -> Minus $ Positive.Unsafe.fromInteger $ Num.negate $ Num.toInteger x
+
+toWord :: Signed -> Maybe Word
+toWord x = case x of
+    Zero -> Just 0
+    Plus p -> if ok then Just (Num.fromInteger i) else Nothing
+      where
+        ok = i Ord.<= Num.toInteger (Bounded.maxBound :: Word)
+        i = Positive.Unsafe.toInteger p
+    Minus _ -> Nothing
+
+fromWord :: Word -> Signed
+fromWord x = case x of
+    0 -> Zero
+    _ -> Plus $ Positive.Unsafe.fromInteger (Num.toInteger x)
+
+type Div a = a -> a -> (a, a)
+
+divisionOp :: Div Integer -> Div Signed
+divisionOp o a b =
+    let (q, r) = o (toInteger a) (toInteger b)
+    in (fromInteger q, fromInteger r)
+
+instance Num Signed
+  where
+    (+) = add
+    (*) = multiply
+    negate = negate
+    abs = abs
+    signum = signum
+    fromInteger = fromInteger
+
+instance Enum Signed
+  where
+    pred = fromInteger . Enum.pred . toInteger
+    succ = fromInteger . Enum.succ . toInteger
+
+    toEnum = fromInteger . Enum.toEnum
+    fromEnum = Enum.fromEnum . toInteger
+
+    enumFrom a = List.map fromInteger $ Enum.enumFrom (toInteger a)
+    enumFromTo a b = List.map fromInteger $ Enum.enumFromTo (toInteger a) (toInteger b)
+    enumFromThen a b = List.map fromInteger $ Enum.enumFromThen (toInteger a) (toInteger b)
+    enumFromThenTo a b c = List.map fromInteger $ Enum.enumFromThenTo (toInteger a) (toInteger b) (toInteger c)
+
+instance Real Signed
+  where
+    toRational = Num.toRational . toInteger
+
+instance Integral Signed
+  where
+    toInteger = toInteger
+    quotRem = divisionOp Num.quotRem
+    divMod = divisionOp Num.divMod
+
+instance Show Signed
+  where
+    show = Show.show . Num.toInteger
+    showsPrec i = Show.showsPrec i . Num.toInteger
diff --git a/integer-types/Integer/Subtraction.hs b/integer-types/Integer/Subtraction.hs
new file mode 100644
--- /dev/null
+++ b/integer-types/Integer/Subtraction.hs
@@ -0,0 +1,47 @@
+module Integer.Subtraction
+  (
+    Subtraction (subtractInteger, subtractSigned),
+    Subtraction' (subtract),
+  )
+  where
+
+import Integer.Integer (Integer)
+import Integer.Natural (Natural)
+import Integer.Positive (Positive)
+import Integer.Signed (Signed)
+
+import qualified Integer.Natural as Natural
+import qualified Integer.Positive as Positive
+import qualified Integer.Signed as Signed
+import qualified Prelude as Num (Num (..))
+
+-- | Domain of a subtraction operation
+class Subtraction a where
+    subtractInteger :: a -> a -> Integer
+    subtractInteger a b = Signed.toInteger (subtractSigned a b)
+
+    subtractSigned  :: a -> a -> Signed
+    subtractSigned a b = Signed.fromInteger (subtractInteger a b)
+
+instance Subtraction Integer where
+    subtractInteger = (Num.-)
+
+instance Subtraction Signed where
+    subtractInteger a b = (Num.-) (Signed.toInteger a) (Signed.toInteger b)
+    subtractSigned = (Num.-)
+
+instance Subtraction Natural where
+    subtractSigned = Natural.subtract
+
+instance Subtraction Positive where
+    subtractSigned = Positive.subtract
+
+-- | Codomain of a subtraction operation
+class Subtraction' b where
+    subtract :: Subtraction a => a -> a -> b
+
+instance Subtraction' Integer where
+    subtract = subtractInteger
+
+instance Subtraction' Signed where
+    subtract = subtractSigned
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,13 @@
+Copyright 2022 Mission Valley Software LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,135 @@
+## Core types: Integer, Natural, Positive
+
+The primary module of the `integer-types` package is `Integer`, which exports
+the following integer-like types:
+
+Type          | Range
+--------------|------------
+`Integer`     | (-∞, ∞)
+`Natural`     | (0, ∞)
+`Positive`    | (1, ∞)
+
+## The Signed type
+
+In addition to `Integer`, there is also an equivalent type called `Signed` that
+is represented as:
+
+```haskell
+data Signed = Zero | NonZero Sign Positive
+
+data Sign = MinusSign | PlusSign
+```
+
+`Signed` also comes with bundled pattern synonyms that allow it to be used as if
+it had the following definition:
+
+```haskell
+data Signed = Minus Positive | Zero | Plus Positive
+```
+
+## Monomorphic conversions
+
+The following modules contain monomorphic conversion functions:
+
+- `Integer.Integer`
+- `Integer.Natural`
+- `Integer.Positive`
+- `Integer.Signed`
+
+For example, you can convert from `Positive` to `Integer` using either
+`Integer.Positive.toInteger` or `Integer.Integer.fromPositive`, which are two
+names for the same function of type `Positive -> Integer`.
+
+Since not all integers are positive, the corresponding function in the reverse
+direction has a `Maybe` codomain. `Integer.Integer.toPositive` and
+`Integer.Positive.fromInteger` have the type `Integer -> Maybe Positive`.
+
+## Polymorphic conversions
+
+The `Integer` module exports two polymorphic conversion functions. The first is
+for conversions that always succeed, such as `Positive -> Integer`.
+
+```haskell
+convert :: IntegerConvert a b => a -> b
+```
+
+The second is for conversions that may fail because they convert to a subset of
+the domain, such as `Integer -> Maybe Positive`.
+
+```haskell
+narrow :: IntegerNarrow a b => a -> Maybe b
+```
+
+## Finite integer subsets
+
+In addition to the conversion utilities discussed above, this library also
+provides some minimal support for converting to/from the `Word` and `Int` types.
+These are system-dependent finite subsets of `Integer` that are sometimes used
+for performance reasons.
+
+```haskell
+toFinite   :: (ConvertWithFinite a, Finite b) => a -> Maybe b
+fromFinite :: (ConvertWithFinite a, Finite b) => b -> Maybe a
+```
+
+For example, `toFinite` may specialize as `Positive -> Maybe Int`, and
+`fromFinite` may specialize as `Int -> Maybe Positive`.
+
+## Monomorphic subtraction
+
+For the `Integer` and `Signed` types that represent the full range of integers,
+the standard arithmetic operations in the `Num` and `Integral` classes are
+suitable.
+
+For `Natural` and `Positive`, which are subsets of the integers, the standard
+classes are not entirely appropriate. Consider, for example, subtraction.
+
+```haskell
+(-) :: Num a => a -> a -> a
+```
+
+`Natural` and `Positive` do belong to the `Num` class, but subtraction and some
+other operations are partial; the expression `1 - 2` throws instead of returning
+a value, because the integer result `-1` is negative and not representable by
+either `Natural` or `Positive`.
+
+For this reason, `Natural` and `Positive` have their own subtraction functions
+that return `Signed`.
+
+```haskell
+-- from Integer.Positive
+subtract :: Positive -> Positive -> Signed
+
+-- from Integer.Natural
+subtract :: Natural -> Natural -> Signed
+```
+
+## Polymorphic subtraction
+
+In addition to the `(-)` method from the `Num` class and the `subtract`
+functions for `Natural` and `Positive`, there are some polymorphic subtraction
+functions in the `Integer` module. `subtractSigned` generalizes the two
+monomorphic functions discussed in the previous section. Its codomain is
+`Signed`.
+
+```haskell
+subtractSigned :: forall a. Subtraction a =>
+    a -> a -> Signed
+```
+
+`subtractInteger` does the same thing, but gives the result as `Integer` instead
+of `Signed`.
+
+```haskell
+subtractInteger :: forall a. Subtraction a =>
+    a -> a -> Integer
+```
+
+The `subtract` function generalizes further. Its domain is any subtractable type
+(`Natural`, `Positive`, `Integer`, or `Signed`) and its codomain is any type
+that can represent the full range of integers (`Integer` or `Signed`).
+
+```haskell
+subtract :: forall b a. (Subtraction' b, Subtraction a) =>
+    a -> a -> b
+```
diff --git a/test-integer-arithmetic/Main.hs b/test-integer-arithmetic/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-integer-arithmetic/Main.hs
@@ -0,0 +1,78 @@
+{-# language TemplateHaskell #-}
+{-# options_ghc -fno-warn-missing-signatures #-}
+
+module Main (main) where
+
+import Integer
+
+import Control.Applicative (pure)
+import Control.Monad (Monad)
+import Data.Eq (Eq)
+import Data.Function (($), (.))
+import Data.Maybe (Maybe (Just, Nothing))
+import Hedgehog ((===))
+import Integer.Gen (GenIntegral)
+import Prelude (Num, fromInteger, toInteger, ($!), (*), (+), (-))
+import Text.Show (Show)
+
+import qualified Control.Exception as Exception (ArithException (Underflow))
+import qualified Control.Monad.Catch as Exception (MonadCatch, try)
+import qualified Data.Either as Either
+import qualified Hedgehog
+import qualified Hedgehog.Main as Hedgehog
+import qualified Integer.Gen as Gen
+
+main = Hedgehog.defaultMain [Hedgehog.checkSequential $$(Hedgehog.discover)]
+
+testLimit :: Hedgehog.TestLimit
+testLimit = 1000
+
+property = Hedgehog.withTests testLimit . Hedgehog.property
+
+-- | Assert that a closed binary 'Num' operation behaves the same
+-- on a type as it does when applied via conversion with 'Integer'
+checkNumOp :: forall a m. GenIntegral a => Monad m =>
+    (forall b. Num b => b -> b -> b) -> Hedgehog.PropertyT m ()
+checkNumOp o = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    y :: a <- Hedgehog.forAll Gen.integral
+    x `o` y === fromInteger (toInteger x `o` toInteger y)
+
+-- | Assert that 'subtract' in @a@ gives the same result
+-- as '(-)' via @b@.
+checkSubtract :: forall a b m.
+    (GenIntegral a, Subtraction a, Subtraction' b, Num b) =>
+    (IntegerConvert a b, IntegerNarrow b a) =>
+    (Eq b, Show b) =>
+    Exception.MonadCatch m => Hedgehog.PropertyT m ()
+checkSubtract = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    y :: a <- Hedgehog.forAll Gen.integral
+    (subtract x y :: b) === (convert x - convert y :: b)
+
+-- | Assert that '(-)' in @a@ gives the same result as
+-- '(-)' via @Integer@ if the result is within the range
+-- of @a@, and is undefined otherwise
+checkPartialSubtract :: forall a m.
+    (GenIntegral a, Subtraction a, IntegerNarrow Integer a) =>
+    Exception.MonadCatch m => Hedgehog.PropertyT m ()
+checkPartialSubtract = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    y :: a <- Hedgehog.forAll Gen.integral
+    case narrow (toInteger x - toInteger y) :: Maybe a of
+        Just z -> x - y === z
+        Nothing -> do
+            z <- Exception.try (pure $! x - y)
+            z === Either.Left Exception.Underflow
+
+prop_add_positive      = property $ checkNumOp @Positive (+)
+prop_add_signed        = property $ checkNumOp @Signed   (+)
+prop_multiply_positive = property $ checkNumOp @Positive (*)
+prop_multiply_signed   = property $ checkNumOp @Signed   (*)
+
+prop_subtract_natural_signed   = property $ checkSubtract @Natural  @Signed
+prop_subtract_natural_integer  = property $ checkSubtract @Natural  @Integer
+prop_subtract_positive_signed  = property $ checkSubtract @Positive @Signed
+prop_subtract_positive_integer = property $ checkSubtract @Positive @Integer
+
+prop_partial_subtract_positive = property $ checkPartialSubtract @Positive
diff --git a/test-integer-conversions/Main.hs b/test-integer-conversions/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-integer-conversions/Main.hs
@@ -0,0 +1,91 @@
+{-# language TemplateHaskell #-}
+{-# options_ghc -fno-warn-missing-signatures #-}
+
+module Main (main) where
+
+import Integer
+
+import Control.Applicative (pure)
+import Control.Monad (Monad)
+import Data.Eq (Eq)
+import Data.Function (($), (.))
+import Data.Maybe (Maybe (..))
+import Hedgehog (evalMaybe, (===))
+import Integer.Gen (GenIntegral)
+import Prelude (($!))
+import Text.Show (Show)
+
+import qualified Control.Exception as Exception (ArithException (Underflow))
+import qualified Control.Monad.Catch as Exception (MonadCatch, try)
+import qualified Data.Either as Either
+import qualified Data.Ord as Ord
+import qualified Hedgehog
+import qualified Hedgehog.Main as Hedgehog
+import qualified Integer.Gen as Gen
+import qualified Prelude as Num (toInteger)
+
+main = Hedgehog.defaultMain [Hedgehog.checkSequential $$(Hedgehog.discover)]
+
+testLimit :: Hedgehog.TestLimit
+testLimit = 1000
+
+property = Hedgehog.withTests testLimit . Hedgehog.property
+
+-- | Half of an isomorphism test: @review (view x)@ = @x@
+checkIso :: forall a b m. (GenIntegral a, IntegerEquiv a b) =>
+    Monad m => Hedgehog.PropertyT m ()
+checkIso = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    convert (convert x :: b) === x
+
+-- | Half of a prism test: @preview (x review)@ = @Just@
+checkConvert :: forall a b m.
+    (GenIntegral a, IntegerConvert a b, IntegerNarrow b a) =>
+    Monad m => Hedgehog.PropertyT m ()
+checkConvert = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    narrow (convert x :: b) === Just x
+
+-- | Half of a prism test: @fmap review (preview x)@ = @Just x@
+-- for @x@ in range, @Nothing@ otherwise
+checkNarrow :: forall a b m. (GenIntegral a, BoundedBelow b) =>
+    (IntegerConvert b a, IntegerNarrow a b) =>
+    (Show b, Eq b) => Monad m => Hedgehog.PropertyT m ()
+checkNarrow = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    let y :: Maybe b = narrow x
+    if x Ord.>= convert (minBound @b)
+      then do
+          z <- evalMaybe y
+          convert z === x
+      else y === Nothing
+
+-- | Like 'checkNarrow @Integer', but tests the partial 'yolo'
+-- function rather than the safe 'convert' function
+checkYolo :: forall a m. (GenIntegral a, BoundedBelow a) =>
+    Exception.MonadCatch m => Hedgehog.PropertyT m ()
+checkYolo = do
+    x :: Integer <- Hedgehog.forAll Gen.integral
+    let y :: a = yolo x
+    if x Ord.>= Num.toInteger (minBound @a)
+      then yolo y === x
+      else do
+          z <- Exception.try (pure $! y)
+          z === Either.Left Exception.Underflow
+
+prop_iso_integer_signed = property $ checkIso @Integer @Signed
+prop_iso_signed_integer = property $ checkIso @Signed @Integer
+
+prop_prism_natural_integer  = property $ checkConvert @Natural  @Integer
+prop_prism_integer_natural  = property $ checkNarrow  @Integer  @Natural
+prop_prism_natural_signed   = property $ checkConvert @Natural  @Signed
+prop_prism_signed_natural   = property $ checkNarrow  @Signed   @Natural
+prop_prism_positive_integer = property $ checkConvert @Positive @Integer
+prop_prism_integer_positive = property $ checkNarrow  @Integer  @Positive
+prop_prism_positive_signed  = property $ checkConvert @Positive @Signed
+prop_prism_signed_positive  = property $ checkNarrow  @Signed   @Positive
+prop_prism_positive_natural = property $ checkConvert @Positive @Natural
+prop_prism_natural_positive = property $ checkNarrow  @Natural  @Positive
+
+prop_yolo_positive = property $ checkYolo @Positive
+prop_yolo_natural  = property $ checkYolo @Natural
diff --git a/test-integer-deepseq/Main.hs b/test-integer-deepseq/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-integer-deepseq/Main.hs
@@ -0,0 +1,40 @@
+{-# options_ghc -fno-warn-missing-signatures #-}
+
+module Main (main) where
+
+import Integer
+
+import Control.Applicative (pure)
+import Control.DeepSeq (NFData, ($!!))
+import Control.Exception (Exception, throw)
+import Data.Either (Either (..))
+import Data.Eq (Eq)
+import Data.Function (($))
+import Test.Hspec (describe, hspec, it, shouldBe)
+import Text.Show (Show)
+
+import qualified Control.Monad.Catch as Exception (MonadCatch, try)
+
+data X = X
+    deriving (Eq, Show)
+
+instance Exception X
+
+force :: NFData a => Exception.MonadCatch m => a -> m (Either X a)
+force x = Exception.try (pure $!! x)
+
+main = hspec $ do
+    describe "Signed" $ do
+        describe "deepseq" $ do
+            it "can succeed" $ do
+                x <- force (NonZero MinusSign 5)
+                x `shouldBe` Right (-5)
+            it "can force an error" $ do
+                x <- force (throw X :: Signed)
+                x `shouldBe` Left X
+            it "can force an error in sign" $ do
+                x <- force (NonZero (throw X) 5)
+                x `shouldBe` Left X
+            it "can force an error in magnitude" $ do
+                x <- force (NonZero MinusSign (throw X))
+                x `shouldBe` Left X
diff --git a/test-integer-enum/Main.hs b/test-integer-enum/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-integer-enum/Main.hs
@@ -0,0 +1,71 @@
+{-# options_ghc -fno-warn-missing-signatures #-}
+
+module Main (main) where
+
+import Integer
+
+import Data.Function (($))
+import Data.List (take)
+import Test.Hspec (describe, hspec, it, shouldBe)
+
+main = hspec $ do
+    describe "Positive" $ do
+        describe "[a ..]" $ do
+            it "counts upward" $
+                take 3 [5 :: Positive ..] `shouldBe` [5, 6, 7]
+            it "can start with 1" $
+                take 3 [1 :: Positive ..] `shouldBe` [1, 2, 3]
+        describe "[a .. b]" $ do
+            it "counts upward" $
+                [5 .. 8 :: Positive] `shouldBe` [5, 6, 7, 8]
+            it "can start with 1" $
+                [1 .. 5 :: Positive] `shouldBe` [1, 2, 3, 4, 5]
+            it "does not count downward" $ do
+                [8 .. 5 :: Positive] `shouldBe` []
+                [8 .. 7 :: Positive] `shouldBe` []
+            it "can return 1 item" $ do
+                [3 .. 3 :: Positive] `shouldBe` [3]
+                [1 .. 1 :: Positive] `shouldBe` [1]
+        describe "[a, b ..]" $ do
+            it "can count upward by 1" $ do
+                take 5 [5, 6 :: Positive ..] `shouldBe` [5, 6, 7, 8, 9]
+                take 5 [1, 2 :: Positive ..] `shouldBe` [1, 2, 3, 4, 5]
+            it "can count downward by 1" $
+                [5, 4 :: Positive ..] `shouldBe` [5, 4, 3, 2, 1]
+            it "can count upward by 2" $ do
+                take 5 [5, 7 :: Positive ..] `shouldBe` [5, 7, 9, 11, 13]
+                take 5 [1, 3 :: Positive ..] `shouldBe` [1, 3, 5, 7, 9]
+            it "can count downward by 2" $
+                [9, 7 :: Positive ..] `shouldBe` [9, 7, 5, 3, 1]
+            it "can count downward by 2 without exactly reaching its lower bound" $
+                [8, 6 :: Positive ..] `shouldBe` [8, 6, 4, 2]
+            it "can repeat 1 item indefinitely" $
+                take 5 [4, 4 :: Positive ..] `shouldBe` [4, 4, 4, 4, 4]
+        describe "[a, b .. c]" $ do
+            it "can count upward by 1" $ do
+                [5, 6 .. 9 :: Positive] `shouldBe` [5, 6, 7, 8, 9]
+                [1, 2 .. 5 :: Positive] `shouldBe` [1, 2, 3, 4, 5]
+            it "can count downward by 1" $
+                [9, 8 .. 5 :: Positive] `shouldBe` [9, 8, 7, 6, 5]
+            it "can count upward by 2" $ do
+                [5, 7 .. 11 :: Positive] `shouldBe` [5, 7, 9, 11]
+                [1, 3 .. 7 :: Positive] `shouldBe` [1, 3, 5, 7]
+            it "can count upward without exactly reaching its upper bound" $
+                [5, 7 .. 12 :: Positive] `shouldBe` [5, 7, 9, 11]
+            it "can count downward by 2" $
+                [11, 9 .. 5 :: Positive] `shouldBe` [11, 9, 7, 5]
+            it "can count downward by 2 without exactly reaching its lower bound" $
+                [11, 9 .. 4 :: Positive] `shouldBe` [11, 9, 7, 5]
+            it "can count downward with a lower bound of 1" $ do
+                [7, 5 .. 1 :: Positive] `shouldBe` [7, 5, 3, 1]
+                [8, 6 .. 1 :: Positive] `shouldBe` [8, 6, 4, 2]
+            it "can repeat 1 item indefinitely" $ do
+                take 5 [4, 4 .. 9 :: Positive] `shouldBe` [4, 4, 4, 4, 4]
+                take 5 [4, 4 .. 4 :: Positive] `shouldBe` [4, 4, 4, 4, 4]
+            it "can return 1 item" $ do
+                [4, 5 .. 4 :: Positive] `shouldBe` [4]
+                [4, 3 .. 4 :: Positive] `shouldBe` [4]
+            it "can return an empty list" $ do
+                [4, 4 .. 3 :: Positive] `shouldBe` []
+                [4, 5 .. 3 :: Positive] `shouldBe` []
+                [5, 4 .. 6 :: Positive] `shouldBe` []
diff --git a/test-integer-finite/Main.hs b/test-integer-finite/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-integer-finite/Main.hs
@@ -0,0 +1,71 @@
+{-# language TemplateHaskell #-}
+{-# options_ghc -fno-warn-missing-signatures #-}
+
+module Main (main) where
+
+import Integer
+
+import Control.Monad (Monad)
+import Data.Eq (Eq)
+import Data.Function (($), (.))
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Word (Word)
+import Hedgehog ((===))
+import Integer.Gen (GenFinite, GenIntegral)
+import Text.Show (Show)
+
+import qualified Data.Bool as Bool
+import qualified Data.Ord as Ord
+import qualified Hedgehog
+import qualified Hedgehog.Main as Hedgehog
+import qualified Integer.Gen as Gen
+import qualified Prelude as Bounded (Bounded (..))
+import qualified Prelude as Num (fromInteger, toInteger)
+
+main = Hedgehog.defaultMain [Hedgehog.checkSequential $$(Hedgehog.discover)]
+
+testLimit :: Hedgehog.TestLimit
+testLimit = 1000
+
+property = Hedgehog.withTests testLimit . Hedgehog.property
+
+checkToFinite :: forall a b m. Monad m =>
+    (ConvertWithFinite a, GenIntegral a, Show a) =>
+    (Integer.Finite b, Eq b, Show b) =>
+    Hedgehog.PropertyT m ()
+checkToFinite = do
+    x :: a <- Hedgehog.forAll Gen.integral
+    let x' = Num.toInteger x
+    let ok = x' Ord.>= Num.toInteger (Bounded.minBound :: b) Bool.&&
+             x' Ord.<= Num.toInteger (Bounded.maxBound :: b)
+    (Integer.toFinite x :: Maybe b) ===
+        if ok then Just (Num.fromInteger x') else Nothing
+
+checkFromFinite :: forall a b m. Monad m =>
+    (ConvertWithFinite a, IntegerNarrow Integer a, Eq a, Show a) =>
+    (Finite b, GenFinite b, Show b) =>
+    Hedgehog.PropertyT m ()
+checkFromFinite = do
+    x :: b <- Hedgehog.forAll Gen.finite
+    (Integer.fromFinite x :: Maybe a) === Integer.narrow (Num.toInteger x)
+
+prop_convert_integer_int   = property $ checkToFinite   @Integer @Int
+prop_convert_int_integer   = property $ checkFromFinite @Integer @Int
+prop_convert_integer_word  = property $ checkToFinite   @Integer @Word
+prop_convert_word_integer  = property $ checkFromFinite @Integer @Word
+
+prop_convert_natural_int   = property $ checkToFinite   @Natural @Int
+prop_convert_int_natural   = property $ checkFromFinite @Natural @Int
+prop_convert_natural_word  = property $ checkToFinite   @Natural @Word
+prop_convert_word_natural  = property $ checkFromFinite @Natural @Word
+
+prop_convert_positive_int  = property $ checkToFinite   @Positive @Int
+prop_convert_int_positive  = property $ checkFromFinite @Positive @Int
+prop_convert_positive_word = property $ checkToFinite   @Positive @Word
+prop_convert_word_positive = property $ checkFromFinite @Positive @Word
+
+prop_convert_signed_int    = property $ checkToFinite   @Signed @Int
+prop_convert_int_signed    = property $ checkFromFinite @Signed @Int
+prop_convert_signed_word   = property $ checkToFinite   @Signed @Word
+prop_convert_word_signed   = property $ checkFromFinite @Signed @Word
