diff --git a/Changes b/Changes
--- a/Changes
+++ b/Changes
@@ -1,3 +1,60 @@
+0.9.0.0
+    This release supports GHC 8.0, 8.2, 8.4 and 8.6.
+
+    Breaking changes:
+
+        Remove 'Prime' type family and introduce 'Prime' newtype. This newtype
+        is now used extensively in public API:
+
+        primes :: Integral a => [Prime a]
+        primeList :: Integral a => PrimeSieve -> [Prime a]
+        sieveFrom :: Integer -> [Prime Integer]
+        nthPrime :: Integer -> Prime Integer
+
+        'sbcFunctionOnPrimePower' now accepts 'Prime Word' instead of 'Word'.
+
+        'Math.NumberTheory.Primes.{Factorisation,Testing,Counting,Sieve}'
+        are no longer re-exported from 'Math.NumberTheory.Primes'.
+        Merge 'Math.NumberTheory.UniqueFactorisation' into
+        'Math.NumberTheory.Primes' (#135, #153).
+
+        From now on 'Math.NumberTheory.Primes.Factorisation.factorise'
+        and similar functions return [(Integer, Word)] instead of [(Integer, Int)].
+
+        Remove deprecated 'Math.NumberTheory.GCD' and 'Math.NumberTheory.GCD.LowLevel'.
+
+        Deprecate 'Math.NumberTheory.Recurrencies.*'.
+        Use 'Math.NumberTheory.Recurrences.*' instead (#146).
+
+    New features:
+
+        New functions 'nextPrime' and 'precPrime'. Implement an instance of 'Enum' for primes (#153):
+
+        > [nextPrime 101 .. precPrime 130]
+        [Prime 101,Prime 103,Prime 107,Prime 109,Prime 113,Prime 127]
+
+        Support Gaussian and Eisenstein integers in smooth numbers (#138).
+
+        Add the Hurwitz zeta function on non-negative integer arguments (#126).
+
+        Implement efficient tests of n-freeness: pointwise and in interval. See 'isNFree' and 'nFreesBlock' (#145).
+
+        Generate preimages of the totient and the sum-of-divisors functions (#142):
+
+        > inverseTotient 120 :: [Integer]
+        [155,310,183,366,225,450,175,350,231,462,143,286,244,372,396,308,248]
+
+        Generate coefficients of Faulhaber polynomials 'faulhaberPoly' (#70).
+
+    Improvements:
+
+        Better precision for exact values of Riemann zeta and Dirichlet beta
+        functions (#123).
+
+        Speed up certain cases of modular multiplication (#160).
+
+        Extend Chinese theorem to non-coprime moduli (#71).
+
 0.8.0.0
     This release supports GHC 7.10, 8.0, 8.2, 8.4 and 8.6.
 
diff --git a/GHC/TypeNats/Compat.hs b/GHC/TypeNats/Compat.hs
--- a/GHC/TypeNats/Compat.hs
+++ b/GHC/TypeNats/Compat.hs
@@ -14,15 +14,12 @@
 #else
 
 module GHC.TypeNats.Compat
-  ( Nat
-  , KnownNat
-  , SomeNat(..)
+  ( module GHC.TypeLits
   , natVal
   , someNatVal
-  , sameNat
   ) where
 
-import GHC.TypeLits (Nat, KnownNat, SomeNat(..), sameNat)
+import GHC.TypeLits hiding (natVal, someNatVal)
 import qualified GHC.TypeLits as TL
 import Numeric.Natural
 
diff --git a/Math/NumberTheory/ArithmeticFunctions.hs b/Math/NumberTheory/ArithmeticFunctions.hs
--- a/Math/NumberTheory/ArithmeticFunctions.hs
+++ b/Math/NumberTheory/ArithmeticFunctions.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- This module provides an interface for defining and manipulating
 -- arithmetic functions. It also defines several most widespreaded
diff --git a/Math/NumberTheory/ArithmeticFunctions/Class.hs b/Math/NumberTheory/ArithmeticFunctions/Class.hs
--- a/Math/NumberTheory/ArithmeticFunctions/Class.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/Class.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Generic type for arithmetic functions over arbitrary unique
 -- factorisation domains.
@@ -18,6 +16,7 @@
 module Math.NumberTheory.ArithmeticFunctions.Class
   ( ArithmeticFunction(..)
   , runFunction
+  , runFunctionOnFactors
   ) where
 
 import Control.Applicative
@@ -25,7 +24,7 @@
 import Data.Semigroup
 #endif
 
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Primes
 
 -- | A typical arithmetic function operates on the canonical factorisation of
 -- a number into prime's powers and consists of two rules. The first one
@@ -34,8 +33,8 @@
 --
 -- In the following definition the first argument is the function on prime's
 -- powers, the monoid instance determines a rule of combination (typically
--- 'Product' or 'Sum'), and the second argument is convenient for unwrapping
--- (typically, 'getProduct' or 'getSum').
+-- 'Data.Semigroup.Product' or 'Data.Semigroup.Sum'), and the second argument is convenient for unwrapping
+-- (typically, 'Data.Semigroup.getProduct' or 'Data.Semigroup.getSum').
 data ArithmeticFunction n a where
   ArithmeticFunction
     :: Monoid m
@@ -43,13 +42,16 @@
     -> (m -> a)
     -> ArithmeticFunction n a
 
--- | Convert to function. The value on 0 is undefined.
+-- | Convert to a function. The value on 0 is undefined.
 runFunction :: UniqueFactorisation n => ArithmeticFunction n a -> n -> a
-runFunction (ArithmeticFunction f g)
+runFunction f = runFunctionOnFactors f . factorise
+
+-- | Convert to a function on prime factorisation.
+runFunctionOnFactors :: ArithmeticFunction n a -> [(Prime n, Word)] -> a
+runFunctionOnFactors (ArithmeticFunction f g)
   = g
   . mconcat
   . map (uncurry f)
-  . factorise
 
 instance Functor (ArithmeticFunction n) where
   fmap f (ArithmeticFunction g h) = ArithmeticFunction g (f . h)
diff --git a/Math/NumberTheory/ArithmeticFunctions/Inverse.hs b/Math/NumberTheory/ArithmeticFunctions/Inverse.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/ArithmeticFunctions/Inverse.hs
@@ -0,0 +1,370 @@
+-- |
+-- Module:      Math.NumberTheory.ArithmeticFunctions.Inverse
+-- Copyright:   (c) 2018 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- Computing inverses of multiplicative functions.
+-- The implementation is based on
+-- <https://www.emis.de/journals/JIS/VOL19/Alekseyev/alek5.pdf Computing the Inverses, their Power Sums, and Extrema for Euler’s Totient and Other Multiplicative Functions>
+-- by M. A. Alekseyev.
+
+{-# LANGUAGE RankNTypes          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Math.NumberTheory.ArithmeticFunctions.Inverse
+  ( inverseTotient
+  , inverseSigma
+  , -- * Wrappers
+    MinWord(..)
+  , MaxWord(..)
+  , MinNatural(..)
+  , MaxNatural(..)
+  , -- * Utils
+    asSetOfPreimages
+  ) where
+
+import Prelude hiding (rem, quot)
+import Data.List
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe
+import Data.Ord (Down(..))
+import Data.Semigroup
+import Data.Semiring (Semiring(..))
+import Data.Set (Set)
+import qualified Data.Set as S
+import Numeric.Natural
+
+import Math.NumberTheory.ArithmeticFunctions
+import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Logarithms
+import Math.NumberTheory.Powers
+import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes.Sieve (primes)
+import Math.NumberTheory.Utils.DirichletSeries (DirichletSeries)
+import qualified Math.NumberTheory.Utils.DirichletSeries as DS
+import Math.NumberTheory.Utils.FromIntegral
+
+data PrimePowers a = PrimePowers
+  { _ppPrime  :: Prime a
+  , _ppPowers :: [Word] -- sorted list
+  }
+
+instance Show a => Show (PrimePowers a) where
+  show (PrimePowers p xs) = "PP " ++ show (unPrime p) ++ " " ++ show xs
+
+-- | Convert a list of powers of a prime into an atomic Dirichlet series
+-- (Section 4, Step 2).
+atomicSeries
+  :: Num a
+  => (a -> b)               -- ^ How to inject a number into a semiring
+  -> ArithmeticFunction a c -- ^ Arithmetic function, which we aim to inverse
+  -> PrimePowers a          -- ^ List of powers of a prime
+  -> DirichletSeries c b    -- ^ Atomic Dirichlet series
+atomicSeries point (ArithmeticFunction f g) (PrimePowers p ks) =
+  DS.fromDistinctAscList (map (\k -> (g (f p k), point (unPrime p ^ k))) ks)
+
+-- | See section 5.1 of the paper.
+invTotient
+  :: forall a. (UniqueFactorisation a, Eq a)
+  => [(Prime a, Word)]
+  -- ^ Factorisation of a value of the totient function
+  -> [PrimePowers a]
+  -- ^ Possible prime factors of an argument of the totient function
+invTotient fs = map (\p -> PrimePowers p (doPrime p)) ps
+  where
+    divs :: [a]
+    divs = runFunctionOnFactors divisorsListA fs
+
+    ps :: [Prime a]
+    ps = mapMaybe (isPrime . (+ 1)) divs
+
+    doPrime :: Prime a -> [Word]
+    doPrime p = case lookup p fs of
+      Nothing -> [1]
+      Just k  -> [1 .. k+1]
+
+-- | See section 5.2 of the paper.
+invSigma
+  :: forall a. (Euclidean a, Integral a, UniqueFactorisation a)
+  => [(Prime a, Word)]
+  -- ^ Factorisation of a value of the sum-of-divisors function
+  -> [PrimePowers a]
+  -- ^ Possible prime factors of an argument of the sum-of-divisors function
+invSigma fs
+  = map (\(x, ys) -> PrimePowers x (S.toList ys))
+  $ M.assocs
+  $ M.unionWith (<>) pksSmall pksLarge
+  where
+    numDivs :: a
+    numDivs = runFunctionOnFactors tauA fs
+
+    divs :: [a]
+    divs = runFunctionOnFactors divisorsListA fs
+
+    n :: a
+    n = product $ map (\(p, k) -> unPrime p ^ k) fs
+
+    -- There are two possible strategies to find possible prime factors
+    -- of an argument of the sum-of-divisors function.
+    -- 1. Take each prime p and each power e such that p^e <= n,
+    -- compute sigma(p^e) and check whether it is a divisor of n.
+    -- (corresponds to 'pksSmall' below)
+    -- 2. Take each divisor d of n and each power e such that e <= log_2 d,
+    -- compute p = floor(e-th root of (d - 1)) and check whether sigma(p^e) = d
+    -- and p is actually prime (correposnds to 'pksLarge' below).
+    --
+    -- Asymptotically the second strategy is beneficial, but computing
+    -- exact e-th roots of huge integers (especially when they exceed MAX_DOUBLE)
+    -- is very costly. That is why we employ the first strategy for primes
+    -- below limit 'lim' and the second one for larger ones. This allows us
+    -- to loop over e <= log_lim d which is much smaller than log_2 d.
+    --
+    -- The value of 'lim' below was chosen heuristically;
+    -- it may be tuned in future in accordance with new experimental data.
+    lim :: a
+    lim = numDivs `max` 2
+
+    pksSmall :: Map (Prime a) (Set Word)
+    pksSmall = M.fromDistinctAscList
+      [ (p, pows)
+      | p <- takeWhile ((< lim) . unPrime) primes
+      , let pows = doPrime p
+      , not (null pows)
+      ]
+
+    doPrime :: Prime a -> Set Word
+    doPrime p' = let p = unPrime p' in S.fromDistinctAscList
+      [ e
+      | e <- [1 .. intToWord (integerLogBase (toInteger p) (toInteger n))]
+      , n `rem` ((p ^ (e + 1) - 1) `quot` (p - 1)) == 0
+      ]
+
+    pksLarge :: Map (Prime a) (Set Word)
+    pksLarge = M.unionsWith (<>)
+      [ maybe mempty (flip M.singleton (S.singleton e)) (isPrime p)
+      | d <- divs
+      , e <- [1 .. intToWord (integerLogBase (toInteger lim) (toInteger d))]
+      , let p = integerRoot e (d - 1)
+      , p ^ (e + 1) - 1 == d * (p - 1)
+      ]
+
+-- | Instead of multiplying all atomic series and filtering out everything,
+-- which is not divisible by @n@, we'd rather split all atomic series into
+-- a couple of batches, each of which corresponds to a prime factor of @n@.
+-- This allows us to crop resulting Dirichlet series (see 'filter' calls
+-- in 'invertFunction' below) at the end of each batch, saving time and memory.
+strategy
+  :: forall a c. (Euclidean c, Ord c)
+  => ArithmeticFunction a c
+  -- ^ Arithmetic function, which we aim to inverse
+  -> [(Prime c, Word)]
+  -- ^ Factorisation of a value of the arithmetic function
+  -> [PrimePowers a]
+  -- ^ Possible prime factors of an argument of the arithmetic function
+  -> [(Maybe (Prime c, Word), [PrimePowers a])]
+  -- ^ Batches, corresponding to each element of the factorisation of a value
+strategy (ArithmeticFunction f g) factors args = (Nothing, ret) : rets
+  where
+    (ret, rets)
+      = mapAccumL go args
+      $ sortOn (Down . fst) factors
+
+    go
+      :: [PrimePowers a]
+      -> (Prime c, Word)
+      -> ([PrimePowers a], (Maybe (Prime c, Word), [PrimePowers a]))
+    go ts (p, k) = (rs, (Just (p, k), qs))
+      where
+        predicate (PrimePowers q ls) = any (\l -> g (f q l) `rem` unPrime p == 0) ls
+        (qs, rs) = partition predicate ts
+
+-- | Main workhorse.
+invertFunction
+  :: forall a b c.
+     (Num a, Semiring b, Euclidean c, UniqueFactorisation c, Ord c)
+  => (a -> b)
+  -- ^ How to inject a number into a semiring
+  -> ArithmeticFunction a c
+  -- ^ Arithmetic function, which we aim to inverse
+  -> ([(Prime c, Word)] -> [PrimePowers a])
+  -- ^ How to find possible prime factors of the argument
+  -> c
+  -- ^ Value of the arithmetic function, which we aim to inverse
+  -> b
+  -- ^ Semiring element, representing preimages
+invertFunction point f invF n
+  = DS.lookup n
+  $ foldl' (\ds b -> uncurry processBatch b ds) (DS.fromDistinctAscList []) batches
+  where
+    factors = factorise n
+    batches = strategy f factors $ invF factors
+
+    processBatch
+      :: Maybe (Prime c, Word)
+      -> [PrimePowers a]
+      -> DirichletSeries c b
+      -> DirichletSeries c b
+    processBatch Nothing xs acc
+      = foldl' (DS.timesAndCrop n) acc
+      $ map (atomicSeries point f) xs
+
+    -- This is equivalent to the next, general case, but is faster,
+    -- since it avoids construction of many intermediate series.
+    processBatch (Just (p, 1)) xs acc
+      = DS.filter (\a -> a `rem` unPrime p == 0)
+      $ foldl' (DS.timesAndCrop n) acc'
+      $ map (atomicSeries point f) xs2
+      where
+        (xs1, xs2) = partition (\(PrimePowers _ ts) -> length ts == 1) xs
+        vs = DS.unions $ map (atomicSeries point f) xs1
+        (ys, zs) = DS.partition (\a -> a `rem` unPrime p == 0) acc
+        acc' = ys `DS.union` DS.timesAndCrop n zs vs
+
+    processBatch (Just pk) xs acc
+      = (\(p, k) -> DS.filter (\a -> a `rem` (unPrime p ^ k) == 0)) pk
+      $ foldl' (DS.timesAndCrop n) acc
+      $ map (atomicSeries point f) xs
+
+{-# SPECIALISE invertFunction :: Semiring b => (Int -> b) -> ArithmeticFunction Int Int -> ([(Prime Int, Word)] -> [PrimePowers Int]) -> Int -> b #-}
+{-# SPECIALISE invertFunction :: Semiring b => (Word -> b) -> ArithmeticFunction Word Word -> ([(Prime Word, Word)] -> [PrimePowers Word]) -> Word -> b #-}
+{-# SPECIALISE invertFunction :: Semiring b => (Integer -> b) -> ArithmeticFunction Integer Integer -> ([(Prime Integer, Word)] -> [PrimePowers Integer]) -> Integer -> b #-}
+{-# SPECIALISE invertFunction :: Semiring b => (Natural -> b) -> ArithmeticFunction Natural Natural -> ([(Prime Natural, Word)] -> [PrimePowers Natural]) -> Natural -> b #-}
+
+-- | The inverse for 'totient' function.
+--
+-- The return value is parameterized by a 'Semiring', which allows
+-- various applications by providing different (multiplicative) embeddings.
+-- E. g., list all preimages (see a helper 'asSetOfPreimages'):
+--
+-- >>> import qualified Data.Set as S
+-- >>> import Data.Semigroup
+-- >>> S.mapMonotonic getProduct (inverseTotient (S.singleton . Product) 120)
+-- fromList [143,155,175,183,225,231,244,248,286,308,310,350,366,372,396,450,462]
+--
+-- Count preimages:
+--
+-- >>> inverseTotient (const 1) 120
+-- 17
+--
+-- Sum preimages:
+--
+-- >>> inverseTotient id 120
+-- 4904
+--
+-- Find minimal and maximal preimages:
+--
+-- >>> unMinWord (inverseTotient MinWord 120)
+-- 143
+-- >>> unMaxWord (inverseTotient MaxWord 120)
+-- 462
+inverseTotient
+  :: (Semiring b, Euclidean a, UniqueFactorisation a, Ord a)
+  => (a -> b)
+  -> a
+  -> b
+inverseTotient point = invertFunction point totientA invTotient
+{-# SPECIALISE inverseTotient :: Semiring b => (Int -> b) -> Int -> b #-}
+{-# SPECIALISE inverseTotient :: Semiring b => (Word -> b) -> Word -> b #-}
+{-# SPECIALISE inverseTotient :: Semiring b => (Integer -> b) -> Integer -> b #-}
+{-# SPECIALISE inverseTotient :: Semiring b => (Natural -> b) -> Natural -> b #-}
+
+-- | The inverse for 'sigma' 1 function.
+--
+-- The return value is parameterized by a 'Semiring', which allows
+-- various applications by providing different (multiplicative) embeddings.
+-- E. g., list all preimages (see a helper 'asSetOfPreimages'):
+--
+-- >>> import qualified Data.Set as S
+-- >>> import Data.Semigroup
+-- >>> S.mapMonotonic getProduct (inverseSigma (S.singleton . Product) 120)
+-- fromList [54,56,87,95]
+--
+-- Count preimages:
+--
+-- >>> inverseSigma (const 1) 120
+-- 4
+--
+-- Sum preimages:
+--
+-- >>> inverseSigma id 120
+-- 292
+--
+-- Find minimal and maximal preimages:
+--
+-- >>> unMinWord (inverseSigma MinWord 120)
+-- 54
+-- >>> unMaxWord (inverseSigma MaxWord 120)
+-- 95
+inverseSigma
+  :: (Semiring b, Euclidean a, UniqueFactorisation a, Integral a)
+  => (a -> b)
+  -> a
+  -> b
+inverseSigma point = invertFunction point (sigmaA 1) invSigma
+{-# SPECIALISE inverseSigma :: Semiring b => (Int -> b) -> Int -> b #-}
+{-# SPECIALISE inverseSigma :: Semiring b => (Word -> b) -> Word -> b #-}
+{-# SPECIALISE inverseSigma :: Semiring b => (Integer -> b) -> Integer -> b #-}
+{-# SPECIALISE inverseSigma :: Semiring b => (Natural -> b) -> Natural -> b #-}
+
+--------------------------------------------------------------------------------
+-- Wrappers
+
+-- | Wrapper to use in conjunction with 'inverseTotient' and 'inverseSigma'.
+-- Extracts the maximal preimage of function.
+newtype MaxWord = MaxWord { unMaxWord :: Word }
+  deriving (Eq, Ord, Show)
+
+instance Semiring MaxWord where
+  zero = MaxWord minBound
+  one  = MaxWord 1
+  plus  (MaxWord a) (MaxWord b) = MaxWord (a `max` b)
+  times (MaxWord a) (MaxWord b) = MaxWord (a * b)
+
+-- | Wrapper to use in conjunction with 'inverseTotient' and 'inverseSigma'.
+-- Extracts the minimal preimage of function.
+newtype MinWord = MinWord { unMinWord :: Word }
+  deriving (Eq, Ord, Show)
+
+instance Semiring MinWord where
+  zero = MinWord maxBound
+  one  = MinWord 1
+  plus  (MinWord a) (MinWord b) = MinWord (a `min` b)
+  times (MinWord a) (MinWord b) = MinWord (a * b)
+
+-- | Wrapper to use in conjunction with 'inverseTotient' and 'inverseSigma'.
+-- Extracts the maximal preimage of function.
+newtype MaxNatural = MaxNatural { unMaxNatural :: Natural }
+  deriving (Eq, Ord, Show)
+
+instance Semiring MaxNatural where
+  zero = MaxNatural 0
+  one  = MaxNatural 1
+  plus  (MaxNatural a) (MaxNatural b) = MaxNatural (a `max` b)
+  times (MaxNatural a) (MaxNatural b) = MaxNatural (a * b)
+
+-- | Wrapper to use in conjunction with 'inverseTotient' and 'inverseSigma'.
+-- Extracts the minimal preimage of function.
+data MinNatural
+  = MinNatural { unMinNatural :: !Natural }
+  | Infinity
+  deriving (Eq, Ord, Show)
+
+instance Semiring MinNatural where
+  zero = Infinity
+  one  = MinNatural 1
+
+  plus a b = a `min` b
+
+  times Infinity _ = Infinity
+  times _ Infinity = Infinity
+  times (MinNatural a) (MinNatural b) = MinNatural (a * b)
+
+-- | Helper to extract a set of preimages for 'inverseTotient' or 'inverseSigma'.
+asSetOfPreimages
+  :: (Euclidean a, Integral a)
+  => (forall b. Semiring b => (a -> b) -> a -> b)
+  -> a
+  -> S.Set a
+asSetOfPreimages f = S.mapMonotonic getProduct . f (S.singleton . Product)
diff --git a/Math/NumberTheory/ArithmeticFunctions/Mertens.hs b/Math/NumberTheory/ArithmeticFunctions/Mertens.hs
--- a/Math/NumberTheory/ArithmeticFunctions/Mertens.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/Mertens.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Values of <https://en.wikipedia.org/wiki/Mertens_function Mertens function>.
 --
diff --git a/Math/NumberTheory/ArithmeticFunctions/Moebius.hs b/Math/NumberTheory/ArithmeticFunctions/Moebius.hs
--- a/Math/NumberTheory/ArithmeticFunctions/Moebius.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/Moebius.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Values of <https://en.wikipedia.org/wiki/Möbius_function Möbius function>.
 --
@@ -38,15 +36,16 @@
 import GHC.Integer.GMP.Internals
 import Unsafe.Coerce
 
-import Math.NumberTheory.Primes (primes)
 import Math.NumberTheory.Powers.Squares (integerSquareRoot)
+import Math.NumberTheory.Primes (unPrime)
+import Math.NumberTheory.Primes.Sieve (primes)
 import Math.NumberTheory.Utils.FromIntegral (wordToInt)
 
 import Math.NumberTheory.Logarithms
 
 -- | Represents three possible values of <https://en.wikipedia.org/wiki/Möbius_function Möbius function>.
 data Moebius
-  = MoebiusN -- ^ −1
+  = MoebiusN -- ^ -1
   | MoebiusZ -- ^  0
   | MoebiusP -- ^  1
   deriving (Eq, Ord, Show)
@@ -127,7 +126,7 @@
 -- Based on the sieving algorithm from p. 3 of <https://arxiv.org/pdf/1610.08551.pdf Computations of the Mertens function and improved bounds on the Mertens conjecture> by G. Hurst. It is approximately 5x faster than 'Math.NumberTheory.ArithmeticFunctions.SieveBlock.sieveBlockUnboxed'.
 --
 -- >>> sieveBlockMoebius 1 10
--- [MoebiusP, MoebiusN, MoebiusN, MoebiusZ, MoebiusN, MoebiusP, MoebiusN, MoebiusZ, MoebiusZ, MoebiusP]
+-- [MoebiusP,MoebiusN,MoebiusN,MoebiusZ,MoebiusN,MoebiusP,MoebiusN,MoebiusZ,MoebiusZ,MoebiusP]
 sieveBlockMoebius
   :: Word
   -> Word
@@ -162,7 +161,7 @@
     -- Bit fiddling in 'mapper' is correct only
     -- if all sufficiently small (<= 191) primes has been sieved out.
     ps :: [Int]
-    ps = takeWhile (<= (191 `max` integerSquareRoot highIndex)) $ map fromInteger primes
+    ps = takeWhile (<= (191 `max` integerSquareRoot highIndex)) $ map unPrime primes
 
     mapper :: Int -> Word8 -> Word8
     mapper ix val
diff --git a/Math/NumberTheory/ArithmeticFunctions/NFreedom.hs b/Math/NumberTheory/ArithmeticFunctions/NFreedom.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/ArithmeticFunctions/NFreedom.hs
@@ -0,0 +1,160 @@
+-- |
+-- Module:      Math.NumberTheory.ArithmeticFunctions.NFreedom
+-- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
+-- Licence:     MIT
+-- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
+--
+-- N-free number generation.
+--
+
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Math.NumberTheory.ArithmeticFunctions.NFreedom
+  ( nFrees
+  , nFreesBlock
+  , sieveBlockNFree
+  ) where
+
+import Control.Monad                         (forM_)
+import Control.Monad.ST                      (runST)
+import Data.List                             (scanl')
+import qualified Data.Vector.Unboxed         as U
+import qualified Data.Vector.Unboxed.Mutable as MU
+
+import Math.NumberTheory.Powers.Squares      (integerSquareRoot)
+import Math.NumberTheory.Primes              (unPrime)
+import Math.NumberTheory.Primes.Sieve        (primes)
+import Math.NumberTheory.Utils.FromIntegral  (wordToInt)
+
+-- | Evaluate the `Math.NumberTheory.ArithmeticFunctions.isNFree` function over a block.
+-- Value at @0@, if zero falls into block, is undefined.
+--
+-- This function should __**not**__ be used with a negative lower bound.
+-- If it is, the result is undefined.
+-- Furthermore, do not:
+--
+-- * use a block length greater than @maxBound :: Int@.
+-- * use a power that is either of @0, 1@.
+--
+-- None of these preconditions are checked, and if any occurs, the result is
+-- undefined, __if__ the function terminates.
+--
+-- >>> sieveBlockNFree 2 1 10
+-- [True,True,True,False,True,True,True,False,False,True]
+sieveBlockNFree
+  :: forall a . Integral a
+  => Word
+  -- ^ Power whose @n@-freedom will be checked.
+  -> a
+  -- ^ Lower index of the block.
+  -> Word
+  -- ^ Length of the block.
+  -> U.Vector Bool
+  -- ^ Vector of flags, where @True@ at index @i@ means the @i@-th element of
+  -- the block is @n@-free.
+sieveBlockNFree _ _ 0 = U.empty
+sieveBlockNFree n lowIndex len'
+  = runST $ do
+    as <- MU.replicate (wordToInt len') True
+    forM_ ps $ \p -> do
+      let pPow :: a
+          pPow = p ^ n
+          offset :: a
+          offset = negate lowIndex `mod` pPow
+          -- The second argument in @Data.Vector.Unboxed.Mutable.write@ is an
+          -- @Int@, so to avoid segmentation faults or out-of-bounds errors,
+          -- the enumeration's higher bound must always be less than
+          -- @maxBound :: Int@.
+          -- As mentioned above, this is not checked when using this function
+          -- by itself, but is carefully managed when this function is called
+          -- by @nFrees@, see the comments in it.
+          indices :: [a]
+          indices = [offset, offset + pPow .. len - 1]
+      forM_ indices $ \ix -> do
+          MU.write as (fromIntegral ix) False
+    U.freeze as
+
+  where
+    len :: a
+    len = fromIntegral len'
+
+    highIndex :: a
+    highIndex = lowIndex + len - 1
+
+    ps :: [a]
+    ps = takeWhile (<= integerSquareRoot highIndex) $ map unPrime primes
+
+-- | For a given nonnegative integer power @n@, generate all @n@-free
+-- numbers in ascending order, starting at @1@.
+--
+-- When @n@ is @0@ or @1@, the resulting list is @[1]@.
+nFrees
+    :: forall a. Integral a
+    => Word
+    -- ^ Power @n@ to be used to generate @n@-free numbers.
+    -> [a]
+    -- ^ Generated infinite list of @n@-free numbers.
+nFrees 0 = [1]
+nFrees 1 = [1]
+nFrees n = concatMap (\(lo, len) -> nFreesBlock n lo len) $ zip bounds strides
+  where
+    -- The 56th element of @iterate (2 *) 256@ is @2^64 :: Word == 0@, so to
+    -- avoid overflow only the first 55 elements of this list are used.
+    -- After those, since @maxBound :: Int@ is the largest a vector can be,
+    -- this value is just repeated. This means after a few dozen iterations,
+    -- the sieve will stop increasing in size.
+    strides :: [Word]
+    strides = take 55 (iterate (2 *) 256) ++ repeat (fromIntegral (maxBound :: Int))
+
+    -- Infinite list of lower bounds at which @sieveBlockNFree@ will be
+    -- applied. This has type @Integral a => a@ instead of @Word@ because
+    -- unlike the sizes of the sieve that eventually stop increasing (see
+    -- above comment), the lower bound at which @sieveBlockNFree@ is called does not.
+    bounds :: [a]
+    bounds = scanl' (+) 1 $ map fromIntegral strides
+
+-- | Generate @n@-free numbers in a block starting at a certain value.
+-- The length of the list is determined by the value passed in as the third
+-- argument. It will be lesser than or equal to this value.
+--
+-- This function should not be used with a negative lower bound. If it is,
+-- the result is undefined.
+--
+-- The block length cannot exceed @maxBound :: Int@, this precondition is not
+-- checked.
+--
+-- As with @nFrees@, passing @n = 0, 1@ results in an empty list.
+nFreesBlock
+    :: forall a . Integral a
+    => Word
+    -- ^ Power @n@ to be used to generate @n@-free numbers.
+    -> a
+    -- ^ Starting number in the block.
+    -> Word
+    -- ^ Maximum length of the block to be generated.
+    -> [a]
+    -- ^ Generated list of @n@-free numbers.
+nFreesBlock 0 lo _ = help lo
+nFreesBlock 1 lo _ = help lo
+nFreesBlock n lowIndex len =
+    let -- When indexing the array of flags @bs@, the index has to be an
+        -- @Int@. As such, it's necessary to cast @strd@ twice.
+        -- * Once, immediately below, to create the range of values whose
+        -- @n@-freedom will be tested. Since @nFrees@ has return type
+        -- @[a]@, this cannot be avoided as @strides@ has type @[Word]@.
+        len' :: Int
+        len' = wordToInt len
+        -- * Twice, immediately below, to create the range of indices with
+        -- which to query @bs@.
+        len'' :: a
+        len'' = fromIntegral len
+        bs  = sieveBlockNFree n lowIndex len
+    in map snd .
+       filter ((bs U.!) . fst) .
+       zip [0 .. len' - 1] $ [lowIndex .. lowIndex + len'']
+{-# INLINE nFreesBlock #-}
+
+help :: Integral a => a -> [a]
+help 1 = [1]
+help _ = []
+{-# INLINE help #-}
diff --git a/Math/NumberTheory/ArithmeticFunctions/SieveBlock.hs b/Math/NumberTheory/ArithmeticFunctions/SieveBlock.hs
--- a/Math/NumberTheory/ArithmeticFunctions/SieveBlock.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/SieveBlock.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Bulk evaluation of arithmetic functions over continuous intervals
 -- without factorisation.
@@ -37,7 +35,7 @@
 import Math.NumberTheory.ArithmeticFunctions.Moebius (sieveBlockMoebius)
 import Math.NumberTheory.ArithmeticFunctions.SieveBlock.Unboxed
 import Math.NumberTheory.Logarithms (integerLogBase')
-import Math.NumberTheory.Primes (primes)
+import Math.NumberTheory.Primes.Sieve (primes)
 import Math.NumberTheory.Primes.Types
 import Math.NumberTheory.Powers.Squares (integerSquareRoot)
 import Math.NumberTheory.Utils (splitOff#)
@@ -55,6 +53,7 @@
 --
 -- This is a thin wrapper over 'sieveBlock', read more details there.
 --
+-- >>> import Math.NumberTheory.ArithmeticFunctions
 -- >>> runFunctionOverBlock carmichaelA 1 10
 -- [1,1,2,2,4,2,6,2,6,4]
 runFunctionOverBlock
@@ -79,12 +78,12 @@
 --
 -- For example, following code lists smallest prime factors:
 --
--- >>> sieveBlock (SieveBlockConfig maxBound (\p _ -> p) min) 2 10
+-- >>> sieveBlock (SieveBlockConfig maxBound (\p _ -> unPrime p) min) 2 10
 -- [2,3,2,5,2,7,2,3,2,11]
 --
 -- And this is how to factorise all numbers in a block:
 --
--- >>> sieveBlock (SieveBlockConfig [] (\p k -> [(p,k)]) (++)) 2 10
+-- >>> sieveBlock (SieveBlockConfig [] (\p k -> [(unPrime p, k)]) (++)) 2 10
 -- [[(2,1)],[(3,1)],[(2,2)],[(5,1)],[(2,1),(3,1)],[(7,1)],[(2,3)],[(3,2)],[(2,1),(5,1)],[(11,1)]]
 sieveBlock
   :: SieveBlockConfig a
@@ -107,7 +106,7 @@
         highIndex = lowIndex + len - 1
 
         ps :: [Int]
-        ps = takeWhile (<= integerSquareRoot highIndex) $ map fromInteger primes
+        ps = takeWhile (<= integerSquareRoot highIndex) $ map unPrime primes
 
     forM_ ps $ \p -> do
 
@@ -116,7 +115,7 @@
 
           fs = V.generate
             (integerLogBase' (toInteger p) (toInteger highIndex))
-            (\k -> f p' (intToWord k + 1))
+            (\k -> f (Prime p') (intToWord k + 1))
 
           offset :: Int
           offset = negate lowIndex `mod` p
@@ -125,10 +124,10 @@
         W# a# <- MV.unsafeRead as ix
         let !(# pow#, a'# #) = splitOff# p# (a# `quotWord#` p#)
         MV.unsafeWrite as ix (W# a'#)
-        MV.unsafeModify bs (\y -> y `append` V.unsafeIndex fs (I# pow#)) ix
+        MV.unsafeModify bs (\y -> y `append` V.unsafeIndex fs (I# (word2Int# pow#))) ix
 
     forM_ [0 .. len - 1] $ \k -> do
       a <- MV.unsafeRead as k
-      MV.unsafeModify bs (\b -> if a /= 1 then b `append` f a 1 else b) k
+      MV.unsafeModify bs (\b -> if a /= 1 then b `append` f (Prime a) 1 else b) k
 
     V.unsafeFreeze bs
diff --git a/Math/NumberTheory/ArithmeticFunctions/SieveBlock/Unboxed.hs b/Math/NumberTheory/ArithmeticFunctions/SieveBlock/Unboxed.hs
--- a/Math/NumberTheory/ArithmeticFunctions/SieveBlock/Unboxed.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/SieveBlock/Unboxed.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Bulk evaluation of arithmetic functions without factorisation
 -- of arguments.
@@ -30,7 +28,8 @@
 
 import Math.NumberTheory.ArithmeticFunctions.Moebius (Moebius)
 import Math.NumberTheory.Logarithms (integerLogBase')
-import Math.NumberTheory.Primes (primes)
+import Math.NumberTheory.Primes.Sieve (primes)
+import Math.NumberTheory.Primes.Types (Prime(..))
 import Math.NumberTheory.Powers.Squares (integerSquareRoot)
 import Math.NumberTheory.Utils (splitOff#)
 import Math.NumberTheory.Utils.FromIntegral (wordToInt, intToWord)
@@ -41,20 +40,20 @@
 --
 -- > SieveBlockConfig
 -- >   { sbcEmpty                = 1
--- >   , sbcFunctionOnPrimePower = (\p a -> (p - 1) * p ^ (a - 1)
+-- >   , sbcFunctionOnPrimePower = \p a -> (unPrime p - 1) * unPrime p ^ (a - 1)
 -- >   , sbcAppend               = (*)
 -- >   }
 data SieveBlockConfig a = SieveBlockConfig
   { sbcEmpty                :: a
     -- ^ value of a function on 1
-  , sbcFunctionOnPrimePower :: Word -> Word -> a
+  , sbcFunctionOnPrimePower :: Prime Word -> Word -> a
     -- ^ how to evaluate a function on prime powers
   , sbcAppend               :: a -> a -> a
     -- ^ how to combine values of a function on coprime arguments
   }
 
 -- | Create a config for a multiplicative function from its definition on prime powers.
-multiplicativeSieveBlockConfig :: Num a => (Word -> Word -> a) -> SieveBlockConfig a
+multiplicativeSieveBlockConfig :: Num a => (Prime Word -> Word -> a) -> SieveBlockConfig a
 multiplicativeSieveBlockConfig f = SieveBlockConfig
   { sbcEmpty                = 1
   , sbcFunctionOnPrimePower = f
@@ -62,7 +61,7 @@
   }
 
 -- | Create a config for an additive function from its definition on prime powers.
-additiveSieveBlockConfig :: Num a => (Word -> Word -> a) -> SieveBlockConfig a
+additiveSieveBlockConfig :: Num a => (Prime Word -> Word -> a) -> SieveBlockConfig a
 additiveSieveBlockConfig f = SieveBlockConfig
   { sbcEmpty                = 0
   , sbcFunctionOnPrimePower = f
@@ -74,7 +73,7 @@
 --
 -- Based on Algorithm M of <https://arxiv.org/pdf/1305.1639.pdf Parity of the number of primes in a given interval and algorithms of the sublinear summation> by A. V. Lelechenko. See Lemma 2 on p. 5 on its algorithmic complexity. For the majority of use-cases its time complexity is O(x^(1+ε)).
 --
--- For example, here is an analogue of divisor function 'tau':
+-- For example, here is an analogue of divisor function 'Math.NumberTheory.ArithmeticFunctions.tau':
 --
 -- >>> sieveBlockUnboxed (SieveBlockConfig 1 (\_ a -> a + 1) (*)) 1 10
 -- [1,2,2,3,2,4,2,4,3,4]
@@ -100,7 +99,7 @@
         highIndex = lowIndex + len - 1
 
         ps :: [Int]
-        ps = takeWhile (<= integerSquareRoot highIndex) $ map fromInteger primes
+        ps = takeWhile (<= integerSquareRoot highIndex) $ map unPrime primes
 
     forM_ ps $ \p -> do
 
@@ -109,7 +108,7 @@
 
           fs = V.generate
             (integerLogBase' (toInteger p) (toInteger highIndex))
-            (\k -> f p' (intToWord k + 1))
+            (\k -> f (Prime p') (intToWord k + 1))
 
           offset :: Int
           offset = negate lowIndex `mod` p
@@ -118,11 +117,11 @@
         W# a# <- MV.unsafeRead as ix
         let !(# pow#, a'# #) = splitOff# p# (a# `quotWord#` p#)
         MV.unsafeWrite as ix (W# a'#)
-        MV.unsafeModify bs (\y -> y `append` V.unsafeIndex fs (I# pow#)) ix
+        MV.unsafeModify bs (\y -> y `append` V.unsafeIndex fs (I# (word2Int# pow#))) ix
 
     forM_ [0 .. len - 1] $ \k -> do
       a <- MV.unsafeRead as k
-      MV.unsafeModify bs (\b -> if a /= 1 then b `append` f a 1 else b) k
+      MV.unsafeModify bs (\b -> if a /= 1 then b `append` f (Prime a) 1 else b) k
 
     V.unsafeFreeze bs
 
diff --git a/Math/NumberTheory/ArithmeticFunctions/Standard.hs b/Math/NumberTheory/ArithmeticFunctions/Standard.hs
--- a/Math/NumberTheory/ArithmeticFunctions/Standard.hs
+++ b/Math/NumberTheory/ArithmeticFunctions/Standard.hs
@@ -3,26 +3,19 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Textbook arithmetic functions.
 --
 
-{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies        #-}
-{-# LANGUAGE ViewPatterns        #-}
 
-{-# OPTIONS_HADDOCK hide #-}
-
 module Math.NumberTheory.ArithmeticFunctions.Standard
   ( -- * Multiplicative functions
     multiplicative
   , divisors, divisorsA
   , divisorsList, divisorsListA
   , divisorsSmall, divisorsSmallA
-  , tau, tauA
+  , divisorCount, tau, tauA
   , sigma, sigmaA
   , totient, totientA
   , jordan, jordanA
@@ -36,6 +29,7 @@
     -- * Misc
   , carmichael, carmichaelA
   , expMangoldt, expMangoldtA
+  , isNFree, isNFreeA, nFrees, nFreesBlock
   ) where
 
 import Data.Coerce
@@ -47,7 +41,8 @@
 
 import Math.NumberTheory.ArithmeticFunctions.Class
 import Math.NumberTheory.ArithmeticFunctions.Moebius
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.ArithmeticFunctions.NFreedom (nFrees, nFreesBlock)
+import Math.NumberTheory.Primes
 import Math.NumberTheory.Utils.FromIntegral
 
 import Numeric.Natural
@@ -56,14 +51,15 @@
 multiplicative :: Num a => (Prime n -> Word -> a) -> ArithmeticFunction n a
 multiplicative f = ArithmeticFunction ((Product .) . f) getProduct
 
-divisors :: (UniqueFactorisation n, Num n, Ord n) => n -> Set n
+-- | See 'divisorsA'.
+divisors :: (UniqueFactorisation n, Ord n) => n -> Set n
 divisors = runFunction divisorsA
 {-# SPECIALIZE divisors :: Natural -> Set Natural #-}
 {-# SPECIALIZE divisors :: Integer -> Set Integer #-}
 
 -- | The set of all (positive) divisors of an argument.
-divisorsA :: forall n. (UniqueFactorisation n, Num n, Ord n) => ArithmeticFunction n (Set n)
-divisorsA = ArithmeticFunction (\((unPrime :: Prime n -> n) -> p) k -> SetProduct $ divisorsHelper p k) (S.insert 1 . getSetProduct)
+divisorsA :: (UniqueFactorisation n, Ord n) => ArithmeticFunction n (Set n)
+divisorsA = ArithmeticFunction (\p -> SetProduct . divisorsHelper (unPrime p)) (S.insert 1 . getSetProduct)
 
 divisorsHelper :: Num n => n -> Word -> Set n
 divisorsHelper _ 0 = S.empty
@@ -71,12 +67,13 @@
 divisorsHelper p a = S.fromDistinctAscList $ p : p * p : map (p ^) [3 .. wordToInt a]
 {-# INLINE divisorsHelper #-}
 
-divisorsList :: (UniqueFactorisation n, Num n) => n -> [n]
+-- | See 'divisorsListA'.
+divisorsList :: UniqueFactorisation n => n -> [n]
 divisorsList = runFunction divisorsListA
 
 -- | The unsorted list of all (positive) divisors of an argument, produced in lazy fashion.
-divisorsListA :: forall n. (UniqueFactorisation n, Num n) => ArithmeticFunction n [n]
-divisorsListA = ArithmeticFunction (\((unPrime :: Prime n -> n) -> p) k -> ListProduct $ divisorsListHelper p k) ((1 :) . getListProduct)
+divisorsListA :: UniqueFactorisation n => ArithmeticFunction n [n]
+divisorsListA = ArithmeticFunction (\p -> ListProduct . divisorsListHelper (unPrime p)) ((1 :) . getListProduct)
 
 divisorsListHelper :: Num n => n -> Word -> [n]
 divisorsListHelper _ 0 = []
@@ -84,12 +81,13 @@
 divisorsListHelper p a = p : p * p : map (p ^) [3 .. wordToInt a]
 {-# INLINE divisorsListHelper #-}
 
-divisorsSmall :: (UniqueFactorisation n, Prime n ~ Prime Int) => n -> IntSet
+-- | See 'divisorsSmallA'.
+divisorsSmall :: Int -> IntSet
 divisorsSmall = runFunction divisorsSmallA
 
 -- | Same as 'divisors', but with better performance on cost of type restriction.
-divisorsSmallA :: forall n. (Prime n ~ Prime Int) => ArithmeticFunction n IntSet
-divisorsSmallA = ArithmeticFunction (\p k -> IntSetProduct $ divisorsHelperSmall (unPrime p) k) (IS.insert 1 . getIntSetProduct)
+divisorsSmallA :: ArithmeticFunction Int IntSet
+divisorsSmallA = ArithmeticFunction (\p -> IntSetProduct . divisorsHelperSmall (unPrime p)) (IS.insert 1 . getIntSetProduct)
 
 divisorsHelperSmall :: Int -> Word -> IntSet
 divisorsHelperSmall _ 0 = IS.empty
@@ -97,6 +95,14 @@
 divisorsHelperSmall p a = IS.fromDistinctAscList $ p : p * p : map (p ^) [3 .. wordToInt a]
 {-# INLINE divisorsHelperSmall #-}
 
+-- | Synonym for 'tau'.
+--
+-- >>> map divisorCount [1..10]
+-- [1,2,2,3,2,4,2,4,3,4]
+divisorCount :: (UniqueFactorisation n, Num a) => n -> a
+divisorCount = tau
+
+-- | See 'tauA'.
 tau :: (UniqueFactorisation n, Num a) => n -> a
 tau = runFunction tauA
 
@@ -106,6 +112,7 @@
 tauA :: Num a => ArithmeticFunction n a
 tauA = multiplicative $ const (fromIntegral . succ)
 
+-- | See 'sigmaA'.
 sigma :: (UniqueFactorisation n, Integral n) => Word -> n -> n
 sigma = runFunction . sigmaA
 
@@ -113,10 +120,10 @@
 --
 -- > sigmaA = multiplicative (\p k -> sum $ map (p ^) [0..k])
 -- > sigmaA 0 = tauA
-sigmaA :: forall n. (UniqueFactorisation n, Integral n) => Word -> ArithmeticFunction n n
+sigmaA :: (UniqueFactorisation n, Integral n) => Word -> ArithmeticFunction n n
 sigmaA 0 = tauA
-sigmaA 1 = multiplicative $ \((unPrime :: Prime n -> n) -> p) -> sigmaHelper p
-sigmaA a = multiplicative $ \((unPrime :: Prime n -> n) -> p) -> sigmaHelper (p ^ wordToInt a)
+sigmaA 1 = multiplicative $ sigmaHelper . unPrime
+sigmaA a = multiplicative $ sigmaHelper . (^ wordToInt a) . unPrime
 
 sigmaHelper :: Integral n => n -> Word -> n
 sigmaHelper pa 1 = pa + 1
@@ -124,25 +131,27 @@
 sigmaHelper pa k = (pa ^ wordToInt (k + 1) - 1) `quot` (pa - 1)
 {-# INLINE sigmaHelper #-}
 
-totient :: (UniqueFactorisation n, Num n) => n -> n
+-- | See 'totientA'.
+totient :: UniqueFactorisation n => n -> n
 totient = runFunction totientA
 
 -- | Calculates the totient of a positive number @n@, i.e.
 --   the number of @k@ with @1 <= k <= n@ and @'gcd' n k == 1@,
 --   in other words, the order of the group of units in @&#8484;/(n)@.
-totientA :: forall n. (UniqueFactorisation n, Num n) => ArithmeticFunction n n
-totientA = multiplicative $ \((unPrime :: Prime n -> n) -> p) -> jordanHelper p
+totientA :: UniqueFactorisation n => ArithmeticFunction n n
+totientA = multiplicative $ jordanHelper . unPrime
 
-jordan :: (UniqueFactorisation n, Num n) => Word -> n -> n
+-- | See 'jordanA'.
+jordan :: UniqueFactorisation n => Word -> n -> n
 jordan = runFunction . jordanA
 
 -- | Calculates the k-th Jordan function of an argument.
 --
 -- > jordanA 1 = totientA
-jordanA :: forall n. (UniqueFactorisation n, Num n) => Word -> ArithmeticFunction n n
+jordanA :: UniqueFactorisation n => Word -> ArithmeticFunction n n
 jordanA 0 = multiplicative $ \_ _ -> 0
 jordanA 1 = totientA
-jordanA a = multiplicative $ \((unPrime :: Prime n -> n) -> p) -> jordanHelper (p ^ wordToInt a)
+jordanA a = multiplicative $ jordanHelper . (^ wordToInt a) . unPrime
 
 jordanHelper :: Num n => n -> Word -> n
 jordanHelper pa 1 = pa - 1
@@ -150,13 +159,14 @@
 jordanHelper pa k = (pa - 1) * pa ^ wordToInt (k - 1)
 {-# INLINE jordanHelper #-}
 
+-- | See 'ramanujanA'.
 ramanujan :: Integer -> Integer
 ramanujan = runFunction ramanujanA
 
 -- | Calculates the <https://en.wikipedia.org/wiki/Ramanujan_tau_function Ramanujan tau function>
 --   of a positive number @n@, using formulas given <http://www.numbertheory.org/php/tau.html here>
 ramanujanA :: ArithmeticFunction Integer Integer
-ramanujanA = multiplicative $ \(unPrime -> p) -> ramanujanHelper p
+ramanujanA = multiplicative $ ramanujanHelper . unPrime
 
 ramanujanHelper :: Integer -> Word -> Integer
 ramanujanHelper _ 0 = 1
@@ -171,6 +181,7 @@
         tpPowers = reverse $ take (length binomials) $ iterate (* tp^(2::Int)) (if even k then 1 else tp)
 {-# INLINE ramanujanHelper #-}
 
+-- | See 'moebiusA'.
 moebius :: UniqueFactorisation n => n -> Moebius
 moebius = runFunction moebiusA
 
@@ -182,6 +193,7 @@
     f 0 = MoebiusP
     f _ = MoebiusZ
 
+-- | See 'liouvilleA'.
 liouville :: (UniqueFactorisation n, Num a) => n -> a
 liouville = runFunction liouvilleA
 
@@ -189,16 +201,18 @@
 liouvilleA :: Num a => ArithmeticFunction n a
 liouvilleA = ArithmeticFunction (const $ Xor . odd) runXor
 
+-- | See 'carmichaelA'.
 carmichael :: (UniqueFactorisation n, Integral n) => n -> n
 carmichael = runFunction carmichaelA
-{- The specializations reflects available specializations of lcm. -}
-{-# SPECIALIZE carmichael :: Int -> Int #-}
+{-# SPECIALIZE carmichael :: Int     -> Int #-}
+{-# SPECIALIZE carmichael :: Word    -> Word #-}
 {-# SPECIALIZE carmichael :: Integer -> Integer #-}
+{-# SPECIALIZE carmichael :: Natural -> Natural #-}
 
 -- | Calculates the Carmichael function for a positive integer, that is,
 --   the (smallest) exponent of the group of units in @&#8484;/(n)@.
-carmichaelA :: forall n. (UniqueFactorisation n, Integral n) => ArithmeticFunction n n
-carmichaelA = ArithmeticFunction (\((unPrime :: Prime n -> n) -> p) k -> LCM $ f p k) getLCM
+carmichaelA :: (UniqueFactorisation n, Integral n) => ArithmeticFunction n n
+carmichaelA = ArithmeticFunction (\p -> LCM . f (unPrime p)) getLCM
   where
     f 2 1 = 1
     f 2 2 = 2
@@ -211,6 +225,7 @@
 additive :: Num a => (Prime n -> Word -> a) -> ArithmeticFunction n a
 additive f = ArithmeticFunction ((Sum .) . f) getSum
 
+-- | See 'smallOmegaA'.
 smallOmega :: (UniqueFactorisation n, Num a) => n -> a
 smallOmega = runFunction smallOmegaA
 
@@ -220,6 +235,7 @@
 smallOmegaA :: Num a => ArithmeticFunction n a
 smallOmegaA = additive (\_ _ -> 1)
 
+-- | See 'bigOmegaA'.
 bigOmega :: UniqueFactorisation n => n -> Word
 bigOmega = runFunction bigOmegaA
 
@@ -229,12 +245,13 @@
 bigOmegaA :: ArithmeticFunction n Word
 bigOmegaA = additive $ const id
 
-expMangoldt :: (UniqueFactorisation n, Num n) => n -> n
+-- | See 'expMangoldtA'.
+expMangoldt :: UniqueFactorisation n => n -> n
 expMangoldt = runFunction expMangoldtA
 
 -- | The exponent of von Mangoldt function. Use @log expMangoldtA@ to recover von Mangoldt function itself.
-expMangoldtA :: forall n. (UniqueFactorisation n, Num n) => ArithmeticFunction n n
-expMangoldtA = ArithmeticFunction (\((unPrime :: Prime n -> n) -> p) _ -> MangoldtOne p) runMangoldt
+expMangoldtA :: UniqueFactorisation n => ArithmeticFunction n n
+expMangoldtA = ArithmeticFunction (const . MangoldtOne . unPrime) runMangoldt
 
 data Mangoldt a
   = MangoldtZero
@@ -255,6 +272,16 @@
 instance Monoid (Mangoldt a) where
   mempty  = MangoldtZero
   mappend = (<>)
+
+-- | See 'isNFreeA'.
+isNFree :: UniqueFactorisation n => Word -> n -> Bool
+isNFree n = runFunction (isNFreeA n)
+
+-- | Check if an integer is @n@-free. An integer @x@ is @n@-free if in its
+-- factorisation into prime factors, no factor has an exponent larger than or
+-- equal to @n@.
+isNFreeA :: Word -> ArithmeticFunction n Bool
+isNFreeA n = ArithmeticFunction (\_ pow -> All $ pow < n) getAll
 
 newtype LCM a = LCM { getLCM :: a }
 
diff --git a/Math/NumberTheory/Curves/Montgomery.hs b/Math/NumberTheory/Curves/Montgomery.hs
--- a/Math/NumberTheory/Curves/Montgomery.hs
+++ b/Math/NumberTheory/Curves/Montgomery.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Arithmetic on Montgomery elliptic curve.
 --
@@ -56,9 +54,11 @@
   , pointZ :: !Integer -- ^ Extract z-coordinate.
   }
 
+-- | Extract (a + 2) \/ 4, where a is a coefficient in curve's equation.
 pointA24 :: forall a24 n. KnownNat a24 => Point a24 n -> Integer
 pointA24 _ = toInteger $ natVal (Proxy :: Proxy a24)
 
+-- | Extract modulo of the curve.
 pointN :: forall a24 n. KnownNat n => Point a24 n -> Integer
 pointN _ = toInteger $ natVal (Proxy :: Proxy n)
 
diff --git a/Math/NumberTheory/Euclidean.hs b/Math/NumberTheory/Euclidean.hs
--- a/Math/NumberTheory/Euclidean.hs
+++ b/Math/NumberTheory/Euclidean.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- This module exports a class to represent Euclidean domains.
 --
@@ -63,7 +61,7 @@
   lcm 0 _ =  0
   lcm x y =  abs ((x `quot` (gcd x y)) * y)
 
-  -- | Test whether two numbers are coprime .
+  -- | Test whether two numbers are coprime.
   coprime :: a -> a -> Bool
   coprime x y = gcd x y == 1
 
@@ -102,7 +100,7 @@
 coprimeIntegral :: Integral a => a -> a -> Bool
 coprimeIntegral x y = (odd x || odd y) && P.gcd x y == 1
 
--- | Wrapper around 'Integral', which has an 'Eucledian' instance.
+-- | Wrapper around 'Integral', which has an 'Euclidean' instance.
 newtype WrappedIntegral a = WrappedIntegral { unWrappedIntegral :: a }
   deriving (Eq, Ord, Show, Num, Integral, Real, Enum)
 
@@ -153,6 +151,7 @@
   -- https://ghc.haskell.org/trac/ghc/ticket/15350
   -- extendedGCD = gcdExtInteger
 
+-- | Beware that 'extendedGCD' does not make any sense for 'Natural'.
 instance Euclidean Natural where
   quotRem = P.quotRem
   divMod  = P.divMod
diff --git a/Math/NumberTheory/GCD.hs b/Math/NumberTheory/GCD.hs
deleted file mode 100644
--- a/Math/NumberTheory/GCD.hs
+++ /dev/null
@@ -1,261 +0,0 @@
--- |
--- Module:      Math.NumberTheory.GCD
--- Copyright:   (c) 2011 Daniel Fischer
--- Licence:     MIT
--- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
---
--- This module exports GCD and coprimality test using the binary gcd algorithm
--- and GCD with the extended Euclidean algorithm.
---
--- Efficiently counting the number of trailing zeros, the binary gcd algorithm
--- can perform considerably faster than the Euclidean algorithm on average.
--- For 'Int', GHC has a rewrite rule to use GMP's fast gcd, depending on
--- hardware and\/or GMP version, that can be faster or slower than the binary
--- algorithm (on my 32-bit box, binary is faster, on my 64-bit box, GMP).
--- For 'Word' and the sized @IntN\/WordN@ types, there is no rewrite rule (yet)
--- in GHC, and the binary algorithm performs consistently (so far as my tests go)
--- much better (if this module's rewrite rules fire).
---
--- When using this module, always compile with optimisations turned on to
--- benefit from GHC's primops and the rewrite rules.
-
-{-# LANGUAGE BangPatterns        #-}
-{-# LANGUAGE CPP                 #-}
-{-# LANGUAGE LambdaCase          #-}
-{-# LANGUAGE MagicHash           #-}
-
-{-# OPTIONS_GHC -fno-warn-unused-imports #-}
-{-# OPTIONS_GHC -fno-warn-deprecations   #-}
-
-module Math.NumberTheory.GCD
-    ( binaryGCD
-    , extendedGCD
-    , coprime
-    ) where
-
-import Data.Bits
-import Data.Semigroup
-
-import GHC.Word
-import GHC.Int
-
-import Math.NumberTheory.GCD.LowLevel
-import Math.NumberTheory.Utils
-
-#include "MachDeps.h"
-
-{-# RULES
-"binaryGCD/Int"     binaryGCD = gcdInt
-"binaryGCD/Word"    binaryGCD = gcdWord
-"binaryGCD/Int8"    binaryGCD = gi8
-"binaryGCD/Int16"   binaryGCD = gi16
-"binaryGCD/Int32"   binaryGCD = gi32
-"binaryGCD/Word8"   binaryGCD = gw8
-"binaryGCD/Word16"  binaryGCD = gw16
-"binaryGCD/Word32"  binaryGCD = gw32
-  #-}
-#if WORD_SIZE_IN_BITS == 64
-gi64 :: Int64 -> Int64 -> Int64
-gi64 (I64# x#) (I64# y#) = I64# (gcdInt# x# y#)
-
-gw64 :: Word64 -> Word64 -> Word64
-gw64 (W64# x#) (W64# y#) = W64# (gcdWord# x# y#)
-
-{-# RULES
-"binaryGCD/Int64"   binaryGCD = gi64
-"binaryGCD/Word64"  binaryGCD = gw64
-  #-}
-#endif
-{-# INLINE [1] binaryGCD #-}
--- | Calculate the greatest common divisor using the binary gcd algorithm.
---   Depending on type and hardware, that can be considerably faster than
---   @'Prelude.gcd'@ but it may also be significantly slower.
---
---   There are specialised functions for @'Int'@ and @'Word'@ and rewrite rules
---   for those and @IntN@ and @WordN@, @N <= WORD_SIZE_IN_BITS@, to use the
---   specialised variants. These types are worth benchmarking, others probably not.
---
---   It is very slow for 'Integer' (and probably every type except the abovementioned),
---   I recommend not using it for those.
---
---   Relies on twos complement or sign and magnitude representaion for signed types.
-binaryGCD :: (Integral a, Bits a) => a -> a -> a
-binaryGCD = binaryGCDImpl
-
-{-# DEPRECATED binaryGCD "Use 'Math.NumberTheory.Euclidean.gcd'" #-}
-
-#if WORD_SIZE_IN_BITS < 64
-{-# SPECIALISE binaryGCDImpl :: Word64 -> Word64 -> Word64,
-                                Int64 -> Int64 -> Int64 #-}
-#endif
-{-# SPECIALISE binaryGCDImpl :: Integer -> Integer -> Integer #-}
-binaryGCDImpl :: (Integral a, Bits a) => a -> a -> a
-binaryGCDImpl a 0 = abs a
-binaryGCDImpl 0 b = abs b
-binaryGCDImpl a b =
-    case shiftToOddCount a' of
-      (!za, !oa) ->
-        case shiftToOddCount b' of
-          (!zb, !ob) -> gcdOdd (abs oa) (abs ob) `shiftL` min za zb
-    where
-      a' = abs a
-      b' = abs b
-
-{-# SPECIALISE extendedGCD :: Int -> Int -> (Int, Int, Int),
-                              Word -> Word -> (Word, Word, Word),
-                              Integer -> Integer -> (Integer, Integer, Integer)
-  #-}
--- | Calculate the greatest common divisor of two numbers and coefficients
---   for the linear combination.
---
---   For signed types satisfies:
---
--- > case extendedGCD a b of
--- >   (d, u, v) -> u*a + v*b == d
--- >                && d == gcd a b
---
---   For unsigned and bounded types the property above holds, but since @u@ and @v@ must also be unsigned,
---   the result may look weird. E. g., on 64-bit architecture
---
--- > extendedGCD (2 :: Word) (3 :: Word) == (1, 2^64-1, 1)
---
---   For unsigned and unbounded types (like 'Numeric.Natural.Natural') the result is undefined.
---
---   For signed types we also have
---
--- > abs u < abs b || abs b <= 1
--- >
--- > abs v < abs a || abs a <= 1
---
---   (except if one of @a@ and @b@ is 'minBound' of a signed type).
-extendedGCD :: Integral a => a -> a -> (a, a, a)
-extendedGCD a b = (d, u, v)
-  where
-    (d, x, y) = eGCD 0 1 1 0 (abs a) (abs b)
-    u | a < 0     = negate x
-      | otherwise = x
-    v | b < 0     = negate y
-      | otherwise = y
-    eGCD !n1 o1 !n2 o2 r s
-      | s == 0    = (r, o1, o2)
-      | otherwise = case r `quotRem` s of
-                      (q, t) -> eGCD (o1 - q*n1) n1 (o2 - q*n2) n2 s t
-{-# DEPRECATED extendedGCD "Use 'Math.NumberTheory.Euclidean.extendedGCD'" #-}
-
-{-# RULES
-"coprime/Int"       coprime = coprimeInt
-"coprime/Word"      coprime = coprimeWord
-"coprime/Int8"      coprime = ci8
-"coprime/Int16"     coprime = ci16
-"coprime/Int32"     coprime = ci32
-"coprime/Word8"     coprime = cw8
-"coprime/Word16"    coprime = cw16
-"coprime/Word32"    coprime = cw32
-  #-}
-#if WORD_SIZE_IN_BITS == 64
-ci64 :: Int64 -> Int64 -> Bool
-ci64 (I64# x#) (I64# y#) = coprimeInt# x# y#
-
-cw64 :: Word64 -> Word64 -> Bool
-cw64 (W64# x#) (W64# y#) = coprimeWord# x# y#
-
-{-# RULES
-"coprime/Int64"     coprime = ci64
-"coprime/Word64"    coprime = cw64
-  #-}
-#endif
-{-# INLINE [1] coprime #-}
--- | Test whether two numbers are coprime using an abbreviated binary gcd algorithm.
---   A little bit faster than checking @binaryGCD a b == 1@ if one of the arguments
---   is even, much faster if both are even.
---
---   The remarks about performance at 'binaryGCD' apply here too, use this function
---   only at the types with rewrite rules.
---
---   Relies on twos complement or sign and magnitude representaion for signed types.
-coprime :: (Integral a, Bits a) => a -> a -> Bool
-coprime = coprimeImpl
-{-# DEPRECATED coprime "Use 'Math.NumberTheory.Euclidean.coprime'" #-}
-
--- Separate implementation to give the rules a chance to fire by not inlining
--- before phase 1, and yet have a specialisation for the types without rules
-#if WORD_SIZE_IN_BITS < 64
-{-# SPECIALISE coprimeImpl :: Word64 -> Word64 -> Bool,
-                              Int64 -> Int64 -> Bool #-}
-#endif
-{-# SPECIALISE coprimeImpl :: Integer -> Integer -> Bool #-}
-coprimeImpl :: (Integral a, Bits a) => a -> a -> Bool
-coprimeImpl a b =
-  (a' == 1 || b' == 1)
-  || (a' /= 0 && b' /= 0 && ((a .|. b) .&. 1) == 1
-      && gcdOdd (abs (shiftToOdd a')) (abs (shiftToOdd b')) == 1)
-    where
-      a' = abs a
-      b' = abs b
-
--- Auxiliaries
-
--- gcd of two odd numbers
-{-# INLINE gcdOdd #-}
-gcdOdd :: (Integral a, Bits a) => a -> a -> a
-gcdOdd a b
-  | a == 1 || b == 1    = 1
-  | a < b               = oddGCD b a
-  | a > b               = oddGCD a b
-  | otherwise           = a
-
-{-# SPECIALISE oddGCD :: Integer -> Integer -> Integer #-}
-#if WORD_SIZE_IN_BITS < 64
-{-# SPECIALISE oddGCD :: Int64 -> Int64 -> Int64,
-                         Word64 -> Word64 -> Word64
-  #-}
-#endif
-oddGCD :: (Integral a, Bits a) => a -> a -> a
-oddGCD a b =
-    case shiftToOdd (a-b) of
-      1 -> 1
-      c | c < b     -> oddGCD b c
-        | c > b     -> oddGCD c b
-        | otherwise -> c
-
--------------------------------------------------------------------------------
---                Blech! Getting the rules to fire isn't easy.               --
--------------------------------------------------------------------------------
-
-gi8 :: Int8 -> Int8 -> Int8
-gi8 (I8# x#) (I8# y#) = I8# (gcdInt# x# y#)
-
-gi16 :: Int16 -> Int16 -> Int16
-gi16 (I16# x#) (I16# y#) = I16# (gcdInt# x# y#)
-
-gi32 :: Int32 -> Int32 -> Int32
-gi32 (I32# x#) (I32# y#) = I32# (gcdInt# x# y#)
-
-gw8 :: Word8 -> Word8 -> Word8
-gw8 (W8# x#) (W8# y#) = W8# (gcdWord# x# y#)
-
-gw16 :: Word16 -> Word16 -> Word16
-gw16 (W16# x#) (W16# y#) = W16# (gcdWord# x# y#)
-
-gw32 :: Word32 -> Word32 -> Word32
-gw32 (W32# x#) (W32# y#) = W32# (gcdWord# x# y#)
-
-ci8 :: Int8 -> Int8 -> Bool
-ci8 (I8# x#) (I8# y#) = coprimeInt# x# y#
-
-ci16 :: Int16 -> Int16 -> Bool
-ci16 (I16# x#) (I16# y#) = coprimeInt# x# y#
-
-ci32 :: Int32 -> Int32 -> Bool
-ci32 (I32# x#) (I32# y#) = coprimeInt# x# y#
-
-cw8 :: Word8 -> Word8 -> Bool
-cw8 (W8# x#) (W8# y#) = coprimeWord# x# y#
-
-cw16 :: Word16 -> Word16 -> Bool
-cw16 (W16# x#) (W16# y#) = coprimeWord# x# y#
-
-cw32 :: Word32 -> Word32 -> Bool
-cw32 (W32# x#) (W32# y#) = coprimeWord# x# y#
diff --git a/Math/NumberTheory/GCD/LowLevel.hs b/Math/NumberTheory/GCD/LowLevel.hs
deleted file mode 100644
--- a/Math/NumberTheory/GCD/LowLevel.hs
+++ /dev/null
@@ -1,104 +0,0 @@
--- |
--- Module:      Math.NumberTheory.GCD.LowLevel
--- Copyright:   (c) 2011 Daniel Fischer
--- Licence:     MIT
--- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
---
--- Low level gcd and coprimality functions using the binary gcd algorithm.
--- Normally, accessing these via the higher level interface of "Math.NumberTheory.GCD"
--- should be sufficient.
---
-{-# LANGUAGE MagicHash     #-}
-{-# LANGUAGE UnboxedTuples #-}
-module Math.NumberTheory.GCD.LowLevel
-  ( -- * Specialised GCDs
-    gcdInt
-  , gcdWord
-    -- ** GCDs for unboxed types
-  , gcdInt#
-  , gcdWord#
-    -- * Specialised tests for coprimality
-  , coprimeInt
-  , coprimeWord
-    -- ** Coprimality tests for unboxed types
-  , coprimeInt#
-  , coprimeWord#
-  ) where
-
-import GHC.Base
-
-import Math.NumberTheory.Utils
-
-{-# DEPRECATED gcdInt, gcdWord, gcdInt#, gcdWord# "Use Math.NumberTheory.Euclidean.gcd" #-}
-{-# DEPRECATED coprimeInt, coprimeWord, coprimeInt#, coprimeWord# "Math.NumberTheory.Euclidean." #-}
-
--- | Greatest common divisor of two 'Int's, calculated with the binary gcd algorithm.
-gcdInt :: Int -> Int -> Int
-gcdInt (I# a#) (I# b#) = I# (gcdInt# a# b#)
-
--- | Test whether two 'Int's are coprime, using an abbreviated binary gcd algorithm.
-coprimeInt :: Int -> Int -> Bool
-coprimeInt (I# a#) (I# b#) = coprimeInt# a# b#
-
--- | Greatest common divisor of two 'Word's, calculated with the binary gcd algorithm.
-gcdWord :: Word -> Word -> Word
-gcdWord (W# a#) (W# b#) = W# (gcdWord# a# b#)
-
--- | Test whether two 'Word's are coprime, using an abbreviated binary gcd algorithm.
-coprimeWord :: Word -> Word -> Bool
-coprimeWord (W# a#) (W# b#) = coprimeWord# a# b#
-
--- | Greatest common divisor of two 'Int#'s, calculated with the binary gcd algorithm.
-gcdInt# :: Int# -> Int# -> Int#
-gcdInt# a# b# = word2Int# (gcdWord# (int2Word# (absInt# a#)) (int2Word# (absInt# b#)))
-
-
--- | Test whether two 'Int#'s are coprime.
-coprimeInt# :: Int# -> Int# -> Bool
-coprimeInt# a# b# = coprimeWord# (int2Word# (absInt# a#)) (int2Word# (absInt# b#))
-
--- | Greatest common divisor of two 'Word#'s, calculated with the binary gcd algorithm.
-gcdWord# :: Word# -> Word# -> Word#
-gcdWord# a# 0## = a#
-gcdWord# 0## b# = b#
-gcdWord# a# b#  =
-    case shiftToOddCount# a# of
-      (# za#, oa# #) ->
-        case shiftToOddCount# b# of
-          (# zb#, ob# #) -> gcdWordOdd# oa# ob# `uncheckedShiftL#` (if isTrue# (za# <# zb#) then za# else zb#)
-
--- | Test whether two 'Word#'s are coprime.
-coprimeWord# :: Word# -> Word# -> Bool
-coprimeWord# a# b# =
-  (isTrue# (a# `eqWord#` 1##) || isTrue# (b# `eqWord#` 1##))
-  || (isTrue# (((a# `or#` b#) `and#` 1##) `eqWord#` 1##) -- not both even
-      && ((isTrue# (a# `neWord#` 0##) && isTrue# (b# `neWord#` 0##)) -- neither is zero
-      && isTrue# (gcdWordOdd# (shiftToOdd# a#) (shiftToOdd# b#) `eqWord#` 1##)))
-
--- Various auxiliary functions
-
--- calculate the gcd of two odd numbers
-{-# INLINE gcdWordOdd# #-}
-gcdWordOdd# :: Word# -> Word# -> Word#
-gcdWordOdd# a# b#
-  | isTrue# (a# `eqWord#` 1##) || isTrue# (b# `eqWord#` 1##)    = 1##
-  | isTrue# (a# `eqWord#` b#)                                   = a#
-  | isTrue# (a# `ltWord#` b#)                                   = oddGCD# b# a#
-  | otherwise                                                   = oddGCD# a# b#
-
--- calculate the gcd of two odd numbers using the binary gcd algorithm
--- Precondition: first argument strictly larger than second (which should be greater than 1)
-oddGCD# :: Word# -> Word# -> Word#
-oddGCD# a# b# =
-    case shiftToOdd# (a# `minusWord#` b#) of
-      1## -> 1##
-      c#  | isTrue# (c# `ltWord#` b#)   -> oddGCD# b# c#
-          | isTrue# (c# `gtWord#` b#)   -> oddGCD# c# b#
-          | otherwise                   -> c#
-
-absInt# :: Int# -> Int#
-absInt# i#
-  | isTrue# (i# <# 0#)  = negateInt# i#
-  | otherwise           = i#
diff --git a/Math/NumberTheory/GaussianIntegers.hs b/Math/NumberTheory/GaussianIntegers.hs
deleted file mode 100644
--- a/Math/NumberTheory/GaussianIntegers.hs
+++ /dev/null
@@ -1,18 +0,0 @@
--- |
--- Module:      Math.NumberTheory.GaussianIntegers
--- Copyright:   (c) 2016 Chris Fredrickson, Google Inc.
--- Licence:     MIT
--- Maintainer:  Chris Fredrickson <chris.p.fredrickson@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
---
--- This module exports functions for manipulating Gaussian integers, including
--- computing their prime factorisations.
---
-
-module Math.NumberTheory.GaussianIntegers
-  {-# DEPRECATED "Use Math.NumberTheory.Quadratic.GaussianIntegers instead" #-}
-  ( module Math.NumberTheory.Quadratic.GaussianIntegers
-  ) where
-
-import Math.NumberTheory.Quadratic.GaussianIntegers
diff --git a/Math/NumberTheory/Moduli.hs b/Math/NumberTheory/Moduli.hs
--- a/Math/NumberTheory/Moduli.hs
+++ b/Math/NumberTheory/Moduli.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Miscellaneous functions related to modular arithmetic.
 --
diff --git a/Math/NumberTheory/Moduli/Chinese.hs b/Math/NumberTheory/Moduli/Chinese.hs
--- a/Math/NumberTheory/Moduli/Chinese.hs
+++ b/Math/NumberTheory/Moduli/Chinese.hs
@@ -1,26 +1,165 @@
 -- |
 -- Module:      Math.NumberTheory.Moduli.Chinese
--- Copyright:   (c) 2011 Daniel Fischer
+-- Copyright:   (c) 2011 Daniel Fischer, 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Chinese remainder theorem
 --
 
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE RankNTypes          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections       #-}
+{-# LANGUAGE TypeApplications    #-}
+{-# LANGUAGE TypeOperators       #-}
 
+#if __GLASGOW_HASKELL__ > 805
+{-# LANGUAGE NoStarIsType #-}
+#endif
+
 module Math.NumberTheory.Moduli.Chinese
-  ( chineseRemainder
+  ( -- * Safe interface
+    chinese
+  , chineseCoprime
+  , chineseSomeMod
+  , chineseCoprimeSomeMod
+
+  , -- * Unsafe interface
+    chineseRemainder
   , chineseRemainder2
   ) where
 
+import Prelude hiding (mod, quot, gcd, lcm)
+
 import Control.Monad (foldM)
+import Data.Foldable
+import Data.Ratio
+import GHC.TypeNats.Compat
+import Numeric.Natural
 
-import Math.NumberTheory.Euclidean (extendedGCD)
-import Math.NumberTheory.Utils (recipMod)
+import Math.NumberTheory.Moduli.Class
+import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Euclidean.Coprimes
+import Math.NumberTheory.Utils (recipMod, splitOff)
 
+-- | 'chineseCoprime' @(n1, m1)@ @(n2, m2)@ returns @n@ such that
+-- @n \`mod\` m1 == n1@ and @n \`mod\` m2 == n2@.
+-- Moduli @m1@ and @m2@ must be coprime, otherwise 'Nothing' is returned.
+--
+-- This function is slightly faster than 'chinese', but more restricted.
+--
+-- >>> chineseCoprime (1, 2) (2, 3)
+-- Just 5
+-- >>> chineseCoprime (3, 4) (5, 6)
+-- Nothing -- moduli must be coprime
+chineseCoprime :: Euclidean a => (a, a) -> (a, a) -> Maybe a
+chineseCoprime (n1, m1) (n2, m2) = case d of
+  1 -> Just $ ((1 - u * m1) * n1 + (1 - v * m2) * n2) `mod` (m1 * m2)
+  _ -> Nothing
+  where
+    (d, u, v) = extendedGCD m1 m2
+
+{-# SPECIALISE chineseCoprime :: (Int, Int) -> (Int, Int) -> Maybe Int #-}
+{-# SPECIALISE chineseCoprime :: (Word, Word) -> (Word, Word) -> Maybe Word #-}
+{-# SPECIALISE chineseCoprime :: (Integer, Integer) -> (Integer, Integer) -> Maybe Integer #-}
+{-# SPECIALISE chineseCoprime :: (Natural, Natural) -> (Natural, Natural) -> Maybe Natural #-}
+
+-- | 'chinese' @(n1, m1)@ @(n2, m2)@ returns @n@ such that
+-- @n \`mod\` m1 == n1@ and @n \`mod\` m2 == n2@, if exists.
+-- Moduli @m1@ and @m2@ are allowed to have common factors.
+--
+-- >>> chinese (1, 2) (2, 3)
+-- Just 5
+-- >>> chinese (3, 4) (5, 6)
+-- Just 11
+-- >>> chinese (3, 4) (2, 6)
+-- Nothing
+chinese :: forall a. Euclidean a => (a, a) -> (a, a) -> Maybe a
+chinese (n1, m1) (n2, m2)
+  | n1 `mod` g == n2 `mod` g
+  = chineseCoprime (n1 `mod` m1', m1') (n2 `mod` m2', m2')
+  | otherwise
+  = Nothing
+  where
+    g :: a
+    g = gcd m1 m2
+
+    ms :: [(a, Word)]
+    ms = unCoprimes $ splitIntoCoprimes [(m1, 1), (m2 `quot` g, 1)]
+
+    m1', m2' :: a
+    (m1', m2') = foldl' go (1, 1) $ map fst ms
+
+    go :: (a, a) -> a -> (a, a)
+    go (t1, t2) p
+      | k1 <= k2
+      = (t1, t2 * p ^ k2)
+      | otherwise
+      = (t1 * p ^ k1, t2)
+      where
+        (k1, _) = splitOff p m1
+        (k2, _) = splitOff p m2
+
+{-# SPECIALISE chinese :: (Int, Int) -> (Int, Int) -> Maybe Int #-}
+{-# SPECIALISE chinese :: (Word, Word) -> (Word, Word) -> Maybe Word #-}
+{-# SPECIALISE chinese :: (Integer, Integer) -> (Integer, Integer) -> Maybe Integer #-}
+{-# SPECIALISE chinese :: (Natural, Natural) -> (Natural, Natural) -> Maybe Natural #-}
+
+isCompatible :: KnownNat m => Mod m -> Rational -> Bool
+isCompatible n r = case invertMod (fromInteger (denominator r)) of
+  Nothing -> False
+  Just r' -> r' * fromInteger (numerator r) == n
+
+chineseWrap
+  :: (Integer -> Integer -> Integer)
+  -> ((Integer, Integer) -> (Integer, Integer) -> Maybe Integer)
+  -> SomeMod
+  -> SomeMod
+  -> Maybe SomeMod
+chineseWrap f g (SomeMod n1) (SomeMod n2)
+  = fmap (`modulo` fromInteger (f m1 m2)) (g (getVal n1, m1) (getVal n2, m2))
+  where
+    m1 = getMod n1
+    m2 = getMod n2
+chineseWrap _ _ (SomeMod n) (InfMod r)
+  | isCompatible n r = Just $ InfMod r
+  | otherwise        = Nothing
+chineseWrap _ _ (InfMod r) (SomeMod n)
+  | isCompatible n r = Just $ InfMod r
+  | otherwise        = Nothing
+chineseWrap _ _ (InfMod r1) (InfMod r2)
+  | r1 == r2  = Just $ InfMod r1
+  | otherwise = Nothing
+
+-- | Same as 'chineseCoprime', but operates on residues.
+--
+-- >>> :set -XDataKinds
+-- >>> import Math.NumberTheory.Moduli.Class
+-- >>> (1 `modulo` 2) `chineseCoprimeSomeMod` (2 `modulo` 3)
+-- Just (5 `modulo` 6)
+-- >>> (3 `modulo` 4) `chineseCoprimeSomeMod` (5 `modulo` 6)
+-- Nothing
+chineseCoprimeSomeMod :: SomeMod -> SomeMod -> Maybe SomeMod
+chineseCoprimeSomeMod = chineseWrap (*) chineseCoprime
+
+-- | Same as 'chinese', but operates on residues.
+--
+-- >>> :set -XDataKinds
+-- >>> import Math.NumberTheory.Moduli.Class
+-- >>> (1 `modulo` 2) `chineseSomeMod` (2 `modulo` 3)
+-- Just (5 `modulo` 6)
+-- >>> (3 `modulo` 4) `chineseSomeMod` (5 `modulo` 6)
+-- Just (11 `modulo` 12)
+-- >>> (3 `modulo` 4) `chineseSomeMod` (2 `modulo` 6)
+-- Nothing
+chineseSomeMod :: SomeMod -> SomeMod -> Maybe SomeMod
+chineseSomeMod = chineseWrap lcm chinese
+
+-------------------------------------------------------------------------------
+-- Unsafe interface
+
 -- | Given a list @[(r_1,m_1), ..., (r_n,m_n)]@ of @(residue,modulus)@
 --   pairs, @chineseRemainder@ calculates the solution to the simultaneous
 --   congruences
@@ -32,7 +171,7 @@
 --   if all moduli are positive and pairwise coprime. Otherwise
 --   the result is @Nothing@ regardless of whether
 --   a solution exists.
-chineseRemainder :: [(Integer,Integer)] -> Maybe Integer
+chineseRemainder :: [(Integer, Integer)] -> Maybe Integer
 chineseRemainder remainders = foldM addRem 0 remainders
   where
     !modulus = product (map snd remainders)
@@ -48,7 +187,7 @@
 -- > r ≡ r_k (mod m_k)
 --
 --   if @m_1@ and @m_2@ are coprime.
-chineseRemainder2 :: (Integer,Integer) -> (Integer,Integer) -> Integer
-chineseRemainder2 (r1, md1) (r2,md2)
-    = case extendedGCD md1 md2 of
-        (_,u,v) -> ((1 - u*md1)*r1 + (1 - v*md2)*r2) `mod` (md1*md2)
+chineseRemainder2 :: (Integer, Integer) -> (Integer, Integer) -> Integer
+chineseRemainder2 (n1, m1) (n2, m2) = ((1 - u * m1) * n1 + (1 - v * m2) * n2) `mod` (m1 * m2)
+  where
+    (_, u, v) = extendedGCD m1 m2
diff --git a/Math/NumberTheory/Moduli/Class.hs b/Math/NumberTheory/Moduli/Class.hs
--- a/Math/NumberTheory/Moduli/Class.hs
+++ b/Math/NumberTheory/Moduli/Class.hs
@@ -3,20 +3,21 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Safe modular arithmetic with modulo on type level.
 --
 
+{-# LANGUAGE BangPatterns               #-}
 {-# LANGUAGE DataKinds                  #-}
 {-# LANGUAGE GADTs                      #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE KindSignatures             #-}
 {-# LANGUAGE LambdaCase                 #-}
+{-# LANGUAGE MagicHash                  #-}
 {-# LANGUAGE RankNTypes                 #-}
 {-# LANGUAGE ScopedTypeVariables        #-}
 {-# LANGUAGE StandaloneDeriving         #-}
+{-# LANGUAGE UnboxedTuples              #-}
 
 module Math.NumberTheory.Moduli.Class
   ( -- * Known modulo
@@ -46,11 +47,11 @@
 import Data.Ratio
 import Data.Semigroup
 import Data.Type.Equality
+import GHC.Exts
 import GHC.Integer.GMP.Internals
+import GHC.Natural (Natural(..), powModNatural)
 import GHC.TypeNats.Compat
 
-import GHC.Natural (Natural, powModNatural)
-
 -- | Wrapper for residues modulo @m@.
 --
 -- @Mod 3 :: Mod 10@ stands for the class of integers, congruent to 3 modulo 10 (…−17, −7, 3, 13, 23…).
@@ -89,9 +90,25 @@
   negate mx@(Mod x) =
     Mod $ if x == 0 then 0 else getNatMod mx - x
   {-# INLINE negate #-}
-  mx@(Mod x) * Mod y =
-    Mod $ x * y `rem` getNatMod mx -- `rem` is slightly faster than `mod`
+
+  -- If modulo is small and fits into one machine word,
+  -- there is no need to use long arithmetic at all
+  -- and we can save some allocations.
+  mx@(Mod (NatS# x#)) * (Mod (NatS# y#)) = case getNatMod mx of
+    NatS# m# -> let !(# z1#, z2# #) = timesWord2# x# y# in
+                let !(# _, r# #) = quotRemWord2# z1# z2# m# in
+                Mod (NatS# r#)
+    NatJ# b# -> let !(# z1#, z2# #) = timesWord2# x# y# in
+                let r# = wordToBigNat2 z1# z2# `remBigNat` b# in
+                Mod $ if isTrue# (sizeofBigNat# r# ==# 1#)
+                  then NatS# (bigNatToWord r#)
+                  else NatJ# r#
+
+  mx@(Mod !x) * (Mod !y) =
+    Mod $ x * y `rem` getNatMod mx
+    -- `rem` is slightly faster than `mod`
   {-# INLINE (*) #-}
+
   abs = id
   {-# INLINE abs #-}
   signum = const $ Mod 1
@@ -152,11 +169,11 @@
     y = recipModInteger (getVal mx) (getMod mx)
 {-# INLINABLE invertMod #-}
 
--- | Drop-in replacement for '^', with much better performance.
+-- | Drop-in replacement for 'Prelude.^', with much better performance.
 --
 -- >>> :set -XDataKinds
 -- >>> powMod (3 :: Mod 10) 4
--- > (1 `modulo` 10)
+-- (1 `modulo` 10)
 powMod :: (KnownNat m, Integral a) => Mod m -> a -> Mod m
 powMod mx a
   | a < 0     = error $ "^{Mod}: negative exponent"
@@ -173,7 +190,10 @@
 "powMod/2/Integer"     forall x. powMod x (2 :: Integer) = let u = x in u*u
 "powMod/3/Integer"     forall x. powMod x (3 :: Integer) = let u = x in u*u*u
 "powMod/2/Int"         forall x. powMod x (2 :: Int)     = let u = x in u*u
-"powMod/3/Int"         forall x. powMod x (3 :: Int)     = let u = x in u*u*u #-}
+"powMod/3/Int"         forall x. powMod x (3 :: Int)     = let u = x in u*u*u
+"powMod/2/Word"        forall x. powMod x (2 :: Word)    = let u = x in u*u
+"powMod/3/Word"        forall x. powMod x (3 :: Word)    = let u = x in u*u*u
+#-}
 
 -- | Infix synonym of 'powMod'.
 (^%) :: (KnownNat m, Integral a) => Mod m -> a -> Mod m
@@ -188,8 +208,9 @@
 
 -- | This type represents elements of the multiplicative group mod m, i.e.
 -- those elements which are coprime to m. Use @toMultElement@ to construct.
-newtype MultMod m = MultMod { multElement :: Mod m }
-  deriving (Eq, Ord, Show)
+newtype MultMod m = MultMod {
+  multElement :: Mod m -- ^ Unwrap a residue.
+  } deriving (Eq, Ord, Show)
 
 instance KnownNat m => Semigroup (MultMod m) where
   MultMod a <> MultMod b = MultMod (a * b)
@@ -225,7 +246,7 @@
 --
 -- >>> 2 `modulo` 10 + 4 `modulo` 15
 -- (1 `modulo` 5)
--- >>> 2 `modulo` 10 * 4 `modulo` 15
+-- >>> (2 `modulo` 10) * (4 `modulo` 15)
 -- (3 `modulo` 5)
 -- >>> 2 `modulo` 10 + fromRational (3 % 7)
 -- (1 `modulo` 10)
@@ -322,7 +343,7 @@
 --
 -- >>> invertSomeMod (3 `modulo` 10)
 -- Just (7 `modulo` 10) -- because 3 * 7 = 1 :: Mod 10
--- >>> invertMod (4 `modulo` 10)
+-- >>> invertSomeMod (4 `modulo` 10)
 -- Nothing
 -- >>> invertSomeMod (fromRational (2 % 5))
 -- Just 5 % 2
@@ -338,8 +359,8 @@
   SomeMod -> Int     -> SomeMod,
   SomeMod -> Word    -> SomeMod #-}
 
--- | Drop-in replacement for '^', with much better performance.
--- When -O is enabled, there is a rewrite rule, which specialises '^' to 'powSomeMod'.
+-- | Drop-in replacement for 'Prelude.^', with much better performance.
+-- When -O is enabled, there is a rewrite rule, which specialises 'Prelude.^' to 'powSomeMod'.
 --
 -- >>> powSomeMod (3 `modulo` 10) 4
 -- (1 `modulo` 10)
diff --git a/Math/NumberTheory/Moduli/DiscreteLogarithm.hs b/Math/NumberTheory/Moduli/DiscreteLogarithm.hs
--- a/Math/NumberTheory/Moduli/DiscreteLogarithm.hs
+++ b/Math/NumberTheory/Moduli/DiscreteLogarithm.hs
@@ -3,8 +3,6 @@
 -- Copyright:    (c) 2018 Bhavik Mehta
 -- License:      MIT
 -- Maintainer:   Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:    Provisional
--- Portability:  Non-portable
 --
 
 {-# LANGUAGE BangPatterns        #-}
@@ -27,7 +25,7 @@
 import Math.NumberTheory.Moduli.Equations     (solveLinear)
 import Math.NumberTheory.Moduli.PrimitiveRoot (PrimitiveRoot(..), CyclicGroup(..))
 import Math.NumberTheory.Powers.Squares       (integerSquareRoot)
-import Math.NumberTheory.UniqueFactorisation  (unPrime)
+import Math.NumberTheory.Primes  (unPrime)
 
 -- | Computes the discrete logarithm. Currently uses a combination of the baby-step
 -- giant-step method and Pollard's rho algorithm, with Bach reduction.
@@ -42,13 +40,17 @@
   -> Natural              -- ^ result
 discreteLogarithm' cg a b =
   case cg of
-    CG2                                    -> 0
-       -- the only valid input was a=1, b=1
-    CG4                                    -> if b == 1 then 0 else 1
-       -- the only possible input here is a=3 with b = 1 or 3
-    CGOddPrimePower       (unPrime -> p) k -> discreteLogarithmPP p k (getVal a) (getVal b)
-    CGDoubleOddPrimePower (unPrime -> p) k -> discreteLogarithmPP p k (getVal a `rem` p^k) (getVal b `rem` p^k)
-       -- we have the isomorphism t -> t `rem` p^k from (Z/2p^kZ)* -> (Z/p^kZ)*
+    CG2
+      -> 0
+      -- the only valid input was a=1, b=1
+    CG4
+      -> if b == 1 then 0 else 1
+      -- the only possible input here is a=3 with b = 1 or 3
+    CGOddPrimePower       (toInteger . unPrime -> p) k
+      -> discreteLogarithmPP p k (getVal a) (getVal b)
+    CGDoubleOddPrimePower (toInteger . unPrime -> p) k
+      -> discreteLogarithmPP p k (getVal a `rem` p^k) (getVal b `rem` p^k)
+      -- we have the isomorphism t -> t `rem` p^k from (Z/2p^kZ)* -> (Z/p^kZ)*
 
 -- Implementation of Bach reduction (https://www2.eecs.berkeley.edu/Pubs/TechRpts/1984/CSD-84-186.pdf)
 {-# INLINE discreteLogarithmPP #-}
diff --git a/Math/NumberTheory/Moduli/Equations.hs b/Math/NumberTheory/Moduli/Equations.hs
--- a/Math/NumberTheory/Moduli/Equations.hs
+++ b/Math/NumberTheory/Moduli/Equations.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Polynomial modular equations.
 --
@@ -22,7 +20,7 @@
 import Math.NumberTheory.Moduli.Chinese
 import Math.NumberTheory.Moduli.Class
 import Math.NumberTheory.Moduli.Sqrt
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Primes
 import Math.NumberTheory.Utils (recipMod)
 
 -------------------------------------------------------------------------------
@@ -65,7 +63,7 @@
   => Mod m   -- ^ a
   -> Mod m   -- ^ b
   -> Mod m   -- ^ c
-  -> [Mod m] -- ^ list of c
+  -> [Mod m] -- ^ list of x
 solveQuadratic a b c
   = map fromInteger
   $ fst
diff --git a/Math/NumberTheory/Moduli/Jacobi.hs b/Math/NumberTheory/Moduli/Jacobi.hs
--- a/Math/NumberTheory/Moduli/Jacobi.hs
+++ b/Math/NumberTheory/Moduli/Jacobi.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer, 2017-2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- <https://en.wikipedia.org/wiki/Jacobi_symbol Jacobi symbol>
 -- is a generalization of the Legendre symbol, useful for primality
@@ -20,7 +18,6 @@
 module Math.NumberTheory.Moduli.Jacobi
   ( JacobiSymbol(..)
   , jacobi
-  , jacobi'
   ) where
 
 import Data.Bits
@@ -91,7 +88,6 @@
                                 s = if evenI z || rem8is1or7 b then r else negJS r
                             in jacOL s b o
   | otherwise = jacOL (if rem4is3 a && rem4is3 b then MinusOne else One) b a
-{-# DEPRECATED jacobi' "Use 'jacobi' instead" #-}
 
 -- numerator positive and smaller than denominator
 jacPS :: (Integral a, Bits a) => JacobiSymbol -> a -> a -> JacobiSymbol
diff --git a/Math/NumberTheory/Moduli/PrimitiveRoot.hs b/Math/NumberTheory/Moduli/PrimitiveRoot.hs
--- a/Math/NumberTheory/Moduli/PrimitiveRoot.hs
+++ b/Math/NumberTheory/Moduli/PrimitiveRoot.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Primitive roots and cyclic groups.
 --
@@ -42,8 +40,7 @@
 import Math.NumberTheory.Powers.General (highestPower)
 import Math.NumberTheory.Powers.Modular
 import Math.NumberTheory.Prefactored
-import Math.NumberTheory.UniqueFactorisation
-import Math.NumberTheory.Utils.FromIntegral
+import Math.NumberTheory.Primes
 
 import Control.DeepSeq
 import Control.Monad (guard)
@@ -62,12 +59,9 @@
   -- ^ Residues modulo @p@^@k@ for __odd__ prime @p@.
   | CGDoubleOddPrimePower (Prime a) Word
   -- ^ Residues modulo 2@p@^@k@ for __odd__ prime @p@.
-  deriving (Generic)
-
-instance NFData (Prime a) => NFData (CyclicGroup a)
+  deriving (Eq, Show, Generic)
 
-deriving instance Eq   (Prime a) => Eq   (CyclicGroup a)
-deriving instance Show (Prime a) => Show (CyclicGroup a)
+instance NFData a => NFData (CyclicGroup a)
 
 -- | Check whether a multiplicative group of residues,
 -- characterized by its modulo, is cyclic and, if yes, return its form.
@@ -75,7 +69,7 @@
 -- >>> cyclicGroupFromModulo 4
 -- Just CG4
 -- >>> cyclicGroupFromModulo (2 * 13 ^ 3)
--- Just (CGDoubleOddPrimePower (PrimeNat 13) 3)
+-- Just (CGDoubleOddPrimePower (Prime 13) 3)
 -- >>> cyclicGroupFromModulo (4 * 13)
 -- Nothing
 cyclicGroupFromModulo
@@ -97,7 +91,7 @@
   :: (Integral a, UniqueFactorisation a)
   => a
   -> Maybe (Prime a, Word)
-isPrimePower n = (, intToWord k) <$> isPrime m
+isPrimePower n = (, k) <$> isPrime m
   where
     (m, k) = highestPower n
 
@@ -105,13 +99,13 @@
 -- a cyclic multiplicative group of residues.
 --
 -- >>> cyclicGroupToModulo CG4
--- Prefactored {prefValue = 4, prefFactors = Coprimes {unCoprimes = fromList [(2,2)]}}
+-- Prefactored {prefValue = 4, prefFactors = Coprimes {unCoprimes = [(2,2)]}}
 --
--- >>> :set -XTypeFamilies
--- >>> cyclicGroupToModulo (CGDoubleOddPrimePower (PrimeNat 13) 3)
--- Prefactored {prefValue = 4394, prefFactors = Coprimes {unCoprimes = fromList [(2,1),(13,3)]}}
+-- >>> import Data.Maybe
+-- >>> cyclicGroupToModulo (CGDoubleOddPrimePower (fromJust (isPrime 13)) 3)
+-- Prefactored {prefValue = 4394, prefFactors = Coprimes {unCoprimes = [(13,3),(2,1)]}}
 cyclicGroupToModulo
-  :: (E.Euclidean a, Ord a, UniqueFactorisation a)
+  :: E.Euclidean a
   => CyclicGroup a
   -> Prefactored a
 cyclicGroupToModulo = fromFactors . \case
@@ -121,7 +115,8 @@
   CGDoubleOddPrimePower p k -> Coprimes.singleton 2 1
                             <> Coprimes.singleton (unPrime p) k
 
--- | 'PrimitiveRoot m' is a type which is only inhabited by primitive roots of n.
+-- | 'PrimitiveRoot' m is a type which is only inhabited
+-- by <https://en.wikipedia.org/wiki/Primitive_root_modulo_n primitive roots> of m.
 data PrimitiveRoot m = PrimitiveRoot
   { unPrimitiveRoot :: MultMod m -- ^ Extract primitive root value.
   , getGroup        :: CyclicGroup Natural -- ^ Get cyclic group structure.
@@ -163,14 +158,9 @@
 --
 -- >>> :set -XDataKinds
 -- >>> isPrimitiveRoot (1 :: Mod 13)
--- False
+-- Nothing
 -- >>> isPrimitiveRoot (2 :: Mod 13)
--- True
---
--- Here is how to list all primitive roots:
---
--- >>> mapMaybe isPrimitiveRoot [minBound .. maxBound] :: [Mod 13]
--- [(2 `modulo` 13), (6 `modulo` 13), (7 `modulo` 13), (11 `modulo` 13)]
+-- Just (PrimitiveRoot {unPrimitiveRoot = MultMod {multElement = (2 `modulo` 13)}, getGroup = CGOddPrimePower (Prime 13) 1})
 --
 -- This function is a convenient wrapper around 'isPrimitiveRoot''. The latter
 -- provides better control and performance, if you need them.
@@ -185,5 +175,5 @@
   return $ PrimitiveRoot r' cg
 
 -- | Calculate the size of a given cyclic group.
-groupSize :: (E.Euclidean a, Ord a, UniqueFactorisation a) => CyclicGroup a -> Prefactored a
+groupSize :: (E.Euclidean a, UniqueFactorisation a) => CyclicGroup a -> Prefactored a
 groupSize = totient . cyclicGroupToModulo
diff --git a/Math/NumberTheory/Moduli/Sqrt.hs b/Math/NumberTheory/Moduli/Sqrt.hs
--- a/Math/NumberTheory/Moduli/Sqrt.hs
+++ b/Math/NumberTheory/Moduli/Sqrt.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Modular square roots.
 --
@@ -30,7 +28,6 @@
   , Old.sqrtModFList
   ) where
 
-import Control.Arrow hiding (loop)
 import Control.Monad (liftM2)
 import Data.Bits
 
@@ -38,9 +35,9 @@
 import Math.NumberTheory.Moduli.Class (Mod, getVal, getMod, KnownNat)
 import Math.NumberTheory.Moduli.Jacobi
 import Math.NumberTheory.Powers.Modular (powMod)
-import qualified Math.NumberTheory.Primes.Factorisation as F (factorise)
 import Math.NumberTheory.Primes.Types
 import Math.NumberTheory.Primes.Sieve (sieveFrom)
+import Math.NumberTheory.Primes (Prime, factorise)
 import Math.NumberTheory.Utils (shiftToOddCount, splitOff, recipMod)
 import Math.NumberTheory.Utils.FromIntegral
 
@@ -50,13 +47,12 @@
 --
 -- >>> :set -XDataKinds
 -- >>> sqrtsMod (1 :: Mod 60)
--- > [(1 `modulo` 60),(49 `modulo` 60),(41 `modulo` 60),(29 `modulo` 60),(31 `modulo` 60),(19 `modulo` 60),(11 `modulo` 60),(59 `modulo` 60)]
+-- [(1 `modulo` 60),(49 `modulo` 60),(41 `modulo` 60),(29 `modulo` 60),(31 `modulo` 60),(19 `modulo` 60),(11 `modulo` 60),(59 `modulo` 60)]
 sqrtsMod :: KnownNat m => Mod m -> [Mod m]
 sqrtsMod a = map fromInteger $ sqrtsModFactorisation (getVal a) (factorise (getMod a))
-  where
-    factorise = map (PrimeNat . integerToNatural *** intToWord) . F.factorise
 
--- | List all square roots modulo a number, which factorisation is passed as a second argument.
+-- | List all square roots modulo a number, the factorisation of which is
+-- passed as a second argument.
 --
 -- >>> sqrtsModFactorisation 1 (factorise 60)
 -- [1,49,41,29,31,19,11,59]
@@ -65,7 +61,7 @@
 sqrtsModFactorisation n pps = map fst $ foldl1 (liftM2 comb) cs
   where
     ms :: [Integer]
-    ms = map (\(PrimeNat p, pow) -> toInteger p ^ pow) pps
+    ms = map (\(Prime p, pow) -> p ^ pow) pps
 
     rs :: [[Integer]]
     rs = map (\(p, pow) -> sqrtsModPrimePower n p pow) pps
@@ -75,15 +71,17 @@
 
     comb t1@(_, m1) t2@(_, m2) = (chineseRemainder2 t1 t2, m1 * m2)
 
--- | List all square roots modulo power of a prime.
+-- | List all square roots modulo the power of a prime.
 --
+-- >>> import Data.Maybe
+-- >>> import Math.NumberTheory.Primes
 -- >>> sqrtsModPrimePower 7 (fromJust (isPrime 3)) 2
 -- [4,5]
 -- >>> sqrtsModPrimePower 9 (fromJust (isPrime 3)) 3
 -- [3,12,21,24,6,15]
 sqrtsModPrimePower :: Integer -> Prime Integer -> Word -> [Integer]
 sqrtsModPrimePower nn p 1 = sqrtsModPrime nn p
-sqrtsModPrimePower nn (PrimeNat (toInteger -> prime)) expo = let primeExpo = prime ^ expo in
+sqrtsModPrimePower nn (Prime prime) expo = let primeExpo = prime ^ expo in
   case splitOff prime (nn `mod` primeExpo) of
     (_, 0) -> [0, prime ^ ((expo + 1) `quot` 2) .. primeExpo - 1]
     (kk, n)
@@ -95,7 +93,7 @@
           then go rr os
           else go rr os ++ go (primeExpo - rr) os
       where
-        k = intToWord kk `quot` 2
+        k = kk `quot` 2
         t = (if prime == 2 then expo - k - 1 else expo - k) `max` ((expo + 1) `quot` 2)
         expo' = expo - 2 * k
         os = [0, prime ^ t .. primeExpo - 1]
@@ -108,6 +106,8 @@
 
 -- | List all square roots by prime modulo.
 --
+-- >>> import Data.Maybe
+-- >>> import Math.NumberTheory.Primes
 -- >>> sqrtsModPrime 1 (fromJust (isPrime 5))
 -- [1,4]
 -- >>> sqrtsModPrime 0 (fromJust (isPrime 5))
@@ -115,8 +115,8 @@
 -- >>> sqrtsModPrime 2 (fromJust (isPrime 5))
 -- []
 sqrtsModPrime :: Integer -> Prime Integer -> [Integer]
-sqrtsModPrime n (PrimeNat 2) = [n `mod` 2]
-sqrtsModPrime n (PrimeNat (toInteger -> prime)) = case jacobi n prime of
+sqrtsModPrime n (Prime 2) = [n `mod` 2]
+sqrtsModPrime n (Prime prime) = case jacobi n prime of
   MinusOne -> []
   Zero     -> [0]
   One      -> let r = sqrtModP' (n `mod` prime) prime in [r, prime - r]
@@ -135,7 +135,7 @@
                     = sqrtOfMinusOne prime
     | otherwise     = tonelliShanks square prime
 
--- | p must be of form 4k + 1
+-- | @p@ must be of form @4k + 1@
 sqrtOfMinusOne :: Integer -> Integer
 sqrtOfMinusOne p
   = head
@@ -152,7 +152,7 @@
 tonelliShanks :: Integer -> Integer -> Integer
 tonelliShanks square prime = loop rc t1 generator log2
   where
-    (log2,q) = shiftToOddCount (prime-1)
+    (wordToInt -> log2,q) = shiftToOddCount (prime-1)
     nonSquare = findNonSquare prime
     generator = powMod nonSquare q prime
     rc = powMod square ((q+1) `quot` 2) prime
@@ -176,7 +176,7 @@
 
 -- | prime must be odd, n must be coprime with prime
 sqrtModPP' :: Integer -> Integer -> Word -> Maybe Integer
-sqrtModPP' n prime expo = case sqrtsModPrime n (PrimeNat (fromInteger prime)) of
+sqrtModPP' n prime expo = case sqrtsModPrime n (Prime prime) of
                             []    -> Nothing
                             r : _ -> fixup r
   where
@@ -184,12 +184,12 @@
               in if diff' == 0
                    then Just r
                    else case splitOff prime diff' of
-                          (e,q) | expo <= intToWord e -> Just r
+                          (e,q) | expo <= e -> Just r
                                 | otherwise -> fmap (\inv -> hoist inv r (q `mod` prime) (prime^e)) (recipMod (2*r) prime)
 
     hoist inv root elim pp
         | diff' == 0    = root'
-        | expo <= intToWord ex    = root'
+        | expo <= ex    = root'
         | otherwise     = hoist inv root' (nelim `mod` prime) (prime^ex)
           where
             root' = (root + (inv*(prime-elim))*pp) `mod` (prime*pp)
@@ -198,13 +198,13 @@
 
 -- dirty, dirty
 sqM2P :: Integer -> Word -> Maybe Integer
-sqM2P n (wordToInt -> e)
+sqM2P n e
     | e < 2     = Just (n `mod` 2)
     | n' == 0   = Just 0
     | odd k     = Nothing
-    | otherwise = fmap ((`mod` mdl) . (`shiftL` k2)) $ solve s e2
+    | otherwise = fmap ((`mod` mdl) . (`shiftL` wordToInt k2)) $ solve s e2
       where
-        mdl = 1 `shiftL` e
+        mdl = 1 `shiftL` wordToInt e
         n' = n `mod` mdl
         (k, s) = shiftToOddCount n'
         k2 = k `quot` 2
@@ -220,7 +220,7 @@
                     | pw >= e2  = Just x
                     | otherwise = fixup x' pw'
                       where
-                        x' = x + (1 `shiftL` (pw-1))
+                        x' = x + (1 `shiftL` (wordToInt pw - 1))
                         d = x'*x' - r
                         pw' = if d == 0 then e2 else fst (shiftToOddCount d)
 
@@ -239,7 +239,7 @@
     | otherwise = search primelist
       where
         primelist = [3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67]
-                        ++ sieveFrom (68 + n `rem` 4) -- prevent sharing
+                        ++ map unPrime (sieveFrom (68 + n `rem` 4)) -- prevent sharing
         search (p:ps) = case jacobi p n of
           MinusOne -> p
           _        -> search ps
diff --git a/Math/NumberTheory/Moduli/SqrtOld.hs b/Math/NumberTheory/Moduli/SqrtOld.hs
--- a/Math/NumberTheory/Moduli/SqrtOld.hs
+++ b/Math/NumberTheory/Moduli/SqrtOld.hs
@@ -3,14 +3,13 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Modular square roots.
 --
 
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP          #-}
+{-# LANGUAGE ViewPatterns #-}
 
 module Math.NumberTheory.Moduli.SqrtOld
   ( sqrtModP
@@ -26,15 +25,14 @@
 import Control.Monad (liftM2)
 import Data.Bits
 import Data.List (nub)
-#if __GLASGOW_HASKELL__ < 709
-import Data.Word
-#endif
 import GHC.Integer.GMP.Internals
 
 import Math.NumberTheory.Moduli.Chinese
 import Math.NumberTheory.Moduli.Jacobi
 import Math.NumberTheory.Primes.Sieve (sieveFrom)
+import Math.NumberTheory.Primes.Types (unPrime)
 import Math.NumberTheory.Utils (shiftToOddCount, splitOff)
+import Math.NumberTheory.Utils.FromIntegral
 
 {-# DEPRECATED sqrtModP, sqrtModP', sqrtModPList, tonelliShanks "Use 'Math.NumberTheory.Moduli.Sqrt.sqrtsModPrime' instead" #-}
 {-# DEPRECATED sqrtModPP, sqrtModPPList "Use 'Math.NumberTheory.Moduli.Sqrt.sqrtsModPrimePower' instead" #-}
@@ -119,12 +117,12 @@
               in if diff' == 0
                    then Just r
                    else case splitOff prime diff' of
-                          (e,q) | expo <= e -> Just r
+                          (wordToInt -> e,q) | expo <= e -> Just r
                                 | otherwise -> fmap (\inv -> hoist inv r (q `mod` prime) (prime^e)) (recipMod (2*r) prime)
 
     hoist inv root elim pp
         | diff' == 0    = root'
-        | expo <= ex    = root'
+        | expo <= wordToInt ex    = root'
         | otherwise     = hoist inv root' (nelim `mod` prime) (prime^ex)
           where
             root' = (root + (inv*(prime-elim))*pp) `mod` (prime*pp)
@@ -141,7 +139,7 @@
       where
         mdl = 1 `shiftL` e
         n' = n `mod` mdl
-        (k,s) = shiftToOddCount n'
+        (wordToInt -> k,s) = shiftToOddCount n'
         k2 = k `quot` 2
         e2 = e-k
         solve _ 1 = Just 1
@@ -149,7 +147,7 @@
         solve r _
             | rem4 r == 3   = Nothing  -- otherwise r ≡ 1 (mod 4)
             | rem8 r == 5   = Nothing  -- otherwise r ≡ 1 (mod 8)
-            | otherwise     = fixup r (fst $ shiftToOddCount (r-1))
+            | otherwise     = fixup r (wordToInt $ fst $ shiftToOddCount (r-1))
               where
                 fixup x pw
                     | pw >= e2  = Just x
@@ -157,7 +155,7 @@
                       where
                         x' = x + (1 `shiftL` (pw-1))
                         d = x'*x' - r
-                        pw' = if d == 0 then e2 else fst (shiftToOddCount d)
+                        pw' = if d == 0 then e2 else wordToInt (fst (shiftToOddCount d))
 
 -- | @sqrtModF n primePowers@ calculates a square root of @n@ modulo
 --   @product [p^k | (p,k) <- primePowers]@ if one exists and all primes
@@ -222,7 +220,7 @@
     | otherwise = search primelist
       where
         primelist = [3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67]
-                        ++ sieveFrom (68 + n `rem` 4) -- prevent sharing
+                        ++ map unPrime (sieveFrom (68 + n `rem` 4)) -- prevent sharing
         search (p:ps) = case jacobi p n of
           MinusOne -> p
           _        -> search ps
diff --git a/Math/NumberTheory/MoebiusInversion.hs b/Math/NumberTheory/MoebiusInversion.hs
--- a/Math/NumberTheory/MoebiusInversion.hs
+++ b/Math/NumberTheory/MoebiusInversion.hs
@@ -3,23 +3,23 @@
 -- Copyright:   (c) 2012 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Generalised Möbius inversion
---
-{-# LANGUAGE BangPatterns, FlexibleContexts #-}
+
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
 module Math.NumberTheory.MoebiusInversion
     ( generalInversion
     , totientSum
     ) where
 
-import Data.Array.ST
 import Control.Monad
 import Control.Monad.ST
+import qualified Data.Vector.Mutable as MV
 
 import Math.NumberTheory.Powers.Squares
-import Math.NumberTheory.Unsafe
 
 -- | @totientSum n@ is, for @n > 0@, the sum of @[totient k | k <- [1 .. n]]@,
 --   computed via generalised Möbius inversion.
@@ -85,58 +85,67 @@
     | otherwise = fastInvert fun n
 
 fastInvert :: (Int -> Integer) -> Int -> Integer
-fastInvert fun n = big `unsafeAt` 0
-  where
-    !k0 = integerSquareRoot (n `quot` 2)
-    !mk0 = n `quot` (2*k0+1)
-    kmax a m = (a `quot` m - 1) `quot` 2
-    big = runSTArray $ do
-        small <- newArray_ (0,mk0) :: ST s (STArray s Int Integer)
-        unsafeWrite small 0 0
-        unsafeWrite small 1 $! (fun 1)
-        when (mk0 >= 2) $
-            unsafeWrite small 2 $! (fun 2 - fun 1)
-        let calcit switch change i
-                | mk0 < i   = return (switch,change)
-                | i == change = calcit (switch+1) (change + 4*switch+6) i
-                | otherwise = do
-                    let mloop !acc k !m
-                            | k < switch    = kloop acc k
-                            | otherwise     = do
-                                val <- unsafeRead small m
-                                let nxtk = kmax i (m+1)
-                                mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
-                        kloop !acc k
-                            | k == 0    = do
-                                unsafeWrite small i $! acc
-                                calcit switch change (i+1)
-                            | otherwise = do
-                                val <- unsafeRead small (i `quot` (2*k+1))
-                                kloop (acc-val) (k-1)
-                    mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
-        (sw, ch) <- calcit 1 8 3
-        large <- newArray_ (0,k0-1)
-        let calcbig switch change j
-                | j == 0    = return large
-                | (2*j-1)*change <= n   = calcbig (switch+1) (change + 4*switch+6) j
-                | otherwise = do
-                    let i = n `quot` (2*j-1)
-                        mloop !acc k m
-                            | k < switch    = kloop acc k
-                            | otherwise     = do
-                                val <- unsafeRead small m
-                                let nxtk = kmax i (m+1)
-                                mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
-                        kloop !acc k
-                            | k == 0    = do
-                                unsafeWrite large (j-1) $! acc
-                                calcbig switch change (j-1)
-                            | otherwise = do
-                                let m = i `quot` (2*k+1)
-                                val <- if m <= mk0
-                                         then unsafeRead small m
-                                         else unsafeRead large (k*(2*j-1)+j-1)
-                                kloop (acc-val) (k-1)
-                    mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
-        calcbig sw ch k0
+fastInvert fun n = runST (fastInvertST fun n)
+
+fastInvertST :: forall s. (Int -> Integer) -> Int -> ST s Integer
+fastInvertST fun n = do
+    let !k0 = integerSquareRoot (n `quot` 2)
+        !mk0 = n `quot` (2*k0+1)
+        kmax a m = (a `quot` m - 1) `quot` 2
+
+    small <- MV.unsafeNew (mk0 + 1) :: ST s (MV.MVector s Integer)
+    MV.unsafeWrite small 0 0
+    MV.unsafeWrite small 1 $! (fun 1)
+    when (mk0 >= 2) $
+        MV.unsafeWrite small 2 $! (fun 2 - fun 1)
+
+    let calcit :: Int -> Int -> Int -> ST s (Int, Int)
+        calcit switch change i
+            | mk0 < i   = return (switch,change)
+            | i == change = calcit (switch+1) (change + 4*switch+6) i
+            | otherwise = do
+                let mloop !acc k !m
+                        | k < switch    = kloop acc k
+                        | otherwise     = do
+                            val <- MV.unsafeRead small m
+                            let nxtk = kmax i (m+1)
+                            mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
+                    kloop !acc k
+                        | k == 0    = do
+                            MV.unsafeWrite small i $! acc
+                            calcit switch change (i+1)
+                        | otherwise = do
+                            val <- MV.unsafeRead small (i `quot` (2*k+1))
+                            kloop (acc-val) (k-1)
+                mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
+
+    (sw, ch) <- calcit 1 8 3
+    large <- MV.unsafeNew k0 :: ST s (MV.MVector s Integer)
+
+    let calcbig :: Int -> Int -> Int -> ST s (MV.MVector s Integer)
+        calcbig switch change j
+            | j == 0    = return large
+            | (2*j-1)*change <= n   = calcbig (switch+1) (change + 4*switch+6) j
+            | otherwise = do
+                let i = n `quot` (2*j-1)
+                    mloop !acc k m
+                        | k < switch    = kloop acc k
+                        | otherwise     = do
+                            val <- MV.unsafeRead small m
+                            let nxtk = kmax i (m+1)
+                            mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
+                    kloop !acc k
+                        | k == 0    = do
+                            MV.unsafeWrite large (j-1) $! acc
+                            calcbig switch change (j-1)
+                        | otherwise = do
+                            let m = i `quot` (2*k+1)
+                            val <- if m <= mk0
+                                     then MV.unsafeRead small m
+                                     else MV.unsafeRead large (k*(2*j-1)+j-1)
+                            kloop (acc-val) (k-1)
+                mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
+
+    mvec <- calcbig sw ch k0
+    MV.unsafeRead mvec 0
 
diff --git a/Math/NumberTheory/MoebiusInversion/Int.hs b/Math/NumberTheory/MoebiusInversion/Int.hs
--- a/Math/NumberTheory/MoebiusInversion/Int.hs
+++ b/Math/NumberTheory/MoebiusInversion/Int.hs
@@ -3,24 +3,23 @@
 -- Copyright:   (c) 2012 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Generalised Möbius inversion for 'Int' valued functions.
---
-{-# LANGUAGE BangPatterns, FlexibleContexts #-}
-{-# OPTIONS_GHC -fspec-constr-count=8 #-}
+
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
 module Math.NumberTheory.MoebiusInversion.Int
     ( generalInversion
     , totientSum
     ) where
 
-import Data.Array.ST
 import Control.Monad
 import Control.Monad.ST
+import qualified Data.Vector.Unboxed.Mutable as MV
 
 import Math.NumberTheory.Powers.Squares
-import Math.NumberTheory.Unsafe
 
 -- | @totientSum n@ is, for @n > 0@, the sum of @[totient k | k <- [1 .. n]]@,
 --   computed via generalised Möbius inversion.
@@ -86,57 +85,66 @@
     | otherwise = fastInvert fun n
 
 fastInvert :: (Int -> Int) -> Int -> Int
-fastInvert fun n = big `unsafeAt` 0
-  where
-    !k0 = integerSquareRoot (n `quot` 2)
-    !mk0 = n `quot` (2*k0+1)
-    kmax a m = (a `quot` m - 1) `quot` 2
-    big = runSTUArray $ do
-        small <- newArray_ (0,mk0) :: ST s (STUArray s Int Int)
-        unsafeWrite small 0 0
-        unsafeWrite small 1 (fun 1)
-        when (mk0 >= 2) $
-            unsafeWrite small 2 (fun 2 - fun 1)
-        let calcit switch change i
-                | mk0 < i   = return (switch,change)
-                | i == change = calcit (switch+1) (change + 4*switch+6) i
-                | otherwise = do
-                    let mloop !acc k !m
-                            | k < switch    = kloop acc k
-                            | otherwise     = do
-                                val <- unsafeRead small m
-                                let nxtk = kmax i (m+1)
-                                mloop (acc - (k-nxtk)*val) nxtk (m+1)
-                        kloop !acc k
-                            | k == 0    = do
-                                unsafeWrite small i acc
-                                calcit switch change (i+1)
-                            | otherwise = do
-                                val <- unsafeRead small (i `quot` (2*k+1))
-                                kloop (acc-val) (k-1)
-                    mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
-        (sw, ch) <- calcit 1 8 3
-        large <- newArray_ (0,k0-1)
-        let calcbig switch change j
-                | j == 0    = return large
-                | (2*j-1)*change <= n   = calcbig (switch+1) (change + 4*switch+6) j
-                | otherwise = do
-                    let i = n `quot` (2*j-1)
-                        mloop !acc k m
-                            | k < switch    = kloop acc k
-                            | otherwise     = do
-                                val <- unsafeRead small m
-                                let nxtk = kmax i (m+1)
-                                mloop (acc - (k-nxtk)*val) nxtk (m+1)
-                        kloop !acc k
-                            | k == 0    = do
-                                unsafeWrite large (j-1) acc
-                                calcbig switch change (j-1)
-                            | otherwise = do
-                                let m = i `quot` (2*k+1)
-                                val <- if m <= mk0
-                                         then unsafeRead small m
-                                         else unsafeRead large (k*(2*j-1)+j-1)
-                                kloop (acc-val) (k-1)
-                    mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
-        calcbig sw ch k0
+fastInvert fun n = runST (fastInvertST fun n)
+
+fastInvertST :: forall s. (Int -> Int) -> Int -> ST s Int
+fastInvertST fun n = do
+    let !k0 = integerSquareRoot (n `quot` 2)
+        !mk0 = n `quot` (2*k0+1)
+        kmax a m = (a `quot` m - 1) `quot` 2
+
+    small <- MV.unsafeNew (mk0 + 1) :: ST s (MV.MVector s Int)
+    MV.unsafeWrite small 0 0
+    MV.unsafeWrite small 1 $! (fun 1)
+    when (mk0 >= 2) $
+        MV.unsafeWrite small 2 $! (fun 2 - fun 1)
+
+    let calcit :: Int -> Int -> Int -> ST s (Int, Int)
+        calcit switch change i
+            | mk0 < i   = return (switch,change)
+            | i == change = calcit (switch+1) (change + 4*switch+6) i
+            | otherwise = do
+                let mloop !acc k !m
+                        | k < switch    = kloop acc k
+                        | otherwise     = do
+                            val <- MV.unsafeRead small m
+                            let nxtk = kmax i (m+1)
+                            mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
+                    kloop !acc k
+                        | k == 0    = do
+                            MV.unsafeWrite small i $! acc
+                            calcit switch change (i+1)
+                        | otherwise = do
+                            val <- MV.unsafeRead small (i `quot` (2*k+1))
+                            kloop (acc-val) (k-1)
+                mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
+
+    (sw, ch) <- calcit 1 8 3
+    large <- MV.unsafeNew k0 :: ST s (MV.MVector s Int)
+
+    let calcbig :: Int -> Int -> Int -> ST s (MV.MVector s Int)
+        calcbig switch change j
+            | j == 0    = return large
+            | (2*j-1)*change <= n   = calcbig (switch+1) (change + 4*switch+6) j
+            | otherwise = do
+                let i = n `quot` (2*j-1)
+                    mloop !acc k m
+                        | k < switch    = kloop acc k
+                        | otherwise     = do
+                            val <- MV.unsafeRead small m
+                            let nxtk = kmax i (m+1)
+                            mloop (acc - fromIntegral (k-nxtk)*val) nxtk (m+1)
+                    kloop !acc k
+                        | k == 0    = do
+                            MV.unsafeWrite large (j-1) $! acc
+                            calcbig switch change (j-1)
+                        | otherwise = do
+                            let m = i `quot` (2*k+1)
+                            val <- if m <= mk0
+                                     then MV.unsafeRead small m
+                                     else MV.unsafeRead large (k*(2*j-1)+j-1)
+                            kloop (acc-val) (k-1)
+                mloop (fun i - fun (i `quot` 2)) ((i-1) `quot` 2) 1
+
+    mvec <- calcbig sw ch k0
+    MV.unsafeRead mvec 0
diff --git a/Math/NumberTheory/Powers.hs b/Math/NumberTheory/Powers.hs
--- a/Math/NumberTheory/Powers.hs
+++ b/Math/NumberTheory/Powers.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Calculating integer roots, modular powers and related things.
 -- This module reexports the most needed functions from the implementation
diff --git a/Math/NumberTheory/Powers/Cubes.hs b/Math/NumberTheory/Powers/Cubes.hs
--- a/Math/NumberTheory/Powers/Cubes.hs
+++ b/Math/NumberTheory/Powers/Cubes.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Functions dealing with cubes. Moderately efficient calculation of integer
 -- cube roots and testing for cubeness.
@@ -20,25 +18,26 @@
 
 #include "MachDeps.h"
 
-import Data.Array.Unboxed
-import Data.Array.ST
-
+import Control.Monad.ST
 import Data.Bits
+import qualified Data.Vector.Unboxed as V
+import qualified Data.Vector.Unboxed.Mutable as MV
 
 import GHC.Base
 import GHC.Integer
 import GHC.Integer.GMP.Internals
 import GHC.Integer.Logarithms (integerLog2#)
 
-import Math.NumberTheory.Unsafe
+import Numeric.Natural
 
 -- | Calculate the integer cube root of an integer @n@,
 --   that is the largest integer @r@ such that @r^3 <= n@.
 --   Note that this is not symmetric about @0@, for example
 --   @integerCubeRoot (-2) = (-2)@ while @integerCubeRoot 2 = 1@.
 {-# SPECIALISE integerCubeRoot :: Int -> Int,
+                                  Word -> Word,
                                   Integer -> Integer,
-                                  Word -> Word
+                                  Natural -> Natural
   #-}
 integerCubeRoot :: Integral a => a -> a
 integerCubeRoot 0 = 0
@@ -68,7 +67,8 @@
 --   @Just r@ if @n == r^3@.
 {-# SPECIALISE exactCubeRoot :: Int -> Maybe Int,
                                 Word -> Maybe Word,
-                                Integer -> Maybe Integer
+                                Integer -> Maybe Integer,
+                                Natural -> Maybe Natural
   #-}
 exactCubeRoot :: Integral a => a -> Maybe a
 exactCubeRoot 0 = Just 0
@@ -85,8 +85,9 @@
 
 -- | Test whether an integer is a cube.
 {-# SPECIALISE isCube :: Int -> Bool,
+                         Word -> Bool,
                          Integer -> Bool,
-                         Word -> Bool
+                         Natural -> Bool
   #-}
 isCube :: Integral a => a -> Bool
 isCube 0 = True
@@ -104,8 +105,9 @@
 --   this is much faster than @let r = cubeRoot n in r*r*r == n@.
 --   The condition @n >= 0@ is /not/ checked.
 {-# SPECIALISE isCube' :: Int -> Bool,
+                          Word -> Bool,
                           Integer -> Bool,
-                          Word -> Bool
+                          Natural -> Bool
   #-}
 isCube' :: Integral a => a -> Bool
 isCube' !n = isPossibleCube n
@@ -117,15 +119,16 @@
 --   Only about 0.08% of all numbers pass this test.
 --   The precondition @n >= 0@ is /not/ checked.
 {-# SPECIALISE isPossibleCube :: Int -> Bool,
+                                 Word -> Bool,
                                  Integer -> Bool,
-                                 Word -> Bool
+                                 Natural -> Bool
   #-}
 isPossibleCube :: Integral a => a -> Bool
-isPossibleCube !n =
-    unsafeAt cr512 (fromIntegral n .&. 511)
-    && unsafeAt cubeRes837 (fromIntegral (n `rem` 837))
-    && unsafeAt cubeRes637 (fromIntegral (n `rem` 637))
-    && unsafeAt cubeRes703 (fromIntegral (n `rem` 703))
+isPossibleCube !n
+    =  V.unsafeIndex cr512 (fromIntegral n .&. 511)
+    && V.unsafeIndex cubeRes837 (fromIntegral (n `rem` 837))
+    && V.unsafeIndex cubeRes637 (fromIntegral (n `rem` 637))
+    && V.unsafeIndex cubeRes703 (fromIntegral (n `rem` 703))
 
 ----------------------------------------------------------------------
 --                         Utility Functions                        --
@@ -168,7 +171,6 @@
 cubeRootIgr 0 = 0
 cubeRootIgr n = newton3 n (approxCuRt n)
 
-{-# SPECIALISE newton3 :: Int -> Int -> Int #-}
 {-# SPECIALISE newton3 :: Integer -> Integer -> Integer #-}
 newton3 :: Integral a => a -> a -> a
 newton3 n a = go (step a)
@@ -210,40 +212,43 @@
 appCuRt _ = error "integerCubeRoot': negative argument"
 
 -- not very discriminating, but cheap, so it's an overall gain
-cr512 :: UArray Int Bool
-cr512 = runSTUArray $ do
-    ar <- newArray (0,511) True
+cr512 :: V.Vector Bool
+cr512 = runST $ do
+    ar <- MV.replicate 512 True
     let note s i
-            | i < 512   = unsafeWrite ar i False >> note s (i+s)
+            | i < 512   = MV.unsafeWrite ar i False >> note s (i+s)
             | otherwise = return ()
     note 4 2
     note 8 4
     note 32 16
     note 64 32
     note 256 128
-    unsafeWrite ar 256 False
-    return ar
+    MV.unsafeWrite ar 256 False
+    V.unsafeFreeze ar
 
 -- Remainders modulo @3^3 * 31@
-cubeRes837 :: UArray Int Bool
-cubeRes837 = runSTUArray $ do
-    ar <- newArray (0,836) False
-    let note 837 = return ar
-        note k = unsafeWrite ar ((k*k*k) `rem` 837) True >> note (k+1)
+cubeRes837 :: V.Vector Bool
+cubeRes837 = runST $ do
+    ar <- MV.replicate 837 False
+    let note 837 = return ()
+        note k = MV.unsafeWrite ar ((k*k*k) `rem` 837) True >> note (k+1)
     note 0
+    V.unsafeFreeze ar
 
 -- Remainders modulo @7^2 * 13@
-cubeRes637 :: UArray Int Bool
-cubeRes637 = runSTUArray $ do
-    ar <- newArray (0,636) False
-    let note 637 = return ar
-        note k = unsafeWrite ar ((k*k*k) `rem` 637) True >> note (k+1)
+cubeRes637 :: V.Vector Bool
+cubeRes637 = runST $ do
+    ar <- MV.replicate 637 False
+    let note 637 = return ()
+        note k = MV.unsafeWrite ar ((k*k*k) `rem` 637) True >> note (k+1)
     note 0
+    V.unsafeFreeze ar
 
 -- Remainders modulo @19 * 37@
-cubeRes703 :: UArray Int Bool
-cubeRes703 = runSTUArray $ do
-    ar <- newArray (0,702) False
-    let note 703 = return ar
-        note k = unsafeWrite ar ((k*k*k) `rem` 703) True >> note (k+1)
+cubeRes703 :: V.Vector Bool
+cubeRes703 = runST $ do
+    ar <- MV.replicate 703 False
+    let note 703 = return ()
+        note k = MV.unsafeWrite ar ((k*k*k) `rem` 703) True >> note (k+1)
     note 0
+    V.unsafeFreeze ar
diff --git a/Math/NumberTheory/Powers/Fourth.hs b/Math/NumberTheory/Powers/Fourth.hs
--- a/Math/NumberTheory/Powers/Fourth.hs
+++ b/Math/NumberTheory/Powers/Fourth.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Functions dealing with fourth powers. Efficient calculation of integer fourth
 -- roots and efficient testing for being a square's square.
@@ -20,24 +18,25 @@
 
 #include "MachDeps.h"
 
+import Control.Monad.ST
+import Data.Bits
+import qualified Data.Vector.Unboxed as V
+import qualified Data.Vector.Unboxed.Mutable as MV
+
 import GHC.Base
 import GHC.Integer
 import GHC.Integer.GMP.Internals
 import GHC.Integer.Logarithms (integerLog2#)
 
-import Data.Array.Unboxed
-import Data.Array.ST
-
-import Data.Bits
-
-import Math.NumberTheory.Unsafe
+import Numeric.Natural
 
 -- | Calculate the integer fourth root of a nonnegative number,
 --   that is, the largest integer @r@ with @r^4 <= n@.
 --   Throws an error on negaitve input.
 {-# SPECIALISE integerFourthRoot :: Int -> Int,
+                                    Word -> Word,
                                     Integer -> Integer,
-                                    Word -> Word
+                                    Natural -> Natural
   #-}
 integerFourthRoot :: Integral a => a -> a
 integerFourthRoot n
@@ -60,8 +59,9 @@
 -- | Returns @Nothing@ if @n@ is not a fourth power,
 --   @Just r@ if @n == r^4@ and @r >= 0@.
 {-# SPECIALISE exactFourthRoot :: Int -> Maybe Int,
+                                  Word -> Maybe Word,
                                   Integer -> Maybe Integer,
-                                  Word -> Maybe Word
+                                  Natural -> Maybe Natural
   #-}
 exactFourthRoot :: Integral a => a -> Maybe a
 exactFourthRoot 0 = Just 0
@@ -77,8 +77,9 @@
 --   First nonnegativity is checked, then the unchecked
 --   test is called.
 {-# SPECIALISE isFourthPower :: Int -> Bool,
+                                Word -> Bool,
                                 Integer -> Bool,
-                                Word -> Bool
+                                Natural -> Bool
   #-}
 isFourthPower :: Integral a => a -> Bool
 isFourthPower 0 = True
@@ -89,8 +90,9 @@
 --   'isPossibleFourthPower' test, its integer fourth root
 --   is calculated.
 {-# SPECIALISE isFourthPower' :: Int -> Bool,
+                                 Word -> Bool,
                                  Integer -> Bool,
-                                 Word -> Bool
+                                 Natural -> Bool
   #-}
 isFourthPower' :: Integral a => a -> Bool
 isFourthPower' n = isPossibleFourthPower n && r2*r2 == n
@@ -102,14 +104,15 @@
 --   The condition is /not/ checked.
 --   This eliminates about 99.958% of numbers.
 {-# SPECIALISE isPossibleFourthPower :: Int -> Bool,
+                                        Word -> Bool,
                                         Integer -> Bool,
-                                        Word -> Bool
+                                        Natural -> Bool
   #-}
 isPossibleFourthPower :: Integral a => a -> Bool
-isPossibleFourthPower n =
-        biSqRes256 `unsafeAt` (fromIntegral n .&. 255)
-      && biSqRes425 `unsafeAt` (fromIntegral (n `rem` 425))
-      && biSqRes377 `unsafeAt` (fromIntegral (n `rem` 377))
+isPossibleFourthPower n
+  =  V.unsafeIndex biSqRes256 (fromIntegral n .&. 255)
+  && V.unsafeIndex biSqRes425 (fromIntegral (n `rem` 425))
+  && V.unsafeIndex biSqRes377 (fromIntegral (n `rem` 377))
 
 {-# SPECIALISE newton4 :: Integer -> Integer -> Integer #-}
 newton4 :: Integral a => a -> a -> a
@@ -151,28 +154,31 @@
 appBiSqrt _ = error "integerFourthRoot': negative argument"
 
 
-biSqRes256 :: UArray Int Bool
-biSqRes256 = runSTUArray $ do
-    ar <- newArray (0,255) False
-    let note 257 = return ar
-        note i = unsafeWrite ar i True >> note (i+16)
-    unsafeWrite ar 0 True
-    unsafeWrite ar 16 True
+biSqRes256 :: V.Vector Bool
+biSqRes256 = runST $ do
+    ar <- MV.replicate 256 False
+    let note 257 = return ()
+        note i = MV.unsafeWrite ar i True >> note (i+16)
+    MV.unsafeWrite ar 0 True
+    MV.unsafeWrite ar 16 True
     note 1
+    V.unsafeFreeze ar
 
-biSqRes425 :: UArray Int Bool
-biSqRes425 = runSTUArray $ do
-    ar <- newArray (0,424) False
-    let note 154 = return ar
-        note i = unsafeWrite ar ((i*i*i*i) `rem` 425) True >> note (i+1)
+biSqRes425 :: V.Vector Bool
+biSqRes425 = runST $ do
+    ar <- MV.replicate 425 False
+    let note 154 = return ()
+        note i = MV.unsafeWrite ar ((i*i*i*i) `rem` 425) True >> note (i+1)
     note 0
+    V.unsafeFreeze ar
 
-biSqRes377 :: UArray Int Bool
-biSqRes377 = runSTUArray $ do
-    ar <- newArray (0,376) False
-    let note 144 = return ar
-        note i = unsafeWrite ar ((i*i*i*i) `rem` 377) True >> note (i+1)
+biSqRes377 :: V.Vector Bool
+biSqRes377 = runST $ do
+    ar <- MV.replicate 377 False
+    let note 144 = return ()
+        note i = MV.unsafeWrite ar ((i*i*i*i) `rem` 377) True >> note (i+1)
     note 0
+    V.unsafeFreeze ar
 
 biSqrtInt :: Int -> Int
 biSqrtInt 0 = 0
diff --git a/Math/NumberTheory/Powers/General.hs b/Math/NumberTheory/Powers/General.hs
--- a/Math/NumberTheory/Powers/General.hs
+++ b/Math/NumberTheory/Powers/General.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Calculating integer roots and determining perfect powers.
 -- The algorithms are moderately efficient.
@@ -31,6 +29,8 @@
 import Data.List (foldl')
 import qualified Data.Set as Set
 
+import Numeric.Natural
+
 import Math.NumberTheory.Logarithms (integerLogBase')
 import Math.NumberTheory.Utils  (shiftToOddCount
                                 , splitOff
@@ -38,6 +38,7 @@
 import qualified Math.NumberTheory.Powers.Squares as P2
 import qualified Math.NumberTheory.Powers.Cubes as P3
 import qualified Math.NumberTheory.Powers.Fourth as P4
+import Math.NumberTheory.Utils.FromIntegral (intToWord, wordToInt)
 
 -- | Calculate an integer root, @'integerRoot' k n@ computes the (floor of) the @k@-th
 --   root of @n@, where @k@ must be positive.
@@ -50,10 +51,13 @@
 {-# SPECIALISE integerRoot :: Int -> Int -> Int,
                               Int -> Word -> Word,
                               Int -> Integer -> Integer,
+                              Int -> Natural -> Natural,
                               Word -> Int -> Int,
                               Word -> Word -> Word,
                               Word -> Integer -> Integer,
-                              Integer -> Integer -> Integer
+                              Word -> Natural -> Natural,
+                              Integer -> Integer -> Integer,
+                              Natural -> Natural -> Natural
   #-}
 integerRoot :: (Integral a, Integral b) => b -> a -> a
 integerRoot 1 n         = n
@@ -155,7 +159,7 @@
 --   remaining factor is examined by trying the divisors of the @gcd@
 --   of the prime exponents if some have been found, otherwise by trying
 --   prime exponents recursively.
-highestPower :: Integral a => a -> (a, Int)
+highestPower :: Integral a => a -> (a, Word)
 highestPower n'
   | abs n <= 1  = (n', 3)
   | n < 0       = case integerHighPower (negate n) of
@@ -167,7 +171,7 @@
       n :: Integer
       n = toInteger n'
 
-      sqr :: Int -> Integer -> Integer
+      sqr :: Word -> Integer -> Integer
       sqr 0 m = m
       sqr k m = sqr (k-1) (m*m)
 
@@ -180,18 +184,19 @@
 --   and primality testing, it is not expected to be generally useful.
 --   The assumptions are not checked, if they are not satisfied, wrong
 --   results and wasted work may be the consequence.
-largePFPower :: Integer -> Integer -> (Integer, Int)
+largePFPower :: Integer -> Integer -> (Integer, Word)
 largePFPower bd n = rawPower ln n
   where
-    ln = integerLogBase' (bd+1) n
+    ln = intToWord (integerLogBase' (bd+1) n)
 
 ------------------------------------------------------------------------------------------
 --                                  Auxiliary functions                                 --
 ------------------------------------------------------------------------------------------
 
 {-# SPECIALISE newtonK :: Int -> Int -> Int -> Int,
+                          Word -> Word -> Word -> Word,
                           Integer -> Integer -> Integer -> Integer,
-                          Word -> Word -> Word -> Word
+                          Natural -> Natural -> Natural -> Natural
   #-}
 newtonK :: Integral a => a -> a -> a -> a
 newtonK k n a = go (step a)
@@ -204,9 +209,10 @@
         where
           l = step m
 
-{-# SPECIALISE approxKthRoot :: Int -> Integer -> Integer,
-                                Int -> Int -> Int,
-                                Int -> Word -> Word
+{-# SPECIALISE approxKthRoot :: Int -> Int -> Int,
+                                Int -> Word -> Word,
+                                Int -> Integer -> Integer,
+                                Int -> Natural -> Natural
   #-}
 approxKthRoot :: Integral a => Int -> a -> a
 approxKthRoot k = fromInteger . appKthRoot k . fromIntegral
@@ -230,7 +236,7 @@
                           `shiftLInteger` (h# -# 401#)
 
 -- assumption: argument is > 1
-integerHighPower :: Integer -> (Integer, Int)
+integerHighPower :: Integer -> (Integer, Word)
 integerHighPower n
   | n < 4       = (n,1)
   | otherwise   = case shiftToOddCount n of
@@ -239,7 +245,7 @@
                              where
                                r = P2.integerSquareRoot m
 
-findHighPower :: Int -> [(Integer,Int)] -> Integer -> Integer -> [Integer] -> (Integer, Int)
+findHighPower :: Word -> [(Integer, Word)] -> Integer -> Integer -> [Integer] -> (Integer, Word)
 findHighPower 1 pws m _ _ = (foldl' (*) m [p^e | (p,e) <- pws], 1)
 findHighPower e pws 1 _ _ = (foldl' (*) 1 [p^(ex `quot` e) | (p,ex) <- pws], e)
 findHighPower e pws m s (p:ps)
@@ -250,7 +256,7 @@
       (k,r) -> findHighPower (gcd k e) ((p,k):pws) r (P2.integerSquareRoot r) ps
 findHighPower e pws m _ [] = finishPower e pws m
 
-spBEx :: Int
+spBEx :: Word
 spBEx = 14
 
 spBound :: Integer
@@ -266,20 +272,20 @@
         go []     = True
 
 -- n large, has no prime divisors < spBound
-finishPower :: Int -> [(Integer, Int)] -> Integer -> (Integer, Int)
+finishPower :: Word -> [(Integer, Word)] -> Integer -> (Integer, Word)
 finishPower e pws n
-  | n < (1 `shiftL` (2*spBEx))  = (foldl' (*) n [p^ex | (p,ex) <- pws], 1)    -- n is prime
+  | n < (1 `shiftL` wordToInt (2*spBEx))  = (foldl' (*) n [p^ex | (p,ex) <- pws], 1)    -- n is prime
   | e == 0  = rawPower maxExp n
   | otherwise = go divs
     where
-      maxExp = (I# (integerLog2# n)) `quot` spBEx
+      maxExp = (W# (int2Word# (integerLog2# n))) `quot` spBEx
       divs = divisorsTo maxExp e
       go [] = (foldl' (*) n [p^ex | (p,ex) <- pws], 1)
       go (d:ds) = case exactRoot d n of
                     Just r -> (foldl' (*) r [p^(ex `quot` d) | (p,ex) <- pws], d)
                     Nothing -> go ds
 
-rawPower :: Int -> Integer -> (Integer, Int)
+rawPower :: Word -> Integer -> (Integer, Word)
 rawPower mx n
   | mx < 2      = (n,1)
   | mx == 2     = case P2.exactSquareRoot n of
@@ -293,7 +299,7 @@
                                            (m,e) -> (m, 2*e)
                                Nothing -> rawOddPower mx n
 
-rawOddPower :: Int -> Integer -> (Integer, Int)
+rawOddPower :: Word -> Integer -> (Integer, Word)
 rawOddPower mx n
   | mx < 3       = (n,1)
 rawOddPower mx n = case P3.exactCubeRoot n of
@@ -301,7 +307,7 @@
                                  (m,e) -> (m, 3*e)
                      Nothing -> badPower mx n
 
-badPower :: Int -> Integer -> (Integer, Int)
+badPower :: Word -> Integer -> (Integer, Word)
 badPower mx n
   | mx < 5      = (n,1)
   | otherwise   = go 1 mx n (takeWhile (<= mx) $ scanl (+) 5 $ cycle [2,4])
@@ -313,25 +319,25 @@
                         Nothing -> go e b m ks
       go e _ m []   = (m,e)
 
-divisorsTo :: Int -> Int -> [Int]
+divisorsTo :: Word -> Word -> [Word]
 divisorsTo mx n = case shiftToOddCount n of
                     (k,o) | k == 0 -> go (Set.singleton 1) n iops
-                          | otherwise -> go (Set.fromDistinctAscList $ takeWhile (<= mx) $ take (k+1) (iterate (*2) 1)) o iops
+                          | otherwise -> go (Set.fromDistinctAscList $ takeWhile (<= mx) $ take (wordToInt k + 1) (iterate (*2) 1)) o iops
   where
     mset k st = fst (Set.split (mx+1) (Set.mapMonotonic (*k) st))
     -- unP p m = (k, m / p ^ k), where k is as large as possible such that p ^ k still divides m
-    unP :: Int -> Int -> (Int,Int)
+    unP :: Word -> Word -> (Word, Word)
     unP p m = goP 0 m
       where
-        goP :: Int -> Int -> (Int,Int)
+        goP :: Word -> Word -> (Word, Word)
         goP !i j = case j `quotRem` p of
                      (q,r) | r == 0 -> goP (i+1) q
                            | otherwise -> (i,j)
-    iops :: [Int]
+    iops :: [Word]
     iops = 3:5:prs
-    prs :: [Int]
+    prs :: [Word]
     prs = 7:filter prm (scanl (+) 11 $ cycle [2,4,2,4,6,2,6,4])
-    prm :: Int -> Bool
+    prm :: Word -> Bool
     prm k = td prs
       where
         td (p:ps) = (p*p > k) || (k `rem` p /= 0 && td ps)
@@ -343,5 +349,5 @@
         case unP p m of
           (0,_) -> go st m ps
           -- iterate f x = [x, f x, f (f x)...]
-          (k,r) -> go (Set.unions (take (k + 1) (iterate (mset p) st))) r ps
+          (k,r) -> go (Set.unions (take (wordToInt k + 1) (iterate (mset p) st))) r ps
     go st m [] = go st m [m+1]
diff --git a/Math/NumberTheory/Powers/Modular.hs b/Math/NumberTheory/Powers/Modular.hs
--- a/Math/NumberTheory/Powers/Modular.hs
+++ b/Math/NumberTheory/Powers/Modular.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Modular powers (a. k. a. modular exponentiation).
 --
@@ -44,7 +42,7 @@
 --
 -- >>> powMod 3 101 (2^60-1 :: Integer)
 -- 1018105167100379328 -- correct
--- >>> powMod 3 101 (2^60-1 :: Int64)
+-- >>> powMod 3 101 (2^60-1 :: Int)
 -- 1115647832265427613 -- incorrect due to overflow
 -- >>> powModInt 3 101 (2^60-1 :: Int)
 -- 1018105167100379328 -- correct
diff --git a/Math/NumberTheory/Powers/Squares.hs b/Math/NumberTheory/Powers/Squares.hs
--- a/Math/NumberTheory/Powers/Squares.hs
+++ b/Math/NumberTheory/Powers/Squares.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Functions dealing with squares. Efficient calculation of integer square roots
 -- and efficient testing for squareness.
@@ -25,12 +23,12 @@
 
 #include "MachDeps.h"
 
-import Data.Array.Unboxed
-import Data.Array.ST
-
+import Control.Monad.ST
 import Data.Bits
+import qualified Data.Vector.Unboxed as V
+import qualified Data.Vector.Unboxed.Mutable as MV
 
-import Math.NumberTheory.Unsafe
+import Numeric.Natural
 
 import Math.NumberTheory.Powers.Squares.Internal
 
@@ -39,7 +37,8 @@
 --   Throws an error on negative input.
 {-# SPECIALISE integerSquareRoot :: Int -> Int,
                                     Word -> Word,
-                                    Integer -> Integer
+                                    Integer -> Integer,
+                                    Natural -> Natural
   #-}
 integerSquareRoot :: Integral a => a -> a
 integerSquareRoot n
@@ -64,7 +63,8 @@
 {-# SPECIALISE integerSquareRootRem ::
         Int -> (Int, Int),
         Word -> (Word, Word),
-        Integer -> (Integer, Integer)
+        Integer -> (Integer, Integer),
+        Natural -> (Natural, Natural)
   #-}
 integerSquareRootRem :: Integral a => a -> (a, a)
 integerSquareRootRem n
@@ -92,7 +92,8 @@
 --   Checks for negativity and 'isPossibleSquare'.
 {-# SPECIALISE exactSquareRoot :: Int -> Maybe Int,
                                   Word -> Maybe Word,
-                                  Integer -> Maybe Integer
+                                  Integer -> Maybe Integer,
+                                  Natural -> Maybe Natural
   #-}
 exactSquareRoot :: Integral a => a -> Maybe a
 exactSquareRoot n
@@ -106,7 +107,8 @@
 --   is checked, if it is, the integer square root is calculated.
 {-# SPECIALISE isSquare :: Int -> Bool,
                            Word -> Bool,
-                           Integer -> Bool
+                           Integer -> Bool,
+                           Natural -> Bool
   #-}
 isSquare :: Integral a => a -> Bool
 isSquare n = n >= 0 && isSquare' n
@@ -119,7 +121,8 @@
 --   arguments may cause any kind of havoc.
 {-# SPECIALISE isSquare' :: Int -> Bool,
                             Word -> Bool,
-                            Integer -> Bool
+                            Integer -> Bool,
+                            Natural -> Bool
   #-}
 isSquare' :: Integral a => a -> Bool
 isSquare' n
@@ -137,17 +140,18 @@
 --   to eliminate altogether about 99.436% of all numbers.
 --
 --   This is the test used by 'exactSquareRoot'. For large numbers,
---   the slower but more discriminating test 'isPossibleSqure2' is
+--   the slower but more discriminating test 'isPossibleSquare2' is
 --   faster.
 {-# SPECIALISE isPossibleSquare :: Int -> Bool,
+                                   Word -> Bool,
                                    Integer -> Bool,
-                                   Word -> Bool
+                                   Natural -> Bool
   #-}
 isPossibleSquare :: Integral a => a -> Bool
-isPossibleSquare n =
-  unsafeAt sr256 ((fromIntegral n) .&. 255)
-  && unsafeAt sr693 (fromIntegral (n `rem` 693))
-  && unsafeAt sr325 (fromIntegral (n `rem` 325))
+isPossibleSquare n
+  =  V.unsafeIndex sr256 ((fromIntegral n) .&. 255)
+  && V.unsafeIndex sr693 (fromIntegral (n `rem` 693))
+  && V.unsafeIndex sr325 (fromIntegral (n `rem` 325))
 
 -- | Test whether a non-negative number may be a square.
 --   Non-negativity is not checked, passing negative arguments may
@@ -163,55 +167,57 @@
 --   numbers, where calculating the square root becomes more expensive,
 --   it is much faster (if the vast majority of tested numbers aren't squares).
 {-# SPECIALISE isPossibleSquare2 :: Int -> Bool,
+                                    Word -> Bool,
                                     Integer -> Bool,
-                                    Word -> Bool
+                                    Natural -> Bool
   #-}
 isPossibleSquare2 :: Integral a => a -> Bool
-isPossibleSquare2 n =
-  unsafeAt sr256 ((fromIntegral n) .&. 255)
-  && unsafeAt sr819  (fromIntegral (n `rem` 819))
-  && unsafeAt sr1025 (fromIntegral (n `rem` 1025))
-  && unsafeAt sr2047 (fromIntegral (n `rem` 2047))
-  && unsafeAt sr4097 (fromIntegral (n `rem` 4097))
-  && unsafeAt sr341  (fromIntegral (n `rem` 341))
+isPossibleSquare2 n
+  =  V.unsafeIndex sr256  ((fromIntegral n) .&. 255)
+  && V.unsafeIndex sr819  (fromIntegral (n `rem` 819))
+  && V.unsafeIndex sr1025 (fromIntegral (n `rem` 1025))
+  && V.unsafeIndex sr2047 (fromIntegral (n `rem` 2047))
+  && V.unsafeIndex sr4097 (fromIntegral (n `rem` 4097))
+  && V.unsafeIndex sr341  (fromIntegral (n `rem` 341))
 
 -----------------------------------------------------------------------------
 --  Auxiliary Stuff
 
 -- Make an array indicating whether a remainder is a square remainder.
-sqRemArray :: Int -> UArray Int Bool
-sqRemArray md = runSTUArray $ do
-  arr <- newArray (0,md-1) False
+sqRemArray :: Int -> V.Vector Bool
+sqRemArray md = runST $ do
+  ar <- MV.replicate md False
   let !stop = (md `quot` 2) + 1
       fill k
-        | k < stop  = unsafeWrite arr ((k*k) `rem` md) True >> fill (k+1)
-        | otherwise = return arr
-  unsafeWrite arr 0 True
-  unsafeWrite arr 1 True
+        | k < stop  = MV.unsafeWrite ar ((k*k) `rem` md) True >> fill (k+1)
+        | otherwise = return ()
+  MV.unsafeWrite ar 0 True
+  MV.unsafeWrite ar 1 True
   fill 2
+  V.unsafeFreeze ar
 
-sr256 :: UArray Int Bool
+sr256 :: V.Vector Bool
 sr256 = sqRemArray 256
 
-sr819 :: UArray Int Bool
+sr819 :: V.Vector Bool
 sr819 = sqRemArray 819
 
-sr4097 :: UArray Int Bool
+sr4097 :: V.Vector Bool
 sr4097 = sqRemArray 4097
 
-sr341 :: UArray Int Bool
+sr341 :: V.Vector Bool
 sr341 = sqRemArray 341
 
-sr1025 :: UArray Int Bool
+sr1025 :: V.Vector Bool
 sr1025 = sqRemArray 1025
 
-sr2047 :: UArray Int Bool
+sr2047 :: V.Vector Bool
 sr2047 = sqRemArray 2047
 
-sr693 :: UArray Int Bool
+sr693 :: V.Vector Bool
 sr693 = sqRemArray 693
 
-sr325 :: UArray Int Bool
+sr325 :: V.Vector Bool
 sr325 = sqRemArray 325
 
 -- Specialisations for Int, Word, and Integer
diff --git a/Math/NumberTheory/Powers/Squares/Internal.hs b/Math/NumberTheory/Powers/Squares/Internal.hs
--- a/Math/NumberTheory/Powers/Squares/Internal.hs
+++ b/Math/NumberTheory/Powers/Squares/Internal.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Internal functions dealing with square roots. End-users should not import this module.
 
diff --git a/Math/NumberTheory/Prefactored.hs b/Math/NumberTheory/Prefactored.hs
--- a/Math/NumberTheory/Prefactored.hs
+++ b/Math/NumberTheory/Prefactored.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Type for numbers, accompanied by their factorisation.
 --
@@ -26,7 +24,8 @@
 
 import Math.NumberTheory.Euclidean
 import Math.NumberTheory.Euclidean.Coprimes
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes.Types
 
 -- | A container for a number and its pairwise coprime (but not neccessarily prime)
 -- factorisation.
@@ -36,29 +35,23 @@
 --
 -- For instance, let @p@ and @q@ be big primes:
 --
--- >>> let p, q :: Integer
--- >>>     p = 1000000000000000000000000000057
--- >>>     q = 2000000000000000000000000000071
+-- >>> let p = 1000000000000000000000000000057 :: Integer
+-- >>> let q = 2000000000000000000000000000071 :: Integer
 --
--- It would be  difficult to compute prime factorisation of their product
--- as is:
--- 'factorise' would take ages. Things become different if we simply
+-- It would be difficult to compute the totient function
+-- of their product as is, because once we multiplied them
+-- the information of factors is lost and
+-- 'Math.NumberTheory.ArithmeticFunctions.totient' (@p@ * @q@)
+-- would take ages. Things become different if we simply
 -- change types of @p@ and @q@ to prefactored ones:
 --
--- >>> let p, q :: Prefactored Integer
--- >>>     p = 1000000000000000000000000000057
--- >>>     q = 2000000000000000000000000000071
---
--- Now prime factorisation is done instantly:
---
--- >>> factorise (p * q)
--- [(PrimeNat 1000000000000000000000000000057, 1), (PrimeNat 2000000000000000000000000000071, 1)]
--- >>> factorise (p^2 * q^3)
--- [(PrimeNat 1000000000000000000000000000057, 2), (PrimeNat 2000000000000000000000000000071, 3)]
+-- >>> let p = 1000000000000000000000000000057 :: Prefactored Integer
+-- >>> let q = 2000000000000000000000000000071 :: Prefactored Integer
 --
--- Moreover, we can instantly compute 'totient' and its iterations.
--- It works fine, because output of 'totient' is also prefactored.
+-- Now the 'Math.NumberTheory.ArithmeticFunctions.totient' function
+-- can be computed instantly:
 --
+-- >>> import Math.NumberTheory.ArithmeticFunctions
 -- >>> prefValue $ totient (p^2 * q^3)
 -- 8000000000000000000000000001752000000000000000000000000151322000000000000000000000006445392000000000000000000000135513014000000000000000000001126361040
 -- >>> prefValue $ totient $ totient (p^2 * q^3)
@@ -66,14 +59,11 @@
 --
 -- Let us look under the hood:
 --
+-- >>> import Math.NumberTheory.ArithmeticFunctions
 -- >>> prefFactors $ totient (p^2 * q^3)
--- Coprimes {unCoprimes = fromList [(2,4),(3,3),
---   (41666666666666666666666666669,1),(111111111111111111111111111115,1),
---   (1000000000000000000000000000057,1),(2000000000000000000000000000071,2)]}
+-- Coprimes {unCoprimes = [(1000000000000000000000000000057,1),(41666666666666666666666666669,1),(2000000000000000000000000000071,2),(111111111111111111111111111115,1),(2,4),(3,3)]}
 -- >>> prefFactors $ totient $ totient (p^2 * q^3)
--- Coprimes {unCoprimes = fromList [(2,22),(3,8),(5,3),(39521,1),(199937,1),(6046667,1),
---   (227098769,1),(361696272343,1),(85331809838489,1),(22222222222222222222222222223,1),
---   (41666666666666666666666666669,1),(2000000000000000000000000000071,1)]}
+-- Coprimes {unCoprimes = [(39521,1),(6046667,1),(22222222222222222222222222223,1),(2000000000000000000000000000071,1),(361696272343,1),(85331809838489,1),(227098769,1),(199937,1),(5,3),(41666666666666666666666666669,1),(2,22),(3,8)]}
 --
 -- Pairwise coprimality of factors is crucial, because it allows
 -- us to process them independently, possibly even
@@ -93,7 +83,7 @@
 -- | Create 'Prefactored' from a given number.
 --
 -- >>> fromValue 123
--- Prefactored {prefValue = 123, prefFactors = Coprimes {unCoprimes = fromList [(123,1)]}}
+-- Prefactored {prefValue = 123, prefFactors = Coprimes {unCoprimes = [(123,1)]}}
 fromValue :: (Eq a, Num a) => a -> Prefactored a
 fromValue a = Prefactored a (singleton a 1)
 
@@ -101,13 +91,13 @@
 -- (but not neccesarily prime) factors with multiplicities.
 --
 -- >>> fromFactors (splitIntoCoprimes [(140, 1), (165, 1)])
--- Prefactored {prefValue = 23100, prefFactors = Coprimes {unCoprimes = fromList [(5,2),(28,1),(33,1)]}}
+-- Prefactored {prefValue = 23100, prefFactors = Coprimes {unCoprimes = [(28,1),(33,1),(5,2)]}}
 -- >>> fromFactors (splitIntoCoprimes [(140, 2), (165, 3)])
--- Prefactored {prefValue = 88045650000, prefFactors = Coprimes {unCoprimes = fromList [(5,5),(28,2),(33,3)]}}
+-- Prefactored {prefValue = 88045650000, prefFactors = Coprimes {unCoprimes = [(28,2),(33,3),(5,5)]}}
 fromFactors :: Num a => Coprimes a Word -> Prefactored a
 fromFactors as = Prefactored (product (map (uncurry (^)) (unCoprimes as))) as
 
-instance (Euclidean a, Ord a) => Num (Prefactored a) where
+instance Euclidean a => Num (Prefactored a) where
   Prefactored v1 _ + Prefactored v2 _
     = fromValue (v1 + v2)
   Prefactored v1 _ - Prefactored v2 _
@@ -119,12 +109,9 @@
   signum (Prefactored v _) = Prefactored (signum v) mempty
   fromInteger n = fromValue (fromInteger n)
 
-type instance Prime (Prefactored a) = Prime a
-
-instance (Eq a, Num a, UniqueFactorisation a) => UniqueFactorisation (Prefactored a) where
-  unPrime p = fromValue (unPrime p)
+instance (Euclidean a, UniqueFactorisation a) => UniqueFactorisation (Prefactored a) where
   factorise (Prefactored _ f)
-    = concatMap (\(x, xm) -> map (second (* xm)) (factorise x)) (unCoprimes f)
+    = concatMap (\(x, xm) -> map (\(p, k) -> (Prime $ fromValue $ unPrime p, k * xm)) (factorise x)) (unCoprimes f)
   isPrime (Prefactored _ f) = case unCoprimes f of
-    [(n, 1)] -> isPrime n
+    [(n, 1)] -> Prime . fromValue . unPrime <$> isPrime n
     _        -> Nothing
diff --git a/Math/NumberTheory/Primes.hs b/Math/NumberTheory/Primes.hs
--- a/Math/NumberTheory/Primes.hs
+++ b/Math/NumberTheory/Primes.hs
@@ -1,19 +1,259 @@
 -- |
 -- Module:      Math.NumberTheory.Primes
--- Copyright:   (c) 2011 Daniel Fischer
+-- Copyright:   (c) 2016-2018 Andrew.Lelechenko
 -- Licence:     MIT
--- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
 --
+
+{-# LANGUAGE CPP               #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase        #-}
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
 module Math.NumberTheory.Primes
-    ( module Math.NumberTheory.Primes.Sieve
-    , module Math.NumberTheory.Primes.Counting
-    , module Math.NumberTheory.Primes.Testing
-    , module Math.NumberTheory.Primes.Factorisation
+    ( Prime
+    , unPrime
+    , nextPrime
+    , precPrime
+    , UniqueFactorisation(..)
+    , -- * Old interface
+      primes
     ) where
 
-import Math.NumberTheory.Primes.Sieve
-import Math.NumberTheory.Primes.Counting
-import Math.NumberTheory.Primes.Testing
-import Math.NumberTheory.Primes.Factorisation
+import Control.Arrow
+import Data.Bits
+import Data.Coerce
+import Data.Maybe
+
+import Math.NumberTheory.Primes.Counting (nthPrime, primeCount)
+import qualified Math.NumberTheory.Primes.Factorisation as F (factorise)
+import qualified Math.NumberTheory.Primes.Testing.Probabilistic as T (isPrime)
+import Math.NumberTheory.Primes.Sieve.Eratosthenes (primes, sieveRange, primeList, psieveFrom, primeSieve)
+import Math.NumberTheory.Primes.Types
+import Math.NumberTheory.Utils (toWheel30, fromWheel30)
+import Math.NumberTheory.Utils.FromIntegral
+
+import Numeric.Natural
+
+-- | A class for unique factorisation domains.
+class Num a => UniqueFactorisation a where
+  -- | Factorise a number into a product of prime powers.
+  -- Factorisation of 0 is an undefined behaviour. Otherwise
+  -- following invariants hold:
+  --
+  -- > abs n == abs (product (map (\(p, k) -> unPrime p ^ k) (factorise n)))
+  -- > all ((> 0) . snd) (factorise n)
+  --
+  -- >>> factorise (1 :: Integer)
+  -- []
+  -- >>> factorise (-1 :: Integer)
+  -- []
+  -- >>> factorise (6 :: Integer)
+  -- [(Prime 2,1),(Prime 3,1)]
+  -- >>> factorise (-108 :: Integer)
+  -- [(Prime 2,2),(Prime 3,3)]
+  --
+  -- This function is a replacement
+  -- for 'Math.NumberTheory.Primes.Factorisation.factorise'.
+  -- If you were looking for the latter, please import
+  -- "Math.NumberTheory.Primes.Factorisation" instead of this module.
+  --
+  -- __Warning:__ there are no guarantees of any particular
+  -- order of prime factors, do not expect them to be ascending. E. g.,
+  --
+  -- >>> factorise 10251562501
+  -- [(Prime 101701,1),(Prime 100801,1)]
+  factorise :: a -> [(Prime a, Word)]
+  -- | Check whether an argument is prime.
+  -- If it is then return an associated prime.
+  --
+  -- >>> isPrime (3 :: Integer)
+  -- Just (Prime 3)
+  -- >>> isPrime (4 :: Integer)
+  -- Nothing
+  -- >>> isPrime (-5 :: Integer)
+  -- Just (Prime 5)
+  --
+  -- This function is a replacement
+  -- for 'Math.NumberTheory.Primes.Testing.isPrime'.
+  -- If you were looking for the latter, please import
+  -- "Math.NumberTheory.Primes.Testing" instead of this module.
+  isPrime   :: a -> Maybe (Prime a)
+
+instance UniqueFactorisation Int where
+  factorise = map (Prime . integerToInt *** id) . F.factorise . intToInteger
+  isPrime n = if T.isPrime (toInteger n) then Just (Prime $ abs n) else Nothing
+
+instance UniqueFactorisation Word where
+  factorise = map (coerce integerToWord *** id) . F.factorise . wordToInteger
+  isPrime n = if T.isPrime (toInteger n) then Just (Prime n) else Nothing
+
+instance UniqueFactorisation Integer where
+  factorise = coerce . F.factorise
+  isPrime n = if T.isPrime n then Just (Prime $ abs n) else Nothing
+
+instance UniqueFactorisation Natural where
+  factorise = map (coerce integerToNatural *** id) . F.factorise . naturalToInteger
+  isPrime n = if T.isPrime (toInteger n) then Just (Prime n) else Nothing
+
+-- | Smallest prime, greater or equal to argument.
+--
+-- > nextPrime (-100) ==    2
+-- > nextPrime  1000  == 1009
+-- > nextPrime  1009  == 1009
+nextPrime :: (Bits a, Integral a, UniqueFactorisation a) => a -> Prime a
+nextPrime n
+  | n <= 2    = Prime 2
+  | n <= 3    = Prime 3
+  | n <= 5    = Prime 5
+  | otherwise = head $ mapMaybe isPrime $
+                  dropWhile (< n) $ map fromWheel30 [toWheel30 n ..]
+                  -- dropWhile is important, because fromWheel30 (toWheel30 n) may appear to be < n.
+                  -- E. g., fromWheel30 (toWheel30 94) == 97
+
+-- | Largest prime, less or equal to argument. Undefined, when argument < 2.
+--
+-- > precPrime 100 == 97
+-- > precPrime  97 == 97
+precPrime :: (Bits a, Integral a, UniqueFactorisation a) => a -> Prime a
+precPrime n
+  | n < 2     = error $ "precPrime: tried to take `precPrime` of an argument less than 2"
+  | n < 3     = Prime 2
+  | n < 5     = Prime 3
+  | n < 7     = Prime 5
+  | otherwise = head $ mapMaybe isPrime $
+                  dropWhile (> n) $ map fromWheel30 [toWheel30 n, toWheel30 n - 1 ..]
+                  -- dropWhile is important, because fromWheel30 (toWheel30 n) may appear to be > n.
+                  -- E. g., fromWheel30 (toWheel30 100) == 101
+
+-------------------------------------------------------------------------------
+-- Prime sequences
+
+data Algorithm = IsPrime | Sieve
+
+chooseAlgorithm :: Integral a => a -> a -> Algorithm
+chooseAlgorithm from to
+  | to <= fromIntegral sieveRange
+  && to < from + truncate (sqrt (fromIntegral from) :: Double)
+  = IsPrime
+  | to > fromIntegral sieveRange
+  && to < from + truncate (0.036 * sqrt (fromIntegral from) + 40000 :: Double)
+  = IsPrime
+  | otherwise
+  = Sieve
+
+succGeneric :: (Bits a, Integral a, UniqueFactorisation a) => Prime a -> Prime a
+succGeneric = \case
+  Prime 2 -> Prime 3
+  Prime 3 -> Prime 5
+  Prime 5 -> Prime 7
+  Prime p -> head $ mapMaybe isPrime $ map fromWheel30 [toWheel30 p + 1 ..]
+
+succGenericBounded
+  :: (Bits a, Integral a, UniqueFactorisation a, Bounded a)
+  => Prime a
+  -> Prime a
+succGenericBounded = \case
+  Prime 2 -> Prime 3
+  Prime 3 -> Prime 5
+  Prime 5 -> Prime 7
+  Prime p -> case mapMaybe isPrime $ map fromWheel30 [toWheel30 p + 1 .. toWheel30 maxBound] of
+    []    -> error "Enum.succ{Prime}: tried to take `succ' near `maxBound'"
+    q : _ -> q
+
+predGeneric :: (Bits a, Integral a, UniqueFactorisation a) => Prime a -> Prime a
+predGeneric = \case
+  Prime 2 -> error "Enum.pred{Prime}: tried to take `pred' of 2"
+  Prime 3 -> Prime 2
+  Prime 5 -> Prime 3
+  Prime 7 -> Prime 5
+  Prime p -> head $ mapMaybe isPrime $ map fromWheel30 [toWheel30 p - 1, toWheel30 p - 2 ..]
+
+-- 'dropWhile' is important, because 'psieveFrom' can actually contain primes less than p.
+enumFromGeneric :: Integral a => Prime a -> [Prime a]
+enumFromGeneric p@(Prime p')
+  = coerce
+  $ dropWhile (< p)
+  $ concat
+  $ takeWhile (not . null)
+  $ map primeList
+  $ psieveFrom
+  $ toInteger p'
+
+enumFromToGeneric :: (Bits a, Integral a, UniqueFactorisation a) => Prime a -> Prime a -> [Prime a]
+enumFromToGeneric p@(Prime p') q@(Prime q') = takeWhile (<= q) $ dropWhile (< p) $
+  case chooseAlgorithm p' q' of
+    IsPrime -> Prime 2 : Prime 3 : Prime 5 : mapMaybe isPrime (map fromWheel30 [toWheel30 p' .. toWheel30 q'])
+    Sieve   ->
+      if q' < fromIntegral sieveRange
+      then           primeList $ primeSieve $ toInteger q'
+      else concatMap primeList $ psieveFrom $ toInteger p'
+
+enumFromThenGeneric :: (Bits a, Integral a, UniqueFactorisation a) => Prime a -> Prime a -> [Prime a]
+enumFromThenGeneric p@(Prime p') (Prime q') = case p' `compare` q' of
+  LT -> filter (\(Prime r') -> (r' - p') `mod` delta == 0) $ enumFromGeneric p
+    where
+      delta = q' - p'
+  EQ -> repeat p
+  GT -> filter (\(Prime r') -> (p' - r') `mod` delta == 0) $ reverse $ enumFromToGeneric (Prime 2) p
+    where
+      delta = p' - q'
+
+enumFromThenToGeneric :: (Bits a, Integral a, UniqueFactorisation a) => Prime a -> Prime a -> Prime a -> [Prime a]
+enumFromThenToGeneric p@(Prime p') (Prime q') r@(Prime r') = case p' `compare` q' of
+  LT -> filter (\(Prime t') -> (t' - p') `mod` delta == 0) $ enumFromToGeneric p r
+    where
+      delta = q' - p'
+  EQ -> if p' <= r' then repeat p else []
+  GT -> filter (\(Prime t') -> (p' - t') `mod` delta == 0) $ reverse $ enumFromToGeneric r p
+    where
+      delta = p' - q'
+
+instance Enum (Prime Integer) where
+  toEnum = nthPrime . intToInteger
+  fromEnum = integerToInt . primeCount . unPrime
+  succ = succGeneric
+  pred = predGeneric
+  enumFrom = enumFromGeneric
+  enumFromTo = enumFromToGeneric
+  enumFromThen = enumFromThenGeneric
+  enumFromThenTo = enumFromThenToGeneric
+
+instance Enum (Prime Natural) where
+  toEnum = Prime . integerToNatural . unPrime . nthPrime . intToInteger
+  fromEnum = integerToInt . primeCount . naturalToInteger . unPrime
+  succ = succGeneric
+  pred = predGeneric
+  enumFrom = enumFromGeneric
+  enumFromTo = enumFromToGeneric
+  enumFromThen = enumFromThenGeneric
+  enumFromThenTo = enumFromThenToGeneric
+
+instance Enum (Prime Int) where
+  toEnum n = if p > intToInteger maxBound
+    then error $ "Enum.toEnum{Prime}: " ++ show n ++ "th prime = " ++ show p ++ " is out of bounds of Int"
+    else Prime (integerToInt p)
+    where
+      Prime p = nthPrime (intToInteger n)
+  fromEnum = integerToInt . primeCount . intToInteger . unPrime
+  succ = succGenericBounded
+  pred = predGeneric
+  enumFrom = enumFromGeneric
+  enumFromTo = enumFromToGeneric
+  enumFromThen = enumFromThenGeneric
+  enumFromThenTo = enumFromThenToGeneric
+
+instance Enum (Prime Word) where
+  toEnum n = if p > wordToInteger maxBound
+    then error $ "Enum.toEnum{Prime}: " ++ show n ++ "th prime = " ++ show p ++ " is out of bounds of Word"
+    else Prime (integerToWord p)
+    where
+      Prime p = nthPrime (intToInteger n)
+  fromEnum = integerToInt . primeCount . wordToInteger . unPrime
+  succ = succGenericBounded
+  pred = predGeneric
+  enumFrom = enumFromGeneric
+  enumFromTo = enumFromToGeneric
+  enumFromThen = enumFromThenGeneric
+  enumFromThenTo = enumFromThenToGeneric
diff --git a/Math/NumberTheory/Primes/Counting.hs b/Math/NumberTheory/Primes/Counting.hs
--- a/Math/NumberTheory/Primes/Counting.hs
+++ b/Math/NumberTheory/Primes/Counting.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: non-portable
 --
 -- Number of primes not exceeding @n@, @&#960;(n)@, and @n@-th prime; also fast, but
 -- reasonably accurate approximations to these.
diff --git a/Math/NumberTheory/Primes/Counting/Approximate.hs b/Math/NumberTheory/Primes/Counting/Approximate.hs
--- a/Math/NumberTheory/Primes/Counting/Approximate.hs
+++ b/Math/NumberTheory/Primes/Counting/Approximate.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: portable
 --
 -- Approximations to the number of primes below a limit and the
 -- n-th prime.
diff --git a/Math/NumberTheory/Primes/Counting/Impl.hs b/Math/NumberTheory/Primes/Counting/Impl.hs
--- a/Math/NumberTheory/Primes/Counting/Impl.hs
+++ b/Math/NumberTheory/Primes/Counting/Impl.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: non-portable
 --
 -- Number of primes not exceeding @n@, @&#960;(n)@, and @n@-th prime.
 --
@@ -27,6 +25,7 @@
 import Math.NumberTheory.Primes.Sieve.Eratosthenes
 import Math.NumberTheory.Primes.Sieve.Indexing
 import Math.NumberTheory.Primes.Counting.Approximate
+import Math.NumberTheory.Primes.Types
 import Math.NumberTheory.Powers.Squares
 import Math.NumberTheory.Powers.Cubes
 import Math.NumberTheory.Logarithms
@@ -61,7 +60,7 @@
 primeCount n
     | n > primeCountMaxArg = error $ "primeCount: can't handle bound " ++ show n
     | n < 2     = 0
-    | n < 1000  = fromIntegral . length . takeWhile (<= n) . primeList . primeSieve $ max 242 n
+    | n < 1000  = fromIntegral . length . takeWhile (<= n) . map unPrime . primeList . primeSieve $ max 242 n
     | n < 30000 = runST $ do
         ba <- sieveTo n
         (s,e) <- getBounds ba
@@ -86,13 +85,13 @@
 --
 --   Requires @/O/((n*log n)^0.5)@ space, the time complexity is roughly @/O/((n*log n)^0.7@.
 --   The argument must be strictly positive, and must not exceed 'nthPrimeMaxArg'.
-nthPrime :: Integer -> Integer
+nthPrime :: Integer -> Prime Integer
 nthPrime n
     | n < 1         = error "Prime indexing starts at 1"
     | n > nthPrimeMaxArg = error $ "nthPrime: can't handle index " ++ show n
-    | n < 200000    = nthPrimeCt n
-    | ct0 < n       = tooLow n p0 (n-ct0) approxGap
-    | otherwise     = tooHigh n p0 (ct0-n) approxGap
+    | n < 200000    = Prime $ nthPrimeCt n
+    | ct0 < n       = Prime $ tooLow n p0 (n-ct0) approxGap
+    | otherwise     = Prime $ tooHigh n p0 (ct0-n) approxGap
       where
         p0 = nthPrimeApprox n
         approxGap = (7 * fromIntegral (integerLog2' p0)) `quot` 10
diff --git a/Math/NumberTheory/Primes/Factorisation.hs b/Math/NumberTheory/Primes/Factorisation.hs
--- a/Math/NumberTheory/Primes/Factorisation.hs
+++ b/Math/NumberTheory/Primes/Factorisation.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Various functions related to prime factorisation.
 -- Many of these functions use the prime factorisation of an 'Integer'.
diff --git a/Math/NumberTheory/Primes/Factorisation/Certified.hs b/Math/NumberTheory/Primes/Factorisation/Certified.hs
--- a/Math/NumberTheory/Primes/Factorisation/Certified.hs
+++ b/Math/NumberTheory/Primes/Factorisation/Certified.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Factorisation proving the primality of the found factors.
 --
@@ -30,12 +28,12 @@
 
 -- | @'certifiedFactorisation' n@ produces the prime factorisation
 --   of @n@, proving the primality of the factors, but doesn't report the proofs.
-certifiedFactorisation :: Integer -> [(Integer,Int)]
+certifiedFactorisation :: Integer -> [(Integer, Word)]
 certifiedFactorisation = map fst . certificateFactorisation
 
 -- | @'certificateFactorisation' n@ produces a 'provenFactorisation'
 --   with a default bound of @100000@.
-certificateFactorisation :: Integer -> [((Integer,Int),PrimalityProof)]
+certificateFactorisation :: Integer -> [((Integer, Word),PrimalityProof)]
 certificateFactorisation n = provenFactorisation 100000 n
 
 -- | @'provenFactorisation' bound n@ constructs a the prime factorisation of @n@
@@ -47,7 +45,7 @@
 --   Construction of primality proofs can take a /very/ long time, so this
 --   will usually be slow (but should be faster than using 'factorise' and
 --   proving the primality of the factors from scratch).
-provenFactorisation :: Integer -> Integer -> [((Integer,Int),PrimalityProof)]
+provenFactorisation :: Integer -> Integer -> [((Integer, Word),PrimalityProof)]
 provenFactorisation _ 1 = []
 provenFactorisation bd n
     | n < 2     = error "provenFactorisation: argument not positive"
@@ -61,7 +59,7 @@
                                                 (mkStdGen $ fromIntegral n `xor` 0xdeadbeef) Nothing k
 
 -- | verify that we indeed have a correct primality proof
-test :: [((Integer,Int),PrimalityProof)] -> [((Integer,Int),PrimalityProof)]
+test :: [((Integer, Word),PrimalityProof)] -> [((Integer, Word),PrimalityProof)]
 test (t@((p,_),prf):more)
     | p == cprime prf && checkPrimalityProof prf    = t : test more
     | otherwise = error (invalid p prf)
@@ -89,7 +87,7 @@
                    -> g                             -- ^ Initial PRNG state
                    -> Maybe Int                     -- ^ Estimated number of digits of the smallest prime factor
                    -> Integer                       -- ^ The number to factorise
-                   -> [((Integer,Int),PrimalityProof)]
+                   -> [((Integer, Word),PrimalityProof)]
                                                     -- ^ List of prime factors, exponents and primality proofs
 certiFactorisation primeBound primeTest prng seed mbdigs n
     = case ptest n of
@@ -147,7 +145,7 @@
                             return  (mergeAll [dp,cp,gp], dc ++ cc ++ gc)
 
 -- | merge two lists of factors, so that the result is strictly increasing (wrt the primes)
-merge :: [((Integer,Int),PrimalityProof)] -> [((Integer,Int),PrimalityProof)] -> [((Integer,Int),PrimalityProof)]
+merge :: [((Integer, Word), PrimalityProof)] -> [((Integer, Word), PrimalityProof)] -> [((Integer, Word), PrimalityProof)]
 merge xxs@(x@((p,e),c):xs) yys@(y@((q,d),_):ys)
     = case compare p q of
         LT -> x : merge xs yys
@@ -157,7 +155,7 @@
 merge xs _  = xs
 
 -- | merge a list of lists of factors so that the result is strictly increasing (wrt the primes)
-mergeAll :: [[((Integer,Int),PrimalityProof)]] -> [((Integer,Int),PrimalityProof)]
+mergeAll :: [[((Integer, Word), PrimalityProof)]] -> [((Integer, Word), PrimalityProof)]
 mergeAll [] = []
 mergeAll [xs] = xs
 mergeAll (xs:ys:zss) = merge (merge xs ys) (mergeAll zss)
diff --git a/Math/NumberTheory/Primes/Factorisation/Montgomery.hs b/Math/NumberTheory/Primes/Factorisation/Montgomery.hs
--- a/Math/NumberTheory/Primes/Factorisation/Montgomery.hs
+++ b/Math/NumberTheory/Primes/Factorisation/Montgomery.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Factorisation of 'Integer's by the elliptic curve algorithm after Montgomery.
 -- The algorithm is explained at
@@ -72,13 +70,20 @@
 import Math.NumberTheory.Primes.Sieve.Eratosthenes
 import Math.NumberTheory.Primes.Sieve.Indexing
 import Math.NumberTheory.Primes.Testing.Probabilistic
+import Math.NumberTheory.Primes.Types (unPrime)
 import Math.NumberTheory.Unsafe
 import Math.NumberTheory.Utils
 
 -- | @'factorise' n@ produces the prime factorisation of @n@. @'factorise' 0@ is
 --   an error and the factorisation of @1@ is empty. Uses a 'StdGen' produced in
 --   an arbitrary manner from the bit-pattern of @n@.
-factorise :: Integer -> [(Integer,Int)]
+--
+-- __Warning:__ there are no guarantees of any particular
+-- order of prime factors, do not expect them to be ascending. E. g.,
+--
+-- >>> factorise 10251562501
+-- [(101701,1),(100801,1)]
+factorise :: Integer -> [(Integer, Word)]
 factorise n
     | abs n == 1 = []
     | n < 0      = factorise (-n)
@@ -86,14 +91,14 @@
     | otherwise  = factorise' n
 
 -- | Like 'factorise', but without input checking, hence @n > 1@ is required.
-factorise' :: Integer -> [(Integer,Int)]
+factorise' :: Integer -> [(Integer, Word)]
 factorise' n = defaultStdGenFactorisation' (mkStdGen $ fromInteger n `xor` 0xdeadbeef) n
 
 -- | @'stepFactorisation'@ is like 'factorise'', except that it doesn't use a
 --   pseudo random generator but steps through the curves in order.
 --   This strategy turns out to be surprisingly fast, on average it doesn't
 --   seem to be slower than the 'StdGen' based variant.
-stepFactorisation :: Integer -> [(Integer,Int)]
+stepFactorisation :: Integer -> [(Integer, Word)]
 stepFactorisation n
     = let (sfs,mb) = smallFactors 100000 n
       in sfs ++ case mb of
@@ -106,7 +111,7 @@
 --   For negative numbers, a factor of @-1@ is included, the factorisation of @1@
 --   is empty. Since @0@ has no prime factorisation, a zero argument causes
 --   an error.
-defaultStdGenFactorisation :: StdGen -> Integer -> [(Integer,Int)]
+defaultStdGenFactorisation :: StdGen -> Integer -> [(Integer, Word)]
 defaultStdGenFactorisation sg n
     | n == 0    = error "0 has no prime factorisation"
     | n < 0     = (-1,1) : defaultStdGenFactorisation sg (-n)
@@ -115,7 +120,7 @@
 
 -- | Like 'defaultStdGenFactorisation', but without input checking, so
 --   @n@ must be larger than @1@.
-defaultStdGenFactorisation' :: StdGen -> Integer -> [(Integer,Int)]
+defaultStdGenFactorisation' :: StdGen -> Integer -> [(Integer, Word)]
 defaultStdGenFactorisation' sg n
     = let (sfs,mb) = smallFactors 100000 n
       in sfs ++ case mb of
@@ -130,11 +135,11 @@
 --   The primality test is 'bailliePSW', the @prng@ function - naturally -
 --   'randomR'. This function also requires small prime factors to have been
 --   stripped before.
-stdGenFactorisation :: Maybe Integer    -- ^ Lower bound for composite divisors
-                    -> StdGen           -- ^ Standard PRNG
-                    -> Maybe Int        -- ^ Estimated number of digits of smallest prime factor
-                    -> Integer          -- ^ The number to factorise
-                    -> [(Integer,Int)]  -- ^ List of prime factors and exponents
+stdGenFactorisation :: Maybe Integer     -- ^ Lower bound for composite divisors
+                    -> StdGen            -- ^ Standard PRNG
+                    -> Maybe Int         -- ^ Estimated number of digits of smallest prime factor
+                    -> Integer           -- ^ The number to factorise
+                    -> [(Integer, Word)] -- ^ List of prime factors and exponents
 stdGenFactorisation primeBound sg digits n
     = curveFactorisation primeBound bailliePSW (\m -> randomR (6,m-2)) sg digits n
 
@@ -164,7 +169,7 @@
   -> g                              -- ^ Initial PRNG state
   -> Maybe Int                      -- ^ Estimated number of digits of the smallest prime factor
   -> Integer                        -- ^ The number to factorise
-  -> [(Integer, Int)]               -- ^ List of prime factors and exponents
+  -> [(Integer, Word)]              -- ^ List of prime factors and exponents
 curveFactorisation primeBound primeTest prng seed mbdigs n
     | n == 1    = []
     | ptest n   = [(n, 1)]
@@ -179,10 +184,10 @@
         rndR :: Integer -> State g Integer
         rndR k = state (prng k)
 
-        perfPw :: Integer -> (Integer, Int)
+        perfPw :: Integer -> (Integer, Word)
         perfPw = maybe highestPower (largePFPower . integerSquareRoot') primeBound
 
-        fact :: Integer -> Int -> State g [(Integer, Int)]
+        fact :: Integer -> Int -> State g [(Integer, Word)]
         fact 1 _ = return mempty
         fact m digs = do
           let (b1, b2, ct) = findParms digs
@@ -227,14 +232,14 @@
                               else repFact x b1 b2 (count - 1)
 
 data Factors = Factors
-  { _primeFactors     :: [(Integer, Int)]
-  , _compositeFactors :: [(Integer, Int)]
+  { _primeFactors     :: [(Integer, Word)]
+  , _compositeFactors :: [(Integer, Word)]
   }
 
-singlePrimeFactor :: Integer -> Int -> Factors
+singlePrimeFactor :: Integer -> Word -> Factors
 singlePrimeFactor a b = Factors [(a, b)] []
 
-singleCompositeFactor :: Integer -> Int -> Factors
+singleCompositeFactor :: Integer -> Word -> Factors
 singleCompositeFactor a b = Factors [] [(a, b)]
 
 instance Semigroup Factors where
@@ -245,7 +250,7 @@
   mempty = Factors [] []
   mappend = (<>)
 
-modifyPowers :: (Int -> Int) -> Factors -> Factors
+modifyPowers :: (Word -> Word) -> Factors -> Factors
 modifyPowers f (Factors pfs cfs)
   = Factors (map (second f) pfs) (map (second f) cfs)
 
@@ -343,12 +348,12 @@
 
 -- | @'smallFactors' bound n@ finds all prime divisors of @n > 1@ up to @bound@ by trial division and returns the
 --   list of these together with their multiplicities, and a possible remaining factor which may be composite.
-smallFactors :: Integer -> Integer -> ([(Integer,Int)], Maybe Integer)
+smallFactors :: Integer -> Integer -> ([(Integer, Word)], Maybe Integer)
 smallFactors bd n = case shiftToOddCount n of
                       (0,m) -> go m prms
                       (k,m) -> (2,k) <: if m == 1 then ([],Nothing) else go m prms
   where
-    prms = tail (primeStore >>= primeList)
+    prms = map unPrime $ tail (primeStore >>= primeList)
     x <: ~(l,b) = (x:l,b)
     go m (p:ps)
         | m < p*p   = ([(m,1)], Nothing)
diff --git a/Math/NumberTheory/Primes/Factorisation/TrialDivision.hs b/Math/NumberTheory/Primes/Factorisation/TrialDivision.hs
--- a/Math/NumberTheory/Primes/Factorisation/TrialDivision.hs
+++ b/Math/NumberTheory/Primes/Factorisation/TrialDivision.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Factorisation and primality testing using trial division.
 --
@@ -21,12 +19,13 @@
 
 import Math.NumberTheory.Primes.Sieve.Eratosthenes
 import Math.NumberTheory.Powers.Squares
+import Math.NumberTheory.Primes.Types
 import Math.NumberTheory.Utils
 
 -- | Factorise an 'Integer' using a given list of numbers considered prime.
 --   If the list is not a list of primes containing all relevant primes, the
 --   result could be surprising.
-trialDivisionWith :: [Integer] -> Integer -> [(Integer,Int)]
+trialDivisionWith :: [Integer] -> Integer -> [(Integer, Word)]
 trialDivisionWith prs n
     | n < 0     = trialDivisionWith prs (-n)
     | n == 0    = error "trialDivision of 0"
@@ -47,11 +46,11 @@
 --   primes @<= bound@. If @n@ has prime divisors @> bound@, the last entry
 --   in the list is the product of all these. If @n <= bound^2@, this is a
 --   full factorisation, but very slow if @n@ has large prime divisors.
-trialDivisionTo :: Integer -> Integer -> [(Integer,Int)]
+trialDivisionTo :: Integer -> Integer -> [(Integer, Word)]
 trialDivisionTo bd
     | bd < 100      = trialDivisionTo 100
-    | bd < 10000000 = trialDivisionWith (primeList $ primeSieve bd)
-    | otherwise     = trialDivisionWith (takeWhile (<= bd) $ (psieveList >>= primeList))
+    | bd < 10000000 = trialDivisionWith (map unPrime $ primeList $ primeSieve bd)
+    | otherwise     = trialDivisionWith (takeWhile (<= bd) $ map unPrime $ (psieveList >>= primeList))
 
 -- | Check whether a number is coprime to all of the numbers in the list
 --   (assuming that list contains only numbers > 1 and is ascending).
@@ -69,5 +68,5 @@
 trialDivisionPrimeTo :: Integer -> Integer -> Bool
 trialDivisionPrimeTo bd
     | bd < 100      = trialDivisionPrimeTo 100
-    | bd < 10000000 = trialDivisionPrimeWith (primeList $ primeSieve bd)
-    | otherwise     = trialDivisionPrimeWith (takeWhile (<= bd) $ (psieveList >>= primeList))
+    | bd < 10000000 = trialDivisionPrimeWith (map unPrime $ primeList $ primeSieve bd)
+    | otherwise     = trialDivisionPrimeWith (takeWhile (<= bd) $ map unPrime $ (psieveList >>= primeList))
diff --git a/Math/NumberTheory/Primes/Sieve.hs b/Math/NumberTheory/Primes/Sieve.hs
--- a/Math/NumberTheory/Primes/Sieve.hs
+++ b/Math/NumberTheory/Primes/Sieve.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Prime generation using a sieve.
 -- Currently, an enhanced sieve of Eratosthenes is used, switching to an
diff --git a/Math/NumberTheory/Primes/Sieve/Eratosthenes.hs b/Math/NumberTheory/Primes/Sieve/Eratosthenes.hs
--- a/Math/NumberTheory/Primes/Sieve/Eratosthenes.hs
+++ b/Math/NumberTheory/Primes/Sieve/Eratosthenes.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Sieve
 --
@@ -38,6 +36,7 @@
 import Control.Monad.ST
 import Data.Array.ST
 import Data.Array.Unboxed
+import Data.Coerce
 import Data.Proxy
 import Control.Monad (when)
 import Data.Bits
@@ -51,6 +50,7 @@
 import Math.NumberTheory.Utils.FromIntegral
 import Math.NumberTheory.Primes.Counting.Approximate
 import Math.NumberTheory.Primes.Sieve.Indexing
+import Math.NumberTheory.Primes.Types
 
 #define IX_MASK     0xFFFFF
 #define IX_BITS     20
@@ -112,12 +112,14 @@
 
 -- | Generate a list of primes for consumption from a
 --   'PrimeSieve'.
-primeList :: forall a. Integral a => PrimeSieve -> [a]
+primeList :: forall a. Integral a => PrimeSieve -> [Prime a]
 primeList ps@(PS v _)
   | doesNotFit (Proxy :: Proxy a) v
               = [] -- has an overflow already happened?
-  | v == 0    = takeWhileIncreasing $ 2 : 3 : 5 : primeListInternal ps
-  | otherwise = takeWhileIncreasing $ primeListInternal ps
+  | v == 0    = (coerce :: [a] -> [Prime a])
+              $ takeWhileIncreasing $ 2 : 3 : 5 : primeListInternal ps
+  | otherwise = (coerce :: [a] -> [Prime a])
+              $ takeWhileIncreasing $ primeListInternal ps
 
 primeListInternal :: Num a => PrimeSieve -> [a]
 primeListInternal (PS v0 bs)
@@ -143,30 +145,32 @@
 -- | Ascending list of primes.
 --
 -- >>> take 10 primes
--- [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
+-- [Prime 2,Prime 3,Prime 5,Prime 7,Prime 11,Prime 13,Prime 17,Prime 19,Prime 23,Prime 29]
 --
 -- 'primes' is a polymorphic list, so the results of computations are not retained in memory.
 -- Make it monomorphic to take advantages of memoization. Compare
 --
 -- >>> :set +s
--- >>> primes !! 1000000 :: Int
--- 15485867
+-- >>> primes !! 1000000 :: Prime Int
+-- Prime 15485867
 -- (5.32 secs, 6,945,267,496 bytes)
--- >>> primes !! 1000000 :: Int
--- 15485867
+-- >>> primes !! 1000000 :: Prime Int
+-- Prime 15485867
 -- (5.19 secs, 6,945,267,496 bytes)
 --
 -- against
 --
--- >>> let primes' = primes :: [Int]
--- >>> primes' !! 1000000 :: Int
--- 15485867
+-- >>> let primes' = primes :: [Prime Int]
+-- >>> primes' !! 1000000 :: Prime Int
+-- Prime 15485867
 -- (5.29 secs, 6,945,269,856 bytes)
--- >>> primes' !! 1000000 :: Int
--- 15485867
+-- >>> primes' !! 1000000 :: Prime Int
+-- Prime 15485867
 -- (0.02 secs, 336,232 bytes)
-primes :: (Ord a, Num a) => [a]
-primes = takeWhileIncreasing $ 2 : 3 : 5 : concatMap primeListInternal psieveList
+primes :: Integral a => [Prime a]
+primes
+  = (coerce :: [a] -> [Prime a])
+  $ takeWhileIncreasing $ 2 : 3 : 5 : concatMap primeListInternal psieveList
 
 -- | List of primes in the form of a list of 'PrimeSieve's, more compact than
 --   'primes', thus it may be better to use @'psieveList' >>= 'primeList'@
@@ -366,9 +370,9 @@
           return (bitCountWord (w1 `shiftL` (RMASK - ei + si)))
 
 -- | @'sieveFrom' n@ creates the list of primes not less than @n@.
-sieveFrom :: Integer -> [Integer]
+sieveFrom :: Integer -> [Prime Integer]
 sieveFrom n = case psieveFrom n of
-                        ps -> dropWhile (< n) (ps >>= primeList)
+                        ps -> dropWhile ((< n) . unPrime) (ps >>= primeList)
 
 -- | @'psieveFrom' n@ creates the list of 'PrimeSieve's starting roughly
 --   at @n@. Due to the organisation of the sieve, the list may contain
diff --git a/Math/NumberTheory/Primes/Sieve/Indexing.hs b/Math/NumberTheory/Primes/Sieve/Indexing.hs
--- a/Math/NumberTheory/Primes/Sieve/Indexing.hs
+++ b/Math/NumberTheory/Primes/Sieve/Indexing.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Auxiliary stuff, conversion between number and index,
 -- remainders modulo 30 and related things.
diff --git a/Math/NumberTheory/Primes/Testing.hs b/Math/NumberTheory/Primes/Testing.hs
--- a/Math/NumberTheory/Primes/Testing.hs
+++ b/Math/NumberTheory/Primes/Testing.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Primality tests.
 
diff --git a/Math/NumberTheory/Primes/Testing/Certificates.hs b/Math/NumberTheory/Primes/Testing/Certificates.hs
--- a/Math/NumberTheory/Primes/Testing/Certificates.hs
+++ b/Math/NumberTheory/Primes/Testing/Certificates.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Certificates for primality or compositeness.
 module Math.NumberTheory.Primes.Testing.Certificates
diff --git a/Math/NumberTheory/Primes/Testing/Certificates/Internal.hs b/Math/NumberTheory/Primes/Testing/Certificates/Internal.hs
--- a/Math/NumberTheory/Primes/Testing/Certificates/Internal.hs
+++ b/Math/NumberTheory/Primes/Testing/Certificates/Internal.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Certificates for primality or compositeness.
 {-# LANGUAGE CPP #-}
@@ -40,6 +38,7 @@
 import Math.NumberTheory.Primes.Factorisation.Montgomery
 import Math.NumberTheory.Primes.Testing.Probabilistic
 import Math.NumberTheory.Primes.Sieve.Eratosthenes
+import Math.NumberTheory.Primes.Types (unPrime)
 import Math.NumberTheory.Powers.Squares
 
 -- | A certificate of either compositeness or primality of an
@@ -63,7 +62,7 @@
       deriving Show
 
 -- | An argument for compositeness of a number (which must be @> 1@).
---   'CompositenessProof's translate directly to 'CompositenessArguments',
+--   'CompositenessProof's translate directly to 'CompositenessArgument's,
 --   correct arguments can be transformed into proofs. This type allows the
 --   manipulation of proofs while maintaining their correctness.
 --   The only way to access components of a 'CompositenessProof' except
@@ -81,7 +80,7 @@
 data PrimalityProof
     = Pocklington { cprime :: !Integer          -- ^ The number whose primality is proved.
                   , factorisedPart, cofactor :: !Integer
-                  , knownFactors :: ![(Integer,Int,Integer,PrimalityProof)]
+                  , knownFactors :: ![(Integer, Word, Integer, PrimalityProof)]
                   }
     | TrialDivision { cprime :: !Integer        -- ^ The number whose primality is proved.
                     , tdLimit :: !Integer }
@@ -90,7 +89,7 @@
       deriving Show
 
 -- | An argument for primality of a number (which must be @> 1@).
---   'PrimalityProof's translate directly to 'PrimalityArguments',
+--   'PrimalityProof's translate directly to 'PrimalityArgument's,
 --   correct arguments can be transformed into proofs. This type allows the
 --   manipulation of proofs while maintaining their correctness.
 --   The only way to access components of a 'PrimalityProof' except
@@ -98,13 +97,14 @@
 data PrimalityArgument
     = Pock { aprime :: Integer
            , largeFactor, smallFactor :: Integer
-           , factorList :: [(Integer,Int,Integer,PrimalityArgument)]
+           , factorList :: [(Integer, Word, Integer, PrimalityArgument)]
            }                                 -- ^ A suggested Pocklington certificate
     | Division { aprime, alimit :: Integer } -- ^ Primality should be provable by trial division to @alimit@
     | Obvious { aprime :: Integer }          -- ^ @aprime@ is said to be obviously prime, that holds for primes @< 30@
     | Assumption { aprime :: Integer }       -- ^ Primality assumed
       deriving (Show, Read, Eq, Ord)
 
+-- | Eliminate 'Certificate'.
 argueCertificate :: Certificate -> Either CompositenessArgument PrimalityArgument
 argueCertificate (Composite proof) = Left (argueCompositeness proof)
 argueCertificate (Prime proof) = Right (arguePrimality proof)
@@ -294,12 +294,12 @@
 
 -- | Find a decomposition of p-1 for the pocklington certificate.
 --   Usually bloody slow if p-1 has two (or more) /large/ prime divisors.
-findDecomposition :: Integer -> (Integer, [(Integer,Int,Bool)], Integer)
+findDecomposition :: Integer -> (Integer, [(Integer, Word, Bool)], Integer)
 findDecomposition n = go 1 n [] prms
   where
     sr = integerSquareRoot' n
     pbd = min 1000000 (sr+20)
-    prms = primeList (primeSieve $ pbd)
+    prms = map unPrime $ primeList (primeSieve $ pbd)
     go a b afs (p:ps)
         | a > b     = (a,afs,b)
         | otherwise = case splitOff p b of
diff --git a/Math/NumberTheory/Primes/Testing/Certified.hs b/Math/NumberTheory/Primes/Testing/Certified.hs
--- a/Math/NumberTheory/Primes/Testing/Certified.hs
+++ b/Math/NumberTheory/Primes/Testing/Certified.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Deterministic primality testing.
 module Math.NumberTheory.Primes.Testing.Certified (isCertifiedPrime) where
diff --git a/Math/NumberTheory/Primes/Testing/Probabilistic.hs b/Math/NumberTheory/Primes/Testing/Probabilistic.hs
--- a/Math/NumberTheory/Primes/Testing/Probabilistic.hs
+++ b/Math/NumberTheory/Primes/Testing/Probabilistic.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer, 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Probabilistic primality tests, Miller-Rabin and Baillie-PSW.
 {-# LANGUAGE CPP, MagicHash, BangPatterns #-}
diff --git a/Math/NumberTheory/Primes/Types.hs b/Math/NumberTheory/Primes/Types.hs
--- a/Math/NumberTheory/Primes/Types.hs
+++ b/Math/NumberTheory/Primes/Types.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- This is an internal module, defining types for primes.
 -- Should not be exposed to users.
@@ -15,41 +13,62 @@
 {-# LANGUAGE DeriveGeneric #-}
 
 module Math.NumberTheory.Primes.Types
-  ( Prime
-  , Prm(..)
-  , PrimeNat(..)
+  ( Prime(..)
   ) where
 
-import Numeric.Natural
 import GHC.Generics
 import Control.DeepSeq
 
-newtype Prm = Prm { unPrm :: Word }
-  deriving (Eq, Ord, Generic)
-
-instance NFData Prm
-
-instance Show Prm where
-  showsPrec d (Prm p) r = (if d > 10 then "(" ++ s ++ ")" else s) ++ r
-    where
-      s = "Prm " ++ show p
-
-newtype PrimeNat = PrimeNat { unPrimeNat :: Natural }
+-- | Wrapper for prime elements of @a@. It is supposed to be constructed
+-- by 'Math.NumberTheory.Primes.nextPrime' / 'Math.NumberTheory.Primes.precPrime'.
+-- and eliminated by 'unPrime'.
+--
+-- One can leverage 'Enum' instance to generate lists of primes.
+-- Here are some examples.
+--
+-- *  Generate primes from the given interval:
+--
+--    >>> [nextPrime 101 .. precPrime 130]
+--    [Prime 101,Prime 103,Prime 107,Prime 109,Prime 113,Prime 127]
+--
+-- *  Generate an infinite list of primes:
+--
+--    >>> [nextPrime 101 ..]
+--    [Prime 101,Prime 103,Prime 107,Prime 109,Prime 113,Prime 127...
+--
+-- *  Generate primes from the given interval of form p = 6k+5:
+--
+--    >>> [nextPrime 101, nextPrime 107 .. precPrime 150]
+--    [Prime 101,Prime 107,Prime 113,Prime 131,Prime 137,Prime 149]
+--
+-- *  Get next prime:
+--
+--    >>> succ (nextPrime 101)
+--    Prime 103
+--
+-- *  Get previous prime:
+--
+--    >>> prec (nextPrime 101)
+--    Prime 97
+--
+-- *  Count primes less than a given number (cf. 'Math.NumberTheory.Primes.Counting.approxPrimeCount'):
+--
+--    >>> fromEnum (precPrime 100)
+--    25
+--
+-- *  Get 25-th prime number (cf. 'Math.NumberTheory.Primes.Counting.nthPrimeApprox'):
+--
+--    >>> toEnum 25 :: Prime Int
+--    Prime 97
+--
+newtype Prime a = Prime
+  { unPrime :: a -- ^ Unwrap prime element.
+  }
   deriving (Eq, Ord, Generic)
 
-instance NFData PrimeNat
+instance NFData a => NFData (Prime a)
 
-instance Show PrimeNat where
-  showsPrec d (PrimeNat p) r = (if d > 10 then "(" ++ s ++ ")" else s) ++ r
+instance Show a => Show (Prime a) where
+  showsPrec d (Prime p) r = (if d > 10 then "(" ++ s ++ ")" else s) ++ r
     where
-      s = "PrimeNat " ++ show p
-
--- | Type of primes of a given unique factorisation domain.
---
--- @abs (unPrime n) == unPrime n@ must hold for all @n@ of type @Prime t@
-type family Prime (f :: *) :: *
-
-type instance Prime Int     = Prm
-type instance Prime Word    = Prm
-type instance Prime Integer = PrimeNat
-type instance Prime Natural = PrimeNat
+      s = "Prime " ++ show p
diff --git a/Math/NumberTheory/Quadratic/EisensteinIntegers.hs b/Math/NumberTheory/Quadratic/EisensteinIntegers.hs
--- a/Math/NumberTheory/Quadratic/EisensteinIntegers.hs
+++ b/Math/NumberTheory/Quadratic/EisensteinIntegers.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- This module exports functions for manipulating Eisenstein integers, including
 -- computing their prime factorisations.
@@ -13,6 +11,7 @@
 {-# LANGUAGE BangPatterns   #-}
 {-# LANGUAGE DeriveGeneric  #-}
 {-# LANGUAGE RankNTypes     #-}
+{-# LANGUAGE TypeFamilies   #-}
 
 module Math.NumberTheory.Quadratic.EisensteinIntegers
   ( EisensteinInteger(..)
@@ -22,15 +21,13 @@
   , associates
   , ids
 
-  , divideByThree
-
   -- * Primality functions
-  , factorise
   , findPrime
-  , isPrime
   , primes
   ) where
 
+import Control.DeepSeq
+import Data.Coerce
 import Data.List                                       (mapAccumL, partition)
 import Data.Maybe                                      (fromMaybe)
 import Data.Ord                                        (comparing)
@@ -38,23 +35,25 @@
 
 import qualified Math.NumberTheory.Euclidean            as ED
 import Math.NumberTheory.Moduli.Sqrt
-import qualified Math.NumberTheory.Primes.Factorisation as Factorisation
-import Math.NumberTheory.Primes.Types                   (PrimeNat(..))
 import qualified Math.NumberTheory.Primes.Sieve         as Sieve
 import qualified Math.NumberTheory.Primes.Testing       as Testing
+import Math.NumberTheory.Primes.Types
+import qualified Math.NumberTheory.Primes  as U
 import Math.NumberTheory.Utils                          (mergeBy)
-import Math.NumberTheory.Utils.FromIntegral             (integerToNatural)
+import Math.NumberTheory.Utils.FromIntegral
 
 infix 6 :+
 
--- | An Eisenstein integer is a + bω, where a and b are both integers.
+-- | An Eisenstein integer is @a + bω@, where @a@ and @b@ are both integers.
 data EisensteinInteger = (:+) { real :: !Integer, imag :: !Integer }
     deriving (Eq, Ord, Generic)
 
+instance NFData EisensteinInteger
+
 -- | The imaginary unit for Eisenstein integers, where
 --
 -- > ω == (-1/2) + ((sqrt 3)/2)ι == exp(2*pi*ι/3)
--- and ι is the usual imaginary unit with ι² == -1.
+-- and @ι@ is the usual imaginary unit with @ι² == -1@.
 ω :: EisensteinInteger
 ω = 0 :+ 1
 
@@ -97,13 +96,6 @@
 associates :: EisensteinInteger -> [EisensteinInteger]
 associates e = map (e *) ids
 
--- | Takes an Eisenstein prime whose norm is of the form @3k + 1@ with @k@
--- a nonnegative integer, and return its primary associate.
--- * Does *not* check for this precondition.
--- * @head@ will fail when supplied a number unsatisfying it.
-primary :: EisensteinInteger -> EisensteinInteger
-primary = head . filter (\p -> p `ED.mod` 3 == 2) . associates
-
 instance ED.Euclidean EisensteinInteger where
   quotRem = divHelper quot
   divMod  = divHelper div
@@ -149,12 +141,12 @@
 
 -- | Remove @1 - ω@ factors from an @EisensteinInteger@, and calculate that
 -- prime's multiplicity in the number's factorisation.
-divideByThree :: EisensteinInteger -> (Int, EisensteinInteger)
+divideByThree :: EisensteinInteger -> (Word, EisensteinInteger)
 divideByThree = go 0
   where
-    go :: Int -> EisensteinInteger -> (Int, EisensteinInteger)
+    go :: Word -> EisensteinInteger -> (Word, EisensteinInteger)
     go !n z@(a :+ b) | r1 == 0 && r2 == 0 = go (n + 1) (q1 :+ q2)
-                      | otherwise          = (n, abs z)
+                     | otherwise          = (n, abs z)
       where
         -- @(a + a - b) :+ (a + b)@ is @z * (2 :+ 1)@, and @z * (2 :+ 1)/3@
         -- is the same as @z / (1 :+ (-1))@.
@@ -167,138 +159,146 @@
 --
 -- The maintainer <https://github.com/cartazio/arithmoi/pull/121#issuecomment-415010647 Andrew Lelechenko>
 -- derived the following:
--- * Each prime of form @3n+1@ is actually of form @6k+1@.
--- * One has @(z+3k)^2 ≡ z^2 + 6kz + 9k^2 ≡ z^2 + (6k+1)z - z + 9k^2 ≡ z^2 - z + 9k^2 (mod 6k+1)@.
 --
--- * The goal is to solve @z^2 - z + 1 ≡ 0 (mod 6k+1)@. One has:
--- @z^2 - z + 9k^2 ≡ 9k^2 - 1 (mod 6k+1)@
--- @(z+3k)^2 ≡ 9k^2-1 (mod 6k+1)@
--- @z+3k = sqrtMod(9k^2-1)@
--- @z = sqrtMod(9k^2-1) - 3k@
+--     * Each prime of the form @3n + 1@ is actually of the form @6k + 1@.
+--     * One has @(z + 3k)^2 ≡ z^2 + 6kz + 9k^2 ≡ z^2 + (6k + 1)z - z + 9k^2 ≡ z^2 - z + 9k^2 (mod 6k + 1)@.
 --
--- * For example, let @p = 7@, then @k = 1@. Square root of @9*1^2-1 modulo 7@ is @1@.
--- * And @z = 1 - 3*1 = -2 ≡ 5 (mod 7)@.
--- * Truly, @norm (5 :+ 1) = 25 - 5 + 1 = 21 ≡ 0 (mod 7)@.
-findPrime :: Integer -> EisensteinInteger
-findPrime p = case sqrtsModPrime (9*k*k - 1) (PrimeNat . integerToNatural $ p) of
+-- The goal is to solve @z^2 - z + 1 ≡ 0 (mod 6k + 1)@. One has:
+--
+--     1. @z^2 - z + 1 ≡ 0 (mod 6k + 1)@
+--     2. @z^2 - z ≡ -1 (mod 6k + 1)@
+--     3. @z^2 - z + 9k^2 ≡ 9k^2 - 1 (mod 6k + 1)@
+--     4. @(z + 3k)^2 ≡ 9k^2 - 1 (mod 6k + 1)@
+--     5. @z + 3k = sqrtsModPrime(9k^2 - 1) (mod 6k + 1)@
+--     6. @z = (sqrtsModPrime(9k^2 - 1) (mod 6k + 1)) - 3k@
+--
+-- For example, let @p = 7@, then @k = 1@.
+-- Square root of @9*1^2-1 ≡ 1 (mod 7)@, and @z = 1 - 3*1 = -2 ≡ 5 (mod 7)@.
+--
+-- Truly, @norm (5 :+ 1) = 25 - 5 + 1 = 21 ≡ 0 (mod 7)@.
+findPrime :: Prime Integer -> U.Prime EisensteinInteger
+findPrime p = case sqrtsModPrime (9*k*k - 1) p of
     []    -> error "findPrime: argument must be prime p = 6k + 1"
-    z : _ -> ED.gcd (p :+ 0) ((z - 3 * k) :+ 1)
+    z : _ -> Prime $ ED.gcd (unPrime p :+ 0) ((z - 3 * k) :+ 1)
     where
         k :: Integer
-        k = p `div` 6
+        k = unPrime p `div` 6
 
--- | An infinite list of Eisenstein primes. Uses primes in Z to exhaustively
--- generate all Eisenstein primes in order of ascending magnitude.
+-- | An infinite list of Eisenstein primes. Uses primes in @Z@ to exhaustively
+-- generate all Eisenstein primes in order of ascending norm.
+--
 -- * Every prime is in the first sextant, so the list contains no associates.
 -- * Eisenstein primes from the whole complex plane can be generated by
--- applying @associates@ to each prime in this list.
-primes :: [EisensteinInteger]
-primes = (2 :+ 1) : mergeBy (comparing norm) l r
-  where (leftPrimes, rightPrimes) = partition (\p -> p `mod` 3 == 2) Sieve.primes
-        rightPrimes' = filter (\prime -> prime `mod` 3 == 1) $ tail rightPrimes
-        l = [p :+ 0 | p <- leftPrimes]
-        r = [g | p <- rightPrimes', let x :+ y = findPrime p, g <- [x :+ y, x :+ (x - y)]]
+-- applying 'associates' to each prime in this list.
+primes :: [Prime EisensteinInteger]
+primes = coerce $ (2 :+ 1) : mergeBy (comparing norm) l r
+  where
+    leftPrimes, rightPrimes :: [Prime Integer]
+    (leftPrimes, rightPrimes) = partition (\p -> unPrime p `mod` 3 == 2) Sieve.primes
+    rightPrimes' = filter (\prime -> unPrime prime `mod` 3 == 1) $ tail rightPrimes
+    l = [unPrime p :+ 0 | p <- leftPrimes]
+    r = [g | p <- rightPrimes', let x :+ y = unPrime (findPrime p), g <- [x :+ y, x :+ (x - y)]]
 
--- | Compute the prime factorisation of a Eisenstein integer. This is unique
--- up to units (+/- 1, +/- ω, +/- ω²).
--- * Unit factors are not included in the result.
--- * All prime factors are primary i.e. @e ≡ 2 (modE 3)@, for an Eisenstein
--- prime factor @e@.
+-- | [Implementation notes for factorise function]
 --
--- * This function works by factorising the norm of an Eisenstein integer
--- and then, for each prime factor, finding the Eisenstein prime whose norm
--- is said prime factor with @findPrime@.
+-- Compute the prime factorisation of a Eisenstein integer.
 --
--- * This is only possible because the norm function of the Euclidean Domain of
--- Eisenstein integers is multiplicative: @norm (e1 * e2) == norm e1 * norm e2@
--- for any two @EisensteinInteger@s @e1, e2@.
+--     1. This function works by factorising the norm of an Eisenstein integer
+--        and then, for each prime factor, finding the Eisenstein prime whose norm
+--        is said prime factor with @findPrime@.
+--     2. This is only possible because the norm function of the Euclidean Domain of
+--        Eisenstein integers is multiplicative: @norm (e1 * e2) == norm e1 * norm e2@
+--        for any two @EisensteinInteger@s @e1, e2@.
+--     3. In the previously mentioned work <http://thekeep.eiu.edu/theses/2467 Bandara, Sarada, "An Exposition of the Eisenstein Integers" (2016)>,
+--        in Theorem 8.4 in Chapter 8, a way is given to express any Eisenstein
+--        integer @μ@ as @(-1)^a * ω^b * (1 - ω)^c * product [π_i^a_i | i <- [1..N]]@
+--        where @a, b, c, a_i@ are nonnegative integers, @N > 1@ is an integer and
+--        @π_i@ are Eisenstein primes.
 --
--- * In the previously mentioned work <http://thekeep.eiu.edu/theses/2467 Bandara, Sarada, "An Exposition of the Eisenstein Integers" (2016)>,
--- in Theorem 8.4 in Chapter 8, a way is given to express any Eisenstein
--- integer @μ@ as @(-1)^a * ω^b * (1 - ω)^c * product [π_i^a_i | i <- [1..N]]@
--- where @a, b, c, a_i@ are nonnegative integers, @N > 1@ is an integer and
--- @π_i@ are primary primes (for a primary Eisenstein prime @p@,
--- @p ≡ 2 (modE 3)@, see @primary@ above).
+-- Aplying @norm@ to both sides of the equation from Theorem 8.4:
 --
--- * Aplying @norm@ to both sides of Theorem 8.4:
---    @norm μ = norm ((-1)^a * ω^b * (1 - ω)^c * product [ π_i^a_i | i <- [1..N]])@
--- == @norm μ = norm ((-1)^a) * norm (ω^b) * norm ((1 - ω)^c) * norm (product [ π_i^a_i | i <- [1..N]])@
--- == @norm μ = (norm (-1))^a * (norm ω)^b * (norm (1 - ω))^c * product [ norm (π_i^a_i) | i <- [1..N]]@
--- == @norm μ = (norm (-1))^a * (norm ω)^b * (norm (1 - ω))^c * product [ (norm π_i)^a_i) | i <- [1..N]]@
--- == @norm μ = 1^a * 1^b * 3^c * product [ (norm π_i)^a_i) | i <- [1..N]]@
--- == @norm μ = 3^c * product [ (norm π_i)^a_i) | i <- [1..N]]@
+-- 1. @norm μ = norm ( (-1)^a * ω^b * (1 - ω)^c * product [ π_i^a_i | i <- [1..N]] ) ==@
+-- 2. @norm μ = norm ((-1)^a) * norm (ω^b) * norm ((1 - ω)^c) * norm (product [ π_i^a_i | i <- [1..N]]) ==@
+-- 3. @norm μ = (norm (-1))^a * (norm ω)^b * (norm (1 - ω))^c * product [ norm (π_i^a_i) | i <- [1..N]] ==@
+-- 4. @norm μ = (norm (-1))^a * (norm ω)^b * (norm (1 - ω))^c * product [ (norm π_i)^a_i) | i <- [1..N]] ==@
+-- 5. @norm μ = 1^a * 1^b * 3^c * product [ (norm π_i)^a_i) | i <- [1..N]] ==@
+-- 6. @norm μ = 3^c * product [ (norm π_i)^a_i) | i <- [1..N]] ==@
+--
 -- where @a, b, c, a_i@ are nonnegative integers, and @N > 1@ is an integer.
 --
--- * The remainder of the Eisenstein integer factorisation problem is about
--- finding appropriate @[e_i | i <- [1..M]@ such that
--- @(nub . map norm) [e_i | i <- [1..N]] == [π_i | i <- [1..N]]@
--- where @ 1 < N <= M@ are integers, @nub@ removes duplicates and @==@
--- is equality on sets.
+-- The remainder of the Eisenstein integer factorisation problem is about
+-- finding appropriate Eisenstein primes @[e_i | i <- [1..M]]@ such that
+-- @map norm [e_i | i <- [1..M]] == map norm [π_i | i <- [1..N]]@
+-- where @ 1 < N <= M@ are integers and @==@ is equality on sets
+-- (i.e.duplicates do not matter).
 --
--- * The reason @M >= N@ is because the prime factors of an Eisenstein integer
--- may include a prime factor and its conjugate, meaning the number may have
--- more Eisenstein prime factors than its norm has integer prime factors.
-factorise :: EisensteinInteger -> [(EisensteinInteger, Int)]
+-- NB: The reason @M >= N@ is because the prime factors of an Eisenstein integer
+-- may include a prime factor and its conjugate (both have the same norm),
+-- meaning the number may have more Eisenstein prime factors than its norm has
+-- integer prime factors.
+factorise :: EisensteinInteger -> [(Prime EisensteinInteger, Word)]
 factorise g = concat $
               snd $
-              mapAccumL go (abs g) (Factorisation.factorise $ norm g)
+              mapAccumL go (abs g) (U.factorise $ norm g)
   where
-    go :: EisensteinInteger -> (Integer, Int) -> (EisensteinInteger, [(EisensteinInteger, Int)])
-    go z (3, e) | e == n    = (q, [(2 :+ 1, e)])
-                | otherwise = error $ "3 is a prime factor of the norm of z\
-                                      \ == " ++ show z ++ " with multiplicity\
-                                      \ " ++ show e ++ " but (1 - ω) only\
-                                      \ divides z " ++ show n ++ "times."
+    go :: EisensteinInteger -> (Prime Integer, Word) -> (EisensteinInteger, [(Prime EisensteinInteger, Word)])
+    go z (Prime 3, e)
+      | e == n    = (q, [(Prime (2 :+ 1), e)])
+      | otherwise = error $ "3 is a prime factor of the norm of z = " ++ show z
+                          ++ " with multiplicity " ++ show e
+                          ++ " but (1 - ω) only divides z " ++ show n ++ "times."
       where
         -- Remove all @1 :+ (-1)@ (which is associated to @2 :+ 1@) factors
         -- from the argument.
         (n, q) = divideByThree z
-    go z (p, e) | p `mod` 3 == 2 =
-                    let e' = e `quot` 2 in (z `quotI` (p ^ e'), [(p :+ 0, e')])
+    go z (p, e)
+      | unPrime p `mod` 3 == 2
+      = let e' = e `quot` 2 in (z `quotI` (unPrime p ^ e'), [(Prime (unPrime p :+ 0), e')])
 
-                -- The @`mod` 3 == 0@ case need not be verified because the
-                -- only Eisenstein primes whose norm are a multiple of 3
-                -- are @1 - ω@ and its associates, which have already been
-                -- removed by the above @go z (3, e)@ pattern match.
-                -- This @otherwise@ is mandatorily @`mod` 3 == 1@.
-                | otherwise   = (z', filter ((> 0) . snd) [(gp, k), (gp', k')])
+      -- The @`mod` 3 == 0@ case need not be verified because the
+      -- only Eisenstein primes whose norm are a multiple of 3
+      -- are @1 - ω@ and its associates, which have already been
+      -- removed by the above @go z (3, e)@ pattern match.
+      -- This @otherwise@ is mandatorily @`mod` 3 == 1@.
+      | otherwise   = (z', filter ((> 0) . snd) [(gp, k), (gp', k')])
       where
-        gp@(x :+ y)      = primary $ findPrime p
+        gp = findPrime p
+        x :+ y = unPrime gp
         -- @gp'@ is @gp@'s conjugate.
-        gp'              = primary $ abs $ x :+ (x - y)
-        (k, k', z') = divideByPrime gp gp' p e z
+        gp' = Prime (x :+ (x - y))
+        (k, k', z') = divideByPrime gp gp' (unPrime p) e z
 
         quotI (a :+ b) n = (a `quot` n :+ b `quot` n)
 
 -- | Remove @p@ and @conjugate p@ factors from the argument, where
 -- @p@ is an Eisenstein prime.
 divideByPrime
-    :: EisensteinInteger   -- ^ Eisenstein prime @p@
-    -> EisensteinInteger   -- ^ Conjugate of @p@
-    -> Integer             -- ^ Precomputed norm of @p@, of form @4k + 1@
-    -> Int                 -- ^ Expected number of factors (either @p@ or @conjugate p@)
-                           --   in Eisenstein integer @z@
-    -> EisensteinInteger   -- ^ Eisenstein integer @z@
-    -> ( Int               -- Multiplicity of factor @p@ in @z@
-       , Int               -- Multiplicity of factor @conjigate p@ in @z@
-       , EisensteinInteger -- Remaining Eisenstein integer
+    :: Prime EisensteinInteger -- ^ Eisenstein prime @p@
+    -> Prime EisensteinInteger -- ^ Conjugate of @p@
+    -> Integer                 -- ^ Precomputed norm of @p@, of form @4k + 1@
+    -> Word                    -- ^ Expected number of factors (either @p@ or @conjugate p@)
+                               --   in Eisenstein integer @z@
+    -> EisensteinInteger       -- ^ Eisenstein integer @z@
+    -> ( Word                  -- Multiplicity of factor @p@ in @z@
+       , Word                  -- Multiplicity of factor @conjigate p@ in @z@
+       , EisensteinInteger     -- Remaining Eisenstein integer
        )
 divideByPrime p p' np k = go k 0
     where
-        go :: Int -> Int -> EisensteinInteger -> (Int, Int, EisensteinInteger)
+        go :: Word -> Word -> EisensteinInteger -> (Word, Word, EisensteinInteger)
         go 0 d z = (d, d, z)
         go c d z | c >= 2, Just z' <- z `quotEvenI` np = go (c - 2) (d + 1) z'
         go c d z = (d + d1, d + d2, z'')
             where
                 (d1, z') = go1 c 0 z
                 d2 = c - d1
-                z'' = head $ drop d2
-                    $ iterate (\g -> fromMaybe err $ (g * p) `quotEvenI` np) z'
+                z'' = head $ drop (wordToInt d2)
+                    $ iterate (\g -> fromMaybe err $ (g * unPrime p) `quotEvenI` np) z'
 
-        go1 :: Int -> Int -> EisensteinInteger -> (Int, EisensteinInteger)
+        go1 :: Word -> Word -> EisensteinInteger -> (Word, EisensteinInteger)
         go1 0 d z = (d, z)
         go1 c d z
-            | Just z' <- (z * p') `quotEvenI` np
+            | Just z' <- (z * unPrime p') `quotEvenI` np
             = go1 (c - 1) (d + 1) z'
             | otherwise
             = (d, z)
@@ -313,3 +313,14 @@
   where
     (xq, xr) = x `quotRem` n
     (yq, yr) = y `quotRem` n
+
+-------------------------------------------------------------------------------
+
+-- | See the source code and Haddock comments for the @factorise@ and @isPrime@
+-- functions in this module (they are not exported) for implementation
+-- details.
+instance U.UniqueFactorisation EisensteinInteger where
+  factorise 0 = []
+  factorise e = coerce $ factorise e
+
+  isPrime e = if isPrime e then Just (Prime e) else Nothing
diff --git a/Math/NumberTheory/Quadratic/GaussianIntegers.hs b/Math/NumberTheory/Quadratic/GaussianIntegers.hs
--- a/Math/NumberTheory/Quadratic/GaussianIntegers.hs
+++ b/Math/NumberTheory/Quadratic/GaussianIntegers.hs
@@ -3,32 +3,26 @@
 -- Copyright:   (c) 2016 Chris Fredrickson, Google Inc.
 -- Licence:     MIT
 -- Maintainer:  Chris Fredrickson <chris.p.fredrickson@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- This module exports functions for manipulating Gaussian integers, including
 -- computing their prime factorisations.
 --
 
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BangPatterns  #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE TypeFamilies  #-}
 
 module Math.NumberTheory.Quadratic.GaussianIntegers (
     GaussianInteger(..),
     ι,
     conjugate,
     norm,
-    (.^),
-    isPrime,
     primes,
-    gcdG,
-    gcdG',
     findPrime,
-    findPrime',
-    factorise,
 ) where
 
 import Control.DeepSeq (NFData)
+import Data.Coerce
 import Data.List (mapAccumL, partition)
 import Data.Maybe (fromMaybe)
 import Data.Ord (comparing)
@@ -38,15 +32,14 @@
 import qualified Math.NumberTheory.Euclidean as ED
 import Math.NumberTheory.Moduli.Sqrt
 import Math.NumberTheory.Powers (integerSquareRoot)
-import Math.NumberTheory.Primes.Types (PrimeNat(..))
-import qualified Math.NumberTheory.Primes.Factorisation as Factorisation
+import Math.NumberTheory.Primes.Types
 import qualified Math.NumberTheory.Primes.Sieve as Sieve
 import qualified Math.NumberTheory.Primes.Testing as Testing
+import qualified Math.NumberTheory.Primes  as U
 import Math.NumberTheory.Utils              (mergeBy)
-import Math.NumberTheory.Utils.FromIntegral (integerToNatural)
+import Math.NumberTheory.Utils.FromIntegral
 
 infix 6 :+
-infixr 8 .^
 -- |A Gaussian integer is a+bi, where a and b are both integers.
 data GaussianInteger = (:+) { real :: !Integer, imag :: !Integer }
     deriving (Eq, Ord, Generic)
@@ -119,77 +112,47 @@
 -- |An infinite list of the Gaussian primes. Uses primes in Z to exhaustively
 -- generate all Gaussian primes (up to associates), in order of ascending
 -- magnitude.
-primes :: [GaussianInteger]
-primes = (1 :+ 1): mergeBy (comparing norm) l r
-  where (leftPrimes, rightPrimes) = partition (\p -> p `mod` 4 == 3) (tail Sieve.primes)
-        l = [p :+ 0 | p <- leftPrimes]
-        r = [g | p <- rightPrimes, let x :+ y = findPrime p, g <- [x :+ y, y :+ x]]
+primes :: [U.Prime GaussianInteger]
+primes = coerce $ (1 :+ 1) : mergeBy (comparing norm) l r
+  where
+    leftPrimes, rightPrimes :: [Prime Integer]
+    (leftPrimes, rightPrimes) = partition (\p -> unPrime p `mod` 4 == 3) (tail Sieve.primes)
+    l = [unPrime p :+ 0 | p <- leftPrimes]
+    r = [g | p <- rightPrimes, let Prime (x :+ y) = findPrime p, g <- [x :+ y, y :+ x]]
 
 
--- | Compute the GCD of two Gaussian integers. Result is always
--- in the first quadrant.
-gcdG :: GaussianInteger -> GaussianInteger -> GaussianInteger
-gcdG = ED.gcd
-{-# DEPRECATED gcdG "Use 'Math.NumberTheory.Euclidean.gcd' instead." #-}
-
-gcdG' :: GaussianInteger -> GaussianInteger -> GaussianInteger
-gcdG' = ED.gcd
-{-# DEPRECATED gcdG' "Use 'Math.NumberTheory.Euclidean.gcd' instead." #-}
-
 -- |Find a Gaussian integer whose norm is the given prime number
 -- of form 4k + 1 using
 -- <http://www.ams.org/journals/mcom/1972-26-120/S0025-5718-1972-0314745-6/S0025-5718-1972-0314745-6.pdf Hermite-Serret algorithm>.
-findPrime :: Integer -> GaussianInteger
-findPrime p = case sqrtsModPrime (-1) (PrimeNat . integerToNatural $ p) of
+findPrime :: Prime Integer -> U.Prime GaussianInteger
+findPrime p = case sqrtsModPrime (-1) p of
     []    -> error "findPrime: an argument must be prime p = 4k + 1"
-    z : _ -> go p z -- Effectively we calculate gcdG' (p :+ 0) (z :+ 1)
+    z : _ -> Prime $ go (unPrime p) z -- Effectively we calculate gcdG' (p :+ 0) (z :+ 1)
     where
         sqrtp :: Integer
-        sqrtp = integerSquareRoot p
+        sqrtp = integerSquareRoot (unPrime p)
 
         go :: Integer -> Integer -> GaussianInteger
         go g h
             | g <= sqrtp = g :+ h
             | otherwise  = go h (g `mod` h)
 
-findPrime' :: Integer -> GaussianInteger
-findPrime' = findPrime
-{-# DEPRECATED findPrime' "Use 'findPrime' instead." #-}
-
--- |Raise a Gaussian integer to a given power.
-(.^) :: (Integral a) => GaussianInteger -> a -> GaussianInteger
-a .^ e
-    | e < 0 && norm a == 1 =
-        case a of
-            1    :+ 0 -> 1
-            (-1) :+ 0 -> if even e then 1 else (-1)
-            0    :+ 1 -> (0 :+ (-1)) .^ (abs e `mod` 4)
-            _         -> (0 :+ 1) .^ (abs e `mod` 4)
-    | e < 0     = error "Cannot exponentiate non-unit Gaussian Int to negative power"
-    | a == 0    = 0
-    | e == 0    = 1
-    | even e    = s * s
-    | otherwise = a * a .^ (e - 1)
-    where
-    s = a .^ div e 2
-{-# DEPRECATED (.^) "Use (^) instead." #-}
-
--- |Compute the prime factorisation of a Gaussian integer. This is unique up to units (+/- 1, +/- i).
+-- | Compute the prime factorisation of a Gaussian integer. This is unique up to units (+/- 1, +/- i).
 -- Unit factors are not included in the result.
-factorise :: GaussianInteger -> [(GaussianInteger, Int)]
-factorise g = concat $ snd $ mapAccumL go g (Factorisation.factorise $ norm g)
+factorise :: GaussianInteger -> [(Prime GaussianInteger, Word)]
+factorise g = concat $ snd $ mapAccumL go g (U.factorise $ norm g)
     where
-        go :: GaussianInteger -> (Integer, Int) -> (GaussianInteger, [(GaussianInteger, Int)])
-        go z (2, e) = (divideByTwo z, [(1 :+ 1, e)])
+        go :: GaussianInteger -> (Prime Integer, Word) -> (GaussianInteger, [(Prime GaussianInteger, Word)])
+        go z (Prime 2, e) = (divideByTwo z, [(Prime (1 :+ 1), e)])
         go z (p, e)
-            | p `mod` 4 == 3
-            = let e' = e `quot` 2 in (z `quotI` (p ^ e'), [(p :+ 0, e')])
+            | unPrime p `mod` 4 == 3
+            = let e' = e `quot` 2 in (z `quotI` (unPrime p ^ e'), [(Prime (unPrime p :+ 0), e')])
             | otherwise
             = (z', filter ((> 0) . snd) [(gp, k), (gp', k')])
                 where
                     gp = findPrime p
-                    (k, k', z') = divideByPrime gp p e z
-                    gp' = abs (conjugate gp)
+                    (k, k', z') = divideByPrime gp (unPrime p) e z
+                    gp' = Prime (abs (conjugate (unPrime gp)))
 
 -- | Remove all (1:+1) factors from the argument,
 -- avoiding complex division.
@@ -205,18 +168,18 @@
 -- | Remove p and conj p factors from the argument,
 -- avoiding complex division.
 divideByPrime
-    :: GaussianInteger   -- ^ Gaussian prime p
-    -> Integer           -- ^ Precomputed norm of p, of form 4k + 1
-    -> Int               -- ^ Expected number of factors (either p or conj p)
-                         --   in Gaussian integer z
-    -> GaussianInteger   -- ^ Gaussian integer z
-    -> ( Int             -- Multiplicity of factor p in z
-       , Int             -- Multiplicity of factor conj p in z
-       , GaussianInteger -- Remaining Gaussian integer
+    :: Prime GaussianInteger -- ^ Gaussian prime p
+    -> Integer               -- ^ Precomputed norm of p, of form 4k + 1
+    -> Word                  -- ^ Expected number of factors (either p or conj p)
+                             --   in Gaussian integer z
+    -> GaussianInteger       -- ^ Gaussian integer z
+    -> ( Word                -- Multiplicity of factor p in z
+       , Word                -- Multiplicity of factor conj p in z
+       , GaussianInteger     -- Remaining Gaussian integer
        )
 divideByPrime p np k = go k 0
     where
-        go :: Int -> Int -> GaussianInteger -> (Int, Int, GaussianInteger)
+        go :: Word -> Word -> GaussianInteger -> (Word, Word, GaussianInteger)
         go 0 d z = (d, d, z)
         go c d z
             | c >= 2
@@ -226,13 +189,13 @@
             where
                 (d1, z') = go1 c 0 z
                 d2 = c - d1
-                z'' = head $ drop d2
-                    $ iterate (\g -> fromMaybe err $ (g * p) `quotEvenI` np) z'
+                z'' = head $ drop (wordToInt d2)
+                    $ iterate (\g -> fromMaybe err $ (g * unPrime p) `quotEvenI` np) z'
 
-        go1 :: Int -> Int -> GaussianInteger -> (Int, GaussianInteger)
+        go1 :: Word -> Word -> GaussianInteger -> (Word, GaussianInteger)
         go1 0 d z = (d, z)
         go1 c d z
-            | Just z' <- (z * conjugate p) `quotEvenI` np
+            | Just z' <- (z * conjugate (unPrime p)) `quotEvenI` np
             = go1 (c - 1) (d + 1) z'
             | otherwise
             = (d, z)
@@ -252,3 +215,11 @@
     where
         (xq, xr) = x `quotRem` n
         (yq, yr) = y `quotRem` n
+
+-------------------------------------------------------------------------------
+
+instance U.UniqueFactorisation GaussianInteger where
+  factorise 0 = []
+  factorise g = coerce $ factorise g
+
+  isPrime g = if isPrime g then Just (Prime g) else Nothing
diff --git a/Math/NumberTheory/Recurrences.hs b/Math/NumberTheory/Recurrences.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Recurrences.hs
@@ -0,0 +1,16 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences
+-- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
+-- Licence:     MIT
+-- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
+--
+
+module Math.NumberTheory.Recurrences
+    ( module Math.NumberTheory.Recurrences.Linear
+    , module Math.NumberTheory.Recurrences.Bilinear
+    , module Math.NumberTheory.Recurrences.Pentagonal
+    ) where
+
+import Math.NumberTheory.Recurrences.Bilinear
+import Math.NumberTheory.Recurrences.Linear
+import Math.NumberTheory.Recurrences.Pentagonal (partition)
diff --git a/Math/NumberTheory/Recurrences/Bilinear.hs b/Math/NumberTheory/Recurrences/Bilinear.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Recurrences/Bilinear.hs
@@ -0,0 +1,270 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.Bilinear
+-- Copyright:   (c) 2016 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- Bilinear recurrent sequences and Bernoulli numbers,
+-- roughly covering Ch. 5-6 of /Concrete Mathematics/
+-- by R. L. Graham, D. E. Knuth and O. Patashnik.
+--
+-- #memory# __Note on memory leaks and memoization.__
+-- Top-level definitions in this module are polymorphic, so the results of computations are not retained in memory.
+-- Make them monomorphic to take advantages of memoization. Compare
+--
+-- >>> :set +s
+-- >>> binomial !! 1000 !! 1000 :: Integer
+-- 1
+-- (0.01 secs, 1,385,512 bytes)
+-- >>> binomial !! 1000 !! 1000 :: Integer
+-- 1
+-- (0.01 secs, 1,381,616 bytes)
+--
+-- against
+--
+-- >>> let binomial' = binomial :: [[Integer]]
+-- >>> binomial' !! 1000 !! 1000 :: Integer
+-- 1
+-- (0.01 secs, 1,381,696 bytes)
+-- >>> binomial' !! 1000 !! 1000 :: Integer
+-- 1
+-- (0.01 secs, 391,152 bytes)
+
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Math.NumberTheory.Recurrences.Bilinear
+  ( binomial
+  , stirling1
+  , stirling2
+  , lah
+  , eulerian1
+  , eulerian2
+  , bernoulli
+  , euler
+  , eulerPolyAt1
+  , faulhaberPoly
+  ) where
+
+import Data.List
+import Data.Ratio
+import Numeric.Natural
+
+import Math.NumberTheory.Recurrences.Linear (factorial)
+
+-- | Infinite zero-based table of binomial coefficients (also known as Pascal triangle):
+-- @binomial !! n !! k == n! \/ k! \/ (n - k)!@.
+--
+-- >>> take 5 (map (take 5) binomial)
+-- [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
+--
+-- Complexity: @binomial !! n !! k@ is O(n) bits long, its computation
+-- takes O(k n) time and forces thunks @binomial !! n !! i@ for @0 <= i <= k@.
+-- Use the symmetry of Pascal triangle @binomial !! n !! k == binomial !! n !! (n - k)@ to speed up computations.
+--
+-- One could also consider 'Math.Combinat.Numbers.binomial' to compute stand-alone values.
+binomial :: Integral a => [[a]]
+binomial = map f [0..]
+  where
+    f n = scanl (\x k -> x * (n - k + 1) `div` k) 1 [1..n]
+{-# SPECIALIZE binomial :: [[Int]]     #-}
+{-# SPECIALIZE binomial :: [[Word]]    #-}
+{-# SPECIALIZE binomial :: [[Integer]] #-}
+{-# SPECIALIZE binomial :: [[Natural]] #-}
+
+-- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind Stirling numbers of the first kind>.
+--
+-- >>> take 5 (map (take 5) stirling1)
+-- [[1],[0,1],[0,1,1],[0,2,3,1],[0,6,11,6,1]]
+--
+-- Complexity: @stirling1 !! n !! k@ is O(n ln n) bits long, its computation
+-- takes O(k n^2 ln n) time and forces thunks @stirling1 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
+--
+-- One could also consider 'Math.Combinat.Numbers.unsignedStirling1st' to compute stand-alone values.
+stirling1 :: (Num a, Enum a) => [[a]]
+stirling1 = scanl f [1] [0..]
+  where
+    f xs n = 0 : zipIndexedListWithTail (\_ x y -> x + n * y) 1 xs 0
+{-# SPECIALIZE stirling1 :: [[Int]]     #-}
+{-# SPECIALIZE stirling1 :: [[Word]]    #-}
+{-# SPECIALIZE stirling1 :: [[Integer]] #-}
+{-# SPECIALIZE stirling1 :: [[Natural]] #-}
+
+-- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind Stirling numbers of the second kind>.
+--
+-- >>> take 5 (map (take 5) stirling2)
+-- [[1],[0,1],[0,1,1],[0,1,3,1],[0,1,7,6,1]]
+--
+-- Complexity: @stirling2 !! n !! k@ is O(n ln n) bits long, its computation
+-- takes O(k n^2 ln n) time and forces thunks @stirling2 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
+--
+-- One could also consider 'Math.Combinat.Numbers.stirling2nd' to compute stand-alone values.
+stirling2 :: (Num a, Enum a) => [[a]]
+stirling2 = iterate f [1]
+  where
+    f xs = 0 : zipIndexedListWithTail (\k x y -> x + k * y) 1 xs 0
+{-# SPECIALIZE stirling2 :: [[Int]]     #-}
+{-# SPECIALIZE stirling2 :: [[Word]]    #-}
+{-# SPECIALIZE stirling2 :: [[Integer]] #-}
+{-# SPECIALIZE stirling2 :: [[Natural]] #-}
+
+-- | Infinite one-based table of <https://en.wikipedia.org/wiki/Lah_number Lah numbers>.
+-- @lah !! n !! k@ equals to lah(n + 1, k + 1).
+--
+-- >>> take 5 (map (take 5) lah)
+-- [[1],[2,1],[6,6,1],[24,36,12,1],[120,240,120,20,1]]
+--
+-- Complexity: @lah !! n !! k@ is O(n ln n) bits long, its computation
+-- takes O(k n ln n) time and forces thunks @lah !! n !! i@ for @0 <= i <= k@.
+lah :: Integral a => [[a]]
+-- Implementation was derived from code by https://github.com/grandpascorpion
+lah = zipWith f (tail factorial) [1..]
+  where
+    f nf n = scanl (\x k -> x * (n - k) `div` (k * (k + 1))) nf [1..n-1]
+{-# SPECIALIZE lah :: [[Int]]     #-}
+{-# SPECIALIZE lah :: [[Word]]    #-}
+{-# SPECIALIZE lah :: [[Integer]] #-}
+{-# SPECIALIZE lah :: [[Natural]] #-}
+
+-- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Eulerian_number Eulerian numbers of the first kind>.
+--
+-- >>> take 5 (map (take 5) eulerian1)
+-- [[],[1],[1,1],[1,4,1],[1,11,11,1]]
+--
+-- Complexity: @eulerian1 !! n !! k@ is O(n ln n) bits long, its computation
+-- takes O(k n^2 ln n) time and forces thunks @eulerian1 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
+--
+eulerian1 :: (Num a, Enum a) => [[a]]
+eulerian1 = scanl f [] [1..]
+  where
+    f xs n = 1 : zipIndexedListWithTail (\k x y -> (n - k) * x + (k + 1) * y) 1 xs 0
+{-# SPECIALIZE eulerian1 :: [[Int]]     #-}
+{-# SPECIALIZE eulerian1 :: [[Word]]    #-}
+{-# SPECIALIZE eulerian1 :: [[Integer]] #-}
+{-# SPECIALIZE eulerian1 :: [[Natural]] #-}
+
+-- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Eulerian_number#Eulerian_numbers_of_the_second_kind Eulerian numbers of the second kind>.
+--
+-- >>> take 5 (map (take 5) eulerian2)
+-- [[],[1],[1,2],[1,8,6],[1,22,58,24]]
+--
+-- Complexity: @eulerian2 !! n !! k@ is O(n ln n) bits long, its computation
+-- takes O(k n^2 ln n) time and forces thunks @eulerian2 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
+--
+eulerian2 :: (Num a, Enum a) => [[a]]
+eulerian2 = scanl f [] [1..]
+  where
+    f xs n = 1 : zipIndexedListWithTail (\k x y -> (2 * n - k - 1) * x + (k + 1) * y) 1 xs 0
+{-# SPECIALIZE eulerian2 :: [[Int]]     #-}
+{-# SPECIALIZE eulerian2 :: [[Word]]    #-}
+{-# SPECIALIZE eulerian2 :: [[Integer]] #-}
+{-# SPECIALIZE eulerian2 :: [[Natural]] #-}
+
+-- | Infinite zero-based sequence of <https://en.wikipedia.org/wiki/Bernoulli_number Bernoulli numbers>,
+-- computed via <https://en.wikipedia.org/wiki/Bernoulli_number#Connection_with_Stirling_numbers_of_the_second_kind connection>
+-- with 'stirling2'.
+--
+-- >>> take 5 bernoulli
+-- [1 % 1,(-1) % 2,1 % 6,0 % 1,(-1) % 30]
+--
+-- Complexity: @bernoulli !! n@ is O(n ln n) bits long, its computation
+-- takes O(n^3 ln n) time and forces thunks @stirling2 !! i !! j@ for @0 <= i <= n@ and @0 <= j <= i@.
+--
+-- One could also consider 'Math.Combinat.Numbers.bernoulli' to compute stand-alone values.
+bernoulli :: Integral a => [Ratio a]
+bernoulli = helperForB_E_EP id (map recip [1..])
+{-# SPECIALIZE bernoulli :: [Ratio Int] #-}
+{-# SPECIALIZE bernoulli :: [Rational] #-}
+
+-- | <https://en.wikipedia.org/wiki/Faulhaber%27s_formula Faulhaber's formula>.
+--
+-- >>> sum (map (^ 10) [0..100])
+-- 959924142434241924250
+-- >>> sum $ zipWith (*) (faulhaberPoly 10) (iterate (* 100) 1)
+-- 959924142434241924250 % 1
+faulhaberPoly :: Integral a => Int -> [Ratio a]
+-- Implementation by https://github.com/CarlEdman
+faulhaberPoly p
+  = zipWith (*) ((0:)
+  $ reverse
+  $ take (p+1) $ bernoulli)
+  $ map (% (fromIntegral p+1))
+  $ zipWith (*) (iterate negate (if odd p then 1 else -1))
+  $ binomial !! (fromIntegral p+1)
+
+-- | Infinite zero-based list of <https://en.wikipedia.org/wiki/Euler_number Euler numbers>.
+-- The algorithm used was derived from <http://www.emis.ams.org/journals/JIS/VOL4/CHEN/AlgBE2.pdf Algorithms for Bernoulli numbers and Euler numbers>
+-- by Kwang-Wu Chen, second formula of the Corollary in page 7.
+-- Sequence <https://oeis.org/A122045 A122045> in OEIS.
+--
+-- >>> take 10 euler' :: [Rational]
+-- [1 % 1,0 % 1,(-1) % 1,0 % 1,5 % 1,0 % 1,(-61) % 1,0 % 1,1385 % 1,0 % 1]
+euler' :: forall a . Integral a => [Ratio a]
+euler' = tail $ helperForB_E_EP tail as
+  where
+    as :: [Ratio a]
+    as = zipWith3
+        (\sgn frac ones -> (sgn * ones) % frac)
+        (cycle [1, 1, 1, 1, -1, -1, -1, -1])
+        (dups (iterate (2 *) 1))
+        (cycle [1, 1, 1, 0])
+
+    dups :: forall x . [x] -> [x]
+    dups = foldr (\n list -> n : n : list) []
+{-# SPECIALIZE euler' :: [Ratio Int]     #-}
+{-# SPECIALIZE euler' :: [Rational]      #-}
+
+-- | The same sequence as @euler'@, but with type @[a]@ instead of @[Ratio a]@
+-- as the denominators in @euler'@ are always @1@.
+--
+-- >>> take 10 euler :: [Integer]
+-- [1,0,-1,0,5,0,-61,0,1385,0]
+euler :: forall a . Integral a => [a]
+euler = map numerator euler'
+
+-- | Infinite zero-based list of the @n@-th order Euler polynomials evaluated at @1@.
+-- The algorithm used was derived from <http://www.emis.ams.org/journals/JIS/VOL4/CHEN/AlgBE2.pdf Algorithms for Bernoulli numbers and Euler numbers>
+-- by Kwang-Wu Chen, third formula of the Corollary in page 7.
+-- Element-by-element division of sequences <https://oeis.org/A198631 A1986631>
+-- and <https://oeis.org/A006519 A006519> in OEIS.
+--
+-- >>> take 10 eulerPolyAt1 :: [Rational]
+-- [1 % 1,1 % 2,0 % 1,(-1) % 4,0 % 1,1 % 2,0 % 1,(-17) % 8,0 % 1,31 % 2]
+eulerPolyAt1 :: forall a . Integral a => [Ratio a]
+eulerPolyAt1 = tail $ helperForB_E_EP tail (map recip (iterate (2 *) 1))
+{-# SPECIALIZE eulerPolyAt1 :: [Ratio Int]     #-}
+{-# SPECIALIZE eulerPolyAt1 :: [Rational]      #-}
+
+-------------------------------------------------------------------------------
+-- Utils
+
+-- zipIndexedListWithTail f n as a == zipWith3 f [n..] as (tail as ++ [a])
+-- but inlines much better and avoids checks for distinct sizes of lists.
+zipIndexedListWithTail :: Enum b => (b -> a -> a -> b) -> b -> [a] -> a -> [b]
+zipIndexedListWithTail f n as a = case as of
+  []       -> []
+  (x : xs) -> go n x xs
+  where
+    go m y ys = case ys of
+      []       -> let v = f m y a in [v]
+      (z : zs) -> let v = f m y z in (v : go (succ m) z zs)
+{-# INLINE zipIndexedListWithTail #-}
+
+-- | Helper for common code in @bernoulli, euler, eulerPolyAt1. All three
+-- sequences rely on @stirling2@ and have the same general structure of
+-- zipping four lists together with multiplication, with one of those lists
+-- being the sublists in @stirling2@, and two of them being the factorial
+-- sequence and @cycle [1, -1]@. The remaining list is passed to
+-- @helperForB_E_EP@ as an argument.
+--
+-- Note: This function has a @([Ratio a] -> [Ratio a])@ argument because
+-- @bernoulli !! n@ will use, for all nonnegative @n@, every element in
+-- @stirling2 !! n@, while @euler, eulerPolyAt1@ only use
+-- @tail $ stirling2 !! n@. As such, this argument serves to pass @id@
+-- in the former case, and @tail@ in the latter.
+helperForB_E_EP :: Integral a => ([Ratio a] -> [Ratio a]) -> [Ratio a] -> [Ratio a]
+helperForB_E_EP g xs = map (f . g) stirling2
+  where
+    f = sum . zipWith4 (\sgn fact x stir -> sgn * fact * x * stir) (cycle [1, -1]) factorial xs
+{-# SPECIALIZE helperForB_E_EP :: ([Ratio Int] -> [Ratio Int]) -> [Ratio Int] -> [Ratio Int] #-}
+{-# SPECIALIZE helperForB_E_EP :: ([Rational] -> [Rational]) -> [Rational] -> [Rational]     #-}
diff --git a/Math/NumberTheory/Recurrences/Linear.hs b/Math/NumberTheory/Recurrences/Linear.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Recurrences/Linear.hs
@@ -0,0 +1,136 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.Linear
+-- Copyright:   (c) 2011 Daniel Fischer
+-- Licence:     MIT
+-- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
+--
+-- Efficient calculation of linear recurrent sequences, including Fibonacci and Lucas sequences.
+
+{-# LANGUAGE CPP #-}
+module Math.NumberTheory.Recurrences.Linear
+  ( factorial
+  , fibonacci
+  , fibonacciPair
+  , lucas
+  , lucasPair
+  , generalLucas
+  ) where
+
+#include "MachDeps.h"
+
+import Data.Bits
+import Numeric.Natural
+
+-- | Infinite zero-based table of factorials.
+--
+-- >>> take 5 factorial
+-- [1,1,2,6,24]
+--
+-- The time-and-space behaviour of 'factorial' is similar to described in
+-- "Math.NumberTheory.Recurrences.Bilinear#memory".
+factorial :: (Num a, Enum a) => [a]
+factorial = scanl (*) 1 [1..]
+{-# SPECIALIZE factorial :: [Int]     #-}
+{-# SPECIALIZE factorial :: [Word]    #-}
+{-# SPECIALIZE factorial :: [Integer] #-}
+{-# SPECIALIZE factorial :: [Natural] #-}
+
+-- | @'fibonacci' k@ calculates the @k@-th Fibonacci number in
+--   /O/(@log (abs k)@) steps. The index may be negative. This
+--   is efficient for calculating single Fibonacci numbers (with
+--   large index), but for computing many Fibonacci numbers in
+--   close proximity, it is better to use the simple addition
+--   formula starting from an appropriate pair of successive
+--   Fibonacci numbers.
+fibonacci :: Num a => Int -> a
+fibonacci = fst . fibonacciPair
+{-# SPECIALIZE fibonacci :: Int -> Int     #-}
+{-# SPECIALIZE fibonacci :: Int -> Word    #-}
+{-# SPECIALIZE fibonacci :: Int -> Integer #-}
+{-# SPECIALIZE fibonacci :: Int -> Natural #-}
+
+-- | @'fibonacciPair' k@ returns the pair @(F(k), F(k+1))@ of the @k@-th
+--   Fibonacci number and its successor, thus it can be used to calculate
+--   the Fibonacci numbers from some index on without needing to compute
+--   the previous. The pair is efficiently calculated
+--   in /O/(@log (abs k)@) steps. The index may be negative.
+fibonacciPair :: Num a => Int -> (a, a)
+fibonacciPair n
+  | n < 0     = let (f,g) = fibonacciPair (-(n+1)) in if testBit n 0 then (g, -f) else (-g, f)
+  | n == 0    = (0, 1)
+  | otherwise = look (WORD_SIZE_IN_BITS - 2)
+    where
+      look k
+        | testBit n k = go (k-1) 0 1
+        | otherwise   = look (k-1)
+      go k g f
+        | k < 0       = (f, f+g)
+        | testBit n k = go (k-1) (f*(f+shiftL1 g)) ((f+g)*shiftL1 f + g*g)
+        | otherwise   = go (k-1) (f*f+g*g) (f*(f+shiftL1 g))
+{-# SPECIALIZE fibonacciPair :: Int -> (Int, Int)         #-}
+{-# SPECIALIZE fibonacciPair :: Int -> (Word, Word)       #-}
+{-# SPECIALIZE fibonacciPair :: Int -> (Integer, Integer) #-}
+{-# SPECIALIZE fibonacciPair :: Int -> (Natural, Natural) #-}
+
+-- | @'lucas' k@ computes the @k@-th Lucas number. Very similar
+--   to @'fibonacci'@.
+lucas :: Num a => Int -> a
+lucas = fst . lucasPair
+{-# SPECIALIZE lucas :: Int -> Int     #-}
+{-# SPECIALIZE lucas :: Int -> Word    #-}
+{-# SPECIALIZE lucas :: Int -> Integer #-}
+{-# SPECIALIZE lucas :: Int -> Natural #-}
+
+-- | @'lucasPair' k@ computes the pair @(L(k), L(k+1))@ of the @k@-th
+--   Lucas number and its successor. Very similar to @'fibonacciPair'@.
+lucasPair :: Num a => Int -> (a, a)
+lucasPair n
+  | n < 0     = let (f,g) = lucasPair (-(n+1)) in if testBit n 0 then (-g, f) else (g, -f)
+  | n == 0    = (2, 1)
+  | otherwise = look (WORD_SIZE_IN_BITS - 2)
+    where
+      look k
+        | testBit n k = go (k-1) 0 1
+        | otherwise   = look (k-1)
+      go k g f
+        | k < 0       = (shiftL1 g + f,g+3*f)
+        | otherwise   = go (k-1) g' f'
+          where
+            (f',g')
+              | testBit n k = (shiftL1 (f*(f+g)) + g*g,f*(shiftL1 g + f))
+              | otherwise   = (f*(shiftL1 g + f),f*f+g*g)
+{-# SPECIALIZE lucasPair :: Int -> (Int, Int)         #-}
+{-# SPECIALIZE lucasPair :: Int -> (Word, Word)       #-}
+{-# SPECIALIZE lucasPair :: Int -> (Integer, Integer) #-}
+{-# SPECIALIZE lucasPair :: Int -> (Natural, Natural) #-}
+
+-- | @'generalLucas' p q k@ calculates the quadruple @(U(k), U(k+1), V(k), V(k+1))@
+--   where @U(i)@ is the Lucas sequence of the first kind and @V(i)@ the Lucas
+--   sequence of the second kind for the parameters @p@ and @q@, where @p^2-4q /= 0@.
+--   Both sequences satisfy the recurrence relation @A(j+2) = p*A(j+1) - q*A(j)@,
+--   the starting values are @U(0) = 0, U(1) = 1@ and @V(0) = 2, V(1) = p@.
+--   The Fibonacci numbers form the Lucas sequence of the first kind for the
+--   parameters @p = 1, q = -1@ and the Lucas numbers form the Lucas sequence of
+--   the second kind for these parameters.
+--   Here, the index must be non-negative, since the terms of the sequence for
+--   negative indices are in general not integers.
+generalLucas :: Num a => a -> a -> Int -> (a, a, a, a)
+generalLucas p q k
+  | k < 0       = error "generalLucas: negative index"
+  | k == 0      = (0,1,2,p)
+  | otherwise   = look (WORD_SIZE_IN_BITS - 2)
+    where
+      look i
+        | testBit k i   = go (i-1) 1 p p q
+        | otherwise     = look (i-1)
+      go i un un1 vn qn
+        | i < 0         = (un, un1, vn, p*un1 - shiftL1 (q*un))
+        | testBit k i   = go (i-1) (un1*vn-qn) ((p*un1-q*un)*vn - p*qn) ((p*un1 - (2*q)*un)*vn - p*qn) (qn*qn*q)
+        | otherwise     = go (i-1) (un*vn) (un1*vn-qn) (vn*vn - 2*qn) (qn*qn)
+{-# SPECIALIZE generalLucas :: Int     -> Int     -> Int -> (Int, Int, Int, Int)                 #-}
+{-# SPECIALIZE generalLucas :: Word    -> Word    -> Int -> (Word, Word, Word, Word)             #-}
+{-# SPECIALIZE generalLucas :: Integer -> Integer -> Int -> (Integer, Integer, Integer, Integer) #-}
+{-# SPECIALIZE generalLucas :: Natural -> Natural -> Int -> (Natural, Natural, Natural, Natural) #-}
+
+shiftL1 :: Num a => a -> a
+shiftL1 n = n + n
diff --git a/Math/NumberTheory/Recurrences/Pentagonal.hs b/Math/NumberTheory/Recurrences/Pentagonal.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Recurrences/Pentagonal.hs
@@ -0,0 +1,95 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.Pentagonal
+-- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
+-- Licence:     MIT
+-- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
+--
+-- Values of <https://en.wikipedia.org/wiki/Partition_(number_theory)#Partition_function partition function>.
+--
+
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE RankNTypes   #-}
+
+module Math.NumberTheory.Recurrences.Pentagonal
+  ( partition
+  , pentagonalSigns
+  , pents
+  ) where
+
+import qualified Data.IntMap as IM
+import Numeric.Natural       (Natural)
+
+-- | Infinite list of generalized pentagonal numbers.
+-- Example:
+--
+-- >>> take 10 pents
+-- [0,1,2,5,7,12,15,22,26,35]
+pents :: (Enum a, Num a) => [a]
+pents = interleave (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
+                   (scanl (\acc n -> acc + 3 * n - 2) 1 [2..])
+  where
+    interleave :: [a] -> [a] -> [a]
+    interleave (n : ns) (m : ms) = n : m : interleave ns ms
+    interleave _ _ = []
+
+-- | When calculating the @n@-th partition number @p(n)@ using the sum
+-- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@, the signs of each
+-- term alternate every two terms, starting with a positive sign.
+-- @pentagonalSigns@ takes a list of numbers and produces such an alternated
+-- sequence.
+-- Examples:
+--
+-- >>> pentagonalSigns [1..5]
+-- [1,2,-3,-4,5]
+--
+-- >>> pentagonalSigns [1..6]
+-- [1,2,-3,-4,5,6]
+pentagonalSigns :: Num a => [a] -> [a]
+pentagonalSigns = zipWith (*) (cycle [1, 1, -1, -1])
+
+-- [Implementation notes for partition function]
+--
+-- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@, where @p(0) = 1@
+-- and @p(k) = 0@ for a negative integer @k@. Uses a @Map@ from the
+-- @containers@ package to memoize previous results.
+--
+-- Example: calculating @partition !! 10@, assuming the memoization map is
+-- filled and called @dict :: Integral a => Map a a@.
+--
+-- * @tail [0, 1, 2, 5, 7, 12 ,15, 22, 26, 35, ..] == [1, 2, 5, 7, 12 ,15, 22, 26, 35, 40, ..]@.
+-- * @takeWhile (\m -> 10 - m >= 0) [1, 2, 5, 7, 12 ,15, 22, 26, 35, 40, ..] == [1, 2, 5, 7]@.
+-- * @map (\m -> dict ! fromIntegral (10 - m)) [1, 2, 5, 7] == [dict ! 9, dict ! 8, dict ! 5, dict ! 3] == [30, 22, 7, 3]@
+-- * @pentagonalSigns [30, 22, 7, 3] == [30, 22, 7, 3] == [30, 22, -7, -3]@
+-- * @sum [30, 22, -7, -3] == 42@
+--
+-- Notes:
+-- 1. @tail@ is applied to @pents@ because otherwise the calculation of
+-- @p(n)@ would involve a duplicated @p(n-1)@ term (see the above example).
+-- 2. Calculating @partition !! k@, where @k@ is any index equal or higher
+-- than @maxBound :: Int@ results in undefined behavior.
+
+-- | Infinite zero-based table of <https://oeis.org/A000041 partition numbers>.
+--
+-- >>> take 10 partition
+-- [1,1,2,3,5,7,11,15,22,30]
+--
+-- >>> :set -XDataKinds
+-- >>> import Math.NumberTheory.Moduli.Class
+-- >>> partition !! 1000 :: Mod 1000
+-- (991 `modulo` 1000)
+partition :: Num a => [a]
+partition = 1 : go (IM.singleton 0 1) 1
+  where
+    go :: Num a => IM.IntMap a -> Int -> [a]
+    go dict !n =
+        let n' = (sum .
+                  pentagonalSigns .
+                  map (\m -> dict IM.! (n - m)) .
+                  takeWhile (\m -> n >= m) .
+                  tail) (pents :: [Int])
+            dict' = IM.insert n n' dict
+        in n' : go dict' (n + 1)
+{-# SPECIALIZE partition :: [Int]     #-}
+{-# SPECIALIZE partition :: [Word]    #-}
+{-# SPECIALIZE partition :: [Integer] #-}
+{-# SPECIALIZE partition :: [Natural] #-}
diff --git a/Math/NumberTheory/Recurrencies.hs b/Math/NumberTheory/Recurrencies.hs
--- a/Math/NumberTheory/Recurrencies.hs
+++ b/Math/NumberTheory/Recurrencies.hs
@@ -1,18 +1,17 @@
 -- |
 -- Module:      Math.NumberTheory.Recurrencies
+-- Description: Deprecated
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 
-module Math.NumberTheory.Recurrencies
-    ( module Math.NumberTheory.Recurrencies.Linear
-    , module Math.NumberTheory.Recurrencies.Bilinear
-    , module Math.NumberTheory.Recurrencies.Pentagonal
+module Math.NumberTheory.Recurrencies {-# DEPRECATED "Use `Math.NumberTheory.Recurrences` instead." #-}
+    ( module Math.NumberTheory.Recurrences.Linear
+    , module Math.NumberTheory.Recurrences.Bilinear
+    , module Math.NumberTheory.Recurrences.Pentagonal
     ) where
 
-import Math.NumberTheory.Recurrencies.Bilinear
-import Math.NumberTheory.Recurrencies.Linear
-import Math.NumberTheory.Recurrencies.Pentagonal (partition)
+import Math.NumberTheory.Recurrences.Bilinear
+import Math.NumberTheory.Recurrences.Linear
+import Math.NumberTheory.Recurrences.Pentagonal (partition)
diff --git a/Math/NumberTheory/Recurrencies/Bilinear.hs b/Math/NumberTheory/Recurrencies/Bilinear.hs
--- a/Math/NumberTheory/Recurrencies/Bilinear.hs
+++ b/Math/NumberTheory/Recurrencies/Bilinear.hs
@@ -1,10 +1,9 @@
 -- |
 -- Module:      Math.NumberTheory.Recurrencies.Bilinear
+-- Description: Deprecated
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Bilinear recurrent sequences and Bernoulli numbers,
 -- roughly covering Ch. 5-6 of /Concrete Mathematics/
@@ -32,224 +31,8 @@
 -- 1
 -- (0.01 secs, 391,152 bytes)
 
-{-# LANGUAGE CPP                 #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
-module Math.NumberTheory.Recurrencies.Bilinear
-  ( binomial
-  , stirling1
-  , stirling2
-  , lah
-  , eulerian1
-  , eulerian2
-  , bernoulli
-  , euler
-  , eulerPolyAt1
-  ) where
-
-import Data.List
-import Data.Ratio
-import Numeric.Natural
-
-import Math.NumberTheory.Recurrencies.Linear (factorial)
-
--- | Infinite zero-based table of binomial coefficients (also known as Pascal triangle):
--- @binomial !! n !! k == n! \/ k! \/ (n - k)!@.
---
--- >>> take 5 (map (take 5) binomial)
--- [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
---
--- Complexity: @binomial !! n !! k@ is O(n) bits long, its computation
--- takes O(k n) time and forces thunks @binomial !! n !! i@ for @0 <= i <= k@.
--- Use the symmetry of Pascal triangle @binomial !! n !! k == binomial !! n !! (n - k)@ to speed up computations.
---
--- One could also consider 'Math.Combinat.Numbers.binomial' to compute stand-alone values.
-binomial :: Integral a => [[a]]
-binomial = map f [0..]
-  where
-    f n = scanl (\x k -> x * (n - k + 1) `div` k) 1 [1..n]
-{-# SPECIALIZE binomial :: [[Int]]     #-}
-{-# SPECIALIZE binomial :: [[Word]]    #-}
-{-# SPECIALIZE binomial :: [[Integer]] #-}
-{-# SPECIALIZE binomial :: [[Natural]] #-}
-
--- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind Stirling numbers of the first kind>.
---
--- >>> take 5 (map (take 5) stirling1)
--- [[1],[0,1],[0,1,1],[0,2,3,1],[0,6,11,6,1]]
---
--- Complexity: @stirling1 !! n !! k@ is O(n ln n) bits long, its computation
--- takes O(k n^2 ln n) time and forces thunks @stirling1 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
---
--- One could also consider 'Math.Combinat.Numbers.unsignedStirling1st' to compute stand-alone values.
-stirling1 :: (Num a, Enum a) => [[a]]
-stirling1 = scanl f [1] [0..]
-  where
-    f xs n = 0 : zipIndexedListWithTail (\_ x y -> x + n * y) 1 xs 0
-{-# SPECIALIZE stirling1 :: [[Int]]     #-}
-{-# SPECIALIZE stirling1 :: [[Word]]    #-}
-{-# SPECIALIZE stirling1 :: [[Integer]] #-}
-{-# SPECIALIZE stirling1 :: [[Natural]] #-}
-
--- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind Stirling numbers of the second kind>.
---
--- >>> take 5 (map (take 5) stirling2)
--- [[1],[0,1],[0,1,1],[0,1,3,1],[0,1,7,6,1]]
---
--- Complexity: @stirling2 !! n !! k@ is O(n ln n) bits long, its computation
--- takes O(k n^2 ln n) time and forces thunks @stirling2 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
---
--- One could also consider 'Math.Combinat.Numbers.stirling2nd' to compute stand-alone values.
-stirling2 :: (Num a, Enum a) => [[a]]
-stirling2 = iterate f [1]
-  where
-    f xs = 0 : zipIndexedListWithTail (\k x y -> x + k * y) 1 xs 0
-{-# SPECIALIZE stirling2 :: [[Int]]     #-}
-{-# SPECIALIZE stirling2 :: [[Word]]    #-}
-{-# SPECIALIZE stirling2 :: [[Integer]] #-}
-{-# SPECIALIZE stirling2 :: [[Natural]] #-}
-
--- | Infinite one-based table of <https://en.wikipedia.org/wiki/Lah_number Lah numbers>.
--- @lah !! n !! k@ equals to lah(n + 1, k + 1).
---
--- >>> take 5 (map (take 5) lah)
--- [[1],[2,1],[6,6,1],[24,36,12,1],[120,240,120,20,1]]
---
--- Complexity: @lah !! n !! k@ is O(n ln n) bits long, its computation
--- takes O(k n ln n) time and forces thunks @lah !! n !! i@ for @0 <= i <= k@.
-lah :: Integral a => [[a]]
--- Implementation was derived from code by https://github.com/grandpascorpion
-lah = zipWith f (tail factorial) [1..]
-  where
-    f nf n = scanl (\x k -> x * (n - k) `div` (k * (k + 1))) nf [1..n-1]
-{-# SPECIALIZE lah :: [[Int]]     #-}
-{-# SPECIALIZE lah :: [[Word]]    #-}
-{-# SPECIALIZE lah :: [[Integer]] #-}
-{-# SPECIALIZE lah :: [[Natural]] #-}
-
--- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Eulerian_number Eulerian numbers of the first kind>.
---
--- >>> take 5 (map (take 5) eulerian1)
--- [[],[1],[1,1],[1,4,1],[1,11,11,1]]
---
--- Complexity: @eulerian1 !! n !! k@ is O(n ln n) bits long, its computation
--- takes O(k n^2 ln n) time and forces thunks @eulerian1 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
---
-eulerian1 :: (Num a, Enum a) => [[a]]
-eulerian1 = scanl f [] [1..]
-  where
-    f xs n = 1 : zipIndexedListWithTail (\k x y -> (n - k) * x + (k + 1) * y) 1 xs 0
-{-# SPECIALIZE eulerian1 :: [[Int]]     #-}
-{-# SPECIALIZE eulerian1 :: [[Word]]    #-}
-{-# SPECIALIZE eulerian1 :: [[Integer]] #-}
-{-# SPECIALIZE eulerian1 :: [[Natural]] #-}
-
--- | Infinite zero-based table of <https://en.wikipedia.org/wiki/Eulerian_number#Eulerian_numbers_of_the_second_kind Eulerian numbers of the second kind>.
---
--- >>> take 5 (map (take 5) eulerian2)
--- [[],[1],[1,2],[1,8,6],[1,22,58,24]]
---
--- Complexity: @eulerian2 !! n !! k@ is O(n ln n) bits long, its computation
--- takes O(k n^2 ln n) time and forces thunks @eulerian2 !! i !! j@ for @0 <= i <= n@ and @max(0, k - n + i) <= j <= k@.
---
-eulerian2 :: (Num a, Enum a) => [[a]]
-eulerian2 = scanl f [] [1..]
-  where
-    f xs n = 1 : zipIndexedListWithTail (\k x y -> (2 * n - k - 1) * x + (k + 1) * y) 1 xs 0
-{-# SPECIALIZE eulerian2 :: [[Int]]     #-}
-{-# SPECIALIZE eulerian2 :: [[Word]]    #-}
-{-# SPECIALIZE eulerian2 :: [[Integer]] #-}
-{-# SPECIALIZE eulerian2 :: [[Natural]] #-}
-
--- | Infinite zero-based sequence of <https://en.wikipedia.org/wiki/Bernoulli_number Bernoulli numbers>,
--- computed via <https://en.wikipedia.org/wiki/Bernoulli_number#Connection_with_Stirling_numbers_of_the_second_kind connection>
--- with 'stirling2'.
---
--- >>> take 5 bernoulli
--- [1 % 1,(-1) % 2,1 % 6,0 % 1,(-1) % 30]
---
--- Complexity: @bernoulli !! n@ is O(n ln n) bits long, its computation
--- takes O(n^3 ln n) time and forces thunks @stirling2 !! i !! j@ for @0 <= i <= n@ and @0 <= j <= i@.
---
--- One could also consider 'Math.Combinat.Numbers.bernoulli' to compute stand-alone values.
-bernoulli :: Integral a => [Ratio a]
-bernoulli = helperForB_E_EP id (map recip [1..])
-{-# SPECIALIZE bernoulli :: [Ratio Int] #-}
-{-# SPECIALIZE bernoulli :: [Rational] #-}
-
--- | Infinite zero-based list of <https://en.wikipedia.org/wiki/Euler_number Euler numbers>.
--- The algorithm used was derived from <http://www.emis.ams.org/journals/JIS/VOL4/CHEN/AlgBE2.pdf Algorithms for Bernoulli numbers and Euler numbers>
--- by Kwang-Wu Chen, second formula of the Corollary in page 7.
--- Sequence <https://oeis.org/A122045 A122045> in OEIS.
---
--- >>> take 10 euler' :: [Rational]
--- [1 % 1,0 % 1,(-1) % 1,0 % 1,5 % 1,0 % 1,(-61) % 1,0 % 1,1385 % 1,0 % 1]
-euler' :: forall a . Integral a => [Ratio a]
-euler' = tail $ helperForB_E_EP tail as
-  where
-    as :: [Ratio a]
-    as = zipWith3
-        (\sgn frac ones -> (sgn * ones) % frac)
-        (cycle [1, 1, 1, 1, -1, -1, -1, -1])
-        (dups (iterate (2 *) 1))
-        (cycle [1, 1, 1, 0])
-
-    dups :: forall x . [x] -> [x]
-    dups = foldr (\n list -> n : n : list) []
-{-# SPECIALIZE euler' :: [Ratio Int]     #-}
-{-# SPECIALIZE euler' :: [Rational]      #-}
-
--- | The same sequence as @euler'@, but with type @[a]@ instead of @[Ratio a]@
--- as the denominators in @euler'@ are always @1@.
---
--- >>> take 10 euler :: [Integer]
--- [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0]
-euler :: forall a . Integral a => [a]
-euler = map numerator euler'
-
--- | Infinite zero-based list of the @n@-th order Euler polynomials evaluated at @1@.
--- The algorithm used was derived from <http://www.emis.ams.org/journals/JIS/VOL4/CHEN/AlgBE2.pdf Algorithms for Bernoulli numbers and Euler numbers>
--- by Kwang-Wu Chen, third formula of the Corollary in page 7.
--- Element-by-element division of sequences <https://oeis.org/A198631 A1986631>
--- and <https://oeis.org/A006519 A006519> in OEIS.
---
--- >>> take 10 eulerPolyAt1 :: [Rational]
--- [1 % 1,1 % 2,0 % 1,(-1) % 4,0 % 1,1 % 2,0 % 1,(-17) % 8,0 % 1,31 % 2]
-eulerPolyAt1 :: forall a . Integral a => [Ratio a]
-eulerPolyAt1 = tail $ helperForB_E_EP tail (map recip (iterate (2 *) 1))
-{-# SPECIALIZE eulerPolyAt1 :: [Ratio Int]     #-}
-{-# SPECIALIZE eulerPolyAt1 :: [Rational]      #-}
-
--------------------------------------------------------------------------------
--- Utils
-
--- zipIndexedListWithTail f n as a == zipWith3 f [n..] as (tail as ++ [a])
--- but inlines much better and avoids checks for distinct sizes of lists.
-zipIndexedListWithTail :: Enum b => (b -> a -> a -> b) -> b -> [a] -> a -> [b]
-zipIndexedListWithTail f n as a = case as of
-  []       -> []
-  (x : xs) -> go n x xs
-  where
-    go m y ys = case ys of
-      []       -> let v = f m y a in [v]
-      (z : zs) -> let v = f m y z in (v : go (succ m) z zs)
-{-# INLINE zipIndexedListWithTail #-}
+module Math.NumberTheory.Recurrencies.Bilinear {-# DEPRECATED "Use `Math.NumberTheory.Recurrences.Bilinear` instead." #-}
+    ( module Math.NumberTheory.Recurrences.Bilinear
+    ) where
 
--- | Helper for common code in @bernoulli, euler, eulerPolyAt1. All three
--- sequences rely on @stirling2@ and have the same general structure of
--- zipping four lists together with multiplication, with one of those lists
--- being the sublists in @stirling2@, and two of them being the factorial
--- sequence and @cycle [1, -1]@. The remaining list is passed to
--- @helperForB_E_EP@ as an argument.
---
--- Note: This function has a @([Ratio a] -> [Ratio a])@ argument because
--- @bernoulli !! n@ will use, for all nonnegative @n@, every element in
--- @stirling2 !! n@, while @euler, eulerPolyAt1@ only use
--- @tail $ stirling2 !! n@. As such, this argument serves to pass @id@
--- in the former case, and @tail@ in the latter.
-helperForB_E_EP :: Integral a => ([Ratio a] -> [Ratio a]) -> [Ratio a] -> [Ratio a]
-helperForB_E_EP g xs = map (f . g) stirling2
-  where
-    f = sum . zipWith4 (\sgn fact x stir -> sgn * fact * x * stir) (cycle [1, -1]) factorial xs
-{-# SPECIALIZE helperForB_E_EP :: ([Ratio Int] -> [Ratio Int]) -> [Ratio Int] -> [Ratio Int] #-}
-{-# SPECIALIZE helperForB_E_EP :: ([Rational] -> [Rational]) -> [Rational] -> [Rational]     #-}
+import Math.NumberTheory.Recurrences.Bilinear
diff --git a/Math/NumberTheory/Recurrencies/Linear.hs b/Math/NumberTheory/Recurrencies/Linear.hs
--- a/Math/NumberTheory/Recurrencies/Linear.hs
+++ b/Math/NumberTheory/Recurrencies/Linear.hs
@@ -1,138 +1,14 @@
 -- |
 -- Module:      Math.NumberTheory.Recurrencies.Linear
+-- Description: Deprecated
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Efficient calculation of linear recurrent sequences, including Fibonacci and Lucas sequences.
 
-{-# LANGUAGE CPP #-}
-module Math.NumberTheory.Recurrencies.Linear
-  ( factorial
-  , fibonacci
-  , fibonacciPair
-  , lucas
-  , lucasPair
-  , generalLucas
-  ) where
-
-#include "MachDeps.h"
-
-import Data.Bits
-import Numeric.Natural
-
--- | Infinite zero-based table of factorials.
---
--- >>> take 5 factorial
--- [1,1,2,6,24]
---
--- The time-and-space behaviour of 'factorial' is similar to described in
--- "Math.NumberTheory.Recurrencies.Bilinear#memory".
-factorial :: (Num a, Enum a) => [a]
-factorial = scanl (*) 1 [1..]
-{-# SPECIALIZE factorial :: [Int]     #-}
-{-# SPECIALIZE factorial :: [Word]    #-}
-{-# SPECIALIZE factorial :: [Integer] #-}
-{-# SPECIALIZE factorial :: [Natural] #-}
-
--- | @'fibonacci' k@ calculates the @k@-th Fibonacci number in
---   /O/(@log (abs k)@) steps. The index may be negative. This
---   is efficient for calculating single Fibonacci numbers (with
---   large index), but for computing many Fibonacci numbers in
---   close proximity, it is better to use the simple addition
---   formula starting from an appropriate pair of successive
---   Fibonacci numbers.
-fibonacci :: Num a => Int -> a
-fibonacci = fst . fibonacciPair
-{-# SPECIALIZE fibonacci :: Int -> Int     #-}
-{-# SPECIALIZE fibonacci :: Int -> Word    #-}
-{-# SPECIALIZE fibonacci :: Int -> Integer #-}
-{-# SPECIALIZE fibonacci :: Int -> Natural #-}
-
--- | @'fibonacciPair' k@ returns the pair @(F(k), F(k+1))@ of the @k@-th
---   Fibonacci number and its successor, thus it can be used to calculate
---   the Fibonacci numbers from some index on without needing to compute
---   the previous. The pair is efficiently calculated
---   in /O/(@log (abs k)@) steps. The index may be negative.
-fibonacciPair :: Num a => Int -> (a, a)
-fibonacciPair n
-  | n < 0     = let (f,g) = fibonacciPair (-(n+1)) in if testBit n 0 then (g, -f) else (-g, f)
-  | n == 0    = (0, 1)
-  | otherwise = look (WORD_SIZE_IN_BITS - 2)
-    where
-      look k
-        | testBit n k = go (k-1) 0 1
-        | otherwise   = look (k-1)
-      go k g f
-        | k < 0       = (f, f+g)
-        | testBit n k = go (k-1) (f*(f+shiftL1 g)) ((f+g)*shiftL1 f + g*g)
-        | otherwise   = go (k-1) (f*f+g*g) (f*(f+shiftL1 g))
-{-# SPECIALIZE fibonacciPair :: Int -> (Int, Int)         #-}
-{-# SPECIALIZE fibonacciPair :: Int -> (Word, Word)       #-}
-{-# SPECIALIZE fibonacciPair :: Int -> (Integer, Integer) #-}
-{-# SPECIALIZE fibonacciPair :: Int -> (Natural, Natural) #-}
-
--- | @'lucas' k@ computes the @k@-th Lucas number. Very similar
---   to @'fibonacci'@.
-lucas :: Num a => Int -> a
-lucas = fst . lucasPair
-{-# SPECIALIZE lucas :: Int -> Int     #-}
-{-# SPECIALIZE lucas :: Int -> Word    #-}
-{-# SPECIALIZE lucas :: Int -> Integer #-}
-{-# SPECIALIZE lucas :: Int -> Natural #-}
-
--- | @'lucasPair' k@ computes the pair @(L(k), L(k+1))@ of the @k@-th
---   Lucas number and its successor. Very similar to @'fibonacciPair'@.
-lucasPair :: Num a => Int -> (a, a)
-lucasPair n
-  | n < 0     = let (f,g) = lucasPair (-(n+1)) in if testBit n 0 then (-g, f) else (g, -f)
-  | n == 0    = (2, 1)
-  | otherwise = look (WORD_SIZE_IN_BITS - 2)
-    where
-      look k
-        | testBit n k = go (k-1) 0 1
-        | otherwise   = look (k-1)
-      go k g f
-        | k < 0       = (shiftL1 g + f,g+3*f)
-        | otherwise   = go (k-1) g' f'
-          where
-            (f',g')
-              | testBit n k = (shiftL1 (f*(f+g)) + g*g,f*(shiftL1 g + f))
-              | otherwise   = (f*(shiftL1 g + f),f*f+g*g)
-{-# SPECIALIZE lucasPair :: Int -> (Int, Int)         #-}
-{-# SPECIALIZE lucasPair :: Int -> (Word, Word)       #-}
-{-# SPECIALIZE lucasPair :: Int -> (Integer, Integer) #-}
-{-# SPECIALIZE lucasPair :: Int -> (Natural, Natural) #-}
-
--- | @'generalLucas' p q k@ calculates the quadruple @(U(k), U(k+1), V(k), V(k+1))@
---   where @U(i)@ is the Lucas sequence of the first kind and @V(i)@ the Lucas
---   sequence of the second kind for the parameters @p@ and @q@, where @p^2-4q /= 0@.
---   Both sequences satisfy the recurrence relation @A(j+2) = p*A(j+1) - q*A(j)@,
---   the starting values are @U(0) = 0, U(1) = 1@ and @V(0) = 2, V(1) = p@.
---   The Fibonacci numbers form the Lucas sequence of the first kind for the
---   parameters @p = 1, q = -1@ and the Lucas numbers form the Lucas sequence of
---   the second kind for these parameters.
---   Here, the index must be non-negative, since the terms of the sequence for
---   negative indices are in general not integers.
-generalLucas :: Num a => a -> a -> Int -> (a, a, a, a)
-generalLucas p q k
-  | k < 0       = error "generalLucas: negative index"
-  | k == 0      = (0,1,2,p)
-  | otherwise   = look (WORD_SIZE_IN_BITS - 2)
-    where
-      look i
-        | testBit k i   = go (i-1) 1 p p q
-        | otherwise     = look (i-1)
-      go i un un1 vn qn
-        | i < 0         = (un, un1, vn, p*un1 - shiftL1 (q*un))
-        | testBit k i   = go (i-1) (un1*vn-qn) ((p*un1-q*un)*vn - p*qn) ((p*un1 - (2*q)*un)*vn - p*qn) (qn*qn*q)
-        | otherwise     = go (i-1) (un*vn) (un1*vn-qn) (vn*vn - 2*qn) (qn*qn)
-{-# SPECIALIZE generalLucas :: Int     -> Int     -> Int -> (Int, Int, Int, Int)                 #-}
-{-# SPECIALIZE generalLucas :: Word    -> Word    -> Int -> (Word, Word, Word, Word)             #-}
-{-# SPECIALIZE generalLucas :: Integer -> Integer -> Int -> (Integer, Integer, Integer, Integer) #-}
-{-# SPECIALIZE generalLucas :: Natural -> Natural -> Int -> (Natural, Natural, Natural, Natural) #-}
+module Math.NumberTheory.Recurrencies.Linear {-# DEPRECATED "Use `Math.NumberTheory.Recurrences.Linear` instead." #-}
+    ( module Math.NumberTheory.Recurrences.Linear
+    ) where
 
-shiftL1 :: Num a => a -> a
-shiftL1 n = n + n
+import Math.NumberTheory.Recurrences.Linear
diff --git a/Math/NumberTheory/Recurrencies/Pentagonal.hs b/Math/NumberTheory/Recurrencies/Pentagonal.hs
deleted file mode 100644
--- a/Math/NumberTheory/Recurrencies/Pentagonal.hs
+++ /dev/null
@@ -1,96 +0,0 @@
--- |
--- Module:      Math.NumberTheory.Recurrencies.Pentagonal
--- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
--- Licence:     MIT
--- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
---
--- Values of <https://en.wikipedia.org/wiki/Partition_(number_theory)#Partition_function partition function>.
---
-
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE RankNTypes   #-}
-
-module Math.NumberTheory.Recurrencies.Pentagonal
-  ( partition
-  , pentagonalSigns
-  , pents
-  ) where
-
-import qualified Data.IntMap as IM
-import Numeric.Natural       (Natural)
-
--- | Infinite list of generalized pentagonal numbers.
--- Example:
---
--- >>> take 10 pents
--- [0, 1, 2, 5, 7, 12 ,15, 22, 26, 35]
-pents :: (Enum a, Num a) => [a]
-pents = interleave (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
-                   (scanl (\acc n -> acc + 3 * n - 2) 1 [2..])
-  where
-    interleave :: [a] -> [a] -> [a]
-    interleave (n : ns) (m : ms) = n : m : interleave ns ms
-    interleave _ _ = []
-
--- | When calculating the @n@-th partition number @p(n)@ using the sum
--- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@, the signs of each
--- term alternate every two terms, starting with a positive sign.
--- @pentagonalSigns@ takes a list of numbers and produces such an alternated
--- sequence.
--- Examples:
---
--- >>> pentagonalSigns [1..5]
--- [1, 2, -3, -4, 5]
---
--- >>> pentagonalSigns [1..6]
--- [1, 2, -3, -4, 5, 6]
-pentagonalSigns :: Num a => [a] -> [a]
-pentagonalSigns = zipWith (*) (cycle [1, 1, -1, -1])
-
--- [Implementation notes for partition function]
---
--- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@, where @p(0) = 1@
--- and @p(k) = 0@ for a negative integer @k@. Uses a @Map@ from the
--- @containers@ package to memoize previous results.
---
--- Example: calculating @partition !! 10@, assuming the memoization map is
--- filled and called @dict :: Integral a => Map a a@.
---
--- * @tail [0, 1, 2, 5, 7, 12 ,15, 22, 26, 35, ..] == [1, 2, 5, 7, 12 ,15, 22, 26, 35, 40, ..]@.
--- * @takeWhile (\m -> 10 - m >= 0) [1, 2, 5, 7, 12 ,15, 22, 26, 35, 40, ..] == [1, 2, 5, 7]@.
--- * @map (\m -> dict ! fromIntegral (10 - m)) [1, 2, 5, 7] == [dict ! 9, dict ! 8, dict ! 5, dict ! 3] == [30, 22, 7, 3]@
--- * @pentagonalSigns [30, 22, 7, 3] == [30, 22, 7, 3] == [30, 22, -7, -3]@
--- * @sum [30, 22, -7, -3] == 42@
---
--- Notes:
--- 1. @tail@ is applied to @pents@ because otherwise the calculation of
--- @p(n)@ would involve a duplicated @p(n-1)@ term (see the above example).
--- 2. Calculating @partition !! k@, where @k@ is any index equal or higher
--- than @maxBound :: Int@ results in undefined behavior.
-
--- | Infinite zero-based table of <https://oeis.org/A000041 partition numbers>.
---
--- >>> take 10 partition
--- [1, 1, 2, 3, 5, 7, 11, 15, 22, 30]
---
--- >>> :set -XDataKinds
--- >>> partition !! 1000 :: Mod 1000
--- (991 `modulo` 1000)
-partition :: Num a => [a]
-partition = 1 : go (IM.singleton 0 1) 1
-  where
-    go :: Num a => IM.IntMap a -> Int -> [a]
-    go dict !n =
-        let n' = (sum .
-                  pentagonalSigns .
-                  map (\m -> dict IM.! (n - m)) .
-                  takeWhile (\m -> n >= m) .
-                  tail) (pents :: [Int])
-            dict' = IM.insert n n' dict
-        in n' : go dict' (n + 1)
-{-# SPECIALIZE partition :: [Int]     #-}
-{-# SPECIALIZE partition :: [Word]    #-}
-{-# SPECIALIZE partition :: [Integer] #-}
-{-# SPECIALIZE partition :: [Natural] #-}
diff --git a/Math/NumberTheory/SmoothNumbers.hs b/Math/NumberTheory/SmoothNumbers.hs
--- a/Math/NumberTheory/SmoothNumbers.hs
+++ b/Math/NumberTheory/SmoothNumbers.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Frederick Schneider
 -- Licence:     MIT
 -- Maintainer:  Frederick Schneider <frederick.schneider2011@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- A <https://en.wikipedia.org/wiki/Smooth_number smooth number>
 -- is an integer, which can be represented as a product of powers of elements
@@ -22,44 +20,49 @@
   , fromSmoothUpperBound
     -- * Generate smooth numbers
   , smoothOver
+  , smoothOver'
   , smoothOverInRange
   , smoothOverInRangeBF
+
+  -- * Verify if a number is smooth
+  , isSmooth
   ) where
 
 import Prelude hiding (div, mod, gcd)
 import Data.Coerce
 import Data.List (nub)
 import qualified Data.Set as S
-import Math.NumberTheory.Euclidean
+import qualified Math.NumberTheory.Euclidean as E
+import Math.NumberTheory.Primes (unPrime)
 import Math.NumberTheory.Primes.Sieve (primes)
 
 -- | An abstract representation of a smooth basis.
--- It consists of a set of coprime numbers ≥2.
+-- It consists of a set of numbers ≥2.
 newtype SmoothBasis a = SmoothBasis { unSmoothBasis :: [a] } deriving (Eq, Show)
 
--- | Build a 'SmoothBasis' from a set of coprime numbers ≥2.
+-- | Build a 'SmoothBasis' from a set of numbers ≥2.
 --
 -- >>> import qualified Data.Set as Set
 -- >>> fromSet (Set.fromList [2, 3])
--- Just (SmoothBasis [2, 3])
--- >>> fromSet (Set.fromList [2, 4]) -- should be coprime
--- Nothing
+-- Just (SmoothBasis {unSmoothBasis = [2,3]})
+-- >>> fromSet (Set.fromList [2, 4])
+-- Just (SmoothBasis {unSmoothBasis = [2,4]})
 -- >>> fromSet (Set.fromList [1, 3]) -- should be >= 2
 -- Nothing
-fromSet :: Euclidean a => S.Set a -> Maybe (SmoothBasis a)
+fromSet :: E.Euclidean a => S.Set a -> Maybe (SmoothBasis a)
 fromSet s = if isValid l then Just (SmoothBasis l) else Nothing where l = S.elems s
 
--- | Build a 'SmoothBasis' from a list of coprime numbers ≥2.
+-- | Build a 'SmoothBasis' from a list of numbers ≥2.
 --
 -- >>> fromList [2, 3]
--- Just (SmoothBasis [2, 3])
+-- Just (SmoothBasis {unSmoothBasis = [2,3]})
 -- >>> fromList [2, 2]
--- Just (SmoothBasis [2])
--- >>> fromList [2, 4] -- should be coprime
--- Nothing
+-- Just (SmoothBasis {unSmoothBasis = [2]})
+-- >>> fromList [2, 4]
+-- Just (SmoothBasis {unSmoothBasis = [2,4]})
 -- >>> fromList [1, 3] -- should be >= 2
 -- Nothing
-fromList :: Euclidean a => [a] -> Maybe (SmoothBasis a)
+fromList :: E.Euclidean a => [a] -> Maybe (SmoothBasis a)
 fromList l = if isValid l' then Just (SmoothBasis l') else Nothing
   where
     l' = nub l
@@ -67,36 +70,53 @@
 -- | Build a 'SmoothBasis' from a list of primes below given bound.
 --
 -- >>> fromSmoothUpperBound 10
--- Just (SmoothBasis [2, 3, 5, 7])
+-- Just (SmoothBasis {unSmoothBasis = [2,3,5,7]})
 -- >>> fromSmoothUpperBound 1
 -- Nothing
 fromSmoothUpperBound :: Integral a => a -> Maybe (SmoothBasis a)
 fromSmoothUpperBound n = if (n < 2)
                          then Nothing
-                         else Just $ SmoothBasis $ takeWhile (<= n) primes
+                         else Just $ SmoothBasis $ takeWhile (<= n) $ map unPrime primes
 
+-- | Helper used by @smoothOver@ (@Integral@ constraint) and @smoothOver'@
+-- (@Euclidean@ constraint) Since the typeclass constraint is just
+-- @Num@, it receives a @norm@ comparison function for the generated smooth
+-- numbers.
+-- This function relies on the fact that for any element of a smooth basis @p@
+-- and any @a@ it is true that @norm (a * p) > norm a@.
+-- This condition is not checked.
+smoothOver' :: forall a b . (Eq a, Num a, Ord b) => (a -> b) -> SmoothBasis a -> [a]
+smoothOver' norm pl =
+    foldr
+    (\p l -> mergeListLists $ iterate (map $ abs . (p*)) l)
+    [1]
+    (nub $ unSmoothBasis pl)
+  where
+    {-# INLINE mergeListLists #-}
+    mergeListLists :: [[a]] -> [a]
+    mergeListLists = foldr go1 []
+      where
+        go1 :: [a] -> [a] -> [a]
+        go1 []    b = b
+        go1 (h:t) b = h:(go2 t b)
+
+        go2 :: [a] -> [a] -> [a]
+        go2 a [] = a
+        go2 [] b = b
+        go2 a@(ah:at) b@(bh:bt)
+          | norm bh < norm ah   = bh : (go2 a bt)
+          | ah == bh    = ah : (go2 at bt)
+          | otherwise = ah : (go2 at b)
+
 -- | Generate an infinite ascending list of
 -- <https://en.wikipedia.org/wiki/Smooth_number smooth numbers>
 -- over a given smooth basis.
 --
 -- >>> import Data.Maybe
 -- >>> take 10 (smoothOver (fromJust (fromList [2, 5])))
--- [1, 2, 4, 5, 8, 10, 16, 20, 25, 32]
+-- [1,2,4,5,8,10,16,20,25,32]
 smoothOver :: Integral a => SmoothBasis a -> [a]
-smoothOver pl = foldr (\p l -> mergeListLists $ iterate (map (p*)) l) [1] (unSmoothBasis pl)
-  where
-    {-# INLINE mergeListLists #-}
-    mergeListLists      = foldr go1 []
-      where
-        go1 :: Ord a => [a] -> [a] -> [a]
-        go1 (h:t) b = h:(go2 t b)
-        go1 _     b = b
-
-        go2 :: Ord a => [a] -> [a] -> [a]
-        go2 a@(ah:at) b@(bh:bt)
-          | bh < ah   = bh : (go2 a bt)
-          | otherwise = ah : (go2 at b) -- no possibility of duplicates
-        go2 a b = if null a then b else a
+smoothOver = smoothOver' abs
 
 -- | Generate an ascending list of
 -- <https://en.wikipedia.org/wiki/Smooth_number smooth numbers>
@@ -108,13 +128,13 @@
 --
 -- >>> import Data.Maybe
 -- >>> smoothOverInRange (fromJust (fromList [2, 5])) 100 200
--- [100, 125, 128, 160, 200]
+-- [100,125,128,160,200]
 smoothOverInRange :: forall a. Integral a => SmoothBasis a -> a -> a -> [a]
 smoothOverInRange s lo hi
   = takeWhile (<= hi)
   $ dropWhile (< lo)
   $ coerce
-  $ smoothOver (coerce s :: SmoothBasis (WrappedIntegral a))
+  $ smoothOver (coerce s :: SmoothBasis (E.WrappedIntegral a))
 
 -- | Generate an ascending list of
 -- <https://en.wikipedia.org/wiki/Smooth_number smooth numbers>
@@ -128,25 +148,34 @@
 --
 -- >>> import Data.Maybe
 -- >>> smoothOverInRangeBF (fromJust (fromList [2, 5])) 100 200
--- [100, 125, 128, 160, 200]
-smoothOverInRangeBF :: forall a. Integral a => SmoothBasis a -> a -> a -> [a]
+-- [100,125,128,160,200]
+smoothOverInRangeBF
+  :: forall a. (Enum a, E.Euclidean a)
+  => SmoothBasis a
+  -> a
+  -> a
+  -> [a]
 smoothOverInRangeBF prs lo hi
   = coerce
-  $ filter (mf prs')
+  $ filter (isSmooth prs)
   $ coerce [lo..hi]
-  where
-    mf :: [WrappedIntegral a] -> WrappedIntegral a -> Bool
-    mf _         0 = False
-    mf []        n = n == 1 -- mf means manually factor
-    mf pl@(p:ps) n = if mod n p == 0
-                     then mf pl (div n p)
-                     else mf ps n
-    prs'           = coerce $ unSmoothBasis prs
 
 -- | isValid assumes that the list is sorted and unique and then checks if the list is suitable to be a SmoothBasis.
-isValid :: Euclidean a => [a] -> Bool
+isValid :: (Eq a, Num a) => [a] -> Bool
 isValid pl = length pl /= 0 && v' pl
   where
-    v' :: Euclidean a => [a] -> Bool
+    v' :: (Eq a, Num a) => [a] -> Bool
     v' []     = True
-    v' (x:xs) = x /= 0 && abs x /= 1 && abs x == x && all (coprime x) xs && v' xs
+    v' (x:xs) = x /= 0 && abs x /= 1 && abs x == x && v' xs
+
+-- | @isSmooth@ checks if a given number is smooth under a certain @SmoothBasis@.
+-- Does not check if the @SmoothBasis@ is valid.
+isSmooth :: forall a . E.Euclidean a => SmoothBasis a -> a -> Bool
+isSmooth prs x = mf (unSmoothBasis prs) x
+  where
+    mf :: [a] -> a -> Bool
+    mf _         0 = False
+    mf []        n = abs n == 1 -- mf means manually factor
+    mf pl@(p:ps) n = if E.mod n p == 0
+                     then mf pl (E.div n p)
+                     else mf ps n
diff --git a/Math/NumberTheory/UniqueFactorisation.hs b/Math/NumberTheory/UniqueFactorisation.hs
--- a/Math/NumberTheory/UniqueFactorisation.hs
+++ b/Math/NumberTheory/UniqueFactorisation.hs
@@ -1,90 +1,13 @@
 -- |
--- Module:      Math.NumberTheory.UniqueFactorisation
--- Copyright:   (c) 2016 Andrew Lelechenko
+-- Module:      Math.NumberTheory.Recurrencies
+-- Description: Deprecated
+-- Copyright:   (c) 2019 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
--- An abstract type class for unique factorisation domains.
---
 
-{-# LANGUAGE CPP               #-}
-{-# LANGUAGE TypeFamilies      #-}
-{-# LANGUAGE DefaultSignatures #-}
-
-module Math.NumberTheory.UniqueFactorisation
-  ( Prime
-  , UniqueFactorisation(..)
-  ) where
-
-import Control.Arrow
-import Data.Coerce
-
-import qualified Math.NumberTheory.Primes.Factorisation as F (factorise)
-import Math.NumberTheory.Primes.Testing.Probabilistic as T (isPrime)
-import Math.NumberTheory.Primes.Types (Prime, Prm(..), PrimeNat(..))
-import qualified Math.NumberTheory.Quadratic.EisensteinIntegers as E
-import qualified Math.NumberTheory.Quadratic.GaussianIntegers as G
-import Math.NumberTheory.Utils.FromIntegral
-
-import Numeric.Natural
-
-type instance Prime G.GaussianInteger = GaussianPrime
-
--- | The following invariant must hold for @n /= 0@:
---
--- > abs n == abs (product (map (\(p, k) -> unPrime p ^ k) (factorise n)))
---
--- The result of 'factorise' should not contain zero powers and should not change after multiplication of the argument by domain's unit.
-class UniqueFactorisation a where
-  unPrime   :: Prime a -> a
-  factorise :: a -> [(Prime a, Word)]
-  isPrime   :: a -> Maybe (Prime a)
-
-  default isPrime :: (Eq a, Num a) => a -> Maybe (Prime a)
-  isPrime 0 = Nothing
-  isPrime n = case factorise n of
-    [(p, 1)] -> Just p
-    _        -> Nothing
-
-  {-# MINIMAL unPrime, factorise #-}
-
-instance UniqueFactorisation Int where
-  unPrime   = coerce wordToInt
-  factorise = map (coerce integerToWord *** intToWord) . F.factorise . intToInteger
-
-instance UniqueFactorisation Word where
-  unPrime   = coerce
-  factorise = map (coerce integerToWord *** intToWord) . F.factorise . wordToInteger
-  isPrime n = if T.isPrime (toInteger n) then Just (coerce n) else Nothing
-
-instance UniqueFactorisation Integer where
-  unPrime   = coerce naturalToInteger
-  factorise = map (coerce integerToNatural *** intToWord) . F.factorise
-  isPrime n = if T.isPrime n then Just (coerce $ integerToNatural $ abs n) else Nothing
-
-instance UniqueFactorisation Natural where
-  unPrime   = coerce
-  factorise = map (coerce integerToNatural *** intToWord) . F.factorise . naturalToInteger
-  isPrime n = if T.isPrime (toInteger n) then Just (coerce n) else Nothing
-
-newtype GaussianPrime = GaussianPrime { _unGaussianPrime :: G.GaussianInteger }
-  deriving (Eq, Show)
-
-instance UniqueFactorisation G.GaussianInteger where
-  unPrime = coerce
-
-  factorise 0 = []
-  factorise g = map (coerce *** intToWord) $ G.factorise g
-
-newtype EisensteinPrime = EisensteinPrime { _unEisensteinPrime :: E.EisensteinInteger }
-  deriving (Eq, Show)
-
-type instance Prime E.EisensteinInteger = EisensteinPrime
-
-instance UniqueFactorisation E.EisensteinInteger where
-  unPrime = coerce
+module Math.NumberTheory.UniqueFactorisation {-# DEPRECATED "Use `Math.NumberTheory.Primes` instead." #-}
+    ( module Math.NumberTheory.Primes
+    ) where
 
-  factorise 0 = []
-  factorise e = map (coerce *** intToWord) $ E.factorise e
+import Math.NumberTheory.Primes
diff --git a/Math/NumberTheory/Unsafe.hs b/Math/NumberTheory/Unsafe.hs
--- a/Math/NumberTheory/Unsafe.hs
+++ b/Math/NumberTheory/Unsafe.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Layer to switch between safe and unsafe arrays.
 --
diff --git a/Math/NumberTheory/Utils.hs b/Math/NumberTheory/Utils.hs
--- a/Math/NumberTheory/Utils.hs
+++ b/Math/NumberTheory/Utils.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2011 Daniel Fischer
 -- Licence:     MIT
 -- Maintainer:  Daniel Fischer <daniel.is.fischer@googlemail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Some utilities, mostly for bit twiddling.
 --
@@ -25,17 +23,23 @@
     , mergeBy
 
     , recipMod
+
+    , toWheel30
+    , fromWheel30
     ) where
 
 #include "MachDeps.h"
 
+import Prelude hiding (mod, quotRem)
+import qualified Prelude as P
+
 import GHC.Base
 
-import GHC.Integer
 import GHC.Integer.GMP.Internals
 import GHC.Natural
 
 import Data.Bits
+import Math.NumberTheory.Euclidean
 
 uncheckedShiftR :: Word -> Int -> Word
 uncheckedShiftR (W# w#) (I# i#) = W# (uncheckedShiftRL# w# i#)
@@ -50,58 +54,56 @@
 "shiftToOddCount/Natural"   shiftToOddCount = shiftOCNatural
   #-}
 {-# INLINE [1] shiftToOddCount #-}
-shiftToOddCount :: Integral a => a -> (Int, a)
+shiftToOddCount :: Integral a => a -> (Word, a)
 shiftToOddCount n = case shiftOCInteger (fromIntegral n) of
                       (z, o) -> (z, fromInteger o)
 
 -- | Specialised version for @'Word'@.
 --   Precondition: argument strictly positive (not checked).
-shiftOCWord :: Word -> (Int, Word)
+shiftOCWord :: Word -> (Word, Word)
 shiftOCWord (W# w#) = case shiftToOddCount# w# of
-                        (# z# , u# #) -> (I# z#, W# u#)
+                        (# z# , u# #) -> (W# z#, W# u#)
 
 -- | Specialised version for @'Int'@.
 --   Precondition: argument nonzero (not checked).
-shiftOCInt :: Int -> (Int, Int)
+shiftOCInt :: Int -> (Word, Int)
 shiftOCInt (I# i#) = case shiftToOddCount# (int2Word# i#) of
-                        (# z#, u# #) -> (I# z#, I# (word2Int# u#))
+                        (# z#, u# #) -> (W# z#, I# (word2Int# u#))
 
 -- | Specialised version for @'Integer'@.
 --   Precondition: argument nonzero (not checked).
-shiftOCInteger :: Integer -> (Int, Integer)
+shiftOCInteger :: Integer -> (Word, Integer)
 shiftOCInteger n@(S# i#) =
     case shiftToOddCount# (int2Word# i#) of
-      (# z#, w# #)
-        | isTrue# (z# ==# 0#) -> (0, n)
-        | otherwise -> (I# z#, S# (word2Int# w#))
+      (# 0##, _ #) -> (0, n)
+      (# z#, w# #) -> (W# z#, wordToInteger w#)
 shiftOCInteger n@(Jp# bn#) = case bigNatZeroCount bn# of
-                                 0#  -> (0, n)
-                                 z#  -> (I# z#, n `shiftRInteger` z#)
+                                 0## -> (0, n)
+                                 z#  -> (W# z#, bigNatToInteger (bn# `shiftRBigNat` (word2Int# z#)))
 shiftOCInteger n@(Jn# bn#) = case bigNatZeroCount bn# of
-                                 0#  -> (0, n)
-                                 z#  -> (I# z#, n `shiftRInteger` z#)
+                                 0## -> (0, n)
+                                 z#  -> (W# z#, bigNatToNegInteger (bn# `shiftRBigNat` (word2Int# z#)))
 
 -- | Specialised version for @'Natural'@.
 --   Precondition: argument nonzero (not checked).
-shiftOCNatural :: Natural -> (Int, Natural)
+shiftOCNatural :: Natural -> (Word, Natural)
 shiftOCNatural n@(NatS# i#) =
     case shiftToOddCount# i# of
-      (# z#, w# #)
-        | isTrue# (z# ==# 0#) -> (0, n)
-        | otherwise -> (I# z#, NatS# w#)
+      (# 0##, _ #) -> (0, n)
+      (# z#, w# #) -> (W# z#, NatS# w#)
 shiftOCNatural n@(NatJ# bn#) = case bigNatZeroCount bn# of
-                                 0#  -> (0, n)
-                                 z#  -> (I# z#, NatJ# (bn# `shiftRBigNat` z#))
+                                 0## -> (0, n)
+                                 z#  -> (W# z#, bigNatToNatural (bn# `shiftRBigNat` (word2Int# z#)))
 
 -- | Count trailing zeros in a @'BigNat'@.
 --   Precondition: argument nonzero (not checked, Integer invariant).
-bigNatZeroCount :: BigNat -> Int#
-bigNatZeroCount bn# = count 0# 0#
+bigNatZeroCount :: BigNat -> Word#
+bigNatZeroCount bn# = count 0## 0#
   where
     count a# i# =
           case indexBigNat# bn# i# of
-            0## -> count (a# +# WORD_SIZE_IN_BITS#) (i# +# 1#)
-            w#  -> a# +# word2Int# (ctz# w#)
+            0## -> count (a# `plusWord#` WORD_SIZE_IN_BITS##) (i# +# 1#)
+            w#  -> a# `plusWord#` ctz# w#
 
 -- | Remove factors of @2@. If @n = 2^k*m@ with @m@ odd, the result is @m@.
 --   Precondition: argument not @0@ (not checked).
@@ -127,13 +129,13 @@
 -- | Specialised version for @'Int'@.
 --   Precondition: argument nonzero (not checked).
 shiftOInteger :: Integer -> Integer
-shiftOInteger (S# i#) = S# (word2Int# (shiftToOdd# (int2Word# i#)))
-shiftOInteger n@(Jn# bn#) = case bigNatZeroCount bn# of
-                                 0#  -> n
-                                 z#  -> n `shiftRInteger` z#
+shiftOInteger (S# i#) = wordToInteger (shiftToOdd# (int2Word# i#))
 shiftOInteger n@(Jp# bn#) = case bigNatZeroCount bn# of
-                                 0#  -> n
-                                 z#  -> n `shiftRInteger` z#
+                                 0## -> n
+                                 z#  -> bigNatToInteger (bn# `shiftRBigNat` (word2Int# z#))
+shiftOInteger n@(Jn# bn#) = case bigNatZeroCount bn# of
+                                 0## -> n
+                                 z#  -> bigNatToNegInteger (bn# `shiftRBigNat` (word2Int# z#))
 
 -- | Shift argument right until the result is odd.
 --   Precondition: argument not @0@, not checked.
@@ -141,9 +143,9 @@
 shiftToOdd# w# = uncheckedShiftRL# w# (word2Int# (ctz# w#))
 
 -- | Like @'shiftToOdd#'@, but count the number of places to shift too.
-shiftToOddCount# :: Word# -> (# Int#, Word# #)
-shiftToOddCount# w# = case word2Int# (ctz# w#) of
-                        k# -> (# k#, uncheckedShiftRL# w# k# #)
+shiftToOddCount# :: Word# -> (# Word#, Word# #)
+shiftToOddCount# w# = case ctz# w# of
+                        k# -> (# k#, uncheckedShiftRL# w# (word2Int# k#) #)
 
 -- | Number of 1-bits in a @'Word#'@.
 bitCountWord# :: Word# -> Int#
@@ -158,7 +160,7 @@
 bitCountInt :: Int -> Int
 bitCountInt = popCount
 
-splitOff :: Integral a => a -> a -> (Int, a)
+splitOff :: Euclidean a => a -> a -> (Word, a)
 splitOff _ 0 = (0, 0) -- prevent infinite loop
 splitOff p n = go 0 n
   where
@@ -169,12 +171,12 @@
 
 -- | It is difficult to convince GHC to unbox output of 'splitOff' and 'splitOff.go',
 -- so we fallback to a specialized unboxed version to minimize allocations.
-splitOff# :: Word# -> Word# -> (# Int#, Word# #)
-splitOff# _ 0## = (# 0#, 0## #)
-splitOff# p n = go 0# n
+splitOff# :: Word# -> Word# -> (# Word#, Word# #)
+splitOff# _ 0## = (# 0##, 0## #)
+splitOff# p n = go 0## n
   where
     go k m = case m `quotRemWord#` p of
-      (# q, 0## #) -> go (k +# 1#) q
+      (# q, 0## #) -> go (k `plusWord#` 1##) q
       _            -> (# k, m #)
 {-# INLINABLE splitOff# #-}
 
@@ -195,3 +197,21 @@
 recipMod x m = case recipModInteger (x `mod` m) m of
   0 -> Nothing
   y -> Just y
+
+bigNatToNatural :: BigNat -> Natural
+bigNatToNatural bn
+  | isTrue# (sizeofBigNat# bn ==# 1#) = NatS# (bigNatToWord bn)
+  | otherwise = NatJ# bn
+
+-------------------------------------------------------------------------------
+-- Helpers for mapping to rough numbers and back.
+-- Copypasted from Data.BitStream.WheelMapping
+
+toWheel30 :: (Integral a, Bits a) => a -> a
+toWheel30 i = q `shiftL` 3 + (r + r `shiftR` 4) `shiftR` 2
+  where
+    (q, r) = i `P.quotRem` 30
+
+fromWheel30 :: (Num a, Bits a) => a -> a
+fromWheel30 i = ((i `shiftL` 2 - i `shiftR` 2) .|. 1)
+              + ((i `shiftL` 1 - i `shiftR` 1) .&. 2)
diff --git a/Math/NumberTheory/Utils/DirichletSeries.hs b/Math/NumberTheory/Utils/DirichletSeries.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Utils/DirichletSeries.hs
@@ -0,0 +1,86 @@
+-- |
+-- Module:      Math.NumberTheory.Utils.DirichletSeries
+-- Copyright:   (c) 2018 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- An abstract representation of a Dirichlet series over a semiring.
+--
+
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE ViewPatterns          #-}
+
+module Math.NumberTheory.Utils.DirichletSeries
+  ( DirichletSeries
+  , fromDistinctAscList
+  , lookup
+  , filter
+  , partition
+  , unions
+  , union
+  , size
+  , timesAndCrop
+  ) where
+
+import Prelude hiding (filter, last, rem, quot, snd, lookup)
+import Data.Coerce
+import Data.Map (Map)
+import qualified Data.Map.Strict as M
+import Data.Semiring (Semiring(..))
+import Numeric.Natural
+
+import Math.NumberTheory.Euclidean
+
+-- Sparse Dirichlet series are represented by an ascending list of pairs.
+-- For instance, [(a, b), (c, d)] stands for 1 + b/a^s + d/c^s.
+-- Note that the representation still may include a term (1, b), so
+-- [(1, b), (c, d)] means (1 + b) + d/c^s.
+newtype DirichletSeries a b = DirichletSeries { _unDirichletSeries :: Map a b }
+  deriving (Show)
+
+fromDistinctAscList :: forall a b. [(a, b)] -> DirichletSeries a b
+fromDistinctAscList = coerce (M.fromDistinctAscList @a @b)
+
+lookup :: (Ord a, Num a, Semiring b) => a -> DirichletSeries a b -> b
+lookup 1 (DirichletSeries m) = M.findWithDefault zero 1 m `plus` one
+lookup a (DirichletSeries m) = M.findWithDefault zero a m
+
+filter :: forall a b. (a -> Bool) -> DirichletSeries a b -> DirichletSeries a b
+filter predicate = coerce (M.filterWithKey @a @b (\k _ -> predicate k))
+
+partition :: forall a b. (a -> Bool) -> DirichletSeries a b -> (DirichletSeries a b, DirichletSeries a b)
+partition predicate = coerce (M.partitionWithKey @a @b (\k _ -> predicate k))
+
+unions :: forall a b. (Ord a, Semiring b) => [DirichletSeries a b] -> DirichletSeries a b
+unions = coerce (M.unionsWith plus :: [Map a b] -> Map a b)
+
+union :: forall a b. (Ord a, Semiring b) => DirichletSeries a b -> DirichletSeries a b -> DirichletSeries a b
+union = coerce (M.unionWith @a @b plus)
+
+size :: forall a b. DirichletSeries a b -> Int
+size = coerce (M.size @a @b)
+
+-- | Let as = sum_i k_i/a_i^s and bs = sum_i l_i/b_i^s be Dirichlet series,
+-- and all a_i and b_i are divisors of n. Return Dirichlet series cs,
+-- which contains all terms as * bs = sum_i m_i/c_i^s such that c_i divides n.
+timesAndCrop
+  :: (Euclidean a, Ord a, Semiring b)
+  => a
+  -> DirichletSeries a b
+  -> DirichletSeries a b
+  -> DirichletSeries a b
+timesAndCrop n (DirichletSeries as) (DirichletSeries bs)
+  = DirichletSeries
+  $ M.unionWith plus (M.unionWith plus as bs)
+  $ M.fromListWith plus
+  [ (a * b, fa `times` fb)
+  | (b, fb) <- M.assocs bs
+  , let nb = n `quot` b
+  , (a, fa) <- takeWhile ((<= nb) . fst) (M.assocs as)
+  , nb `rem` a == 0
+  ]
+{-# SPECIALISE timesAndCrop :: Semiring b => Int -> DirichletSeries Int b -> DirichletSeries Int b -> DirichletSeries Int b #-}
+{-# SPECIALISE timesAndCrop :: Semiring b => Word -> DirichletSeries Word b -> DirichletSeries Word b -> DirichletSeries Word b #-}
+{-# SPECIALISE timesAndCrop :: Semiring b => Integer -> DirichletSeries Integer b -> DirichletSeries Integer b -> DirichletSeries Integer b #-}
+{-# SPECIALISE timesAndCrop :: Semiring b => Natural -> DirichletSeries Natural b -> DirichletSeries Natural b -> DirichletSeries Natural b #-}
diff --git a/Math/NumberTheory/Utils/FromIntegral.hs b/Math/NumberTheory/Utils/FromIntegral.hs
--- a/Math/NumberTheory/Utils/FromIntegral.hs
+++ b/Math/NumberTheory/Utils/FromIntegral.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Monomorphic `fromIntegral`.
 --
@@ -19,6 +17,7 @@
   , naturalToInteger
   , integerToNatural
   , integerToWord
+  , integerToInt
   ) where
 
 import Numeric.Natural
@@ -50,3 +49,7 @@
 integerToWord :: Integer -> Word
 integerToWord = fromIntegral
 {-# INLINE integerToWord #-}
+
+integerToInt :: Integer -> Int
+integerToInt = fromIntegral
+{-# INLINE integerToInt #-}
diff --git a/Math/NumberTheory/Utils/Hyperbola.hs b/Math/NumberTheory/Utils/Hyperbola.hs
--- a/Math/NumberTheory/Utils/Hyperbola.hs
+++ b/Math/NumberTheory/Utils/Hyperbola.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Highest points under hyperbola.
 --
diff --git a/Math/NumberTheory/Zeta.hs b/Math/NumberTheory/Zeta.hs
--- a/Math/NumberTheory/Zeta.hs
+++ b/Math/NumberTheory/Zeta.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Interface to work with Riemann zeta-function and Dirichlet beta-function.
 
@@ -12,10 +10,12 @@
 
 module Math.NumberTheory.Zeta
   ( module Math.NumberTheory.Zeta.Dirichlet
+  , module Math.NumberTheory.Zeta.Hurwitz
   , module Math.NumberTheory.Zeta.Riemann
   , module Math.NumberTheory.Zeta.Utils
   ) where
 
 import Math.NumberTheory.Zeta.Dirichlet
+import Math.NumberTheory.Zeta.Hurwitz
 import Math.NumberTheory.Zeta.Riemann
 import Math.NumberTheory.Zeta.Utils
diff --git a/Math/NumberTheory/Zeta/Dirichlet.hs b/Math/NumberTheory/Zeta/Dirichlet.hs
--- a/Math/NumberTheory/Zeta/Dirichlet.hs
+++ b/Math/NumberTheory/Zeta/Dirichlet.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Dirichlet beta-function.
 
@@ -16,25 +14,21 @@
   , betasOdd
   ) where
 
-import Data.ExactPi                     (ExactPi (..), approximateValue)
-import Data.List                        (zipWith4)
-import Data.Ratio                       ((%))
+import Data.ExactPi
+import Data.List                      (zipWith4)
+import Data.Ratio                     ((%))
 
-import Math.NumberTheory.Recurrencies   (euler, eulerPolyAt1, factorial)
-import Math.NumberTheory.Zeta.Riemann   (zetasOdd)
-import Math.NumberTheory.Zeta.Utils     (intertwine, skipEvens, skipOdds,
-                                         suminf)
+import Math.NumberTheory.Recurrences  (euler, factorial)
+import Math.NumberTheory.Zeta.Hurwitz (zetaHurwitz)
+import Math.NumberTheory.Zeta.Utils   (intertwine, skipOdds)
 
 -- | Infinite sequence of exact values of Dirichlet beta-function at odd arguments, starting with @β(1)@.
 --
--- > > approximateValue (betasOdd !! 25) :: Double
--- > 0.9999999999999987
---
--- Using 'Data.Number.Fixed.Fixed':
---
--- > > approximateValue (betasOdd !! 25) :: Fixed Prec50
--- > 0.99999999999999999999999960726927497384196726751694z
---
+-- >>> approximateValue (betasOdd !! 25) :: Double
+-- 0.9999999999999987
+-- >>> import Data.Number.Fixed
+-- >>> approximateValue (betasOdd !! 25) :: Fixed Prec50
+-- 0.99999999999999999999999960726927497384196726751694
 betasOdd :: [ExactPi]
 betasOdd = zipWith Exact [1, 3 ..] $ zipWith4
                                      (\sgn denom eul twos -> sgn * (eul % (twos * denom)))
@@ -43,127 +37,34 @@
                                      (skipOdds euler)
                                      (iterate (4 *) 4)
 
--- | @betasOdd@, but with @forall a . Floating a => a@ instead of @ExactPi@s.
--- Used in @betasEven@.
-betasOdd' :: Floating a => [a]
-betasOdd' = map approximateValue betasOdd
-
 -- | Infinite sequence of approximate values of the Dirichlet @β@ function at
 -- positive even integer arguments, starting with @β(0)@.
 betasEven :: forall a. (Floating a, Ord a) => a -> [a]
-betasEven eps = (1 / 2) : bets
+betasEven eps = (1 / 2) : hurwitz
   where
-    bets :: [a]
-    bets = zipWith3 (\r1 r2 r3 -> (r1 + (negate r2) + r3)) rhs1 rhs2 rhs3
-
-    -- [1!, 3!, 5!..]
-    factorial1AsInteger :: [Integer]
-    factorial1AsInteger = skipEvens factorial
-
-    -- [1!, 3!, 5!..]
-    factorial1 :: [a]
-    factorial1 = map fromInteger factorial1AsInteger
-
-    -- [2^1 * 1!, 2^3 * 3!, 2^5 * 5!, 2^7 * 7! ..]
-    denoms :: [a]
-    denoms = zipWith
-             (\pow fac -> fromInteger $ pow * fac)
-             factorial1AsInteger
-             (iterate (4 *) 2)
-
-    -- First term of the right hand side of (12).
-    rhs1 = zipWith
-           (\sgn piFrac -> sgn * piFrac * log 2)
-           (cycle [1, -1])
-           (zipWith (\p f -> p / f) (iterate ((pi * pi) *) pi) denoms)
-
-    -- [1 - (1 / (2^2)), 1 - (1 / (2^4)), 1 - (1 / (2^6)), ..]
-    second :: [a]
-    second = map (1 -) $ (iterate (/ 4) (1/4))
-
-    -- [- (1 - (1 / (2^2))) * zeta(3), (1 - (1 / (2^4))) * zeta(5), - (1 - (1 / (2^6))) * zeta(7), ..]
-    zets :: [a]
-    zets = zipWith3
-           (\sgn twosFrac z -> sgn * twosFrac * z)
-           (cycle [-1, 1])
-           second
-           (tail $ zetasOdd eps)
-
-    -- [pi / (2^1 * 1!), pi^3 / (2^3 * 3!), pi^5 / (2^5 * 5!), ..]
-    pisAndFacs :: [a]
-    pisAndFacs = zipWith3
-                 (\p pow fac -> p / (pow * fac))
-                 (iterate ((pi * pi) *) pi)
-                 (iterate (4 *) 2)
-                 factorial1
-
-    -- [[], [pisAndFacs !! 0], [pisAndFacs !! 1, pisAndFacs !! 0], [pisAndFacs !! 2, pisAndFacs !! 1, pisAndFacs !! 0]...]
-    pisAndFacs' :: [[a]]
-    pisAndFacs' = scanl (flip (:)) [] pisAndFacs
-
-    -- Second summand of RHS in (12) for k = [1 ..]
-    rhs2 :: [a]
-    rhs2 = zipWith (*) (cycle [-1, 1]) $ map (sum . zipWith (*) zets) pisAndFacs'
-
-    -- [pi^3 / (2^4), pi^5 / (2^6), pi^7 / (2^8) ..]
-    -- Second factor of third addend in RHS of (12).
-    pis :: [a]
-    pis = zipWith
-          (\p f -> p / f)
-          (iterate ((pi * pi) *) (pi ^^ (3 :: Integer)))
-          (iterate (4 *) 16)
-
-    -- [[3!, 5!, 7! ..], [5!, 7! ..] ..]
-    oddFacs :: [[a]]
-    oddFacs = iterate tail (tail factorial1)
-
-    -- [1, 4, 16 ..]
-    fours :: [a]
-    fours = iterate (4 *) 1
-
-    -- [[3! * 2^0, 5! * 2^2, 7! * 2^4 ..], [5! * 2^0, 7! * 2^2, 9! * 2^4 ..] ..]
-    infSumDenoms :: [[a]]
-    infSumDenoms = map (zipWith (*) fours) oddFacs
-
-    -- [pi^0, pi^2, pi^4, pi^6 ..]
-    pis2 :: [a]
-    pis2 = iterate ((pi * pi) *) 1
-
-    -- [pi^0 * E_1(1), - pi^2 * E_3(1), pi^4 * E_5(1) ..]
-    infSumNum :: [a]
-    infSumNum = zipWith3
-                (\sgn p eulerP -> sgn * p * eulerP)
-                (cycle [1, -1])
-                pis2
-                (map fromRational . skipEvens $ eulerPolyAt1)
-
-    -- [     [ pi^0 * E_1(1)  (-1) * pi^2 * E_3(1)   ]      [ (-1) * pi^2 * E_3(1)  pi^4 * E_5(1)    ]      [ pi^4 * E_5(1)  (-1) * pi^6 * E_7(1)    ]  ]
-    -- | sum | -------------, -------------------- ..|, sum | --------------------, ------------- .. |, sum | -------------, -------------------- .. |..|
-    -- [     [       3!                 5!           ]      [          5!                 7!         ]      [       7!                9!             ]  ]
-    infSum :: [a]
-    infSum = map (suminf eps . zipWith (/) infSumNum) infSumDenoms
-
-    -- Third summand of the right hand side of (12).
-    rhs3 :: [a]
-    rhs3 = zipWith3
-           (\sgn p inf -> sgn * p * inf)
-           (cycle [-1, 1])
-           pis
-           infSum
+    hurwitz :: [a]
+    hurwitz =
+        zipWith3 (\quarter threeQuarters four ->
+            (quarter - threeQuarters) / four)
+        (tail . skipOdds $ zetaHurwitz eps 0.25)
+        (tail . skipOdds $ zetaHurwitz eps 0.75)
+        (iterate (16 *) 16)
 
 -- | Infinite sequence of approximate (up to given precision)
 -- values of Dirichlet beta-function at integer arguments, starting with @β(0)@.
--- The algorithm used to compute @β@ for even arguments was derived from
--- <https://arxiv.org/pdf/0910.5004.pdf An Euler-type formula for β(2n) and closed-form expressions for a class of zeta series>
--- by F. M. S. Lima, formula (12).
 --
--- > > take 5 (betas 1e-14) :: [Double]
--- > [0.5,0.7853981633974483,0.9159655941772191,0.9689461462593693,0.988944551741105]
+-- The algorithm previously used to compute @β@ for even arguments was derived
+-- from <https://arxiv.org/pdf/0910.5004.pdf An Euler-type formula for β(2n) and closed-form expressions for a class of zeta series>
+-- by F. M. S. Lima, formula (12), but is now based on the
+-- 'Math.NumberTheory.Zeta.Hurwitz.zetaHurwitz' recurrence.
+--
+-- >>> take 5 (betas 1e-14) :: [Double]
+-- [0.5,0.7853981633974483,0.9159655941772189,0.9689461462593694,0.9889445517411051]
 betas :: (Floating a, Ord a) => a -> [a]
 betas eps = e : o : scanl1 f (intertwine es os)
   where
     e : es = betasEven eps
-    o : os = betasOdd'
+    o : os = map (getRationalLimit (\a b -> abs (a - b) < eps) . rationalApproximations) betasOdd
 
     -- Cap-and-floor to improve numerical stability:
     -- 1 > beta(n + 1) - 1 > (beta(n) - 1) / 2
diff --git a/Math/NumberTheory/Zeta/Hurwitz.hs b/Math/NumberTheory/Zeta/Hurwitz.hs
new file mode 100644
--- /dev/null
+++ b/Math/NumberTheory/Zeta/Hurwitz.hs
@@ -0,0 +1,125 @@
+-- |
+-- Module:      Math.NumberTheory.Zeta.Hurwitz
+-- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
+-- Licence:     MIT
+-- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
+--
+-- Hurwitz zeta function.
+
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Math.NumberTheory.Zeta.Hurwitz
+  ( zetaHurwitz
+  ) where
+
+import Math.NumberTheory.Recurrences (bernoulli, factorial)
+import Math.NumberTheory.Zeta.Utils  (skipEvens, skipOdds)
+
+-- | Values of Hurwitz zeta function evaluated at @ζ(s, a)@ with
+-- @forall t1 . (Floating t1, Ord t1) => a ∈ t1@, and @s ∈ [0, 1 ..]@.
+--
+-- The algorithm used was based on the Euler-Maclaurin formula and was derived
+-- from <http://fredrikj.net/thesis/thesis.pdf Fast and Rigorous Computation of Special Functions to High Precision>
+-- by F. Johansson, chapter 4.8, formula 4.8.5.
+--
+-- The error for each value in this recurrence is given in formula 4.8.9 as an
+--  indefinite integral, and in formula 4.8.12 as a closed form formula.
+--
+-- It is the __user's responsibility__ to provide an appropriate precision for
+-- the type chosen.
+--
+-- For instance, when using @Double@s, it does not make sense
+-- to provide a number @ε >= 1e-53@ as the desired precision. For @Float@s,
+-- providing an @ε >= 1e-24@ also does not make sense.
+-- Example of how to call the function:
+--
+-- >>> zetaHurwitz 1e-15 0.25 !! 5
+-- 1024.3489745265808
+zetaHurwitz :: forall a . (Floating a, Ord a) => a -> a -> [a]
+zetaHurwitz eps a = zipWith3 (\s i t -> s + i + t) ss is ts
+  where
+    -- When given @1e-14@ as the @eps@ argument, this'll be
+    -- @div (33 * (length . takeWhile (>= 1) . iterate (/ 10) . recip) 1e-14) 10 == div (33 * 14) 10@
+    -- @div (33 * 14) 10 == 46.
+    -- meaning @N,M@ in formula 4.8.5 will be @46@.
+    -- Multiplying by 33 and dividing by 10 is because asking for @14@ digits
+    -- of decimal precision equals asking for @(log 10 / log 2) * 14 ~ 3.3 * 14 ~ 46@
+    -- bits of precision.
+    digitsOfPrecision :: Integer
+    digitsOfPrecision =
+       let magnitude = toInteger . length . takeWhile (>= 1) . iterate (/ 10) . recip $ eps
+       in  div (magnitude * 33) 10
+
+    -- @a + n@
+    aPlusN :: a
+    aPlusN = a + fromIntegral digitsOfPrecision
+
+    -- @[(a + n)^s | s <- [0, 1, 2 ..]]@
+    powsOfAPlusN :: [a]
+    powsOfAPlusN = iterate (aPlusN *) 1
+
+    -- [                   [      1      ] |                   ]
+    -- | \sum_{k=0}^\(n-1) | ----------- | | s <- [0, 1, 2 ..] |
+    -- [                   [ (a + k) ^ s ] |                   ]
+    -- @S@ value in 4.8.5 formula.
+    ss :: [a]
+    ss = let numbers = map ((a +) . fromInteger) [0..digitsOfPrecision-1]
+             denoms  = replicate (fromInteger digitsOfPrecision) 1 :
+                       iterate (zipWith (*) numbers) numbers
+         in map (sum . map recip) denoms
+
+    -- [ (a + n) ^ (1 - s)            a + n         |                   ]
+    -- | ----------------- = ---------------------- | s <- [0, 1, 2 ..] |
+    -- [       s - 1          (a + n) ^ s * (s - 1) |                   ]
+    -- @I@ value in 4.8.5 formula.
+    is :: [a]
+    is = let denoms = zipWith
+                      (\powOfA int -> powOfA * fromInteger int)
+                      powsOfAPlusN
+                      [-1, 0..]
+         in zipWith (/) (repeat aPlusN) denoms
+
+    -- [      1      |             ]
+    -- [ ----------- | s <- [0 ..] ]
+    -- [ (a + n) ^ s |             ]
+    constants2 :: [a]
+    constants2 = map recip powsOfAPlusN
+
+    -- [ [(s)_(2*k - 1) | k <- [1 ..]], s <- [0 ..]], i.e. odd indices of
+    -- infinite rising factorial sequences, each sequence starting at a
+    -- positive integer.
+    pochhammers :: [[Integer]]
+    pochhammers = let -- [ [(s)_k | k <- [1 ..]], s <- [1 ..]]
+                      pochhs :: [[Integer]]
+                      pochhs = iterate (\(x : xs) -> map (`div` x) xs) (tail factorial)
+                  in -- When @s@ is @0@, the infinite sequence of rising
+                     -- factorials starting at @s@ is @[0,0,0,0..]@.
+                     repeat 0 : map skipOdds pochhs
+
+    -- [            B_2k           |             ]
+    -- | ------------------------- | k <- [1 ..] |
+    -- [ (2k)! (a + n) ^ (2*k - 1) |             ]
+    second :: [a]
+    second =
+        take (fromInteger digitsOfPrecision) $
+        zipWith3
+        (\bern evenFac denom -> fromRational bern / (denom * fromInteger evenFac))
+        (tail $ skipOdds bernoulli)
+        (tail $ skipOdds factorial)
+        -- Recall that @powsOfAPlusN = [(a + n) ^ s | s <- [0 ..]]@, so this
+        -- is @[(a + n) ^ (2 * s - 1) | s <- [1 ..]]@
+        (skipEvens powsOfAPlusN)
+
+    fracs :: [a]
+    fracs = zipWith
+            (\sec pochh -> sum $ zipWith (\s p -> s * fromInteger p) sec pochh)
+            (repeat second)
+            pochhammers
+
+    -- Infinite list of @T@ values in 4.8.5 formula, for every @s@ in
+    -- @[0, 1, 2 ..]@.
+    ts :: [a]
+    ts = zipWith
+         (\constant2 frac -> constant2 * (0.5 + frac))
+         constants2
+         fracs
diff --git a/Math/NumberTheory/Zeta/Riemann.hs b/Math/NumberTheory/Zeta/Riemann.hs
--- a/Math/NumberTheory/Zeta/Riemann.hs
+++ b/Math/NumberTheory/Zeta/Riemann.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Riemann zeta-function.
 
@@ -16,11 +14,12 @@
   , zetasOdd
   ) where
 
-import Data.ExactPi                     (ExactPi (..), approximateValue)
-import Data.Ratio                       ((%))
+import Data.ExactPi
+import Data.Ratio                     ((%))
 
-import Math.NumberTheory.Recurrencies   (bernoulli, factorial)
-import Math.NumberTheory.Zeta.Utils     (intertwine, skipOdds, suminf)
+import Math.NumberTheory.Recurrences  (bernoulli)
+import Math.NumberTheory.Zeta.Hurwitz (zetaHurwitz)
+import Math.NumberTheory.Zeta.Utils   (intertwine, skipEvens, skipOdds)
 
 -- | Infinite sequence of exact values of Riemann zeta-function at even arguments, starting with @ζ(0)@.
 -- Note that due to numerical errors conversion to 'Double' may return values below 1:
@@ -39,66 +38,29 @@
   where
     cs = (- 1 % 2) : zipWith (\i f -> i * (-4) / fromInteger (2 * f * (2 * f - 1))) cs [1..]
 
-zetasEven' :: Floating a => [a]
-zetasEven' = map approximateValue zetasEven
-
+-- | Infinite sequence of approximate values of Riemann zeta-function
+-- at odd arguments, starting with @ζ(1)@.
 zetasOdd :: forall a. (Floating a, Ord a) => a -> [a]
-zetasOdd eps = (1 / 0) : zets
-  where
-    zets :: [a] -- [zeta(3), zeta(5), zeta(7)...]
-    zets = zipWith (*) zs (tail (iterate (* (- pi * pi)) 1))
-
-    zs :: [a] -- [zeta(3) / (-pi^2), zeta(5) / pi^4, zeta(7) / (-pi^6)...]
-    zs = zipWith (\w f -> negate (w / (1 + f))) ws fourth
-
-    ys :: [a] -- [(1 - 1/4) * zeta(3) / (-pi^2), (1 - 1/4^2) * zeta(5) / pi^4...]
-    ys = zipWith (*) zs fourth
-    yss :: [[a]] -- [[], [ys !! 0], [ys !! 1, ys !! 0], [ys !! 2, ys !! 1, ys !! 0]...]
-    yss = scanl (flip (:)) [] ys
-
-    xs :: [a] -- first summand of RHS in (57) for m=[1..]
-    xs = map (sum . zipWith (flip (/)) factorial2) yss
-
-    ws :: [a] -- RHS in (57) for m=[1..]
-    ws = zipWith (+) xs cs
-
-    rs :: [a] -- [1, 1/2, 1/3, 1/4...]
-    rs = map (\n -> recip (fromInteger n)) [1..]
-    rss :: [[a]] -- [[1, 1/2, 1/3...], [1/2, 1/3, 1/4...], [1/3, 1/4...]]
-    rss = iterate tail rs
-
-    factorial2 :: [a] -- [2!, 4!, 6!..]
-    factorial2 = map fromInteger $ tail $ skipOdds factorial
-
-    fourth :: [a] -- [1 - 1/4, 1 - 1/4^2, 1 - 1/4^3...]
-    fourth = tail $ map (1 -) $ iterate (/ 4) 1
-
-    as :: [a] -- [zeta(0), zeta(2)/4, zeta(2*2)/4^2, zeta(2*3)/4^3...]
-    as = zipWith (/) zetasEven' (iterate (* 4) 1)
-
-    bs :: [a] -- map (+ log 2) [b(1), b(2), b(3)...],
-              -- where b(m) = \sum_{n=0}^\infty (zeta(2n) / 4^n) / (n + m)
-    bs = map ((+ log 2) . suminf eps . zipWith (*) as) rss
-
-    cs :: [a] -- second summand of RHS in (57) for m = [1..]
-    cs = zipWith (\b f -> b / f) bs factorial2
+zetasOdd eps = (1 / 0) : tail (skipEvens $ zetaHurwitz eps 1)
 
 -- | Infinite sequence of approximate (up to given precision)
 -- values of Riemann zeta-function at integer arguments, starting with @ζ(0)@.
--- Computations for odd arguments are performed in accordance to
+--
+-- Computations for odd arguments were formerly performed in accordance to
 -- <https://cr.yp.to/bib/2000/borwein.pdf Computational strategies for the Riemann zeta function>
--- by J. M. Borwein, D. M. Bradley, R. E. Crandall, formula (57).
+-- by J. M. Borwein, D. M. Bradley, R. E. Crandall, formula (57), but now use
+-- the 'Math.NumberTheory.Zeta.Hurwitz.zetaHurwitz' recurrence.
 --
 -- >>> take 5 (zetas 1e-14) :: [Double]
--- > [-0.5,Infinity,1.6449340668482262,1.2020569031595942,1.0823232337111381]
+-- [-0.5,Infinity,1.6449340668482264,1.2020569031595942,1.0823232337111381]
 --
--- Beware to force evaluation of @zetas !! 1@, if the type @a@ does not support infinite values
+-- Beware to force evaluation of @zetas !! 1@ if the type @a@ does not support infinite values
 -- (for instance, 'Data.Number.Fixed.Fixed').
 --
 zetas :: (Floating a, Ord a) => a -> [a]
 zetas eps = e : o : scanl1 f (intertwine es os)
   where
-    e : es = zetasEven'
+    e : es = map (getRationalLimit (\a b -> abs (a - b) < eps) . rationalApproximations) zetasEven
     o : os = zetasOdd eps
 
     -- Cap-and-floor to improve numerical stability:
diff --git a/Math/NumberTheory/Zeta/Utils.hs b/Math/NumberTheory/Zeta/Utils.hs
--- a/Math/NumberTheory/Zeta/Utils.hs
+++ b/Math/NumberTheory/Zeta/Utils.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Shared utilities used by functions from @Math.NumberTheory.Zeta@.
 
@@ -12,23 +10,22 @@
   ( intertwine
   , skipEvens
   , skipOdds
-  , suminf
   ) where
 
 -- | Joins two lists element-by-element together into one, starting with the
 -- first one provided as argument.
 --
 -- >>> take 10 $ intertwine [0, 2 ..] [1, 3 ..]
--- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+-- [0,1,2,3,4,5,6,7,8,9]
 intertwine :: [a] -> [a] -> [a]
-intertwine [] ys = ys 
-intertwine (x : xs) ys = x : intertwine ys xs 
+intertwine [] ys = ys
+intertwine (x : xs) ys = x : intertwine ys xs
 
 -- | Skips every odd-indexed element from an infinite list.
 -- Do NOT use with finite lists.
 --
 -- >>> take 10 (skipOdds [0, 1 ..])
--- [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
+-- [0,2,4,6,8,10,12,14,16,18]
 skipOdds :: [a] -> [a]
 skipOdds (x : _ : xs) = x : skipOdds xs
 skipOdds xs = xs
@@ -37,15 +34,6 @@
 -- Do NOT use with finite lists.
 --
 -- >>> take 10 (skipEvens [0, 1 ..])
--- [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
+-- [1,3,5,7,9,11,13,15,17,19]
 skipEvens :: [a] -> [a]
 skipEvens = skipOdds . tail
-
--- | Sums every element of an infinite list up to a certain precision.
--- I.e. once an element falls below the given threshold it stops traversing
--- the list.
---
--- >>> suminf 1e-14 (iterate (/ 10) 1)
--- 1.1111111111111112
-suminf :: (Floating a, Ord a) => a -> [a] -> a
-suminf eps = sum . takeWhile ((>= eps / 111) . abs)
diff --git a/app/SequenceModel.hs b/app/SequenceModel.hs
new file mode 100644
--- /dev/null
+++ b/app/SequenceModel.hs
@@ -0,0 +1,83 @@
+-- Model fitting to derive coefficients in
+-- Math.NumberTheory.Primes.Sequence.chooseAlgorithm
+
+module Main where
+
+import Numeric.GSL.Fitting
+
+-- | Benchmarks Sequence/filterIsPrime
+-- ([start, length], ([time in microseconds], weight))
+filterIsPrimeBenchData :: [([Double], ([Double], Double))]
+filterIsPrimeBenchData =
+  [ ([100000, 1000], ([777], 0.1))
+  , ([100000, 10000], ([8523], 0.1))
+  , ([1000000, 1000], ([813], 0.1))
+  , ([1000000, 10000], ([8247], 0.1))
+  , ([1000000, 100000], ([78600], 0.1))
+  , ([10000000, 1000], ([765], 0.1))
+  , ([10000000, 10000], ([7685], 0.1))
+  , ([10000000, 100000], ([78900], 0.1))
+  , ([10000000, 1000000], ([785000], 0.1))
+  , ([100000000, 1000], ([792], 0.1))
+  , ([100000000, 10000], ([8094], 0.1))
+  , ([100000000, 100000], ([79280], 0.1))
+  , ([100000000, 1000000], ([771600], 0.1))
+  , ([100000000, 10000000], ([7670000], 0.1))
+  ]
+
+filterIsPrimeBenchModel :: [(Double, Double)]
+filterIsPrimeBenchModel = sol
+  where
+    model [d] [from, len] = [len * d]
+    modelDer [d] [from, len] = [[len]]
+    (sol, _) = fitModelScaled 1E-10 1E-10 20 (model, modelDer) filterIsPrimeBenchData [1]
+
+filterIsPrimeBenchApprox :: ([Double], ([Double], Double)) -> [Double]
+filterIsPrimeBenchApprox ([from, len], ([exact], _)) = [from, len, exact, fromInteger (floor (appr / exact * 1000)) / 1000]
+  where
+    [(d, _)] = filterIsPrimeBenchModel
+    appr = len * d
+
+-- | Benchmarks Sequence/eratosthenes
+-- ([start, length], ([time in microseconds], weight))
+eratosthenesData :: [([Double], ([Double], Double))]
+eratosthenesData =
+  [ ([10000000000,1000000], ([21490], 0.1))
+  , ([10000000000,10000000], ([103200], 0.1))
+  , ([10000000000,100000000], ([956800], 0.1))
+  , ([10000000000,1000000000], ([9473000], 0.1))
+  , ([100000000000,10000000], ([107000], 0.1))
+  , ([1000000000000,10000000], ([129900], 0.1))
+  , ([10000000000000,10000000], ([202900], 0.1))
+  , ([100000000000000,10000000], ([420400], 0.1))
+  , ([1000000000000000,10000000], ([1048000], 0.1))
+  , ([10000000000000000,10000000], ([2940000], 0.1))
+  , ([100000000000000000,10000000], ([8763000], 0.1))
+  ]
+
+eratosthenesModel :: [(Double, Double)]
+eratosthenesModel = sol
+  where
+    model [a, b, c] [from, len] = [a * len + b * sqrt from + c]
+    modelDer [a, b, c] [from, len] = [[len, sqrt from, 1]]
+    (sol, _) = fitModelScaled 1E-10 1E-10 20 (model, modelDer) eratosthenesData [1,0,0]
+
+eratosthenesApprox :: ([Double], ([Double], Double)) -> [Double]
+eratosthenesApprox ([from, len], ([exact], _)) = [from, len, exact, fromInteger (floor (appr / exact * 1000)) / 1000]
+  where
+    [(a, _), (b, _), (c, _)] = eratosthenesModel
+    appr = a * len + b * sqrt from + c
+
+coeffs :: (Double, Double)
+coeffs = (b / (d - a), c / (d - a))
+  where
+    [(a, _), (b, _), (c, _)] = eratosthenesModel
+    [(d, _)] = filterIsPrimeBenchModel
+
+main :: IO ()
+main = do
+  print filterIsPrimeBenchModel
+  mapM_ (print . filterIsPrimeBenchApprox) filterIsPrimeBenchData
+  print eratosthenesModel
+  mapM_ (print . eratosthenesApprox) eratosthenesData
+  print coeffs
diff --git a/arithmoi.cabal b/arithmoi.cabal
--- a/arithmoi.cabal
+++ b/arithmoi.cabal
@@ -1,5 +1,5 @@
 name:          arithmoi
-version:       0.8.0.0
+version:       0.9.0.0
 cabal-version: >=1.10
 build-type:    Simple
 license:       MIT
@@ -19,7 +19,7 @@
   powers (integer roots and tests, modular exponentiation).
 category:      Math, Algorithms, Number Theory
 author:        Daniel Fischer
-tested-with:   GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
+tested-with:   GHC ==8.0.2 GHC ==8.2.2 GHC ==8.4.4 GHC ==8.6.5 GHC ==8.8.1
 extra-source-files:
   Changes
 
@@ -35,32 +35,29 @@
 
 library
   build-depends:
-    base >=4.7 && <5,
+    base >=4.9 && <5,
     array >=0.5 && <0.6,
     containers >=0.5 && <0.7,
     deepseq,
-    exact-pi >=0.4.1.1,
+    exact-pi >=0.5,
     ghc-prim <0.6,
     integer-gmp <1.1,
     integer-logarithms >=1.0,
     random >=1.0 && <1.2,
     transformers >=0.4 && <0.6,
+    semirings >= 0.2,
     vector >= 0.12
-  if impl(ghc <8.0)
-    build-depends:
-      semigroups >=0.8
   exposed-modules:
     GHC.TypeNats.Compat
     Math.NumberTheory.ArithmeticFunctions
+    Math.NumberTheory.ArithmeticFunctions.Inverse
     Math.NumberTheory.ArithmeticFunctions.Mertens
+    Math.NumberTheory.ArithmeticFunctions.NFreedom
     Math.NumberTheory.ArithmeticFunctions.Moebius
     Math.NumberTheory.ArithmeticFunctions.SieveBlock
     Math.NumberTheory.Curves.Montgomery
     Math.NumberTheory.Euclidean
     Math.NumberTheory.Euclidean.Coprimes
-    Math.NumberTheory.GaussianIntegers
-    Math.NumberTheory.GCD
-    Math.NumberTheory.GCD.LowLevel
     Math.NumberTheory.Moduli
     Math.NumberTheory.Moduli.Chinese
     Math.NumberTheory.Moduli.Class
@@ -88,13 +85,17 @@
     Math.NumberTheory.Primes.Testing.Certificates
     Math.NumberTheory.Quadratic.GaussianIntegers
     Math.NumberTheory.Quadratic.EisensteinIntegers
+    Math.NumberTheory.Recurrences
     Math.NumberTheory.Recurrencies
+    Math.NumberTheory.Recurrences.Bilinear
     Math.NumberTheory.Recurrencies.Bilinear
+    Math.NumberTheory.Recurrences.Linear
     Math.NumberTheory.Recurrencies.Linear
     Math.NumberTheory.SmoothNumbers
     Math.NumberTheory.UniqueFactorisation
     Math.NumberTheory.Zeta
     Math.NumberTheory.Zeta.Dirichlet
+    Math.NumberTheory.Zeta.Hurwitz
     Math.NumberTheory.Zeta.Riemann
   other-modules:
     Math.NumberTheory.ArithmeticFunctions.Class
@@ -111,9 +112,10 @@
     Math.NumberTheory.Primes.Testing.Certified
     Math.NumberTheory.Primes.Testing.Probabilistic
     Math.NumberTheory.Primes.Types
-    Math.NumberTheory.Recurrencies.Pentagonal
+    Math.NumberTheory.Recurrences.Pentagonal
     Math.NumberTheory.Unsafe
     Math.NumberTheory.Utils
+    Math.NumberTheory.Utils.DirichletSeries
     Math.NumberTheory.Utils.FromIntegral
     Math.NumberTheory.Utils.Hyperbola
     Math.NumberTheory.Zeta.Utils
@@ -124,30 +126,29 @@
 
 test-suite spec
   build-depends:
-    base >=4.6 && <5,
+    base >=4.9 && <5,
     arithmoi,
     containers,
     exact-pi >=0.4.1.1,
     integer-gmp <1.1,
-    QuickCheck >=2.10 && <2.13,
+    QuickCheck >=2.10,
+    semirings >= 0.2,
     smallcheck >=1.1.3 && <1.2,
-    tasty >=0.10 && <1.2,
+    tasty >=0.10,
     tasty-hunit >=0.9 && <0.11,
     tasty-quickcheck >=0.9 && <0.11,
     tasty-smallcheck >=0.8 && <0.9,
     transformers >=0.5,
     vector
-  if impl(ghc <8.0)
-    build-depends:
-      semigroups >=0.8
   other-modules:
     Math.NumberTheory.ArithmeticFunctionsTests
+    Math.NumberTheory.ArithmeticFunctions.InverseTests
     Math.NumberTheory.ArithmeticFunctions.MertensTests
     Math.NumberTheory.ArithmeticFunctions.SieveBlockTests
     Math.NumberTheory.CurvesTests
     Math.NumberTheory.EisensteinIntegersTests
     Math.NumberTheory.GaussianIntegersTests
-    Math.NumberTheory.GCDTests
+    Math.NumberTheory.EuclideanTests
     Math.NumberTheory.Moduli.ChineseTests
     Math.NumberTheory.Moduli.DiscreteLogarithmTests
     Math.NumberTheory.Moduli.ClassTests
@@ -165,12 +166,13 @@
     Math.NumberTheory.PrefactoredTests
     Math.NumberTheory.Primes.CountingTests
     Math.NumberTheory.Primes.FactorisationTests
+    Math.NumberTheory.Primes.SequenceTests
     Math.NumberTheory.Primes.SieveTests
     Math.NumberTheory.Primes.TestingTests
     Math.NumberTheory.PrimesTests
-    Math.NumberTheory.Recurrencies.PentagonalTests
-    Math.NumberTheory.Recurrencies.BilinearTests
-    Math.NumberTheory.Recurrencies.LinearTests
+    Math.NumberTheory.Recurrences.PentagonalTests
+    Math.NumberTheory.Recurrences.BilinearTests
+    Math.NumberTheory.Recurrences.LinearTests
     Math.NumberTheory.SmoothNumbersTests
     Math.NumberTheory.TestUtils
     Math.NumberTheory.TestUtils.MyCompose
@@ -188,30 +190,42 @@
   build-depends:
     base,
     arithmoi,
+    array,
     containers,
     deepseq,
     gauge,
     integer-logarithms,
     random,
     vector
-  if impl(ghc <8.0)
-    build-depends:
-      semigroups >=0.8
   other-modules:
     Math.NumberTheory.ArithmeticFunctionsBench
     Math.NumberTheory.DiscreteLogarithmBench
     Math.NumberTheory.EisensteinIntegersBench
+    Math.NumberTheory.EuclideanBench
     Math.NumberTheory.GaussianIntegersBench
-    Math.NumberTheory.GCDBench
+    Math.NumberTheory.InverseBench
     Math.NumberTheory.JacobiBench
     Math.NumberTheory.MertensBench
     Math.NumberTheory.PowersBench
     Math.NumberTheory.PrimesBench
     Math.NumberTheory.PrimitiveRootsBench
-    Math.NumberTheory.RecurrenciesBench
+    Math.NumberTheory.RecurrencesBench
+    Math.NumberTheory.SequenceBench
     Math.NumberTheory.SieveBlockBench
     Math.NumberTheory.SmoothNumbersBench
+    Math.NumberTheory.ZetaBench
   type: exitcode-stdio-1.0
   main-is: Bench.hs
   default-language: Haskell2010
   hs-source-dirs: benchmark
+
+executable sequence-model
+  build-depends:
+    base,
+    arithmoi,
+    containers,
+    hmatrix-gsl
+  buildable: False
+  main-is: SequenceModel.hs
+  hs-source-dirs: app
+  default-language: Haskell2010
diff --git a/benchmark/Bench.hs b/benchmark/Bench.hs
--- a/benchmark/Bench.hs
+++ b/benchmark/Bench.hs
@@ -5,30 +5,36 @@
 import Math.NumberTheory.ArithmeticFunctionsBench as ArithmeticFunctions
 import Math.NumberTheory.DiscreteLogarithmBench as DiscreteLogarithm
 import Math.NumberTheory.EisensteinIntegersBench as Eisenstein
+import Math.NumberTheory.EuclideanBench as Euclidean
 import Math.NumberTheory.GaussianIntegersBench as Gaussian
-import Math.NumberTheory.GCDBench as GCD
+import Math.NumberTheory.InverseBench as Inverse
 import Math.NumberTheory.JacobiBench as Jacobi
 import Math.NumberTheory.MertensBench as Mertens
 import Math.NumberTheory.PowersBench as Powers
 import Math.NumberTheory.PrimesBench as Primes
 import Math.NumberTheory.PrimitiveRootsBench as PrimitiveRoots
-import Math.NumberTheory.RecurrenciesBench as Recurrencies
+import Math.NumberTheory.RecurrencesBench as Recurrences
+import Math.NumberTheory.SequenceBench as Sequence
 import Math.NumberTheory.SieveBlockBench as SieveBlock
 import Math.NumberTheory.SmoothNumbersBench as SmoothNumbers
+import Math.NumberTheory.ZetaBench as Zeta
 
 main :: IO ()
 main = defaultMain
   [ ArithmeticFunctions.benchSuite
   , DiscreteLogarithm.benchSuite
   , Eisenstein.benchSuite
+  , Euclidean.benchSuite
   , Gaussian.benchSuite
-  , GCD.benchSuite
+  , Inverse.benchSuite
   , Jacobi.benchSuite
   , Mertens.benchSuite
   , Powers.benchSuite
   , Primes.benchSuite
   , PrimitiveRoots.benchSuite
-  , Recurrencies.benchSuite
+  , Recurrences.benchSuite
+  , Sequence.benchSuite
   , SieveBlock.benchSuite
   , SmoothNumbers.benchSuite
+  , Zeta.benchSuite
   ]
diff --git a/benchmark/Math/NumberTheory/EisensteinIntegersBench.hs b/benchmark/Math/NumberTheory/EisensteinIntegersBench.hs
--- a/benchmark/Math/NumberTheory/EisensteinIntegersBench.hs
+++ b/benchmark/Math/NumberTheory/EisensteinIntegersBench.hs
@@ -5,16 +5,15 @@
   ( benchSuite
   ) where
 
-import Control.DeepSeq
+import Data.Maybe
 import Gauge.Main
 
 import Math.NumberTheory.ArithmeticFunctions (tau)
+import Math.NumberTheory.Primes (isPrime)
 import Math.NumberTheory.Quadratic.EisensteinIntegers
 
-instance NFData EisensteinInteger
-
 benchFindPrime :: Integer -> Benchmark
-benchFindPrime n = bench (show n) $ nf findPrime n
+benchFindPrime n = bench (show n) $ nf findPrime (fromJust (isPrime n))
 
 benchTau :: Integer -> Benchmark
 benchTau n = bench (show n) $ nf (\m -> sum [tau (x :+ y) | x <- [1..m], y <- [0..m]] :: Word) n
diff --git a/benchmark/Math/NumberTheory/EuclideanBench.hs b/benchmark/Math/NumberTheory/EuclideanBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Math/NumberTheory/EuclideanBench.hs
@@ -0,0 +1,19 @@
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.EuclideanBench
+  ( benchSuite
+  ) where
+
+import Gauge.Main
+
+import Math.NumberTheory.Euclidean
+
+doBench :: Integral a => (a -> a -> (a, a, a)) -> a -> a
+doBench func lim = sum [ let (a, b, c) = func x y in a + b + c | y <- [3, 5 .. lim], x <- [0..y] ]
+
+benchSuite :: Benchmark
+benchSuite = bgroup "Euclidean"
+  [ bench "extendedGCD/Int"      $ nf (doBench extendedGCD :: Int -> Int)         1000
+  , bench "extendedGCD/Word"     $ nf (doBench extendedGCD :: Word -> Word)       1000
+  , bench "extendedGCD/Integer"  $ nf (doBench extendedGCD :: Integer -> Integer) 1000
+  ]
diff --git a/benchmark/Math/NumberTheory/GCDBench.hs b/benchmark/Math/NumberTheory/GCDBench.hs
deleted file mode 100644
--- a/benchmark/Math/NumberTheory/GCDBench.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-type-defaults #-}
-{-# OPTIONS_GHC -fno-warn-deprecations  #-}
-
-module Math.NumberTheory.GCDBench
-  ( benchSuite
-  ) where
-
-import Gauge.Main
-
-import Math.NumberTheory.GCD as A
-import Prelude as P
-import Numeric.Natural
-
-averageGCD :: Integral a => (a -> a -> a) -> a -> a
-averageGCD gcdF lim = sum [ gcdF x y | x <- [lim .. 2 * lim], y <- [lim .. x] ]
-
-benchSuite :: Benchmark
-benchSuite = bgroup "GCD"
-  [ subSuite "large coprimes" 1073741823 100003
-  , subSuite "powers of 2" (2^12) (2^19)
-  , subSuite "power of 23" (23^3) (23^7)
-  , bench "average prelude  Int"     $ nf (averageGCD P.gcd)       (2000 :: Int)
-  , bench "average arithmoi Int"     $ nf (averageGCD A.binaryGCD) (2000 :: Int)
-  , bench "average prelude  Word"    $ nf (averageGCD P.gcd)       (2000 :: Word)
-  , bench "average arithmoi Word"    $ nf (averageGCD A.binaryGCD) (2000 :: Word)
-  , bench "average prelude  Integer" $ nf (averageGCD P.gcd)       (2000 :: Integer)
-  , bench "average arithmoi Integer" $ nf (averageGCD A.binaryGCD) (2000 :: Integer)
-  , bench "average prelude  Natural" $ nf (averageGCD P.gcd)       (2000 :: Natural)
-  , bench "average arithmoi Natural" $ nf (averageGCD A.binaryGCD) (2000 :: Natural)
-  ]
-  where subSuite :: String -> Int -> Int -> Benchmark
-        subSuite name m n = bgroup name
-          [ bench "Prelude.gcd" $ nf (P.gcd m) n
-          , bench "binaryGCD" $ nf (A.binaryGCD m) n
-          , bench "Prelude.coprime" $ nf (\t -> 1 == P.gcd m t) n
-          , bench "coprime" $ nf (A.coprime m) n
-          ]
diff --git a/benchmark/Math/NumberTheory/GaussianIntegersBench.hs b/benchmark/Math/NumberTheory/GaussianIntegersBench.hs
--- a/benchmark/Math/NumberTheory/GaussianIntegersBench.hs
+++ b/benchmark/Math/NumberTheory/GaussianIntegersBench.hs
@@ -4,13 +4,15 @@
   ( benchSuite
   ) where
 
+import Data.Maybe
 import Gauge.Main
 
 import Math.NumberTheory.ArithmeticFunctions (tau)
+import Math.NumberTheory.Primes (isPrime)
 import Math.NumberTheory.Quadratic.GaussianIntegers
 
 benchFindPrime :: Integer -> Benchmark
-benchFindPrime n = bench (show n) $ nf findPrime n
+benchFindPrime n = bench (show n) $ nf findPrime (fromJust (isPrime n))
 
 benchTau :: Integer -> Benchmark
 benchTau n = bench (show n) $ nf (\m -> sum [tau (x :+ y) | x <- [1..m], y <- [0..m]] :: Word) n
diff --git a/benchmark/Math/NumberTheory/InverseBench.hs b/benchmark/Math/NumberTheory/InverseBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Math/NumberTheory/InverseBench.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE TypeApplications      #-}
+
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.InverseBench
+  ( benchSuite
+  ) where
+
+import Gauge.Main
+import Numeric.Natural
+
+import Math.NumberTheory.ArithmeticFunctions.Inverse
+import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Primes
+
+fact :: (Enum a, Num a) => a
+fact = product [1..13]
+
+tens :: Num a => a
+tens = 10 ^ 18
+
+countInverseTotient :: (Ord a, Euclidean a, UniqueFactorisation a) => a -> Word
+countInverseTotient = inverseTotient (const 1)
+
+countInverseSigma :: (Integral a, Euclidean a, UniqueFactorisation a) => a -> Word
+countInverseSigma = inverseSigma (const 1)
+
+benchSuite :: Benchmark
+benchSuite = bgroup "Inverse"
+  [ bgroup "Totient"
+    [ bgroup "factorial"
+      [ bench "Int"     $ nf (countInverseTotient @Int)     fact
+      , bench "Word"    $ nf (countInverseTotient @Word)    fact
+      , bench "Integer" $ nf (countInverseTotient @Integer) fact
+      , bench "Natural" $ nf (countInverseTotient @Natural) fact
+      ]
+    , bgroup "power of 10"
+      [ bench "Int"     $ nf (countInverseTotient @Int)     tens
+      , bench "Word"    $ nf (countInverseTotient @Word)    tens
+      , bench "Integer" $ nf (countInverseTotient @Integer) tens
+      , bench "Natural" $ nf (countInverseTotient @Natural) tens
+      ]
+    ]
+  , bgroup "Sigma1"
+    [ bgroup "factorial"
+      [ bench "Int"     $ nf (countInverseSigma @Int)     fact
+      , bench "Word"    $ nf (countInverseSigma @Word)    fact
+      , bench "Integer" $ nf (countInverseSigma @Integer) fact
+      , bench "Natural" $ nf (countInverseSigma @Natural) fact
+      ]
+    , bgroup "power of 10"
+      [ bench "Int"     $ nf (countInverseSigma @Int)     tens
+      , bench "Word"    $ nf (countInverseSigma @Word)    tens
+      , bench "Integer" $ nf (countInverseSigma @Integer) tens
+      , bench "Natural" $ nf (countInverseSigma @Natural) tens
+      ]
+    ]
+  ]
diff --git a/benchmark/Math/NumberTheory/PrimesBench.hs b/benchmark/Math/NumberTheory/PrimesBench.hs
--- a/benchmark/Math/NumberTheory/PrimesBench.hs
+++ b/benchmark/Math/NumberTheory/PrimesBench.hs
@@ -8,7 +8,8 @@
 import System.Random
 
 import Math.NumberTheory.Logarithms (integerLog2)
-import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes.Factorisation
+import Math.NumberTheory.Primes.Testing
 
 genInteger :: Int -> Int -> Integer
 genInteger salt bits
diff --git a/benchmark/Math/NumberTheory/PrimitiveRootsBench.hs b/benchmark/Math/NumberTheory/PrimitiveRootsBench.hs
--- a/benchmark/Math/NumberTheory/PrimitiveRootsBench.hs
+++ b/benchmark/Math/NumberTheory/PrimitiveRootsBench.hs
@@ -8,7 +8,7 @@
 import Data.Maybe
 
 import Math.NumberTheory.Moduli.PrimitiveRoot
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Primes
 
 primRootWrap :: Integer -> Word -> Integer -> Bool
 primRootWrap p k g = isPrimitiveRoot' (CGOddPrimePower p' k) g
diff --git a/benchmark/Math/NumberTheory/RecurrencesBench.hs b/benchmark/Math/NumberTheory/RecurrencesBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Math/NumberTheory/RecurrencesBench.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE RankNTypes #-}
+
+module Math.NumberTheory.RecurrencesBench
+  ( benchSuite
+  ) where
+
+import Gauge.Main
+
+import Math.NumberTheory.Recurrences (binomial, eulerian1, eulerian2,
+                                      stirling1, stirling2, partition)
+
+benchTriangle :: String -> (forall a. (Integral a) => [[a]]) -> Int -> Benchmark
+benchTriangle name triangle n = bgroup name
+  [ benchAt (10 * n)  (1 * n)
+  , benchAt (10 * n)  (2 * n)
+  , benchAt (10 * n)  (5 * n)
+  , benchAt (10 * n)  (9 * n)
+  ]
+  where
+    benchAt i j = bench ("!! " ++ show i ++ " !! " ++ show j)
+                $ nf (\(x, y) -> triangle !! x !! y :: Integer) (i, j)
+
+benchPartition :: Int -> Benchmark
+benchPartition n = bgroup "partition"
+  [ benchAt n
+  , benchAt (n * 10)
+  , benchAt (n * 100)
+  ]
+  where
+    benchAt m = bench ("!!" ++ show m) $  nf (\k -> partition !! k :: Integer) m
+
+benchSuite :: Benchmark
+benchSuite = bgroup "Recurrences"
+  [
+    bgroup "Bilinear"
+    [ benchTriangle "binomial"  binomial 1000
+    , benchTriangle "stirling1" stirling1 100
+    , benchTriangle "stirling2" stirling2 100
+    , benchTriangle "eulerian1" eulerian1 100
+    , benchTriangle "eulerian2" eulerian2 100
+    ]
+    ,
+    bgroup "Pentagonal"
+    [ bgroup "Partition function"
+      [ benchPartition 1000
+      ]
+    ]
+  ]
diff --git a/benchmark/Math/NumberTheory/RecurrenciesBench.hs b/benchmark/Math/NumberTheory/RecurrenciesBench.hs
deleted file mode 100644
--- a/benchmark/Math/NumberTheory/RecurrenciesBench.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-{-# LANGUAGE RankNTypes #-}
-
-module Math.NumberTheory.RecurrenciesBench
-  ( benchSuite
-  ) where
-
-import Gauge.Main
-
-import Math.NumberTheory.Recurrencies (binomial, eulerian1, eulerian2,
-                                       stirling1, stirling2, partition)
-
-benchTriangle :: String -> (forall a. (Integral a) => [[a]]) -> Int -> Benchmark
-benchTriangle name triangle n = bgroup name
-  [ benchAt (10 * n)  (1 * n)
-  , benchAt (10 * n)  (2 * n)
-  , benchAt (10 * n)  (5 * n)
-  , benchAt (10 * n)  (9 * n)
-  ]
-  where
-    benchAt i j = bench ("!! " ++ show i ++ " !! " ++ show j)
-                $ nf (\(x, y) -> triangle !! x !! y :: Integer) (i, j)
-
-benchPartition :: Int -> Benchmark
-benchPartition n = bgroup "partition"
-  [ benchAt n
-  , benchAt (n * 10)
-  , benchAt (n * 100)
-  ]
-  where
-    benchAt m = bench ("!!" ++ show m) $  nf (\k -> partition !! k :: Integer) m
-
-benchSuite :: Benchmark
-benchSuite = bgroup "Recurrencies"
-  [
-    bgroup "Bilinear"
-    [ benchTriangle "binomial"  binomial 1000
-    , benchTriangle "stirling1" stirling1 100
-    , benchTriangle "stirling2" stirling2 100
-    , benchTriangle "eulerian1" eulerian1 100
-    , benchTriangle "eulerian2" eulerian2 100
-    ]
-    ,
-    bgroup "Pentagonal"
-    [ bgroup "Partition function"
-      [ benchPartition 1000
-      ]
-    ]
-  ]
diff --git a/benchmark/Math/NumberTheory/SequenceBench.hs b/benchmark/Math/NumberTheory/SequenceBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Math/NumberTheory/SequenceBench.hs
@@ -0,0 +1,71 @@
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.SequenceBench
+  ( benchSuite
+  ) where
+
+import Gauge.Main
+
+import Data.Array.IArray ((!))
+import Data.Array.Unboxed
+import Data.Bits
+
+import Math.NumberTheory.Primes (Prime(..))
+import Math.NumberTheory.Primes.Sieve as P
+import Math.NumberTheory.Primes.Testing as P
+
+filterIsPrime :: (Integer, Integer) -> Integer
+filterIsPrime (p, q) = sum $ takeWhile (<= q) $ dropWhile (< p) $ filter isPrime (map toPrim [toIdx p .. toIdx q])
+
+eratosthenes :: (Integer, Integer) -> Integer
+eratosthenes (p, q) = sum $ takeWhile (<= q) $ dropWhile (< p) $ map unPrime $ if q < toInteger sieveRange
+        then           primeList $ primeSieve q
+        else concatMap primeList $ psieveFrom p
+
+filterIsPrimeBench :: Benchmark
+filterIsPrimeBench = bgroup "filterIsPrime" $
+  map (\(x, y) -> bench (show (x, y)) $ nf filterIsPrime (x, x + y))
+  [ (10 ^ x, 10 ^ y)
+  | x <- [5..8]
+  , y <- [3..x-1]
+  ]
+
+eratosthenesBench :: Benchmark
+eratosthenesBench = bgroup "eratosthenes" $
+  map (\(x, y) -> bench (show (x, y)) $ nf eratosthenes (x, x + y))
+  [ (10 ^ x, 10 ^ y)
+  | x <- [10..17]
+  , y <- [6..x-1]
+  , x == 10 || y == 7
+  ]
+
+benchSuite :: Benchmark
+benchSuite = bgroup "Sequence"
+    [ filterIsPrimeBench
+    , eratosthenesBench
+    ]
+
+-------------------------------------------------------------------------------
+-- Utils copypasted from internal modules
+
+sieveRange :: Int
+sieveRange = 30*128*1024
+
+rho :: Int -> Int
+rho i = residues ! i
+
+residues :: UArray Int Int
+residues = listArray (0,7) [7,11,13,17,19,23,29,31]
+
+toIdx :: Integral a => a -> Int
+toIdx n = 8*fromIntegral q+r2
+  where
+    (q,r) = (n-7) `quotRem` 30
+    r1 = fromIntegral r `quot` 3
+    r2 = min 7 (if r1 > 5 then r1-1 else r1)
+
+toPrim :: Integral a => Int -> a
+toPrim ix = 30*fromIntegral k + fromIntegral (rho i)
+  where
+    i = ix .&. 7
+    k = ix `shiftR` 3
diff --git a/benchmark/Math/NumberTheory/SieveBlockBench.hs b/benchmark/Math/NumberTheory/SieveBlockBench.hs
--- a/benchmark/Math/NumberTheory/SieveBlockBench.hs
+++ b/benchmark/Math/NumberTheory/SieveBlockBench.hs
@@ -17,6 +17,7 @@
 
 import Math.NumberTheory.ArithmeticFunctions.Moebius
 import Math.NumberTheory.ArithmeticFunctions.SieveBlock
+import Math.NumberTheory.Primes
 
 blockLen :: Word
 blockLen = 1000000
@@ -30,7 +31,7 @@
 totientBlockConfig = SieveBlockConfig
   { sbcEmpty                = 1
   , sbcAppend               = (*)
-  , sbcFunctionOnPrimePower = totientHelper
+  , sbcFunctionOnPrimePower = totientHelper . unPrime
   }
 
 carmichaelHelper :: Word -> Word -> Word
@@ -46,7 +47,7 @@
   { sbcEmpty                = 1
   -- There is a specialized 'gcd' for Word, but not 'lcm'.
   , sbcAppend               = (\x y -> (x `quot` (gcd x y)) * y)
-  , sbcFunctionOnPrimePower = carmichaelHelper
+  , sbcFunctionOnPrimePower = carmichaelHelper . unPrime
   }
 
 moebiusConfig :: SieveBlockConfig Moebius
diff --git a/benchmark/Math/NumberTheory/SmoothNumbersBench.hs b/benchmark/Math/NumberTheory/SmoothNumbersBench.hs
--- a/benchmark/Math/NumberTheory/SmoothNumbersBench.hs
+++ b/benchmark/Math/NumberTheory/SmoothNumbersBench.hs
@@ -8,9 +8,10 @@
 import Data.Maybe
 import Gauge.Main
 
+import Math.NumberTheory.Euclidean (Euclidean)
 import Math.NumberTheory.SmoothNumbers
 
-doBench :: Integral a => a -> a
+doBench :: (Euclidean a, Integral a) => a -> a
 doBench lim = sum $ genericTake lim $ smoothOver $ fromJust $ fromSmoothUpperBound lim
 
 benchSuite :: Benchmark
diff --git a/benchmark/Math/NumberTheory/ZetaBench.hs b/benchmark/Math/NumberTheory/ZetaBench.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Math/NumberTheory/ZetaBench.hs
@@ -0,0 +1,15 @@
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.ZetaBench
+  ( benchSuite
+  ) where
+
+import Gauge.Main
+
+import Math.NumberTheory.Zeta
+
+benchSuite :: Benchmark
+benchSuite = bgroup "Zeta"
+  [ bench "riemann zeta"   $ nf (\eps -> sum $ take 20 $ zetas eps) (1e-15 :: Double)
+  , bench "dirichlet beta" $ nf (\eps -> sum $ take 20 $ betas eps) (1e-15 :: Double)
+  ]
diff --git a/test-suite/Math/NumberTheory/ArithmeticFunctions/InverseTests.hs b/test-suite/Math/NumberTheory/ArithmeticFunctions/InverseTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/ArithmeticFunctions/InverseTests.hs
@@ -0,0 +1,262 @@
+-- |
+-- Module:      Math.NumberTheory.ArithmeticFunctions.InverseTests
+-- Copyright:   (c) 2018 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+-- Stability:   Provisional
+--
+-- Tests for Math.NumberTheory.ArithmeticFunctions.Inverse
+--
+
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.ArithmeticFunctions.InverseTests
+  ( testSuite
+  ) where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import qualified Data.Set as S
+
+import Math.NumberTheory.ArithmeticFunctions
+import Math.NumberTheory.ArithmeticFunctions.Inverse
+import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Primes
+import Math.NumberTheory.Recurrences
+import Math.NumberTheory.TestUtils
+
+-------------------------------------------------------------------------------
+-- Totient
+
+totientProperty1 :: forall a. (Euclidean a, Integral a, UniqueFactorisation a) => Positive a -> Bool
+totientProperty1 (Positive x) = x `S.member` asSetOfPreimages inverseTotient (totient x)
+
+totientProperty2 :: (Euclidean a, Integral a, UniqueFactorisation a) => Positive a -> Bool
+totientProperty2 (Positive x) = all (== x) (S.map totient (asSetOfPreimages inverseTotient x))
+
+-- | http://oeis.org/A055506
+totientCountFactorial :: [Word]
+totientCountFactorial =
+  [ 2
+  , 3
+  , 4
+  , 10
+  , 17
+  , 49
+  , 93
+  , 359
+  , 1138
+  , 3802
+  , 12124
+  , 52844
+  , 182752
+  , 696647
+  , 2852886
+  , 16423633
+  , 75301815
+  , 367900714
+  ]
+
+totientSpecialCases1 :: [Assertion]
+totientSpecialCases1 = zipWith mkAssert (tail factorial) totientCountFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (totientCount n)
+
+    totientCount :: Word -> Word
+    totientCount = inverseTotient (const 1)
+
+-- | http://oeis.org/A055487
+totientMinFactorial :: [Word]
+totientMinFactorial =
+  [ 1
+  , 3
+  , 7
+  , 35
+  , 143
+  , 779
+  , 5183
+  , 40723
+  , 364087
+  , 3632617
+  , 39916801
+  , 479045521
+  , 6227180929
+  , 87178882081
+  , 1307676655073
+  , 20922799053799
+  , 355687465815361
+  , 6402373865831809
+  ]
+
+totientSpecialCases2 :: [Assertion]
+totientSpecialCases2 = zipWith mkAssert (tail factorial) totientMinFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (totientMin n)
+
+    totientMin :: Word -> Word
+    totientMin = unMinWord . inverseTotient MinWord
+
+-- | http://oeis.org/A165774
+totientMaxFactorial :: [Word]
+totientMaxFactorial =
+  [ 2
+  , 6
+  , 18
+  , 90
+  , 462
+  , 3150
+  , 22050
+  , 210210
+  , 1891890
+  , 19969950
+  , 219669450
+  , 2847714870
+  , 37020293310
+  , 520843112790
+  , 7959363061650
+  , 135309172048050
+  , 2300255924816850
+  , 41996101027370490
+  ]
+
+totientSpecialCases3 :: [Assertion]
+totientSpecialCases3 = zipWith mkAssert (tail factorial) totientMaxFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (totientMax n)
+
+    totientMax :: Word -> Word
+    totientMax = unMaxWord . inverseTotient MaxWord
+
+-------------------------------------------------------------------------------
+-- Sigma
+
+sigmaProperty1 :: forall a. (Euclidean a, UniqueFactorisation a, Integral a) => Positive a -> Bool
+sigmaProperty1 (Positive x) = x `S.member` asSetOfPreimages inverseSigma (sigma 1 x)
+
+sigmaProperty2 :: (Euclidean a, UniqueFactorisation a, Integral a) => Positive a -> Bool
+sigmaProperty2 (Positive x) = all (== x) (S.map (sigma 1) (asSetOfPreimages inverseSigma x))
+
+-- | http://oeis.org/A055486
+sigmaCountFactorial :: [Word]
+sigmaCountFactorial =
+  [ 1
+  , 0
+  , 1
+  , 3
+  , 4
+  , 15
+  , 33
+  , 111
+  , 382
+  , 1195
+  , 3366
+  , 14077
+  , 53265
+  , 229603
+  , 910254
+  , 4524029
+  , 18879944
+  , 91336498
+  ]
+
+sigmaSpecialCases1 :: [Assertion]
+sigmaSpecialCases1 = zipWith mkAssert (tail factorial) sigmaCountFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (sigmaCount n)
+
+    sigmaCount :: Word -> Word
+    sigmaCount = inverseSigma (const 1)
+
+-- | http://oeis.org/A055488
+sigmaMinFactorial :: [Word]
+sigmaMinFactorial =
+  [ 5
+  , 14
+  , 54
+  , 264
+  , 1560
+  , 10920
+  , 97440
+  , 876960
+  , 10263240
+  , 112895640
+  , 1348827480
+  , 18029171160
+  , 264370186080
+  , 3806158356000
+  , 62703141621120
+  , 1128159304272000
+  ]
+
+sigmaSpecialCases2 :: [Assertion]
+sigmaSpecialCases2 = zipWith mkAssert (drop 3 factorial) sigmaMinFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (sigmaMin n)
+
+    sigmaMin :: Word -> Word
+    sigmaMin = unMinWord . inverseSigma MinWord
+
+-- | http://oeis.org/A055489
+sigmaMaxFactorial :: [Word]
+sigmaMaxFactorial =
+  [ 5
+  , 23
+  , 95
+  , 719
+  , 5039
+  , 39917
+  , 361657
+  , 3624941
+  , 39904153
+  , 479001599
+  , 6226862869
+  , 87178291199
+  , 1307672080867
+  , 20922780738961
+  , 355687390376431
+  , 6402373545694717
+  ]
+
+sigmaSpecialCases3 :: [Assertion]
+sigmaSpecialCases3 = zipWith mkAssert (drop 3 factorial) sigmaMaxFactorial
+  where
+    mkAssert n m = assertEqual "should be equal" m (sigmaMax n)
+
+    sigmaMax :: Word -> Word
+    sigmaMax = unMaxWord . inverseSigma MaxWord
+
+sigmaSpecialCase4 :: Assertion
+sigmaSpecialCase4 = assertBool "200 should be in inverseSigma(sigma(200))" $
+  sigmaProperty1 $ Positive (200 :: Word)
+
+-------------------------------------------------------------------------------
+-- TestTree
+
+testSuite :: TestTree
+testSuite = testGroup "Inverse"
+  [ testGroup "Totient"
+    [ testIntegralPropertyNoLarge "forward"  totientProperty1
+    , testIntegralPropertyNoLarge "backward" totientProperty2
+    , testGroup "count"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] totientSpecialCases1)
+    , testGroup "min"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] totientSpecialCases2)
+    , testGroup "max"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] totientSpecialCases3)
+    ]
+  , testGroup "Sigma1"
+    [ testIntegralPropertyNoLarge "forward"  sigmaProperty1
+    , testIntegralPropertyNoLarge "backward" sigmaProperty2
+    , testCase "200" sigmaSpecialCase4
+    , testGroup "count"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] sigmaSpecialCases1)
+    , testGroup "min"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] sigmaSpecialCases2)
+    , testGroup "max"
+      (zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] sigmaSpecialCases3)
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/ArithmeticFunctions/MertensTests.hs b/test-suite/Math/NumberTheory/ArithmeticFunctions/MertensTests.hs
--- a/test-suite/Math/NumberTheory/ArithmeticFunctions/MertensTests.hs
+++ b/test-suite/Math/NumberTheory/ArithmeticFunctions/MertensTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.ArithmeticFunctions.Mertens
 --
diff --git a/test-suite/Math/NumberTheory/ArithmeticFunctions/SieveBlockTests.hs b/test-suite/Math/NumberTheory/ArithmeticFunctions/SieveBlockTests.hs
--- a/test-suite/Math/NumberTheory/ArithmeticFunctions/SieveBlockTests.hs
+++ b/test-suite/Math/NumberTheory/ArithmeticFunctions/SieveBlockTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.ArithmeticFunctions.SieveBlock
 --
@@ -28,6 +27,7 @@
 
 import Math.NumberTheory.ArithmeticFunctions
 import Math.NumberTheory.ArithmeticFunctions.SieveBlock
+import Math.NumberTheory.Primes (unPrime)
 
 pointwiseTest :: (Eq a, Show a) => ArithmeticFunction Word a -> Word -> Word -> IO ()
 pointwiseTest f lowIndex len = assertEqual "pointwise"
@@ -72,7 +72,7 @@
 multiplicativeConfig f = SieveBlockConfig
   { sbcEmpty                = 1
   , sbcAppend               = (*)
-  , sbcFunctionOnPrimePower = f
+  , sbcFunctionOnPrimePower = f . unPrime
   }
 
 moebiusConfig :: SieveBlockConfig Moebius
@@ -98,7 +98,7 @@
     ]
   , testGroup "unboxed"
     [ testCase "id"      $ unboxedTest $ multiplicativeConfig (^)
-    , testCase "tau"     $ unboxedTest $ multiplicativeConfig (const id)
+    , testCase "tau"     $ unboxedTest $ multiplicativeConfig (\_ a -> succ a )
     , testCase "moebius" $ unboxedTest moebiusConfig
     , testCase "totient" $ unboxedTest $ multiplicativeConfig (\p a -> (p - 1) * p ^ (a - 1))
     ]
diff --git a/test-suite/Math/NumberTheory/ArithmeticFunctionsTests.hs b/test-suite/Math/NumberTheory/ArithmeticFunctionsTests.hs
--- a/test-suite/Math/NumberTheory/ArithmeticFunctionsTests.hs
+++ b/test-suite/Math/NumberTheory/ArithmeticFunctionsTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.ArithmeticFunctions
 --
@@ -25,7 +24,9 @@
 import qualified Data.IntSet as IS
 
 import Math.NumberTheory.ArithmeticFunctions
+import Math.NumberTheory.Primes (UniqueFactorisation (factorise))
 import Math.NumberTheory.TestUtils
+import Math.NumberTheory.Zeta (zetas)
 
 import Numeric.Natural
 
@@ -271,6 +272,46 @@
   , 73, 1, 1, 1, 1, 1, 79, 1, 3, 1, 83, 1, 1, 1, 1, 1, 89, 1, 1, 1, 1, 1, 1
   ]
 
+nFreedomProperty1 :: Word -> NonZero Natural -> Bool
+nFreedomProperty1 n (NonZero m) =
+    isNFree n m == (all ((< n) . snd) . factorise) m
+
+nFreedomProperty2 :: Power Word -> NonNegative Int -> Bool
+nFreedomProperty2 (Power n) (NonNegative m) =
+    let n' | n == maxBound = n
+           | otherwise     = n + 1
+    in take m (filter (isNFree n') [1 ..]) == take m (nFrees n' :: [Integer])
+
+nFreedomProperty3 :: Power Word -> Positive Int -> Bool
+nFreedomProperty3 (Power n) (Positive m) =
+    let n' | n == maxBound = n
+           | otherwise     = n + 1
+        zet = 1 / zetas 1e-14 !! (fromIntegral n') :: Double
+        m' = 100 * m
+        nfree = fromIntegral m' /
+                fromIntegral (head (drop (m' - 1) $ nFrees n' :: [Integer]))
+    in 1 / fromIntegral m >= abs (zet - nfree)
+
+-- |
+-- * Using a bounded integer type like @Int@ instead of @Integer@ here means
+-- even a relatively low value of @n@, e.g. 20 may cause out-of-bounds memory
+-- accesses in @nFreesBlock@.
+-- * Using @Integer@ prevents this, so that is the numeric type used here.
+nFreesBlockProperty1 :: Power Word -> Positive Integer -> Word -> Bool
+nFreesBlockProperty1 (Power n) (Positive lo) w =
+    let block = nFreesBlock n lo w
+        len   = length block
+        blk   = take len . dropWhile (< lo) . nFrees $ n
+    in block == blk
+
+nFreedomAssertion1 :: Assertion
+nFreedomAssertion1 =
+    assertEqual "1 is the sole 0-free number" (nFrees 0) ([1] :: [Int])
+
+nFreedomAssertion2 :: Assertion
+nFreedomAssertion2 =
+    assertEqual "1 is the sole 1-free number" (nFrees 1) ([1] :: [Int])
+
 testSuite :: TestTree
 testSuite = testGroup "ArithmeticFunctions"
   [ testGroup "Divisors"
@@ -326,5 +367,13 @@
     ]
   , testGroup "Mangoldt"
     [ testCase "OEIS" mangoldtOeis
+    ]
+  , testGroup "N-freedom"
+    [ testSmallAndQuick "`isNFree` matches the definition" nFreedomProperty1
+    , testSmallAndQuick "numbers produces by `nFrees`s are `n`-free" nFreedomProperty2
+    , testSmallAndQuick "distribution of n-free numbers matches expected" nFreedomProperty3
+    , testSmallAndQuick "nFreesBlock matches nFrees" nFreesBlockProperty1
+    , testCase "`1` is the only 0-free number" nFreedomAssertion1
+    , testCase "`1` is the only 1-free number" nFreedomAssertion2
     ]
   ]
diff --git a/test-suite/Math/NumberTheory/CurvesTests.hs b/test-suite/Math/NumberTheory/CurvesTests.hs
--- a/test-suite/Math/NumberTheory/CurvesTests.hs
+++ b/test-suite/Math/NumberTheory/CurvesTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Curves
 --
diff --git a/test-suite/Math/NumberTheory/EisensteinIntegersTests.hs b/test-suite/Math/NumberTheory/EisensteinIntegersTests.hs
--- a/test-suite/Math/NumberTheory/EisensteinIntegersTests.hs
+++ b/test-suite/Math/NumberTheory/EisensteinIntegersTests.hs
@@ -5,7 +5,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.EisensteinIntegers
 --
@@ -14,13 +13,15 @@
   ( testSuite
   ) where
 
-import qualified Math.NumberTheory.Euclidean    as ED
-import qualified Math.NumberTheory.Quadratic.EisensteinIntegers as E
-import Math.NumberTheory.Primes                       (primes)
+import Data.Maybe (fromJust, isJust)
 import Test.Tasty                                     (TestTree, testGroup)
 import Test.Tasty.HUnit                               (Assertion, assertEqual,
                                                       testCase)
 
+import qualified Math.NumberTheory.Euclidean as ED
+import qualified Math.NumberTheory.Quadratic.EisensteinIntegers as E
+import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes.Sieve (primes)
 import Math.NumberTheory.TestUtils                    (Positive (..),
                                                        testSmallAndQuick)
 
@@ -95,9 +96,9 @@
 findPrimesProperty1 :: Positive Int -> Bool
 findPrimesProperty1 (Positive index) =
     let -- Only retain primes that are of the form @6k + 1@, for some nonzero natural @k@.
-        prop prime = prime `mod` 6 == 1
+        prop prime = unPrime prime `mod` 6 == 1
         p = (!! index) $ filter prop $ drop 3 primes
-    in E.isPrime $ E.findPrime p
+    in isJust (isPrime (unPrime (E.findPrime p) :: E.EisensteinInteger))
 
 -- | Checks that the @norm@ of the Euclidean domain of Eisenstein integers
 -- is multiplicative i.e.
@@ -108,21 +109,21 @@
 -- | Checks that the numbers produced by @primes@ are actually Eisenstein
 -- primes.
 primesProperty1 :: Positive Int -> Bool
-primesProperty1 (Positive index) = all E.isPrime $ take index $ E.primes
+primesProperty1 (Positive index) = all (isJust . isPrime . (unPrime :: Prime E.EisensteinInteger -> E.EisensteinInteger)) $ take index $ E.primes
 
 -- | Checks that the infinite list of Eisenstein primes @primes@ is ordered
 -- by the numbers' norm.
 primesProperty2 :: Positive Int -> Bool
 primesProperty2 (Positive index) =
-    let isOrdered :: [E.EisensteinInteger] -> Bool
-        isOrdered xs = all (\(x,y) -> E.norm x <= E.norm y) . zip xs $ tail xs
+    let isOrdered :: [Prime E.EisensteinInteger] -> Bool
+        isOrdered xs = all (\(x, y) -> E.norm (unPrime x) <= E.norm (unPrime y)) . zip xs $ tail xs
     in isOrdered $ take index E.primes
 
 -- | Checks that the numbers produced by @primes@ are all in the first
 -- sextant.
 primesProperty3 :: Positive Int -> Bool
 primesProperty3 (Positive index) =
-    all (\e -> abs e == e) $ take index $ E.primes
+    all (\e -> abs (unPrime e) == (unPrime e :: E.EisensteinInteger)) $ take index $ E.primes
 
 -- | An Eisenstein integer is either zero or associated (i.e. equal up to
 -- multiplication by a unit) to the product of its factors raised to their
@@ -130,30 +131,23 @@
 factoriseProperty1 :: E.EisensteinInteger -> Bool
 factoriseProperty1 g = g == 0 || abs g == abs g'
   where
-    factors = E.factorise g
-    g' = product $ map (uncurry (^)) factors
+    factors = factorise g
+    g' = product $ map (\(p, k) -> unPrime p ^ k) factors
 
 -- | Check that there are no factors with exponent @0@ in the factorisation.
 factoriseProperty2 :: E.EisensteinInteger -> Bool
-factoriseProperty2 z = z == 0 || all ((> 0) . snd) (E.factorise z)
+factoriseProperty2 z = z == 0 || all ((> 0) . snd) (factorise z)
 
 -- | Check that no factor produced by @factorise@ is a unit.
 factoriseProperty3 :: E.EisensteinInteger -> Bool
-factoriseProperty3 z = z == 0 || all ((> 1) . E.norm . fst) (E.factorise z)
-
--- | Check that every prime factor in the factorisation is primary, excluding
--- @1 - ω@, if it is a factor.
-factoriseProperty4 :: E.EisensteinInteger -> Bool
-factoriseProperty4 z =
-    z == 0 ||
-    (all (\e -> e `ED.mod` 3 == 2) $
-     filter (\e -> not $ elem e $ E.associates $ 1 E.:+ (-1)) $
-     map fst $ E.factorise z)
+factoriseProperty3 z = z == 0 || all ((> 1) . E.norm . unPrime . fst) (factorise z)
 
 factoriseSpecialCase1 :: Assertion
 factoriseSpecialCase1 = assertEqual "should be equal"
-  [(2 E.:+ 1, 3), (2 E.:+ 3, 1)]
-  (E.factorise (15 E.:+ 12))
+  [ (fromJust $ isPrime $ 2 E.:+ 1, 3)
+  , (fromJust $ isPrime $ 3 E.:+ 1, 1)
+  ]
+  (factorise (15 E.:+ 12))
 
 testSuite :: TestTree
 testSuite = testGroup "EisensteinIntegers" $
@@ -201,8 +195,6 @@
                           factoriseProperty2
       , testSmallAndQuick "factorise produces no unit factors"
                           factoriseProperty3
-      , testSmallAndQuick "factorise only produces primary primes"
-                          factoriseProperty4
       , testCase          "factorise 15:+12" factoriseSpecialCase1
       ]
   ]
diff --git a/test-suite/Math/NumberTheory/EuclideanTests.hs b/test-suite/Math/NumberTheory/EuclideanTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/EuclideanTests.hs
@@ -0,0 +1,148 @@
+-- |
+-- Module:      Math.NumberTheory.EuclideanTests
+-- Copyright:   (c) 2016 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- Tests for Math.NumberTheory.Euclidean
+--
+
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-# OPTIONS_GHC -fno-warn-type-defaults  #-}
+{-# OPTIONS_GHC -fno-warn-unused-imports #-}
+{-# OPTIONS_GHC -fno-warn-deprecations   #-}
+
+module Math.NumberTheory.EuclideanTests
+  ( testSuite
+  ) where
+
+import Prelude hiding (gcd)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Control.Arrow
+import Data.Bits
+import Data.Maybe
+import Data.Semigroup
+import Data.List (tails, sort)
+import Numeric.Natural
+
+import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Euclidean.Coprimes
+import Math.NumberTheory.TestUtils
+
+-- | Check that 'extendedGCD' is consistent with documentation.
+extendedGCDProperty :: forall a. (Bits a, Euclidean a, Ord a) => AnySign a -> AnySign a -> Bool
+extendedGCDProperty (AnySign a) (AnySign b)
+  | isNatural a = True -- extendedGCD does not make sense for Natural
+  | otherwise =
+  u * a + v * b == d
+  && d == gcd a b
+  -- (-1) >= 0 is true for unsigned types
+  && (abs u < abs b || abs b <= 1 || (-1 :: a) >= 0)
+  && (abs v < abs a || abs a <= 1 || (-1 :: a) >= 0)
+  where
+    (d, u, v) = extendedGCD a b
+
+isNatural :: Bits a => a -> Bool
+isNatural a = isNothing (bitSizeMaybe a) && not (isSigned a)
+
+-- | Check that numbers are coprime iff their gcd equals to 1.
+coprimeProperty :: (Euclidean a) => AnySign a -> AnySign a -> Bool
+coprimeProperty (AnySign a) (AnySign b) = coprime a b == (gcd a b == 1)
+
+splitIntoCoprimesProperty1 :: [(Positive Natural, Power Word)] -> Bool
+splitIntoCoprimesProperty1 fs' = factorback fs == factorback (unCoprimes $ splitIntoCoprimes fs)
+  where
+    fs = map (getPositive *** getPower) fs'
+    factorback = product . map (uncurry (^))
+
+splitIntoCoprimesProperty2 :: [(Positive Natural, Power Word)] -> Bool
+splitIntoCoprimesProperty2 fs' = multiplicities fs <= multiplicities (unCoprimes $ splitIntoCoprimes fs)
+  where
+    fs = map (getPositive *** getPower) fs'
+    multiplicities = sum . map snd . filter ((/= 1) . fst)
+
+splitIntoCoprimesProperty3 :: [(Positive Natural, Power Word)] -> Bool
+splitIntoCoprimesProperty3 fs' = and [ coprime x y | (x : xs) <- tails fs, y <- xs ]
+  where
+    fs = map fst $ unCoprimes $ splitIntoCoprimes $ map (getPositive *** getPower) fs'
+
+-- | Check that evaluation never freezes.
+splitIntoCoprimesProperty4 :: [(Integer, Word)] -> Bool
+splitIntoCoprimesProperty4 fs' = fs == fs
+  where
+    fs = splitIntoCoprimes fs'
+
+-- | This is an undefined behaviour, but at least it should not
+-- throw exceptions or loop forever.
+splitIntoCoprimesSpecialCase1 :: Assertion
+splitIntoCoprimesSpecialCase1 =
+  assertBool "should not fail" $ splitIntoCoprimesProperty4 [(0, 0), (0, 0)]
+
+-- | This is an undefined behaviour, but at least it should not
+-- throw exceptions or loop forever.
+splitIntoCoprimesSpecialCase2 :: Assertion
+splitIntoCoprimesSpecialCase2 =
+  assertBool "should not fail" $ splitIntoCoprimesProperty4 [(0, 1), (-2, 0)]
+
+toListReturnsCorrectValues :: Assertion
+toListReturnsCorrectValues = assertEqual
+  "should be equal"
+  (sort $ unCoprimes $ splitIntoCoprimes [(140, 1), (165, 1)])
+  ([(5,2),(28,1),(33,1)] :: [(Integer, Word)])
+
+unionReturnsCorrectValues :: Assertion
+unionReturnsCorrectValues = assertEqual "should be equal" expected actual
+  where
+    a :: Coprimes Integer Word
+    a = splitIntoCoprimes [(700, 1), (165, 1)] -- [(5,3),(28,1),(33,1)]
+    b = splitIntoCoprimes [(360, 1), (210, 1)] -- [(2,4),(3,3),(5,2),(7,1)]
+    expected = [(2,6),(3,4),(5,5),(7,2),(11,1)]
+    actual = sort $ unCoprimes (a <> b)
+
+insertReturnsCorrectValuesWhenCoprimeBase :: Assertion
+insertReturnsCorrectValuesWhenCoprimeBase =
+  let a = insert 5 2 (singleton 4 3)
+      expected = [(4,3), (5,2)]
+      actual = sort $ unCoprimes a :: [(Int, Int)]
+  in assertEqual "should be equal" expected actual
+
+insertReturnsCorrectValuesWhenNotCoprimeBase :: Assertion
+insertReturnsCorrectValuesWhenNotCoprimeBase =
+  let a = insert 2 4 (insert 7 1 (insert 5 2 (singleton 4 3)))
+      actual = sort $ unCoprimes a :: [(Int, Int)]
+      expected = [(2,10), (5,2), (7,1)]
+  in assertEqual "should be equal" expected actual
+
+unionProperty1 :: [(Positive Natural, Power Word)] -> [(Positive Natural, Power Word)] -> Bool
+unionProperty1 xs ys
+  =  sort (unCoprimes (splitIntoCoprimes (xs' <> ys')))
+  == sort (unCoprimes (splitIntoCoprimes xs' <> splitIntoCoprimes ys'))
+  where
+    xs' = map (getPositive *** getPower) xs
+    ys' = map (getPositive *** getPower) ys
+
+testSuite :: TestTree
+testSuite = testGroup "Euclidean"
+  [ testSameIntegralProperty "extendedGCD" extendedGCDProperty
+  , testSameIntegralProperty "coprime"     coprimeProperty
+  , testGroup "splitIntoCoprimes"
+    [ testSmallAndQuick "preserves product of factors"        splitIntoCoprimesProperty1
+    , testSmallAndQuick "number of factors is non-decreasing" splitIntoCoprimesProperty2
+    , testSmallAndQuick "output factors are coprime"          splitIntoCoprimesProperty3
+
+    , testCase          "does not freeze 1"                   splitIntoCoprimesSpecialCase1
+    , testCase          "does not freeze 2"                   splitIntoCoprimesSpecialCase2
+    , testSmallAndQuick "does not freeze random"              splitIntoCoprimesProperty4
+    ]
+  , testGroup "Coprimes"
+    [  testCase         "test equality"                       toListReturnsCorrectValues
+    ,  testCase         "test union"                          unionReturnsCorrectValues
+    ,  testCase         "test insert with coprime base"       insertReturnsCorrectValuesWhenCoprimeBase
+    ,  testCase         "test insert with non-coprime base"   insertReturnsCorrectValuesWhenNotCoprimeBase
+    ,  testSmallAndQuick "property union"                     unionProperty1
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/GCDTests.hs b/test-suite/Math/NumberTheory/GCDTests.hs
deleted file mode 100644
--- a/test-suite/Math/NumberTheory/GCDTests.hs
+++ /dev/null
@@ -1,147 +0,0 @@
--- |
--- Module:      Math.NumberTheory.GCDTests
--- Copyright:   (c) 2016 Andrew Lelechenko
--- Licence:     MIT
--- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
---
--- Tests for Math.NumberTheory.GCD
---
-
-{-# LANGUAGE CPP                 #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
-{-# OPTIONS_GHC -fno-warn-type-defaults  #-}
-{-# OPTIONS_GHC -fno-warn-unused-imports #-}
-{-# OPTIONS_GHC -fno-warn-deprecations   #-}
-
-module Math.NumberTheory.GCDTests
-  ( testSuite
-  ) where
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Control.Arrow
-import Data.Bits
-import Data.Semigroup
-import Data.List (tails, sort)
-import Numeric.Natural
-
-import Math.NumberTheory.Euclidean.Coprimes
-import Math.NumberTheory.GCD
-import Math.NumberTheory.TestUtils
-
--- | Check that 'binaryGCD' matches 'gcd'.
-binaryGCDProperty :: (Integral a, Bits a) => AnySign a -> AnySign a -> Bool
-binaryGCDProperty (AnySign a) (AnySign b) = binaryGCD a b == gcd a b
-
--- | Check that 'extendedGCD' is consistent with documentation.
-extendedGCDProperty :: forall a. Integral a => AnySign a -> AnySign a -> Bool
-extendedGCDProperty (AnySign a) (AnySign b) =
-  u * a + v * b == d
-  && d == gcd a b
-  -- (-1) >= 0 is true for unsigned types
-  && (abs u < abs b || abs b <= 1 || (-1 :: a) >= 0)
-  && (abs v < abs a || abs a <= 1 || (-1 :: a) >= 0)
-  where
-    (d, u, v) = extendedGCD a b
-
--- | Check that numbers are coprime iff their gcd equals to 1.
-coprimeProperty :: (Integral a, Bits a) => AnySign a -> AnySign a -> Bool
-coprimeProperty (AnySign a) (AnySign b) = coprime a b == (gcd a b == 1)
-
-splitIntoCoprimesProperty1 :: [(Positive Natural, Power Word)] -> Bool
-splitIntoCoprimesProperty1 fs' = factorback fs == factorback (unCoprimes $ splitIntoCoprimes fs)
-  where
-    fs = map (getPositive *** getPower) fs'
-    factorback = product . map (uncurry (^))
-
-splitIntoCoprimesProperty2 :: [(Positive Natural, Power Word)] -> Bool
-splitIntoCoprimesProperty2 fs' = multiplicities fs <= multiplicities (unCoprimes $ splitIntoCoprimes fs)
-  where
-    fs = map (getPositive *** getPower) fs'
-    multiplicities = sum . map snd . filter ((/= 1) . fst)
-
-splitIntoCoprimesProperty3 :: [(Positive Natural, Power Word)] -> Bool
-splitIntoCoprimesProperty3 fs' = and [ coprime x y | (x : xs) <- tails fs, y <- xs ]
-  where
-    fs = map fst $ unCoprimes $ splitIntoCoprimes $ map (getPositive *** getPower) fs'
-
--- | Check that evaluation never freezes.
-splitIntoCoprimesProperty4 :: [(Integer, Word)] -> Bool
-splitIntoCoprimesProperty4 fs' = fs == fs
-  where
-    fs = splitIntoCoprimes fs'
-
--- | This is an undefined behaviour, but at least it should not
--- throw exceptions or loop forever.
-splitIntoCoprimesSpecialCase1 :: Assertion
-splitIntoCoprimesSpecialCase1 =
-  assertBool "should not fail" $ splitIntoCoprimesProperty4 [(0, 0), (0, 0)]
-
--- | This is an undefined behaviour, but at least it should not
--- throw exceptions or loop forever.
-splitIntoCoprimesSpecialCase2 :: Assertion
-splitIntoCoprimesSpecialCase2 =
-  assertBool "should not fail" $ splitIntoCoprimesProperty4 [(0, 1), (-2, 0)]
-
-toListReturnsCorrectValues :: Assertion
-toListReturnsCorrectValues = assertEqual
-  "should be equal"
-  (sort $ unCoprimes $ splitIntoCoprimes [(140, 1), (165, 1)])
-  ([(5,2),(28,1),(33,1)] :: [(Integer, Word)])
-
-unionReturnsCorrectValues :: Assertion
-unionReturnsCorrectValues = assertEqual "should be equal" expected actual
-  where
-    a :: Coprimes Integer Word
-    a = splitIntoCoprimes [(700, 1), (165, 1)] -- [(5,3),(28,1),(33,1)]
-    b = splitIntoCoprimes [(360, 1), (210, 1)] -- [(2,4),(3,3),(5,2),(7,1)]
-    expected = [(2,6),(3,4),(5,5),(7,2),(11,1)]
-    actual = sort $ unCoprimes (a <> b)
-
-insertReturnsCorrectValuesWhenCoprimeBase :: Assertion
-insertReturnsCorrectValuesWhenCoprimeBase =
-  let a = insert 5 2 (singleton 4 3)
-      expected = [(4,3), (5,2)]
-      actual = sort $ unCoprimes a :: [(Int, Int)]
-  in assertEqual "should be equal" expected actual
-
-insertReturnsCorrectValuesWhenNotCoprimeBase :: Assertion
-insertReturnsCorrectValuesWhenNotCoprimeBase =
-  let a = insert 2 4 (insert 7 1 (insert 5 2 (singleton 4 3)))
-      actual = sort $ unCoprimes a :: [(Int, Int)]
-      expected = [(2,10), (5,2), (7,1)]
-  in assertEqual "should be equal" expected actual
-
-unionProperty1 :: [(Positive Natural, Power Word)] -> [(Positive Natural, Power Word)] -> Bool
-unionProperty1 xs ys
-  =  sort (unCoprimes (splitIntoCoprimes (xs' <> ys')))
-  == sort (unCoprimes (splitIntoCoprimes xs' <> splitIntoCoprimes ys'))
-  where
-    xs' = map (getPositive *** getPower) xs
-    ys' = map (getPositive *** getPower) ys
-
-testSuite :: TestTree
-testSuite = testGroup "GCD"
-  [ testSameIntegralProperty "binaryGCD"   binaryGCDProperty
-  , testSameIntegralProperty "extendedGCD" extendedGCDProperty
-  , testSameIntegralProperty "coprime"     coprimeProperty
-  , testGroup "splitIntoCoprimes"
-    [ testSmallAndQuick "preserves product of factors"        splitIntoCoprimesProperty1
-    , testSmallAndQuick "number of factors is non-decreasing" splitIntoCoprimesProperty2
-    , testSmallAndQuick "output factors are coprime"          splitIntoCoprimesProperty3
-
-    , testCase          "does not freeze 1"                   splitIntoCoprimesSpecialCase1
-    , testCase          "does not freeze 2"                   splitIntoCoprimesSpecialCase2
-    , testSmallAndQuick "does not freeze random"              splitIntoCoprimesProperty4
-    ]
-  , testGroup "Coprimes"
-    [  testCase         "test equality"                       toListReturnsCorrectValues
-    ,  testCase         "test union"                          unionReturnsCorrectValues
-    ,  testCase         "test insert with coprime base"       insertReturnsCorrectValuesWhenCoprimeBase
-    ,  testCase         "test insert with non-coprime base"   insertReturnsCorrectValuesWhenNotCoprimeBase
-    ,  testSmallAndQuick "property union"                     unionProperty1
-    ]
-  ]
diff --git a/test-suite/Math/NumberTheory/GaussianIntegersTests.hs b/test-suite/Math/NumberTheory/GaussianIntegersTests.hs
--- a/test-suite/Math/NumberTheory/GaussianIntegersTests.hs
+++ b/test-suite/Math/NumberTheory/GaussianIntegersTests.hs
@@ -5,7 +5,6 @@
 -- Copyright:   (c) 2016 Chris Fredrickson, Google Inc.
 -- Licence:     MIT
 -- Maintainer:  Chris Fredrickson <chris.p.fredrickson@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.GaussianIntegers
 --
@@ -16,6 +15,7 @@
 
 import Control.Monad (zipWithM_)
 import Data.List (groupBy, sort)
+import Data.Maybe (fromJust, mapMaybe)
 import Test.Tasty
 import Test.Tasty.HUnit
 
@@ -23,15 +23,15 @@
 import Math.NumberTheory.Quadratic.GaussianIntegers
 import Math.NumberTheory.Moduli.Sqrt
 import Math.NumberTheory.Powers (integerSquareRoot)
-import Math.NumberTheory.UniqueFactorisation (unPrime)
+import Math.NumberTheory.Primes (Prime, unPrime, UniqueFactorisation(..))
 import Math.NumberTheory.TestUtils
 
-lazyCases :: [(GaussianInteger, [(GaussianInteger, Int)])]
+lazyCases :: [(GaussianInteger, [(Prime GaussianInteger, Word)])]
 lazyCases =
   [ ( 14145130733
     * 10000000000000000000000000000000000000121
     * 100000000000000000000000000000000000000000000000447
-    , [(117058 :+ 21037, 1), (21037 :+ 117058, 1)]
+    , [(fromJust $ isPrime $ 117058 :+ 21037, 1), (fromJust $ isPrime $ 21037 :+ 117058, 1)]
     )
   ]
 
@@ -42,24 +42,27 @@
   || abs g == abs g'
   where
     factors = factorise g
-    g' = product $ map (uncurry (^)) factors
+    g' = product $ map (\(p, k) -> unPrime p ^ k) factors
 
 factoriseProperty2 :: GaussianInteger -> Bool
 factoriseProperty2 z = z == 0 || all ((> 0) . snd) (factorise z)
 
 factoriseProperty3 :: GaussianInteger -> Bool
-factoriseProperty3 z = z == 0 || all ((> 1) . norm . fst) (factorise z)
+factoriseProperty3 z = z == 0 || all ((> 1) . norm . unPrime . fst) (factorise z)
 
 factoriseSpecialCase1 :: Assertion
 factoriseSpecialCase1 = assertEqual "should be equal"
-  [(3, 2), (1 :+ 2, 1), (2 :+ 3, 1)]
+  [ (fromJust $ isPrime $ 3 :+ 0, 2)
+  , (fromJust $ isPrime $ 1 :+ 2, 1)
+  , (fromJust $ isPrime $ 2 :+ 3, 1)
+  ]
   (factorise (63 :+ 36))
 
-factoriseSpecialCase2 :: (GaussianInteger, [(GaussianInteger, Int)]) -> Assertion
+factoriseSpecialCase2 :: (GaussianInteger, [(Prime GaussianInteger, Word)]) -> Assertion
 factoriseSpecialCase2 (n, fs) = zipWithM_ (assertEqual (show n)) fs (factorise n)
 
-findPrimeReference :: PrimeWrapper Integer -> GaussianInteger
-findPrimeReference (PrimeWrapper p) =
+findPrimeReference :: Prime Integer -> GaussianInteger
+findPrimeReference p =
     let c : _ = sqrtsModPrime (-1) p
         k  = integerSquareRoot (unPrime p)
         bs = [1 .. k]
@@ -67,22 +70,22 @@
         (a, b) = head [ (a', b') | (a', b') <- asbs, a' <= k]
     in a :+ b
 
-findPrimeProperty1 :: PrimeWrapper Integer -> Bool
-findPrimeProperty1 p'@(PrimeWrapper p)
+findPrimeProperty1 :: Prime Integer -> Bool
+findPrimeProperty1 p
   = unPrime p `mod` 4 /= (1 :: Integer)
   || p1 == p2
   || abs (p1 * p2) == fromInteger (unPrime p)
   where
-    p1 = findPrimeReference p'
-    p2 = findPrime (unPrime p)
+    p1 = findPrimeReference p
+    p2 = unPrime (findPrime p)
 
 -- | Number is prime iff it is non-zero
 --   and has exactly one (non-unit) factor.
 isPrimeProperty :: GaussianInteger -> Bool
-isPrimeProperty g
-  =  g == 0
-  || isPrime g && n == 1
-  || not (isPrime g) && n /= 1
+isPrimeProperty 0 = True
+isPrimeProperty g = case isPrime g of
+  Nothing -> n /= 1
+  Just{}  -> n == 1
   where
     factors = factorise g
     -- Count factors taking into account multiplicity
@@ -90,25 +93,27 @@
 
 primesSpecialCase1 :: Assertion
 primesSpecialCase1 = assertEqual "primes"
-  (f [1+ι,2+ι,1+2*ι,3,3+2*ι,2+3*ι,4+ι,1+4*ι,5+2*ι,2+5*ι,6+ι,1+6*ι,5+4*ι,4+5*ι,7,7+2*ι,2+7*ι,6+5*ι,5+6*ι,8+3*ι,3+8*ι,8+5*ι,5+8*ι,9+4*ι,4+9*ι,10+ι,1+10*ι,10+3*ι,3+10*ι,8+7*ι,7+8*ι,11,11+4*ι,4+11*ι,10+7*ι,7+10*ι,11+6*ι,6+11*ι,13+2*ι,2+13*ι,10+9*ι,9+10*ι,12+7*ι,7+12*ι,14+ι,1+14*ι,15+2*ι,2+15*ι,13+8*ι,8+13*ι,15+4*ι])
+  (f $ mapMaybe isPrime [1+ι,2+ι,1+2*ι,3,3+2*ι,2+3*ι,4+ι,1+4*ι,5+2*ι,2+5*ι,6+ι,1+6*ι,5+4*ι,4+5*ι,7,7+2*ι,2+7*ι,6+5*ι,5+6*ι,8+3*ι,3+8*ι,8+5*ι,5+8*ι,9+4*ι,4+9*ι,10+ι,1+10*ι,10+3*ι,3+10*ι,8+7*ι,7+8*ι,11,11+4*ι,4+11*ι,10+7*ι,7+10*ι,11+6*ι,6+11*ι,13+2*ι,2+13*ι,10+9*ι,9+10*ι,12+7*ι,7+12*ι,14+ι,1+14*ι,15+2*ι,2+15*ι,13+8*ι,8+13*ι,15+4*ι])
   (f $ take 51 primes)
   where
-    f :: [GaussianInteger] -> [[GaussianInteger]]
-    f = map sort . groupBy (\g1 g2 -> norm g1 == norm g2)
+    f :: [Prime GaussianInteger] -> [[Prime GaussianInteger]]
+    f = map sort . groupBy (\g1 g2 -> norm (unPrime g1) == norm (unPrime g2))
 
 -- | The list of primes should include only primes.
 primesGeneratesPrimesProperty :: NonNegative Int -> Bool
-primesGeneratesPrimesProperty (NonNegative i) = isPrime (primes !! i)
+primesGeneratesPrimesProperty (NonNegative i) = case isPrime (unPrime (primes !! i) :: GaussianInteger) of
+  Nothing -> False
+  Just{}  -> True
 
 -- | Check that primes generates the primes in order.
 orderingPrimes :: Assertion
 orderingPrimes = assertBool "primes are ordered" (and $ zipWith (<=) xs (tail xs))
-  where xs = map norm $ take 1000 primes
+  where xs = map (norm . unPrime) $ take 1000 primes
 
 numberOfPrimes :: Assertion
 numberOfPrimes = assertEqual "counting primes: OEIS A091100"
   [16,100,668,4928,38404,313752,2658344]
-  [4 * (length $ takeWhile ((<= 10^n) . norm) primes) | n <- [1..7]]
+  [4 * (length $ takeWhile ((<= 10^n) . norm . unPrime) primes) | n <- [1..7]]
 
 -- | signum and abs should satisfy: z == signum z * abs z
 signumAbsProperty :: GaussianInteger -> Bool
diff --git a/test-suite/Math/NumberTheory/Moduli/ChineseTests.hs b/test-suite/Math/NumberTheory/Moduli/ChineseTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/ChineseTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/ChineseTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Moduli.Chinese
 --
@@ -41,8 +40,32 @@
 chineseRemainder2Property r1 (Positive m1) r2 (Positive m2) = gcd m1 m2 /= 1
   || Just (chineseRemainder2 (r1, m1) (r2, m2)) == chineseRemainder [(r1, m1), (r2, m2)]
 
+chineseCoprimeProperty :: Integer -> Positive Integer -> Integer -> Positive Integer -> Bool
+chineseCoprimeProperty n1 (Positive m1) n2 (Positive m2) = case gcd m1 m2 of
+  1 -> case chineseCoprime (n1, m1) (n2, m2) of
+    Nothing -> False
+    Just n  -> n `mod` m1 == n1 `mod` m1 && n `mod` m2 == n2 `mod` m2
+  _ -> case chineseCoprime (n1, m1) (n2, m2) of
+    Nothing -> True
+    Just{}  -> False
+
+chineseProperty :: Integer -> Positive Integer -> Integer -> Positive Integer -> Bool
+chineseProperty n1 (Positive m1) n2 (Positive m2) = if compatible
+  then case chinese (n1, m1) (n2, m2) of
+    Nothing -> False
+    Just n  -> n `mod` m1 == n1 `mod` m1 && n `mod` m2 == n2 `mod` m2
+  else case chineseCoprime (n1, m1) (n2, m2) of
+    Nothing -> True
+    Just{}  -> False
+  where
+    g = gcd m1 m2
+    compatible = n1 `mod` g == n2 `mod` g
+
+
 testSuite :: TestTree
 testSuite = testGroup "Chinese"
   [ testSmallAndQuick "chineseRemainder"  chineseRemainderProperty
   , testSmallAndQuick "chineseRemainder2" chineseRemainder2Property
+  , testSmallAndQuick "chineseCoprime"    chineseCoprimeProperty
+  , testSmallAndQuick "chinese"           chineseProperty
   ]
diff --git a/test-suite/Math/NumberTheory/Moduli/ClassTests.hs b/test-suite/Math/NumberTheory/Moduli/ClassTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/ClassTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/ClassTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Moduli.Class
 --
@@ -18,6 +17,7 @@
   ) where
 
 import Test.Tasty
+import qualified Test.Tasty.QuickCheck as QC
 
 import Data.Maybe
 import Numeric.Natural
@@ -94,6 +94,27 @@
     m3 = toInteger $ m1 `gcd` m2
     x3 = (x1 * x2) `mod` m3
 
+sameSomeModMulProperty :: Integer -> Integer -> Positive Natural -> Bool
+sameSomeModMulProperty x1 x2 (Positive m) = case (x1 `modulo` m) * (x2 `modulo` m) of
+  SomeMod z -> getMod z == toInteger m && getVal z == x3
+  InfMod{}  -> False
+  where
+    x3 = (x1 * x2) `mod` toInteger m
+
+sameSomeModMulHugeProperty :: Integer -> Integer -> Positive (Huge Natural) -> Bool
+sameSomeModMulHugeProperty x1 x2 (Positive (Huge m)) = case (x1 `modulo` m) * (x2 `modulo` m) of
+  SomeMod z -> getMod z == toInteger m && getVal z == x3
+  InfMod{}  -> False
+  where
+    x3 = (x1 * x2) `mod` toInteger m
+
+sameSomeModMulHugeAllProperty :: Huge Integer -> Huge Integer -> Positive (Huge Natural) -> Bool
+sameSomeModMulHugeAllProperty (Huge x1) (Huge x2) (Positive (Huge m)) = case (x1 `modulo` m) * (x2 `modulo` m) of
+  SomeMod z -> getMod z == toInteger m && getVal z == x3
+  InfMod{}  -> False
+  where
+    x3 = (x1 * x2) `mod` toInteger m
+
 someModNegProperty :: Integer -> Positive Natural -> Bool
 someModNegProperty x1 (Positive m1) = case negate (x1 `modulo` m1) of
   SomeMod z -> getMod z == m3 && getVal z == x3
@@ -150,6 +171,11 @@
       [ testSmallAndQuick "multiplicative by base"  powerModProperty2_Integer
       , testSmallAndQuick "additive by exponent"    powerModProperty3_Integer
       ]
+    ]
+  , testGroup "Same SomeMod"
+    [ testSmallAndQuick "mul" sameSomeModMulProperty
+    , QC.testProperty "mul huge" sameSomeModMulHugeProperty
+    , QC.testProperty "mul huge all" sameSomeModMulHugeAllProperty
     ]
   , testGroup "SomeMod"
     [ testSmallAndQuick "add" someModAddProperty
diff --git a/test-suite/Math/NumberTheory/Moduli/EquationsTests.hs b/test-suite/Math/NumberTheory/Moduli/EquationsTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/EquationsTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/EquationsTests.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 
 {-# LANGUAGE ScopedTypeVariables #-}
diff --git a/test-suite/Math/NumberTheory/Moduli/JacobiTests.hs b/test-suite/Math/NumberTheory/Moduli/JacobiTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/JacobiTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/JacobiTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Moduli.Jacobi
 --
@@ -23,7 +22,6 @@
 #if __GLASGOW_HASKELL__ < 803
 import Data.Semigroup
 #endif
-import Numeric.Natural
 
 import Math.NumberTheory.Moduli hiding (invertMod)
 import Math.NumberTheory.TestUtils
@@ -50,36 +48,12 @@
   doesProductOverflow a b ||
   jacobi (a * b) n == jacobi a n <> jacobi b n
 
-jacobiProperty4_Int :: AnySign Int -> AnySign Int -> (MyCompose Positive Odd) Int -> Bool
-jacobiProperty4_Int = jacobiProperty4
-
-jacobiProperty4_Word :: AnySign Word -> AnySign Word -> (MyCompose Positive Odd) Word -> Bool
-jacobiProperty4_Word = jacobiProperty4
-
-jacobiProperty4_Integer :: AnySign Integer -> AnySign Integer -> (MyCompose Positive Odd) Integer -> Bool
-jacobiProperty4_Integer = jacobiProperty4
-
-jacobiProperty4_Natural :: AnySign Natural -> AnySign Natural -> (MyCompose Positive Odd) Natural -> Bool
-jacobiProperty4_Natural = jacobiProperty4
-
 -- https://en.wikipedia.org/wiki/Jacobi_symbol#Properties, item 5
 jacobiProperty5 :: (Integral a, Bits a) => AnySign a -> (MyCompose Positive Odd) a -> (MyCompose Positive Odd) a -> Bool
 jacobiProperty5 (AnySign a) (MyCompose (Positive (Odd m))) (MyCompose (Positive (Odd n))) =
   doesProductOverflow m n ||
   jacobi a (m * n) == jacobi a m <> jacobi a n
 
-jacobiProperty5_Int :: AnySign Int -> (MyCompose Positive Odd) Int -> (MyCompose Positive Odd) Int -> Bool
-jacobiProperty5_Int = jacobiProperty5
-
-jacobiProperty5_Word :: AnySign Word -> (MyCompose Positive Odd) Word -> (MyCompose Positive Odd) Word -> Bool
-jacobiProperty5_Word = jacobiProperty5
-
-jacobiProperty5_Integer :: AnySign Integer -> (MyCompose Positive Odd) Integer -> (MyCompose Positive Odd) Integer -> Bool
-jacobiProperty5_Integer = jacobiProperty5
-
-jacobiProperty5_Natural :: AnySign Natural -> (MyCompose Positive Odd) Natural -> (MyCompose Positive Odd) Natural -> Bool
-jacobiProperty5_Natural = jacobiProperty5
-
 -- https://en.wikipedia.org/wiki/Jacobi_symbol#Properties, item 6
 jacobiProperty6 :: (Integral a, Bits a) => (MyCompose Positive Odd) a -> (MyCompose Positive Odd) a -> Bool
 jacobiProperty6 (MyCompose (Positive (Odd m))) (MyCompose (Positive (Odd n))) = gcd m n /= 1 || jacobi m n <> jacobi n m == (if m `mod` 4 == 1 || n `mod` 4 == 1 then One else MinusOne)
@@ -112,19 +86,13 @@
 
 testSuite :: TestTree
 testSuite = testGroup "Jacobi"
-  [ testSameIntegralProperty "same modulo n"                jacobiProperty2
-  , testSameIntegralProperty "consistent with gcd"          jacobiProperty3
-  , testSmallAndQuick        "multiplicative 1 Int"         jacobiProperty4_Int
-  , testSmallAndQuick        "multiplicative 1 Word"        jacobiProperty4_Word
-  , testSmallAndQuick        "multiplicative 1 Integer"     jacobiProperty4_Integer
-  , testSmallAndQuick        "multiplicative 1 Natural"     jacobiProperty4_Natural
-  , testSmallAndQuick        "multiplicative 2 Int"         jacobiProperty5_Int
-  , testSmallAndQuick        "multiplicative 2 Word"        jacobiProperty5_Word
-  , testSmallAndQuick        "multiplicative 2 Integer"     jacobiProperty5_Integer
-  , testSmallAndQuick        "multiplicative 2 Natural"     jacobiProperty5_Natural
-  , testSameIntegralProperty "law of quadratic reciprocity" jacobiProperty6
-  , testSmallAndQuick        "-1 Int"                       jacobiProperty7_Int
-  , testSmallAndQuick        "-1 Integer"                   jacobiProperty7_Integer
-  , testIntegralProperty     "2"                            jacobiProperty8
-  , testSmallAndQuick        "minBound Int"                 jacobiProperty9_Int
+  [ testSameIntegralProperty  "same modulo n"                jacobiProperty2
+  , testSameIntegralProperty  "consistent with gcd"          jacobiProperty3
+  , testSameIntegralProperty3 "multiplicative 1"             jacobiProperty4
+  , testSameIntegralProperty3 "multiplicative 2"             jacobiProperty5
+  , testSameIntegralProperty  "law of quadratic reciprocity" jacobiProperty6
+  , testSmallAndQuick         "-1 Int"                       jacobiProperty7_Int
+  , testSmallAndQuick         "-1 Integer"                   jacobiProperty7_Integer
+  , testIntegralProperty      "2"                            jacobiProperty8
+  , testSmallAndQuick         "minBound Int"                 jacobiProperty9_Int
   ]
diff --git a/test-suite/Math/NumberTheory/Moduli/PrimitiveRootTests.hs b/test-suite/Math/NumberTheory/Moduli/PrimitiveRootTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/PrimitiveRootTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/PrimitiveRootTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Moduli.PrimitiveRoot
 --
@@ -34,8 +33,8 @@
 import Math.NumberTheory.Moduli.Class (Mod, SomeMod(..), modulo)
 import Math.NumberTheory.Moduli.PrimitiveRoot
 import Math.NumberTheory.Prefactored (fromFactors, prefFactors, prefValue, Prefactored)
+import Math.NumberTheory.Primes
 import Math.NumberTheory.TestUtils
-import Math.NumberTheory.UniqueFactorisation
 
 cyclicGroupProperty1 :: (Euclidean a, Integral a, UniqueFactorisation a) => AnySign a -> Bool
 cyclicGroupProperty1 (AnySign n) = case cyclicGroupFromModulo n of
diff --git a/test-suite/Math/NumberTheory/Moduli/SqrtTests.hs b/test-suite/Math/NumberTheory/Moduli/SqrtTests.hs
--- a/test-suite/Math/NumberTheory/Moduli/SqrtTests.hs
+++ b/test-suite/Math/NumberTheory/Moduli/SqrtTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Moduli.Sqrt
 --
@@ -26,49 +25,46 @@
 import Numeric.Natural
 
 import Math.NumberTheory.Moduli hiding (invertMod)
-import Math.NumberTheory.UniqueFactorisation (unPrime, isPrime, Prime)
+import Math.NumberTheory.Primes (unPrime, isPrime, Prime)
 import Math.NumberTheory.TestUtils
 
-unwrapP :: PrimeWrapper Integer -> Prime Integer
-unwrapP (PrimeWrapper p) = p
-
-unwrapPP :: (PrimeWrapper Integer, Power Word) -> (Prime Integer, Word)
-unwrapPP (p, Power e) = (unwrapP p, e `mod` 5)
+unwrapPP :: (Prime Integer, Power Word) -> (Prime Integer, Word)
+unwrapPP (p, Power e) = (p, e `mod` 5)
 
 nubOrd :: Ord a => [a] -> [a]
 nubOrd = map head . group . sort
 
 -- | Check that 'sqrtMod' is defined iff a quadratic residue exists.
 --   Also check that the result is a solution of input modular equation.
-sqrtsModPrimeProperty1 :: AnySign Integer -> PrimeWrapper Integer -> Bool
-sqrtsModPrimeProperty1 (AnySign n) (unwrapP -> p'@(unPrime -> p)) = case sqrtsModPrime n p' of
+sqrtsModPrimeProperty1 :: AnySign Integer -> Prime Integer -> Bool
+sqrtsModPrimeProperty1 (AnySign n) p'@(unPrime -> p) = case sqrtsModPrime n p' of
   []     -> jacobi n p == MinusOne
   rt : _ -> (p == 2 || jacobi n p /= MinusOne) && rt ^ 2 `mod` p == n `mod` p
 
-sqrtsModPrimeProperty2 :: AnySign Integer -> PrimeWrapper Integer -> Bool
-sqrtsModPrimeProperty2 (AnySign n) (unwrapP -> p'@(unPrime -> p)) = all (\rt -> rt ^ 2 `mod` p == n `mod` p) (sqrtsModPrime n p')
+sqrtsModPrimeProperty2 :: AnySign Integer -> Prime Integer -> Bool
+sqrtsModPrimeProperty2 (AnySign n) p'@(unPrime -> p) = all (\rt -> rt ^ 2 `mod` p == n `mod` p) (sqrtsModPrime n p')
 
-sqrtsModPrimeProperty3 :: AnySign Integer -> PrimeWrapper Integer -> Bool
-sqrtsModPrimeProperty3 (AnySign n) (unwrapP -> p'@(unPrime -> p)) = nubOrd rts == sort rts
+sqrtsModPrimeProperty3 :: AnySign Integer -> Prime Integer -> Bool
+sqrtsModPrimeProperty3 (AnySign n) p'@(unPrime -> p) = nubOrd rts == sort rts
   where
     rts = map (`mod` p) $ sqrtsModPrime n p'
 
-sqrtsModPrimeProperty4 :: AnySign Integer -> PrimeWrapper Integer -> Bool
-sqrtsModPrimeProperty4 (AnySign n) (unwrapP -> p'@(unPrime -> p)) = all (\rt -> rt >= 0 && rt < p) (sqrtsModPrime n p')
+sqrtsModPrimeProperty4 :: AnySign Integer -> Prime Integer -> Bool
+sqrtsModPrimeProperty4 (AnySign n) p'@(unPrime -> p) = all (\rt -> rt >= 0 && rt < p) (sqrtsModPrime n p')
 
-tonelliShanksProperty1 :: Positive Integer -> PrimeWrapper Integer -> Bool
-tonelliShanksProperty1 (Positive n) (unwrapP -> p'@(unPrime -> p)) = p `mod` 4 /= 1 || jacobi n p /= One || rt ^ 2 `mod` p == n `mod` p
+tonelliShanksProperty1 :: Positive Integer -> Prime Integer -> Bool
+tonelliShanksProperty1 (Positive n) p'@(unPrime -> p) = p `mod` 4 /= 1 || jacobi n p /= One || rt ^ 2 `mod` p == n `mod` p
   where
     rt : _ = sqrtsModPrime n p'
 
-tonelliShanksProperty2 :: PrimeWrapper Integer -> Bool
-tonelliShanksProperty2 (unwrapP -> p'@(unPrime -> p)) = p `mod` 4 /= 1 || rt ^ 2 `mod` p == n `mod` p
+tonelliShanksProperty2 :: Prime Integer -> Bool
+tonelliShanksProperty2 p'@(unPrime -> p) = p `mod` 4 /= 1 || rt ^ 2 `mod` p == n `mod` p
   where
     n  = head $ filter (\s -> jacobi s p == One) [2..p-1]
     rt : _ = sqrtsModPrime n p'
 
-tonelliShanksProperty3 :: PrimeWrapper Integer -> Bool
-tonelliShanksProperty3 (unwrapP -> p'@(unPrime -> p))
+tonelliShanksProperty3 :: Prime Integer -> Bool
+tonelliShanksProperty3 p'@(unPrime -> p)
   = p `mod` 4 /= 1
   || rt ^ 2 `mod` p == p - 1
   where
@@ -82,32 +78,32 @@
     ps = [17, 73, 241, 1009, 2689, 8089, 33049, 53881, 87481, 483289, 515761, 1083289, 3818929, 9257329, 22000801, 48473881, 175244281, 427733329, 898716289, 8114538721, 9176747449, 23616331489]
     rts = map (head . sqrtsModPrime 2 . fromJust . isPrime) ps
 
-sqrtsModPrimePowerProperty1 :: AnySign Integer -> (PrimeWrapper Integer, Power Word) -> Bool
-sqrtsModPrimePowerProperty1 (AnySign n) (unwrapP -> p'@(unPrime -> p), Power e) = gcd n p > 1
+sqrtsModPrimePowerProperty1 :: AnySign Integer -> (Prime Integer, Power Word) -> Bool
+sqrtsModPrimePowerProperty1 (AnySign n) (p'@(unPrime -> p), Power e) = gcd n p > 1
   || all (\rt -> rt ^ 2 `mod` (p ^ e) == n `mod` (p ^ e)) (sqrtsModPrimePower n p' e)
 
 sqrtsModPrimePowerProperty2 :: AnySign Integer -> Power Word -> Bool
-sqrtsModPrimePowerProperty2 n e = sqrtsModPrimePowerProperty1 n (PrimeWrapper $ fromJust $ isPrime (2 :: Integer), e)
+sqrtsModPrimePowerProperty2 n e = sqrtsModPrimePowerProperty1 n (fromJust $ isPrime (2 :: Integer), e)
 
-sqrtsModPrimePowerProperty3 :: AnySign Integer -> (PrimeWrapper Integer, Power Word) -> Bool
-sqrtsModPrimePowerProperty3 (AnySign n) (unwrapP -> p'@(unPrime -> p), Power e') = nubOrd rts == sort rts
+sqrtsModPrimePowerProperty3 :: AnySign Integer -> (Prime Integer, Power Word) -> Bool
+sqrtsModPrimePowerProperty3 (AnySign n) (p'@(unPrime -> p), Power e') = nubOrd rts == sort rts
   where
     e = e' `mod` 5
     m = p ^ e
     rts = map (`mod` m) $ sqrtsModPrimePower n p' e
 
 sqrtsModPrimePowerProperty4 :: AnySign Integer -> Power Word -> Bool
-sqrtsModPrimePowerProperty4 n e = sqrtsModPrimePowerProperty3 n (PrimeWrapper $ fromJust $ isPrime (2 :: Integer), e)
+sqrtsModPrimePowerProperty4 n e = sqrtsModPrimePowerProperty3 n (fromJust $ isPrime (2 :: Integer), e)
 
-sqrtsModPrimePowerProperty5 :: AnySign Integer -> (PrimeWrapper Integer, Power Word) -> Bool
-sqrtsModPrimePowerProperty5 (AnySign n) (unwrapP -> p'@(unPrime -> p), Power e') = all (\rt -> rt >= 0 && rt < m) rts
+sqrtsModPrimePowerProperty5 :: AnySign Integer -> (Prime Integer, Power Word) -> Bool
+sqrtsModPrimePowerProperty5 (AnySign n) (p'@(unPrime -> p), Power e') = all (\rt -> rt >= 0 && rt < m) rts
   where
     e = e' `mod` 5
     m = p ^ e
     rts = sqrtsModPrimePower n p' e
 
 sqrtsModPrimePowerProperty6 :: AnySign Integer -> Power Word -> Bool
-sqrtsModPrimePowerProperty6 n e = sqrtsModPrimePowerProperty5 n (PrimeWrapper $ fromJust $ isPrime (2 :: Integer), e)
+sqrtsModPrimePowerProperty6 n e = sqrtsModPrimePowerProperty5 n (fromJust $ isPrime (2 :: Integer), e)
 
 sqrtsModPrimePowerSpecialCase1 :: Assertion
 sqrtsModPrimePowerSpecialCase1 =
@@ -153,7 +149,7 @@
 sqrtsModPrimePowerSpecialCase11 =
   assertEqual "should be equal" [4,12,20,28,36,44,52,60] (sort (sqrtsModPrimePower 16 (fromJust (isPrime (2 :: Integer))) 6))
 
-sqrtsModFactorisationProperty1 :: AnySign Integer -> [(PrimeWrapper Integer, Power Word)] -> Bool
+sqrtsModFactorisationProperty1 :: AnySign Integer -> [(Prime Integer, Power Word)] -> Bool
 sqrtsModFactorisationProperty1 (AnySign n) (take 10 . map unwrapPP -> pes'@(map (first unPrime) -> pes))
   = nubOrd ps /= sort ps || all
     (\rt -> all (\(p, e) -> rt ^ 2 `mod` (p ^ e) == n `mod` (p ^ e)) pes)
@@ -161,7 +157,7 @@
   where
     ps = map fst pes
 
-sqrtsModFactorisationProperty2 :: AnySign Integer -> [(PrimeWrapper Integer, Power Word)] -> Bool
+sqrtsModFactorisationProperty2 :: AnySign Integer -> [(Prime Integer, Power Word)] -> Bool
 sqrtsModFactorisationProperty2 (AnySign n) (take 10 . map unwrapPP -> pes'@(map (first unPrime) -> pes))
   = nubOrd ps /= sort ps || nubOrd rts == sort rts
   where
@@ -169,7 +165,7 @@
     m = product $ map (\(p, e) -> p ^ e) pes
     rts = map (`mod` m) $ take 1000 $ sqrtsModFactorisation n pes'
 
-sqrtsModFactorisationProperty3 :: AnySign Integer -> [(PrimeWrapper Integer, Power Word)] -> Bool
+sqrtsModFactorisationProperty3 :: AnySign Integer -> [(Prime Integer, Power Word)] -> Bool
 sqrtsModFactorisationProperty3 (AnySign n) (take 10 . map unwrapPP -> pes'@(map (first unPrime) -> pes))
   = nubOrd ps /= sort ps || all (\rt -> rt >= 0 && rt < m) rts
   where
diff --git a/test-suite/Math/NumberTheory/MoebiusInversion/IntTests.hs b/test-suite/Math/NumberTheory/MoebiusInversion/IntTests.hs
--- a/test-suite/Math/NumberTheory/MoebiusInversion/IntTests.hs
+++ b/test-suite/Math/NumberTheory/MoebiusInversion/IntTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.MoebiusInversion.Int
 --
diff --git a/test-suite/Math/NumberTheory/MoebiusInversionTests.hs b/test-suite/Math/NumberTheory/MoebiusInversionTests.hs
--- a/test-suite/Math/NumberTheory/MoebiusInversionTests.hs
+++ b/test-suite/Math/NumberTheory/MoebiusInversionTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.MoebiusInversion
 --
diff --git a/test-suite/Math/NumberTheory/Powers/CubesTests.hs b/test-suite/Math/NumberTheory/Powers/CubesTests.hs
--- a/test-suite/Math/NumberTheory/Powers/CubesTests.hs
+++ b/test-suite/Math/NumberTheory/Powers/CubesTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Powers.Cubes
 --
@@ -37,9 +36,9 @@
   where
     m = integerCubeRoot n
     cond
-      | m == -1   = n == -1
-      | m < 0     = (m + 1) ^ 2 <= n `div` (m + 1)
-      | otherwise = (m + 1) ^ 2 >= n `div` (m + 1)
+      | m < 0 && m == -1 = n == -1
+      | m < 0            = (m + 1) ^ 2 <= n `div` (m + 1)
+      | otherwise        = (m + 1) ^ 2 >= n `div` (m + 1)
 
 -- | Specialized to trigger 'cubeRootInt''.
 integerCubeRootProperty_Int :: AnySign Int -> Bool
@@ -55,14 +54,14 @@
 
 -- | Check that 'integerCubeRoot' returns the largest integer @m@ with @m^3 <= n@, , where @n@ has form @k@^3-1.
 integerCubeRootProperty2 :: Integral a => AnySign a -> Bool
-integerCubeRootProperty2 (AnySign k) = m ^ 3 <= n && (m + 1) ^ 3 /= n && cond
+integerCubeRootProperty2 (AnySign k) = k == 0 || (m ^ 3 <= n && (m + 1) ^ 3 /= n && cond)
   where
     n = k ^ 3 - 1
     m = integerCubeRoot n
     cond
-      | m == -1   = n == -1
-      | m < 0     = (m + 1) ^ 2 <= n `div` (m + 1)
-      | otherwise = (m + 1) ^ 2 >= n `div` (m + 1)
+      | m < 0 && m == -1 = n == -1
+      | m < 0            = (m + 1) ^ 2 <= n `div` (m + 1)
+      | otherwise        = (m + 1) ^ 2 >= n `div` (m + 1)
 
 -- | Specialized to trigger 'cubeRootInt''.
 integerCubeRootProperty2_Int :: AnySign Int -> Bool
diff --git a/test-suite/Math/NumberTheory/Powers/FourthTests.hs b/test-suite/Math/NumberTheory/Powers/FourthTests.hs
--- a/test-suite/Math/NumberTheory/Powers/FourthTests.hs
+++ b/test-suite/Math/NumberTheory/Powers/FourthTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Powers.Fourth
 --
@@ -50,18 +49,18 @@
 integerFourthRootProperty_Integer = integerFourthRootProperty
 
 -- | Check that 'integerFourthRoot' returns the largest integer @m@ with @m^4 <= n@, , where @n@ has form @k@^4-1.
-integerFourthRootProperty2 :: Integral a => NonNegative a -> Bool
-integerFourthRootProperty2 (NonNegative k) = n < 0 || m >= 0 && m ^ 4 <= n && (m + 1) ^ 4 /= n && (m + 1) ^ 3 >= n `div` (m + 1)
+integerFourthRootProperty2 :: Integral a => Positive a -> Bool
+integerFourthRootProperty2 (Positive k) = n < 0 || m >= 0 && m ^ 4 <= n && (m + 1) ^ 4 /= n && (m + 1) ^ 3 >= n `div` (m + 1)
   where
     n = k ^ 4 - 1
     m = integerFourthRoot n
 
 -- | Specialized to trigger 'biSqrtInt.
-integerFourthRootProperty2_Int :: NonNegative Int -> Bool
+integerFourthRootProperty2_Int :: Positive Int -> Bool
 integerFourthRootProperty2_Int = integerFourthRootProperty2
 
 -- | Specialized to trigger 'biSqrtWord'.
-integerFourthRootProperty2_Word :: NonNegative Word -> Bool
+integerFourthRootProperty2_Word :: Positive Word -> Bool
 integerFourthRootProperty2_Word = integerFourthRootProperty2
 
 #if WORD_SIZE_IN_BITS == 64
diff --git a/test-suite/Math/NumberTheory/Powers/GeneralTests.hs b/test-suite/Math/NumberTheory/Powers/GeneralTests.hs
--- a/test-suite/Math/NumberTheory/Powers/GeneralTests.hs
+++ b/test-suite/Math/NumberTheory/Powers/GeneralTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Powers.General
 --
@@ -52,7 +51,7 @@
 
 -- | Check that the first component of 'highestPower' is square-free.
 highestPowerProperty :: Integral a => AnySign a -> Bool
-highestPowerProperty (AnySign n) = (n `elem` [-1, 0, 1] && k == 3) || (b ^ k == n && b' == b && k' == 1)
+highestPowerProperty (AnySign n) = (n + 1 `elem` [0, 1, 2] && k == 3) || (b ^ k == n && b' == b && k' == 1)
   where
     (b, k) = highestPower n
     (b', k') = highestPower b
diff --git a/test-suite/Math/NumberTheory/Powers/ModularTests.hs b/test-suite/Math/NumberTheory/Powers/ModularTests.hs
--- a/test-suite/Math/NumberTheory/Powers/ModularTests.hs
+++ b/test-suite/Math/NumberTheory/Powers/ModularTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Powers.Modular
 --
diff --git a/test-suite/Math/NumberTheory/Powers/SquaresTests.hs b/test-suite/Math/NumberTheory/Powers/SquaresTests.hs
--- a/test-suite/Math/NumberTheory/Powers/SquaresTests.hs
+++ b/test-suite/Math/NumberTheory/Powers/SquaresTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Powers.Squares
 --
@@ -50,23 +49,23 @@
 integerSquareRootProperty_Integer = integerSquareRootProperty
 
 -- | Check that 'integerSquareRoot' returns the largest integer @m@ with @m*m <= n@, where @n@ has form @k@^2-1.
-integerSquareRootProperty2 :: Integral a => NonNegative a -> Bool
-integerSquareRootProperty2 (NonNegative k) = n < 0
+integerSquareRootProperty2 :: Integral a => Positive a -> Bool
+integerSquareRootProperty2 (Positive k) = n < 0
   || m >=0 && m * m <= n && (m + 1) ^ 2 /= n && m + 1 >= n `div` (m + 1)
   where
     n = k ^ 2 - 1
     m = integerSquareRoot n
 
 -- | Specialized to trigger 'isqrtInt''.
-integerSquareRootProperty2_Int :: NonNegative Int -> Bool
+integerSquareRootProperty2_Int :: Positive Int -> Bool
 integerSquareRootProperty2_Int = integerSquareRootProperty2
 
 -- | Specialized to trigger 'isqrtWord'.
-integerSquareRootProperty2_Word :: NonNegative Word -> Bool
+integerSquareRootProperty2_Word :: Positive Word -> Bool
 integerSquareRootProperty2_Word = integerSquareRootProperty2
 
 -- | Specialized to trigger 'isqrtInteger'.
-integerSquareRootProperty2_Integer :: NonNegative Integer -> Bool
+integerSquareRootProperty2_Integer :: Positive Integer -> Bool
 integerSquareRootProperty2_Integer = integerSquareRootProperty2
 
 #if WORD_SIZE_IN_BITS == 64
diff --git a/test-suite/Math/NumberTheory/PrefactoredTests.hs b/test-suite/Math/NumberTheory/PrefactoredTests.hs
--- a/test-suite/Math/NumberTheory/PrefactoredTests.hs
+++ b/test-suite/Math/NumberTheory/PrefactoredTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Prefactored
 --
diff --git a/test-suite/Math/NumberTheory/Primes/CountingTests.hs b/test-suite/Math/NumberTheory/Primes/CountingTests.hs
--- a/test-suite/Math/NumberTheory/Primes/CountingTests.hs
+++ b/test-suite/Math/NumberTheory/Primes/CountingTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Primes.Counting
 --
@@ -17,6 +16,7 @@
 import Test.Tasty
 import Test.Tasty.HUnit
 
+import Math.NumberTheory.Primes (unPrime)
 import Math.NumberTheory.Primes.Counting
 import Math.NumberTheory.Primes.Testing
 import Math.NumberTheory.TestUtils
@@ -73,13 +73,13 @@
 primeCountSpecialCases :: [Assertion]
 primeCountSpecialCases = map a table
   where
-  a (n, m) = assertEqual "primeCount" m (primeCount n)
+    a (n, m) = assertEqual "primeCount" m (primeCount n)
 
 
 -- | Check that values of 'nthPrime' are positive.
 nthPrimeProperty1 :: Positive Integer -> Bool
 nthPrimeProperty1 (Positive n) = n > nthPrimeMaxArg
-  || nthPrime n > 0
+  || unPrime (nthPrime n) > 0
 
 -- | Check that 'nthPrime' is monotonically increasing function.
 nthPrimeProperty2 :: Positive Integer -> Positive Integer -> Bool
@@ -94,13 +94,13 @@
 
 -- | Check that values of 'nthPrime' are prime.
 nthPrimeProperty3 :: Positive Integer -> Bool
-nthPrimeProperty3 (Positive n) = isPrime $ nthPrime n
+nthPrimeProperty3 (Positive n) = isPrime $ unPrime $ nthPrime n
 
 -- | Check tabulated values.
 nthPrimeSpecialCases :: [Assertion]
 nthPrimeSpecialCases = map a table
   where
-  a (n, m) = assertBool "nthPrime" $ n > nthPrime m
+  a (n, m) = assertBool "nthPrime" $ n > unPrime (nthPrime m)
 
 
 -- | Check that values of 'approxPrimeCount' are non-negative.
@@ -120,7 +120,7 @@
 -- | Check that 'nthPrimeApprox' is consistent with 'nthPrimeApproxUnderestimateLimit'.
 nthPrimeApproxProperty2 :: Positive Integer -> Bool
 nthPrimeApproxProperty2 (Positive a) = a >= nthPrimeApproxUnderestimateLimit
-  || toInteger (nthPrimeApprox a) <= nthPrime (toInteger a)
+  || toInteger (nthPrimeApprox a) <= unPrime (nthPrime (toInteger a))
 
 
 testSuite :: TestTree
diff --git a/test-suite/Math/NumberTheory/Primes/FactorisationTests.hs b/test-suite/Math/NumberTheory/Primes/FactorisationTests.hs
--- a/test-suite/Math/NumberTheory/Primes/FactorisationTests.hs
+++ b/test-suite/Math/NumberTheory/Primes/FactorisationTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Primes.Factorisation
 --
@@ -24,7 +23,7 @@
 import Math.NumberTheory.Primes.Testing
 import Math.NumberTheory.TestUtils
 
-specialCases :: [(Integer, [(Integer, Int)])]
+specialCases :: [(Integer, [(Integer, Word)])]
 specialCases =
   [ (4181339589500970917,[(15034813,1),(278110515209,1)])
   , (4181339589500970918,[(2,1),(3,2),(7,1),(2595773,1),(12784336241,1)])
@@ -49,7 +48,7 @@
                              (1676321,1),(5070721,1),(5882353,1),(5964848081,1),(19721061166646717498359681,1)])
   ]
 
-lazyCases :: [(Integer, [(Integer, Int)])]
+lazyCases :: [(Integer, [(Integer, Word)])]
 lazyCases =
   [ ( 14145130711
     * 10000000000000000000000000000000000000121
@@ -75,10 +74,10 @@
 factoriseProperty5 :: Positive Integer -> Bool
 factoriseProperty5 (Positive n) = product (map (uncurry (^)) (factorise n)) == n
 
-factoriseProperty6 :: (Integer, [(Integer, Int)]) -> Assertion
+factoriseProperty6 :: (Integer, [(Integer, Word)]) -> Assertion
 factoriseProperty6 (n, fs) = assertEqual (show n) (sort fs) (sort (factorise n))
 
-factoriseProperty7 :: (Integer, [(Integer, Int)]) -> Assertion
+factoriseProperty7 :: (Integer, [(Integer, Word)]) -> Assertion
 factoriseProperty7 (n, fs) = zipWithM_ (assertEqual (show n)) fs (factorise n)
 
 testSuite :: TestTree
diff --git a/test-suite/Math/NumberTheory/Primes/SequenceTests.hs b/test-suite/Math/NumberTheory/Primes/SequenceTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/Primes/SequenceTests.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications    #-}
+
+module Math.NumberTheory.Primes.SequenceTests
+  ( testSuite
+  ) where
+
+import Test.Tasty
+
+import Data.Bits
+import Data.Maybe
+import Data.Proxy
+import Numeric.Natural
+
+import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes.Counting (nthPrime, primeCount)
+import Math.NumberTheory.TestUtils
+
+nextPrimeProperty
+  :: (Bits a, Integral a, UniqueFactorisation a)
+  => AnySign a
+  -> Bool
+nextPrimeProperty (AnySign n) = unPrime (nextPrime n) >= n
+
+precPrimeProperty
+  :: (Bits a, Integral a, UniqueFactorisation a)
+  => Positive a
+  -> Bool
+precPrimeProperty (Positive n) = n <= 2 || unPrime (precPrime n) <= n
+
+toEnumProperty
+  :: forall a.
+     (Enum (Prime a), Integral a)
+  => Proxy a
+  -> Int
+  -> Bool
+toEnumProperty _ n = n <= 0 || unPrime (toEnum n :: Prime a) == fromInteger (unPrime (nthPrime (toInteger n)))
+
+fromEnumProperty
+  :: (Enum (Prime a), Integral a)
+  => Prime a
+  -> Bool
+fromEnumProperty p = fromEnum p == fromInteger (primeCount (toInteger (unPrime p)))
+
+succProperty
+  :: (Enum a, Enum (Prime a), Num a, UniqueFactorisation a)
+  => Prime a
+  -> Bool
+succProperty p = all (isNothing . isPrime) [unPrime p + 1 .. unPrime (succ p) - 1]
+
+predProperty
+  :: (Enum a, Enum (Prime a), Ord a, Num a, UniqueFactorisation a)
+  => Prime a
+  -> Bool
+predProperty p = unPrime p <= 2 || all (isNothing . isPrime) [unPrime (pred p) + 1 .. unPrime p - 1]
+
+enumFromProperty
+  :: (Ord a, Enum (Prime a))
+  => Prime a
+  -> Prime a
+  -> Bool
+enumFromProperty p q = [p..q] == takeWhile (<= q) [p..]
+
+enumFromToProperty
+  :: (Eq a, Enum a, Enum (Prime a), UniqueFactorisation a)
+  => Prime a
+  -> Prime a
+  -> Bool
+enumFromToProperty p q = [p..q] == mapMaybe isPrime [unPrime p .. unPrime q]
+
+enumFromThenProperty
+  :: (Show a, Ord a, Enum (Prime a))
+  => Prime a
+  -> Prime a
+  -> Prime a
+  -> Bool
+enumFromThenProperty p q r = case p `compare` q of
+  LT -> enumFromThenTo p q r == takeWhile (<= r) (enumFromThen p q)
+  EQ -> True
+  GT -> enumFromThenTo p q r == takeWhile (>= r) (enumFromThen p q)
+
+enumFromThenToProperty
+  :: (Ord a, Enum a, Enum (Prime a), UniqueFactorisation a, Show a)
+  => Prime a
+  -> Prime a
+  -> Prime a
+  -> Bool
+enumFromThenToProperty p q r
+  | p == q && q <= r = True
+  | otherwise
+  = [p, q .. r] == mapMaybe isPrime [unPrime p, unPrime q .. unPrime r]
+
+testSuite :: TestTree
+testSuite = testGroup "Sequence"
+  [ testIntegralPropertyNoLarge "nextPrime" nextPrimeProperty
+  , testIntegralPropertyNoLarge "precPrime" precPrimeProperty
+  , testGroup "toEnum"
+    [ testSmallAndQuick "Int" (toEnumProperty (Proxy @Int))
+    , testSmallAndQuick "Word" (toEnumProperty (Proxy @Word))
+    , testSmallAndQuick "Integer" (toEnumProperty (Proxy @Integer))
+    , testSmallAndQuick "Natural" (toEnumProperty (Proxy @Natural))
+    ]
+  , testGroup "fromEnum"
+    [ testSmallAndQuick "Int" (fromEnumProperty @Int)
+    , testSmallAndQuick "Word" (fromEnumProperty @Word)
+    , testSmallAndQuick "Integer" (fromEnumProperty @Integer)
+    , testSmallAndQuick "Natural" (fromEnumProperty @Natural)
+    ]
+  , testGroup "succ"
+    [ testSmallAndQuick "Int" (succProperty @Int)
+    , testSmallAndQuick "Word" (succProperty @Word)
+    , testSmallAndQuick "Integer" (succProperty @Integer)
+    , testSmallAndQuick "Natural" (succProperty @Natural)
+    ]
+  , testGroup "pred"
+    [ testSmallAndQuick "Int" (predProperty @Int)
+    , testSmallAndQuick "Word" (predProperty @Word)
+    , testSmallAndQuick "Integer" (predProperty @Integer)
+    , testSmallAndQuick "Natural" (predProperty @Natural)
+    ]
+  , testGroup "enumFrom"
+    [ testSmallAndQuick "Int" (enumFromProperty @Int)
+    , testSmallAndQuick "Word" (enumFromProperty @Word)
+    , testSmallAndQuick "Integer" (enumFromProperty @Integer)
+    , testSmallAndQuick "Natural" (enumFromProperty @Natural)
+    ]
+  , testGroup "enumFromTo"
+    [ testSmallAndQuick "Int" (enumFromToProperty @Int)
+    , testSmallAndQuick "Word" (enumFromToProperty @Word)
+    , testSmallAndQuick "Integer" (enumFromToProperty @Integer)
+    , testSmallAndQuick "Natural" (enumFromToProperty @Natural)
+    ]
+  , testGroup "enumFromThen"
+    [ testSmallAndQuick "Int" (enumFromThenProperty @Int)
+    , testSmallAndQuick "Word" (enumFromThenProperty @Word)
+    , testSmallAndQuick "Integer" (enumFromThenProperty @Integer)
+    , testSmallAndQuick "Natural" (enumFromThenProperty @Natural)
+    ]
+  , testGroup "enumFromThenTo"
+    [ testSmallAndQuick "Int" (enumFromThenToProperty @Int)
+    , testSmallAndQuick "Word" (enumFromThenToProperty @Word)
+    , testSmallAndQuick "Integer" (enumFromThenToProperty @Integer)
+    , testSmallAndQuick "Natural" (enumFromThenToProperty @Natural)
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/Primes/SieveTests.hs b/test-suite/Math/NumberTheory/Primes/SieveTests.hs
--- a/test-suite/Math/NumberTheory/Primes/SieveTests.hs
+++ b/test-suite/Math/NumberTheory/Primes/SieveTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016-2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Primes.Sieve
 --
@@ -28,6 +27,7 @@
 import Data.Word
 import Numeric.Natural (Natural)
 
+import Math.NumberTheory.Primes (Prime, unPrime)
 import Math.NumberTheory.Primes.Sieve
 import Math.NumberTheory.Primes.Testing
 import Math.NumberTheory.TestUtils
@@ -44,45 +44,45 @@
 -- | Check that 'primes' matches 'isPrime'.
 primesProperty1 :: forall a. (Integral a, Show a) => Proxy a -> Assertion
 primesProperty1 _ = assertEqual "primes matches isPrime"
-  (takeWhile (<= lim1) primes :: [a])
+  (takeWhile (<= lim1) (map unPrime primes) :: [a])
   (filter (isPrime . toInteger) [1..lim1])
 
 primesProperty2 :: forall a. (Integral a, Bounded a, Show a) => Proxy a -> Assertion
 primesProperty2 _ = assertEqual "primes matches isPrime"
-  (primes :: [a])
+  (map unPrime primes :: [a])
   (filter (isPrime . toInteger) [1..maxBound])
 
 -- | Check that 'primeList' from 'primeSieve' matches truncated 'primes'.
 primeSieveProperty1 :: AnySign Integer -> Bool
 primeSieveProperty1 (AnySign highBound')
   =  primeList (primeSieve highBound)
-  == takeWhile (<= (highBound `max` 7)) primes
+  == takeWhile ((<= (highBound `max` 7)) . unPrime) primes
   where
     highBound = highBound' `rem` lim1
 
 -- | Check that 'primeList' from 'psieveList' matches 'primes'.
 psieveListProperty1 :: forall a. (Integral a, Show a) => Proxy a -> Assertion
 psieveListProperty1 _ = assertEqual "primes == primeList . psieveList"
-  (take lim2 primes :: [a])
+  (take lim2 primes :: [Prime a])
   (take lim2 $ concatMap primeList psieveList)
 
 psieveListProperty2 :: forall a. (Integral a, Show a) => Proxy a -> Assertion
 psieveListProperty2 _ = assertEqual "primes == primeList . psieveList"
-  (primes :: [a])
+  (primes :: [Prime a])
   (concat $ takeWhile (not . null) $ map primeList psieveList)
 
 -- | Check that 'sieveFrom' matches 'primeList' of 'psieveFrom'.
 sieveFromProperty1 :: AnySign Integer -> Bool
 sieveFromProperty1 (AnySign lowBound')
   =  take lim3 (sieveFrom lowBound)
-  == take lim3 (filter (>= lowBound) (concatMap primeList $ psieveFrom lowBound))
+  == take lim3 (filter ((>= lowBound) . unPrime) (concatMap primeList $ psieveFrom lowBound))
   where
     lowBound = lowBound' `rem` lim1
 
 -- | Check that 'sieveFrom' matches 'isPrime' near 0.
 sieveFromProperty2 :: AnySign Integer -> Bool
 sieveFromProperty2 (AnySign lowBound')
-  =  take lim3 (sieveFrom lowBound)
+  =  take lim3 (map unPrime (sieveFrom lowBound))
   == take lim3 (filter (isPrime . toInteger) [lowBound `max` 0 ..])
   where
     lowBound = lowBound' `rem` lim1
diff --git a/test-suite/Math/NumberTheory/Primes/TestingTests.hs b/test-suite/Math/NumberTheory/Primes/TestingTests.hs
--- a/test-suite/Math/NumberTheory/Primes/TestingTests.hs
+++ b/test-suite/Math/NumberTheory/Primes/TestingTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Primes.Testing
 --
diff --git a/test-suite/Math/NumberTheory/PrimesTests.hs b/test-suite/Math/NumberTheory/PrimesTests.hs
--- a/test-suite/Math/NumberTheory/PrimesTests.hs
+++ b/test-suite/Math/NumberTheory/PrimesTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Primes
 --
@@ -17,14 +16,15 @@
 
 import Test.Tasty
 
-import Math.NumberTheory.Primes
+import Math.NumberTheory.Primes (unPrime)
+import Math.NumberTheory.Primes.Sieve (primeSieve, primeList, primes)
 import Math.NumberTheory.TestUtils
 
 primesSumWonk :: Int -> Int
-primesSumWonk upto = sum . takeWhile (< upto) . map fromInteger . primeList $ primeSieve (toInteger upto)
+primesSumWonk upto = sum . takeWhile (< upto) . map unPrime . primeList $ primeSieve (toInteger upto)
 
 primesSum :: Int -> Int
-primesSum upto = sum . takeWhile (< upto) . map fromInteger $ primes
+primesSum upto = sum . takeWhile (< upto) . map unPrime $ primes
 
 primesSumProperty :: NonNegative Int -> Bool
 primesSumProperty (NonNegative n) = primesSumWonk n == primesSum n
diff --git a/test-suite/Math/NumberTheory/Recurrences/BilinearTests.hs b/test-suite/Math/NumberTheory/Recurrences/BilinearTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/Recurrences/BilinearTests.hs
@@ -0,0 +1,233 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.BilinearTests
+-- Copyright:   (c) 2016 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- Tests for Math.NumberTheory.Recurrences.Bilinear
+--
+
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.Recurrences.BilinearTests
+  ( testSuite
+  ) where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Data.Ratio
+
+import Math.NumberTheory.Recurrences.Bilinear (bernoulli, binomial, euler,
+                                               eulerian1, eulerian2,
+                                               eulerPolyAt1, lah, stirling1,
+                                               stirling2)
+import Math.NumberTheory.TestUtils
+
+binomialProperty1 :: NonNegative Int -> Bool
+binomialProperty1 (NonNegative i) = length (binomial !! i) == i + 1
+
+binomialProperty2 :: NonNegative Int -> Bool
+binomialProperty2 (NonNegative i) = binomial !! i !! 0 == 1
+
+binomialProperty3 :: NonNegative Int -> Bool
+binomialProperty3 (NonNegative i) = binomial !! i !! i == 1
+
+binomialProperty4 :: Positive Int -> Positive Int -> Bool
+binomialProperty4 (Positive i) (Positive j)
+  =  j >= i
+  || binomial !! i !! j
+  == binomial !! (i - 1) !! (j - 1)
+  +  binomial !! (i - 1) !! j
+
+stirling1Property1 :: NonNegative Int -> Bool
+stirling1Property1 (NonNegative i) = length (stirling1 !! i) == i + 1
+
+stirling1Property2 :: NonNegative Int -> Bool
+stirling1Property2 (NonNegative i)
+  =  stirling1 !! i !! 0
+  == if i == 0 then 1 else 0
+
+stirling1Property3 :: NonNegative Int -> Bool
+stirling1Property3 (NonNegative i) = stirling1 !! i !! i == 1
+
+stirling1Property4 :: Positive Int -> Positive Int -> Bool
+stirling1Property4 (Positive i) (Positive j)
+  =  j >= i
+  || stirling1 !! i !! j
+  == stirling1 !! (i - 1) !! (j - 1)
+  +  (toInteger i - 1) * stirling1 !! (i - 1) !! j
+
+stirling2Property1 :: NonNegative Int -> Bool
+stirling2Property1 (NonNegative i) = length (stirling2 !! i) == i + 1
+
+stirling2Property2 :: NonNegative Int -> Bool
+stirling2Property2 (NonNegative i)
+  =  stirling2 !! i !! 0
+  == if i == 0 then 1 else 0
+
+stirling2Property3 :: NonNegative Int -> Bool
+stirling2Property3 (NonNegative i) = stirling2 !! i !! i == 1
+
+stirling2Property4 :: Positive Int -> Positive Int -> Bool
+stirling2Property4 (Positive i) (Positive j)
+  =  j >= i
+  || stirling2 !! i !! j
+  == stirling2 !! (i - 1) !! (j - 1)
+  +  toInteger j * stirling2 !! (i - 1) !! j
+
+lahProperty1 :: NonNegative Int -> Bool
+lahProperty1 (NonNegative i) = length (lah !! i) == i + 1
+
+lahProperty2 :: NonNegative Int -> Bool
+lahProperty2 (NonNegative i)
+  =  lah !! i !! 0
+  == product [1 .. i+1]
+
+lahProperty3 :: NonNegative Int -> Bool
+lahProperty3 (NonNegative i) = lah !! i !! i == 1
+
+lahProperty4 :: Positive Int -> Positive Int -> Bool
+lahProperty4 (Positive i) (Positive j)
+  =  j >= i
+  || lah !! i !! j
+  == sum [ stirling1 !! (i + 1) !! k * stirling2 !! k !! (j + 1) | k <- [j + 1 .. i + 1] ]
+
+eulerian1Property1 :: NonNegative Int -> Bool
+eulerian1Property1 (NonNegative i) = length (eulerian1 !! i) == i
+
+eulerian1Property2 :: Positive Int -> Bool
+eulerian1Property2 (Positive i) = eulerian1 !! i !! 0 == 1
+
+eulerian1Property3 :: Positive Int -> Bool
+eulerian1Property3 (Positive i) = eulerian1 !! i !! (i - 1) == 1
+
+eulerian1Property4 :: Positive Int -> Positive Int -> Bool
+eulerian1Property4 (Positive i) (Positive j)
+  =  j >= i - 1
+  || eulerian1 !! i !! j
+  == (toInteger $ i - j) * eulerian1 !! (i - 1) !! (j - 1)
+  +  (toInteger   j + 1) * eulerian1 !! (i - 1) !! j
+
+eulerian2Property1 :: NonNegative Int -> Bool
+eulerian2Property1 (NonNegative i) = length (eulerian2 !! i) == i
+
+eulerian2Property2 :: Positive Int -> Bool
+eulerian2Property2 (Positive i)
+  =  eulerian2 !! i !! 0 == 1
+
+eulerian2Property3 :: Positive Int -> Bool
+eulerian2Property3 (Positive i)
+  =  eulerian2 !! i !! (i - 1)
+  == product [1 .. toInteger i]
+
+eulerian2Property4 :: Positive Int -> Positive Int -> Bool
+eulerian2Property4 (Positive i) (Positive j)
+  =  j >= i - 1
+  || eulerian2 !! i !! j
+  == (toInteger $ 2 * i - j - 1) * eulerian2 !! (i - 1) !! (j - 1)
+  +  (toInteger j + 1) * eulerian2 !! (i - 1) !! j
+
+bernoulliSpecialCase1 :: Assertion
+bernoulliSpecialCase1 = assertEqual "B_0 = 1" (bernoulli !! 0) 1
+
+bernoulliSpecialCase2 :: Assertion
+bernoulliSpecialCase2 = assertEqual "B_1 = -1/2" (bernoulli !! 1) (- 1 % 2)
+
+bernoulliProperty1 :: NonNegative Int -> Bool
+bernoulliProperty1 (NonNegative m)
+  = case signum (bernoulli !! m) of
+    1  -> m == 0 || m `mod` 4 == 2
+    0  -> m /= 1 && odd m
+    -1 -> m == 1 || (m /= 0 && m `mod` 4 == 0)
+    _  -> False
+
+bernoulliProperty2 :: NonNegative Int -> Bool
+bernoulliProperty2 (NonNegative m)
+  =  bernoulli !! m
+  == (if m == 0 then 1 else 0)
+  -  sum [ bernoulli !! k
+         * (binomial !! m !! k % (toInteger $ m - k + 1))
+         | k <- [0 .. m - 1]
+         ]
+
+-- | For every odd positive integer @n@, @E_n@ is @0@.
+eulerProperty1 :: Positive Int -> Bool
+eulerProperty1 (Positive n) = euler !! (2 * n - 1) == 0
+
+-- | Every positive even index produces a negative result.
+eulerProperty2 :: NonNegative Int -> Bool
+eulerProperty2 (NonNegative n) = euler !! (2 + 4 * n) < 0
+
+-- | The Euler number sequence is https://oeis.org/A122045
+eulerSpecialCase1 :: Assertion
+eulerSpecialCase1 = assertEqual "euler"
+    (take 20 euler)
+    [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521, 0, 2702765, 0, -199360981, 0, 19391512145, 0, -2404879675441, 0]
+
+-- | For any even positive integer @n@, @E_n(1)@ is @0@.
+eulerPAt1Property1 :: Positive Int -> Bool
+eulerPAt1Property1 (Positive n) = (eulerPolyAt1 !! (2 * n)) == 0
+
+-- | The numerators in this sequence are from https://oeis.org/A198631 while the
+-- denominators are from https://oeis.org/A006519.
+eulerPAt1SpecialCase1 :: Assertion
+eulerPAt1SpecialCase1 = assertEqual "eulerPolyAt1"
+    (take 20 eulerPolyAt1)
+    (zipWith (%) [1, 1, 0, -1, 0, 1, 0, -17, 0, 31, 0, -691, 0, 5461, 0, -929569, 0, 3202291, 0, -221930581]
+                 [1, 2, 1, 4, 1, 2, 1, 8, 1, 2, 1, 4, 1, 2, 1, 16, 1, 2, 1, 4])
+
+testSuite :: TestTree
+testSuite = testGroup "Bilinear"
+  [ testGroup "binomial"
+    [ testSmallAndQuick "shape"      binomialProperty1
+    , testSmallAndQuick "left side"  binomialProperty2
+    , testSmallAndQuick "right side" binomialProperty3
+    , testSmallAndQuick "recurrency" binomialProperty4
+    ]
+  , testGroup "stirling1"
+    [ testSmallAndQuick "shape"      stirling1Property1
+    , testSmallAndQuick "left side"  stirling1Property2
+    , testSmallAndQuick "right side" stirling1Property3
+    , testSmallAndQuick "recurrency" stirling1Property4
+    ]
+  , testGroup "stirling2"
+    [ testSmallAndQuick "shape"      stirling2Property1
+    , testSmallAndQuick "left side"  stirling2Property2
+    , testSmallAndQuick "right side" stirling2Property3
+    , testSmallAndQuick "recurrency" stirling2Property4
+    ]
+  , testGroup "lah"
+    [ testSmallAndQuick "shape"         lahProperty1
+    , testSmallAndQuick "left side"     lahProperty2
+    , testSmallAndQuick "right side"    lahProperty3
+    , testSmallAndQuick "zip stirlings" lahProperty4
+    ]
+  , testGroup "eulerian1"
+    [ testSmallAndQuick "shape"      eulerian1Property1
+    , testSmallAndQuick "left side"  eulerian1Property2
+    , testSmallAndQuick "right side" eulerian1Property3
+    , testSmallAndQuick "recurrency" eulerian1Property4
+    ]
+  , testGroup "eulerian2"
+    [ testSmallAndQuick "shape"      eulerian2Property1
+    , testSmallAndQuick "left side"  eulerian2Property2
+    , testSmallAndQuick "right side" eulerian2Property3
+    , testSmallAndQuick "recurrency" eulerian2Property4
+    ]
+  , testGroup "bernoulli"
+    [ testCase "B_0"                           bernoulliSpecialCase1
+    , testCase "B_1"                           bernoulliSpecialCase2
+    , testSmallAndQuick "sign"                 bernoulliProperty1
+    , testSmallAndQuick "recursive definition" bernoulliProperty2
+    ]
+    , testGroup "Euler numbers"
+    [ testCase "First 20 elements of E_n are correct"           eulerSpecialCase1
+    , testSmallAndQuick "E_n with n odd is 0"                   eulerProperty1
+    , testSmallAndQuick "E_n for n in [2,6,8,12..] is negative" eulerProperty2
+    ]
+  , testGroup "Euler Polynomial of order N evaluated at 1"
+    [ testCase "First 20 elements of E_n(1) are correct"        eulerPAt1SpecialCase1
+    , testSmallAndQuick "E_n(1) with n in [2,4,6..] is 0"       eulerPAt1Property1
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/Recurrences/LinearTests.hs b/test-suite/Math/NumberTheory/Recurrences/LinearTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/Recurrences/LinearTests.hs
@@ -0,0 +1,103 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.LinearTests
+-- Copyright:   (c) 2016 Andrew Lelechenko
+-- Licence:     MIT
+-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
+--
+-- Tests for Math.NumberTheory.Recurrences.Linear
+--
+
+{-# LANGUAGE CPP       #-}
+
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.Recurrences.LinearTests
+  ( testSuite
+  ) where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Math.NumberTheory.Recurrences.Linear
+import Math.NumberTheory.TestUtils
+
+-- | Check that 'fibonacci' matches the definition of Fibonacci sequence.
+fibonacciProperty1 :: AnySign Int -> Bool
+fibonacciProperty1 (AnySign n) = fibonacci n + fibonacci (n + 1) == fibonacci (n +2)
+
+-- | Check that 'fibonacci' for negative indices is correctly defined.
+fibonacciProperty2 :: NonNegative Int -> Bool
+fibonacciProperty2 (NonNegative n) = fibonacci n == (if even n then negate else id) (fibonacci (- n))
+
+-- | Check that 'fibonacciPair' is a pair of consequent 'fibonacci'.
+fibonacciPairProperty :: AnySign Int -> Bool
+fibonacciPairProperty (AnySign n) = fibonacciPair n == (fibonacci n, fibonacci (n + 1))
+
+-- | Check that 'fibonacci 0' is 0.
+fibonacciSpecialCase0 :: Assertion
+fibonacciSpecialCase0 = assertEqual "fibonacci" (fibonacci 0) 0
+
+-- | Check that 'fibonacci 1' is 1.
+fibonacciSpecialCase1 :: Assertion
+fibonacciSpecialCase1 = assertEqual "fibonacci" (fibonacci 1) 1
+
+
+-- | Check that 'lucas' matches the definition of Lucas sequence.
+lucasProperty1 :: AnySign Int -> Bool
+lucasProperty1 (AnySign n) = lucas n + lucas (n + 1) == lucas (n +2)
+
+-- | Check that 'lucas' for negative indices is correctly defined.
+lucasProperty2 :: NonNegative Int -> Bool
+lucasProperty2 (NonNegative n) = lucas n == (if odd n then negate else id) (lucas (- n))
+
+-- | Check that 'lucasPair' is a pair of consequent 'lucas'.
+lucasPairProperty :: AnySign Int -> Bool
+lucasPairProperty (AnySign n) = lucasPair n == (lucas n, lucas (n + 1))
+
+-- | Check that 'lucas 0' is 2.
+lucasSpecialCase0 :: Assertion
+lucasSpecialCase0 = assertEqual "lucas" (lucas 0) 2
+
+-- | Check that 'lucas 1' is 1.
+lucasSpecialCase1 :: Assertion
+lucasSpecialCase1 = assertEqual "lucas" (lucas 1) 1
+
+-- | Check that 'generalLucas' matches its definition.
+generalLucasProperty1 :: AnySign Integer -> AnySign Integer -> NonNegative Int -> Bool
+generalLucasProperty1 (AnySign p) (AnySign q) (NonNegative n) = un1 == un1' && vn1 == vn1' && un2 == p * un1 - q * un && vn2 == p * vn1 - q * vn
+  where
+    (un, un1, vn, vn1) = generalLucas p q n
+    (un1', un2, vn1', vn2) = generalLucas p q (n + 1)
+
+-- | Check that 'generalLucas' 1 (-1) is 'fibonacciPair' plus 'lucasPair'.
+generalLucasProperty2 :: NonNegative Int -> Bool
+generalLucasProperty2 (NonNegative n) = (un, un1) == fibonacciPair n && (vn, vn1) == lucasPair n
+  where
+    (un, un1, vn, vn1) = generalLucas 1 (-1) n
+
+-- | Check that 'generalLucas' p _ 0 is (0, 1, 2, p).
+generalLucasProperty3 :: AnySign Integer -> AnySign Integer -> Bool
+generalLucasProperty3 (AnySign p) (AnySign q) = generalLucas p q 0 == (0, 1, 2, p)
+
+testSuite :: TestTree
+testSuite = testGroup "Linear"
+  [ testGroup "fibonacci"
+    [ testSmallAndQuick "matches definition"  fibonacciProperty1
+    , testSmallAndQuick "negative indices"    fibonacciProperty2
+    , testSmallAndQuick "pair"                fibonacciPairProperty
+    , testCase          "fibonacci 0"         fibonacciSpecialCase0
+    , testCase          "fibonacci 1"         fibonacciSpecialCase1
+    ]
+  , testGroup "lucas"
+    [ testSmallAndQuick "matches definition"  lucasProperty1
+    , testSmallAndQuick "negative indices"    lucasProperty2
+    , testSmallAndQuick "pair"                lucasPairProperty
+    , testCase          "lucas 0"             lucasSpecialCase0
+    , testCase          "lucas 1"             lucasSpecialCase1
+    ]
+  , testGroup "generalLucas"
+    [ testSmallAndQuick "matches definition"  generalLucasProperty1
+    , testSmallAndQuick "generalLucas 1 (-1)" generalLucasProperty2
+    , testSmallAndQuick "generalLucas _ _ 0"  generalLucasProperty3
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/Recurrences/PentagonalTests.hs b/test-suite/Math/NumberTheory/Recurrences/PentagonalTests.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Math/NumberTheory/Recurrences/PentagonalTests.hs
@@ -0,0 +1,103 @@
+-- |
+-- Module:      Math.NumberTheory.Recurrences.PentagonalTests
+-- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
+-- Licence:     MIT
+-- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
+--
+-- Tests for Math.NumberTheory.Recurrences.Pentagonal
+--
+
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns        #-}
+
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+module Math.NumberTheory.Recurrences.PentagonalTests
+  ( testSuite
+  ) where
+
+import Data.Proxy                    (Proxy (..))
+import GHC.Natural                   (Natural)
+import GHC.TypeNats.Compat           (SomeNat (..), someNatVal)
+
+import Math.NumberTheory.Moduli      (Mod, getVal)
+import Math.NumberTheory.Recurrences (partition)
+import Math.NumberTheory.TestUtils
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+-- | Helper to avoid writing @partition !!@ too many times.
+partition' :: Num a => Int -> a
+partition' = (partition !!)
+
+-- | Check that the @k@-th generalized pentagonal number is
+-- @div (3 * k² - k) 2@, where @k ∈ {0, 1, -1, 2, -2, 3, -3, 4, ...}@.
+-- Notice that @-1@ is the @2 * abs (-1) == 2@-nd index in the zero-based list,
+-- while @2@ is the @2 * 2 - 1 == 3@-rd, and so on.
+pentagonalNumbersProperty1 :: AnySign Int -> Bool
+pentagonalNumbersProperty1 (AnySign n)
+    | n == 0    = pents !! 0           == 0
+    | n > 0     = pents !! (2 * n - 1) == pent n
+    | otherwise = pents !! (2 * abs n) == pent n
+  where
+    pent m = div (3 * (m * m) - m) 2
+
+-- | Check that the first 20 elements of @partition@ are correct per
+-- https://oeis.org/A000041.
+partitionSpecialCase20 :: Assertion
+partitionSpecialCase20 = assertEqual "partition"
+    (take 20 partition)
+    [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77, 101, 135, 176, 231, 297, 385, 490]
+
+-- | Copied from @Math.NumberTheory.Recurrences.Pentagonal@ to test the
+-- reference implementation of @partition@.
+pentagonalSigns :: Num a => [a] -> [a]
+pentagonalSigns = zipWith (*) (cycle [1, 1, -1, -1])
+
+-- | Copied from @Math.NumberTheory.Recurrences.Pentagonal@ to test the
+-- reference implementation of @partition@.
+pents :: (Enum a, Num a) => [a]
+pents = interleave (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
+                   (scanl (\acc n -> acc + 3 * n - 2) 1 [2..])
+  where
+    interleave :: [a] -> [a] -> [a]
+    interleave (n : ns) (m : ms) = n : m : interleave ns ms
+    interleave _ _ = []
+
+-- | Check that @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@,
+-- where @p(x) = 0@ for any negative integer and @p(0) = 1@.
+partitionProperty1 :: Positive Int -> Bool
+partitionProperty1 (Positive n) =
+    partition' n == (sum .
+                     pentagonalSigns .
+                     map (\m -> partition' (n - m)) .
+                     takeWhile (\m -> n - m >= 0) .
+                     tail $ pents)
+
+-- | Check that
+-- @partition :: [Math.NumberTheory.Moduli.Mod n] == map (`mod` n) partition@.
+partitionProperty2 :: NonNegative Integer -> Positive Natural -> Bool
+partitionProperty2 (NonNegative m)
+                   n@(someNatVal . getPositive -> (SomeNat (Proxy :: Proxy n))) =
+    (take m' . map getVal $ (partition :: [Mod n])) ==
+    map helper (take m' partition :: [Integer])
+  where
+    m' = fromIntegral m
+    n' = fromIntegral n
+    helper x = x `mod` n'
+
+testSuite :: TestTree
+testSuite = testGroup "Pentagonal"
+  [ testGroup "partition"
+    [ testSmallAndQuick "matches definition"  partitionProperty1
+    , testSmallAndQuick "mapping residue modulus 'n' is the same as giving\
+                        \'partition' type '[Mod n]'" partitionProperty2
+    , testCase          "first 20 elements of partition are correct"
+                        partitionSpecialCase20
+    ]
+  , testGroup "Generalized pentagonal numbers"
+    [ testSmallAndQuick "matches definition" pentagonalNumbersProperty1
+    ]
+  ]
diff --git a/test-suite/Math/NumberTheory/Recurrencies/BilinearTests.hs b/test-suite/Math/NumberTheory/Recurrencies/BilinearTests.hs
deleted file mode 100644
--- a/test-suite/Math/NumberTheory/Recurrencies/BilinearTests.hs
+++ /dev/null
@@ -1,234 +0,0 @@
--- |
--- Module:      Math.NumberTheory.Recurrencies.BilinearTests
--- Copyright:   (c) 2016 Andrew Lelechenko
--- Licence:     MIT
--- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
---
--- Tests for Math.NumberTheory.Recurrencies.Bilinear
---
-
-{-# OPTIONS_GHC -fno-warn-type-defaults #-}
-
-module Math.NumberTheory.Recurrencies.BilinearTests
-  ( testSuite
-  ) where
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Data.Ratio
-
-import Math.NumberTheory.Recurrencies.Bilinear (bernoulli, binomial, euler,
-                                                eulerian1, eulerian2,
-                                                eulerPolyAt1, lah, stirling1,
-                                                stirling2)
-import Math.NumberTheory.TestUtils
-
-binomialProperty1 :: NonNegative Int -> Bool
-binomialProperty1 (NonNegative i) = length (binomial !! i) == i + 1
-
-binomialProperty2 :: NonNegative Int -> Bool
-binomialProperty2 (NonNegative i) = binomial !! i !! 0 == 1
-
-binomialProperty3 :: NonNegative Int -> Bool
-binomialProperty3 (NonNegative i) = binomial !! i !! i == 1
-
-binomialProperty4 :: Positive Int -> Positive Int -> Bool
-binomialProperty4 (Positive i) (Positive j)
-  =  j >= i
-  || binomial !! i !! j
-  == binomial !! (i - 1) !! (j - 1)
-  +  binomial !! (i - 1) !! j
-
-stirling1Property1 :: NonNegative Int -> Bool
-stirling1Property1 (NonNegative i) = length (stirling1 !! i) == i + 1
-
-stirling1Property2 :: NonNegative Int -> Bool
-stirling1Property2 (NonNegative i)
-  =  stirling1 !! i !! 0
-  == if i == 0 then 1 else 0
-
-stirling1Property3 :: NonNegative Int -> Bool
-stirling1Property3 (NonNegative i) = stirling1 !! i !! i == 1
-
-stirling1Property4 :: Positive Int -> Positive Int -> Bool
-stirling1Property4 (Positive i) (Positive j)
-  =  j >= i
-  || stirling1 !! i !! j
-  == stirling1 !! (i - 1) !! (j - 1)
-  +  (toInteger i - 1) * stirling1 !! (i - 1) !! j
-
-stirling2Property1 :: NonNegative Int -> Bool
-stirling2Property1 (NonNegative i) = length (stirling2 !! i) == i + 1
-
-stirling2Property2 :: NonNegative Int -> Bool
-stirling2Property2 (NonNegative i)
-  =  stirling2 !! i !! 0
-  == if i == 0 then 1 else 0
-
-stirling2Property3 :: NonNegative Int -> Bool
-stirling2Property3 (NonNegative i) = stirling2 !! i !! i == 1
-
-stirling2Property4 :: Positive Int -> Positive Int -> Bool
-stirling2Property4 (Positive i) (Positive j)
-  =  j >= i
-  || stirling2 !! i !! j
-  == stirling2 !! (i - 1) !! (j - 1)
-  +  toInteger j * stirling2 !! (i - 1) !! j
-
-lahProperty1 :: NonNegative Int -> Bool
-lahProperty1 (NonNegative i) = length (lah !! i) == i + 1
-
-lahProperty2 :: NonNegative Int -> Bool
-lahProperty2 (NonNegative i)
-  =  lah !! i !! 0
-  == product [1 .. i+1]
-
-lahProperty3 :: NonNegative Int -> Bool
-lahProperty3 (NonNegative i) = lah !! i !! i == 1
-
-lahProperty4 :: Positive Int -> Positive Int -> Bool
-lahProperty4 (Positive i) (Positive j)
-  =  j >= i
-  || lah !! i !! j
-  == sum [ stirling1 !! (i + 1) !! k * stirling2 !! k !! (j + 1) | k <- [j + 1 .. i + 1] ]
-
-eulerian1Property1 :: NonNegative Int -> Bool
-eulerian1Property1 (NonNegative i) = length (eulerian1 !! i) == i
-
-eulerian1Property2 :: Positive Int -> Bool
-eulerian1Property2 (Positive i) = eulerian1 !! i !! 0 == 1
-
-eulerian1Property3 :: Positive Int -> Bool
-eulerian1Property3 (Positive i) = eulerian1 !! i !! (i - 1) == 1
-
-eulerian1Property4 :: Positive Int -> Positive Int -> Bool
-eulerian1Property4 (Positive i) (Positive j)
-  =  j >= i - 1
-  || eulerian1 !! i !! j
-  == (toInteger $ i - j) * eulerian1 !! (i - 1) !! (j - 1)
-  +  (toInteger   j + 1) * eulerian1 !! (i - 1) !! j
-
-eulerian2Property1 :: NonNegative Int -> Bool
-eulerian2Property1 (NonNegative i) = length (eulerian2 !! i) == i
-
-eulerian2Property2 :: Positive Int -> Bool
-eulerian2Property2 (Positive i)
-  =  eulerian2 !! i !! 0 == 1
-
-eulerian2Property3 :: Positive Int -> Bool
-eulerian2Property3 (Positive i)
-  =  eulerian2 !! i !! (i - 1)
-  == product [1 .. toInteger i]
-
-eulerian2Property4 :: Positive Int -> Positive Int -> Bool
-eulerian2Property4 (Positive i) (Positive j)
-  =  j >= i - 1
-  || eulerian2 !! i !! j
-  == (toInteger $ 2 * i - j - 1) * eulerian2 !! (i - 1) !! (j - 1)
-  +  (toInteger j + 1) * eulerian2 !! (i - 1) !! j
-
-bernoulliSpecialCase1 :: Assertion
-bernoulliSpecialCase1 = assertEqual "B_0 = 1" (bernoulli !! 0) 1
-
-bernoulliSpecialCase2 :: Assertion
-bernoulliSpecialCase2 = assertEqual "B_1 = -1/2" (bernoulli !! 1) (- 1 % 2)
-
-bernoulliProperty1 :: NonNegative Int -> Bool
-bernoulliProperty1 (NonNegative m)
-  = case signum (bernoulli !! m) of
-    1  -> m == 0 || m `mod` 4 == 2
-    0  -> m /= 1 && odd m
-    -1 -> m == 1 || (m /= 0 && m `mod` 4 == 0)
-    _  -> False
-
-bernoulliProperty2 :: NonNegative Int -> Bool
-bernoulliProperty2 (NonNegative m)
-  =  bernoulli !! m
-  == (if m == 0 then 1 else 0)
-  -  sum [ bernoulli !! k
-         * (binomial !! m !! k % (toInteger $ m - k + 1))
-         | k <- [0 .. m - 1]
-         ]
-
--- | For every odd positive integer @n@, @E_n@ is @0@.
-eulerProperty1 :: Positive Int -> Bool
-eulerProperty1 (Positive n) = euler !! (2 * n - 1) == 0
-
--- | Every positive even index produces a negative result.
-eulerProperty2 :: NonNegative Int -> Bool
-eulerProperty2 (NonNegative n) = euler !! (2 + 4 * n) < 0
-
--- | The Euler number sequence is https://oeis.org/A122045
-eulerSpecialCase1 :: Assertion
-eulerSpecialCase1 = assertEqual "euler"
-    (take 20 euler)
-    [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521, 0, 2702765, 0, -199360981, 0, 19391512145, 0, -2404879675441, 0]
-
--- | For any even positive integer @n@, @E_n(1)@ is @0@.
-eulerPAt1Property1 :: Positive Int -> Bool
-eulerPAt1Property1 (Positive n) = (eulerPolyAt1 !! (2 * n)) == 0
-
--- | The numerators in this sequence are from https://oeis.org/A198631 while the
--- denominators are from https://oeis.org/A006519.
-eulerPAt1SpecialCase1 :: Assertion
-eulerPAt1SpecialCase1 = assertEqual "eulerPolyAt1"
-    (take 20 eulerPolyAt1)
-    (zipWith (%) [1, 1, 0, -1, 0, 1, 0, -17, 0, 31, 0, -691, 0, 5461, 0, -929569, 0, 3202291, 0, -221930581]
-                 [1, 2, 1, 4, 1, 2, 1, 8, 1, 2, 1, 4, 1, 2, 1, 16, 1, 2, 1, 4])
-
-testSuite :: TestTree
-testSuite = testGroup "Bilinear"
-  [ testGroup "binomial"
-    [ testSmallAndQuick "shape"      binomialProperty1
-    , testSmallAndQuick "left side"  binomialProperty2
-    , testSmallAndQuick "right side" binomialProperty3
-    , testSmallAndQuick "recurrency" binomialProperty4
-    ]
-  , testGroup "stirling1"
-    [ testSmallAndQuick "shape"      stirling1Property1
-    , testSmallAndQuick "left side"  stirling1Property2
-    , testSmallAndQuick "right side" stirling1Property3
-    , testSmallAndQuick "recurrency" stirling1Property4
-    ]
-  , testGroup "stirling2"
-    [ testSmallAndQuick "shape"      stirling2Property1
-    , testSmallAndQuick "left side"  stirling2Property2
-    , testSmallAndQuick "right side" stirling2Property3
-    , testSmallAndQuick "recurrency" stirling2Property4
-    ]
-  , testGroup "lah"
-    [ testSmallAndQuick "shape"         lahProperty1
-    , testSmallAndQuick "left side"     lahProperty2
-    , testSmallAndQuick "right side"    lahProperty3
-    , testSmallAndQuick "zip stirlings" lahProperty4
-    ]
-  , testGroup "eulerian1"
-    [ testSmallAndQuick "shape"      eulerian1Property1
-    , testSmallAndQuick "left side"  eulerian1Property2
-    , testSmallAndQuick "right side" eulerian1Property3
-    , testSmallAndQuick "recurrency" eulerian1Property4
-    ]
-  , testGroup "eulerian2"
-    [ testSmallAndQuick "shape"      eulerian2Property1
-    , testSmallAndQuick "left side"  eulerian2Property2
-    , testSmallAndQuick "right side" eulerian2Property3
-    , testSmallAndQuick "recurrency" eulerian2Property4
-    ]
-  , testGroup "bernoulli"
-    [ testCase "B_0"                           bernoulliSpecialCase1
-    , testCase "B_1"                           bernoulliSpecialCase2
-    , testSmallAndQuick "sign"                 bernoulliProperty1
-    , testSmallAndQuick "recursive definition" bernoulliProperty2
-    ]
-    , testGroup "Euler numbers"
-    [ testCase "First 20 elements of E_n are correct"           eulerSpecialCase1
-    , testSmallAndQuick "E_n with n odd is 0"                   eulerProperty1
-    , testSmallAndQuick "E_n for n in [2,6,8,12..] is negative" eulerProperty2
-    ]
-  , testGroup "Euler Polynomial of order N evaluated at 1"
-    [ testCase "First 20 elements of E_n(1) are correct"        eulerPAt1SpecialCase1
-    , testSmallAndQuick "E_n(1) with n in [2,4,6..] is 0"       eulerPAt1Property1
-    ]
-  ]
diff --git a/test-suite/Math/NumberTheory/Recurrencies/LinearTests.hs b/test-suite/Math/NumberTheory/Recurrencies/LinearTests.hs
deleted file mode 100644
--- a/test-suite/Math/NumberTheory/Recurrencies/LinearTests.hs
+++ /dev/null
@@ -1,104 +0,0 @@
--- |
--- Module:      Math.NumberTheory.Recurrencies.LinearTests
--- Copyright:   (c) 2016 Andrew Lelechenko
--- Licence:     MIT
--- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
---
--- Tests for Math.NumberTheory.Recurrencies.Linear
---
-
-{-# LANGUAGE CPP       #-}
-
-{-# OPTIONS_GHC -fno-warn-type-defaults #-}
-
-module Math.NumberTheory.Recurrencies.LinearTests
-  ( testSuite
-  ) where
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Math.NumberTheory.Recurrencies.Linear
-import Math.NumberTheory.TestUtils
-
--- | Check that 'fibonacci' matches the definition of Fibonacci sequence.
-fibonacciProperty1 :: AnySign Int -> Bool
-fibonacciProperty1 (AnySign n) = fibonacci n + fibonacci (n + 1) == fibonacci (n +2)
-
--- | Check that 'fibonacci' for negative indices is correctly defined.
-fibonacciProperty2 :: NonNegative Int -> Bool
-fibonacciProperty2 (NonNegative n) = fibonacci n == (if even n then negate else id) (fibonacci (- n))
-
--- | Check that 'fibonacciPair' is a pair of consequent 'fibonacci'.
-fibonacciPairProperty :: AnySign Int -> Bool
-fibonacciPairProperty (AnySign n) = fibonacciPair n == (fibonacci n, fibonacci (n + 1))
-
--- | Check that 'fibonacci 0' is 0.
-fibonacciSpecialCase0 :: Assertion
-fibonacciSpecialCase0 = assertEqual "fibonacci" (fibonacci 0) 0
-
--- | Check that 'fibonacci 1' is 1.
-fibonacciSpecialCase1 :: Assertion
-fibonacciSpecialCase1 = assertEqual "fibonacci" (fibonacci 1) 1
-
-
--- | Check that 'lucas' matches the definition of Lucas sequence.
-lucasProperty1 :: AnySign Int -> Bool
-lucasProperty1 (AnySign n) = lucas n + lucas (n + 1) == lucas (n +2)
-
--- | Check that 'lucas' for negative indices is correctly defined.
-lucasProperty2 :: NonNegative Int -> Bool
-lucasProperty2 (NonNegative n) = lucas n == (if odd n then negate else id) (lucas (- n))
-
--- | Check that 'lucasPair' is a pair of consequent 'lucas'.
-lucasPairProperty :: AnySign Int -> Bool
-lucasPairProperty (AnySign n) = lucasPair n == (lucas n, lucas (n + 1))
-
--- | Check that 'lucas 0' is 2.
-lucasSpecialCase0 :: Assertion
-lucasSpecialCase0 = assertEqual "lucas" (lucas 0) 2
-
--- | Check that 'lucas 1' is 1.
-lucasSpecialCase1 :: Assertion
-lucasSpecialCase1 = assertEqual "lucas" (lucas 1) 1
-
--- | Check that 'generalLucas' matches its definition.
-generalLucasProperty1 :: AnySign Integer -> AnySign Integer -> NonNegative Int -> Bool
-generalLucasProperty1 (AnySign p) (AnySign q) (NonNegative n) = un1 == un1' && vn1 == vn1' && un2 == p * un1 - q * un && vn2 == p * vn1 - q * vn
-  where
-    (un, un1, vn, vn1) = generalLucas p q n
-    (un1', un2, vn1', vn2) = generalLucas p q (n + 1)
-
--- | Check that 'generalLucas' 1 (-1) is 'fibonacciPair' plus 'lucasPair'.
-generalLucasProperty2 :: NonNegative Int -> Bool
-generalLucasProperty2 (NonNegative n) = (un, un1) == fibonacciPair n && (vn, vn1) == lucasPair n
-  where
-    (un, un1, vn, vn1) = generalLucas 1 (-1) n
-
--- | Check that 'generalLucas' p _ 0 is (0, 1, 2, p).
-generalLucasProperty3 :: AnySign Integer -> AnySign Integer -> Bool
-generalLucasProperty3 (AnySign p) (AnySign q) = generalLucas p q 0 == (0, 1, 2, p)
-
-testSuite :: TestTree
-testSuite = testGroup "Linear"
-  [ testGroup "fibonacci"
-    [ testSmallAndQuick "matches definition"  fibonacciProperty1
-    , testSmallAndQuick "negative indices"    fibonacciProperty2
-    , testSmallAndQuick "pair"                fibonacciPairProperty
-    , testCase          "fibonacci 0"         fibonacciSpecialCase0
-    , testCase          "fibonacci 1"         fibonacciSpecialCase1
-    ]
-  , testGroup "lucas"
-    [ testSmallAndQuick "matches definition"  lucasProperty1
-    , testSmallAndQuick "negative indices"    lucasProperty2
-    , testSmallAndQuick "pair"                lucasPairProperty
-    , testCase          "lucas 0"             lucasSpecialCase0
-    , testCase          "lucas 1"             lucasSpecialCase1
-    ]
-  , testGroup "generalLucas"
-    [ testSmallAndQuick "matches definition"  generalLucasProperty1
-    , testSmallAndQuick "generalLucas 1 (-1)" generalLucasProperty2
-    , testSmallAndQuick "generalLucas _ _ 0"  generalLucasProperty3
-    ]
-  ]
diff --git a/test-suite/Math/NumberTheory/Recurrencies/PentagonalTests.hs b/test-suite/Math/NumberTheory/Recurrencies/PentagonalTests.hs
deleted file mode 100644
--- a/test-suite/Math/NumberTheory/Recurrencies/PentagonalTests.hs
+++ /dev/null
@@ -1,105 +0,0 @@
--- |
--- Module:      Math.NumberTheory.Recurrencies.PentagonalTests
--- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
--- Licence:     MIT
--- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
---
--- Tests for Math.NumberTheory.Recurrencies.Pentagonal
---
-
-{-# LANGUAGE CPP                 #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE ViewPatterns        #-}
-
-{-# OPTIONS_GHC -fno-warn-type-defaults #-}
-
-module Math.NumberTheory.Recurrencies.PentagonalTests
-  ( testSuite
-  ) where
-
-import Data.Proxy                     (Proxy (..))
-import GHC.Natural                    (Natural)
-import GHC.TypeNats.Compat            (SomeNat (..), someNatVal)
-
-import Math.NumberTheory.Moduli       (Mod, getVal)
-import Math.NumberTheory.Recurrencies (partition)
-import Math.NumberTheory.TestUtils
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
--- | Helper to avoid writing @partition !!@ too many times.
-partition' :: Num a => Int -> a
-partition' = (partition !!)
-
--- | Check that the @k@-th generalized pentagonal number is
--- @div (3 * k² - k) 2@, where @k ∈ {0, 1, −1, 2, −2, 3, −3, 4, ...}@.
--- Notice that @-1@ is the @2 * abs (-1) == 2@-nd index in the zero-based list,
--- while @2@ is the @2 * 2 - 1 == 3@-rd, and so on.
-pentagonalNumbersProperty1 :: AnySign Int -> Bool
-pentagonalNumbersProperty1 (AnySign n)
-    | n == 0    = pents !! 0           == 0
-    | n > 0     = pents !! (2 * n - 1) == pent n
-    | otherwise = pents !! (2 * abs n) == pent n
-  where
-    pent m = div (3 * (m * m) - m) 2
-
--- | Check that the first 20 elements of @partition@ are correct per
--- https://oeis.org/A000041.
-partitionSpecialCase20 :: Assertion
-partitionSpecialCase20 = assertEqual "partition"
-    (take 20 partition)
-    [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77, 101, 135, 176, 231, 297, 385, 490]
-
--- | Copied from @Math.NumberTheory.Recurrencies.Pentagonal@ to test the
--- reference implementation of @partition@.
-pentagonalSigns :: Num a => [a] -> [a]
-pentagonalSigns = zipWith (*) (cycle [1, 1, -1, -1])
-
--- | Copied from @Math.NumberTheory.Recurrencies.Pentagonal@ to test the
--- reference implementation of @partition@.
-pents :: (Enum a, Num a) => [a]
-pents = interleave (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
-                   (scanl (\acc n -> acc + 3 * n - 2) 1 [2..])
-  where
-    interleave :: [a] -> [a] -> [a]
-    interleave (n : ns) (m : ms) = n : m : interleave ns ms
-    interleave _ _ = []
-
--- | Check that @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-11) + ...@,
--- where @p(x) = 0@ for any negative integer and @p(0) = 1@.
-partitionProperty1 :: Positive Int -> Bool
-partitionProperty1 (Positive n) =
-    partition' n == (sum .
-                     pentagonalSigns .
-                     map (\m -> partition' (n - m)) .
-                     takeWhile (\m -> n - m >= 0) .
-                     tail $ pents)
-
--- | Check that
--- @partition :: [Math.NumberTheory.Moduli.Mod n] == map (`mod` n) partition@.
-partitionProperty2 :: NonNegative Integer -> Positive Natural -> Bool
-partitionProperty2 (NonNegative m)
-                   n@(someNatVal . getPositive -> (SomeNat (Proxy :: Proxy n))) =
-    (take m' . map getVal $ (partition :: [Mod n])) ==
-    map helper (take m' partition :: [Integer])
-  where
-    m' = fromIntegral m
-    n' = fromIntegral n
-    helper x = x `mod` n'
-
-testSuite :: TestTree
-testSuite = testGroup "Pentagonal"
-  [ testGroup "partition"
-    [ testSmallAndQuick "matches definition"  partitionProperty1
-    , testSmallAndQuick "mapping residue modulus 'n' is the same as giving\
-                        \'partition' type '[Mod n]'" partitionProperty2
-    , testCase          "first 20 elements of partition are correct"
-                        partitionSpecialCase20
-    ]
-  , testGroup "Generalized pentagonal numbers"
-    [ testSmallAndQuick "matches definition" pentagonalNumbersProperty1
-    ]
-  ]
diff --git a/test-suite/Math/NumberTheory/SmoothNumbersTests.hs b/test-suite/Math/NumberTheory/SmoothNumbersTests.hs
--- a/test-suite/Math/NumberTheory/SmoothNumbersTests.hs
+++ b/test-suite/Math/NumberTheory/SmoothNumbersTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2018 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.SmoothNumbersTests
 --
@@ -16,19 +15,38 @@
 
 import Prelude hiding (mod)
 import Test.Tasty
+import Test.Tasty.HUnit
 
 import Data.Coerce
-import Data.List (genericDrop, sort)
+import Data.List (genericDrop, nub, sort)
+import Data.Maybe (fromJust)
 import qualified Data.Set as S
 import Numeric.Natural
 
-import Math.NumberTheory.Euclidean
+import Math.NumberTheory.Euclidean (Euclidean (..), WrappedIntegral (..))
+import Math.NumberTheory.Primes (Prime (..))
+import qualified Math.NumberTheory.Quadratic.GaussianIntegers as G
+import qualified Math.NumberTheory.Quadratic.EisensteinIntegers as E
 import Math.NumberTheory.SmoothNumbers
 import Math.NumberTheory.TestUtils
 
 fromSetListProperty :: (Euclidean a, Ord a) => [a] -> Bool
 fromSetListProperty xs = fromSet (S.fromList xs) == fromList (sort xs)
 
+isSmoothPropertyHelper :: Euclidean a => (a -> Integer) -> [a] -> Int -> Int -> Bool
+isSmoothPropertyHelper norm primes' i1 i2 =
+    let primes = take i1 primes'
+        basis  = fromJust (fromList primes)
+    in all (isSmooth basis) $ take i2 $ smoothOver' norm basis
+
+isSmoothProperty1 :: Positive Int -> Positive Int -> Bool
+isSmoothProperty1 (Positive i1) (Positive i2) =
+    isSmoothPropertyHelper G.norm (map unPrime G.primes) i1 i2
+
+isSmoothProperty2 :: Positive Int -> Positive Int -> Bool
+isSmoothProperty2 (Positive i1) (Positive i2) =
+    isSmoothPropertyHelper E.norm (map unPrime E.primes) i1 i2
+
 fromSmoothUpperBoundProperty :: Integral a => Positive a -> Bool
 fromSmoothUpperBoundProperty (Positive n') = case fromSmoothUpperBound n of
     Nothing -> n < 2
@@ -46,6 +64,19 @@
     xs   = smoothOverInRange   (coerce s) lo hi
     ys   = smoothOverInRangeBF (coerce s) lo hi
 
+smoothNumbersAreUniqueProperty :: Integral a => SmoothBasis a -> Positive Int -> Bool
+smoothNumbersAreUniqueProperty s (Positive len)
+  = nub l == l
+  where
+    l = take len $ smoothOver s
+
+isSmoothSpecialCase1 :: Assertion
+isSmoothSpecialCase1 = assertBool "should be distinct" $ nub l == l
+  where
+    b = fromJust $ fromList [1+3*G.ι,6+8*G.ι]
+    l = take 10 $ map abs $ smoothOver' G.norm b
+
+
 testSuite :: TestTree
 testSuite = testGroup "SmoothNumbers"
   [ testGroup "fromSet == fromList"
@@ -64,5 +95,19 @@
       (smoothOverInRangeProperty :: SmoothBasis Integer -> Positive Integer -> Positive Integer -> Bool)
     , testSmallAndQuick "Natural"
       (smoothOverInRangeProperty :: SmoothBasis Natural -> Positive Natural -> Positive Natural -> Bool)
+    ]
+  , testGroup "smoothOver generates a list without duplicates"
+    [ testSmallAndQuick "Integer"
+      (smoothNumbersAreUniqueProperty :: SmoothBasis Integer -> Positive Int -> Bool)
+    , testSmallAndQuick "Natural"
+      (smoothNumbersAreUniqueProperty :: SmoothBasis Natural -> Positive Int -> Bool)
+    ]
+  , testGroup "Quadratic rings (Gaussian/Eisenstein)"
+    [ testGroup "Check that a list of smooth numbers generated by `smoothOver` \
+                \ only contains valid smooth numbers for the generated basis."
+      [ testSmallAndQuick "Gaussian" isSmoothProperty1
+      , testSmallAndQuick "Eisenstein" isSmoothProperty2
+      ]
+    , testCase "all distinct for base [1+3*i,6+8*i]" isSmoothSpecialCase1
     ]
   ]
diff --git a/test-suite/Math/NumberTheory/TestUtils.hs b/test-suite/Math/NumberTheory/TestUtils.hs
--- a/test-suite/Math/NumberTheory/TestUtils.hs
+++ b/test-suite/Math/NumberTheory/TestUtils.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Utils to test Math.NumberTheory
 --
@@ -21,12 +19,9 @@
 {-# LANGUAGE TypeFamilies               #-}
 {-# LANGUAGE TypeOperators              #-}
 {-# LANGUAGE UndecidableInstances       #-}
-
-#if __GLASGOW_HASKELL__ >= 800
 {-# LANGUAGE UndecidableSuperClasses    #-}
 
 {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
-#endif
 
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {-# OPTIONS_GHC -fno-warn-type-defaults #-}
@@ -38,7 +33,9 @@
   , Large(..)
   , NonZero(..)
   , testIntegralProperty
+  , testIntegralPropertyNoLarge
   , testSameIntegralProperty
+  , testSameIntegralProperty3
   , testIntegral2Property
   , testSmallAndQuick
 
@@ -55,6 +52,7 @@
 import Test.SmallCheck.Series (Positive(..), NonNegative(..), Serial(..), Series, generate, (\/))
 
 import Data.Bits
+import Data.Semiring (Semiring)
 import GHC.Exts
 import Numeric.Natural
 
@@ -62,8 +60,8 @@
 import qualified Math.NumberTheory.Quadratic.EisensteinIntegers as E (EisensteinInteger(..))
 import Math.NumberTheory.Quadratic.GaussianIntegers (GaussianInteger(..))
 import Math.NumberTheory.Moduli.PrimitiveRoot (CyclicGroup(..))
+import Math.NumberTheory.Primes (UniqueFactorisation, Prime, unPrime)
 import qualified Math.NumberTheory.SmoothNumbers as SN
-import Math.NumberTheory.UniqueFactorisation (UniqueFactorisation, Prime, unPrime)
 
 import Math.NumberTheory.TestUtils.MyCompose
 import Math.NumberTheory.TestUtils.Wrappers
@@ -94,10 +92,10 @@
     [ (1, pure CG2)
     , (1, pure CG4)
     , (9, CGOddPrimePower
-      <$> (arbitrary :: Gen (PrimeWrapper a)) `suchThatMap` isOddPrime
+      <$> (arbitrary :: Gen (Prime a)) `suchThatMap` isOddPrime
       <*> (getPower <$> arbitrary))
     , (9, CGDoubleOddPrimePower
-      <$> (arbitrary :: Gen (PrimeWrapper a)) `suchThatMap` isOddPrime
+      <$> (arbitrary :: Gen (Prime a)) `suchThatMap` isOddPrime
       <*> (getPower <$> arbitrary))
     ]
 
@@ -105,17 +103,17 @@
   series = pure CG2
         \/ pure CG4
         \/ (CGOddPrimePower
-           <$> (series :: Series m (PrimeWrapper a)) `suchThatMapSerial` isOddPrime
+           <$> (series :: Series m (Prime a)) `suchThatMapSerial` isOddPrime
            <*> (getPower <$> series))
         \/ (CGDoubleOddPrimePower
-           <$> (series :: Series m (PrimeWrapper a)) `suchThatMapSerial` isOddPrime
+           <$> (series :: Series m (Prime a)) `suchThatMapSerial` isOddPrime
            <*> (getPower <$> series))
 
 isOddPrime
   :: forall a. (Eq a, Num a, UniqueFactorisation a)
-  => PrimeWrapper a
+  => Prime a
   -> Maybe (Prime a)
-isOddPrime (PrimeWrapper p) = if (unPrime p :: a) == 2 then Nothing else Just p
+isOddPrime p = if (unPrime p :: a) == 2 then Nothing else Just p
 
 -------------------------------------------------------------------------------
 -- SmoothNumbers
@@ -144,44 +142,82 @@
     Matrix (a ': as) w bs = (ConcatMap (a `Compose` w) bs, Matrix as w bs)
 
 type TestableIntegral wrapper =
-  ( Matrix '[Arbitrary, Show, Serial IO] wrapper '[Int, Word, Integer]
-  , Matrix '[Arbitrary, Show] wrapper '[Large Int, Large Word, Huge Integer]
+  ( Matrix '[Arbitrary, Show, Serial IO] wrapper '[Int, Word, Integer, Natural]
+  , Matrix '[Arbitrary, Show] wrapper '[Large Int, Large Word, Huge Integer, Huge Natural]
   , Matrix '[Bounded, Integral] wrapper '[Int, Word]
   , Num (wrapper Integer)
+  , Num (wrapper Natural)
   , Functor wrapper
   )
 
-
 testIntegralProperty
   :: forall wrapper bool. (TestableIntegral wrapper, SC.Testable IO bool, QC.Testable bool)
-  => String -> (forall a. (Euclidean a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper a -> bool) -> TestTree
+  => String -> (forall a. (Euclidean a, Semiring a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper a -> bool) -> TestTree
 testIntegralProperty name f = testGroup name
   [ SC.testProperty "smallcheck Int"     (f :: wrapper Int     -> bool)
   , SC.testProperty "smallcheck Word"    (f :: wrapper Word    -> bool)
   , SC.testProperty "smallcheck Integer" (f :: wrapper Integer -> bool)
+  , SC.testProperty "smallcheck Natural" (f :: wrapper Natural -> bool)
   , QC.testProperty "quickcheck Int"     (f :: wrapper Int     -> bool)
   , QC.testProperty "quickcheck Word"    (f :: wrapper Word    -> bool)
   , QC.testProperty "quickcheck Integer" (f :: wrapper Integer -> bool)
+  , QC.testProperty "quickcheck Natural" (f :: wrapper Natural -> bool)
   , QC.testProperty "quickcheck Large Int"     ((f :: wrapper Int     -> bool) . getLarge)
   , QC.testProperty "quickcheck Large Word"    ((f :: wrapper Word    -> bool) . getLarge)
   , QC.testProperty "quickcheck Huge  Integer" ((f :: wrapper Integer -> bool) . getHuge)
+  , QC.testProperty "quickcheck Huge  Natural" ((f :: wrapper Natural -> bool) . getHuge)
   ]
 
+testIntegralPropertyNoLarge
+  :: forall wrapper bool. (TestableIntegral wrapper, SC.Testable IO bool, QC.Testable bool)
+  => String -> (forall a. (Euclidean a, Semiring a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper a -> bool) -> TestTree
+testIntegralPropertyNoLarge name f = testGroup name
+  [ SC.testProperty "smallcheck Int"     (f :: wrapper Int     -> bool)
+  , SC.testProperty "smallcheck Word"    (f :: wrapper Word    -> bool)
+  , SC.testProperty "smallcheck Integer" (f :: wrapper Integer -> bool)
+  , SC.testProperty "smallcheck Natural" (f :: wrapper Natural -> bool)
+  , QC.testProperty "quickcheck Int"     (f :: wrapper Int     -> bool)
+  , QC.testProperty "quickcheck Word"    (f :: wrapper Word    -> bool)
+  , QC.testProperty "quickcheck Integer" (f :: wrapper Integer -> bool)
+  , QC.testProperty "quickcheck Natural" (f :: wrapper Natural -> bool)
+  ]
+
 testSameIntegralProperty
   :: forall wrapper1 wrapper2 bool. (TestableIntegral wrapper1, TestableIntegral wrapper2, SC.Testable IO bool, QC.Testable bool)
-  => String -> (forall a. (Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper1 a -> wrapper2 a -> bool) -> TestTree
+  => String -> (forall a. (Euclidean a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper1 a -> wrapper2 a -> bool) -> TestTree
 testSameIntegralProperty name f = testGroup name
   [ SC.testProperty "smallcheck Int"     (f :: wrapper1 Int     -> wrapper2 Int     -> bool)
   , SC.testProperty "smallcheck Word"    (f :: wrapper1 Word    -> wrapper2 Word    -> bool)
   , SC.testProperty "smallcheck Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> bool)
+  , SC.testProperty "smallcheck Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> bool)
   , QC.testProperty "quickcheck Int"     (f :: wrapper1 Int     -> wrapper2 Int     -> bool)
   , QC.testProperty "quickcheck Word"    (f :: wrapper1 Word    -> wrapper2 Word    -> bool)
   , QC.testProperty "quickcheck Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> bool)
+  , QC.testProperty "quickcheck Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> bool)
   , QC.testProperty "quickcheck Large Int"     (\a b -> (f :: wrapper1 Int     -> wrapper2 Int     -> bool) (getLarge <$> a) (getLarge <$> b))
   , QC.testProperty "quickcheck Large Word"    (\a b -> (f :: wrapper1 Word    -> wrapper2 Word    -> bool) (getLarge <$> a) (getLarge <$> b))
   , QC.testProperty "quickcheck Huge  Integer" (\a b -> (f :: wrapper1 Integer -> wrapper2 Integer -> bool) (getHuge  <$> a) (getHuge  <$> b))
+  , QC.testProperty "quickcheck Huge  Natural" (\a b -> (f :: wrapper1 Natural -> wrapper2 Natural -> bool) (getHuge  <$> a) (getHuge  <$> b))
   ]
 
+testSameIntegralProperty3
+  :: forall wrapper1 wrapper2 wrapper3 bool. (TestableIntegral wrapper1, TestableIntegral wrapper2, TestableIntegral wrapper3, SC.Testable IO bool, QC.Testable bool)
+  => String -> (forall a. (Euclidean a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper1 a -> wrapper2 a -> wrapper3 a -> bool) -> TestTree
+testSameIntegralProperty3 name f = testGroup name
+  [ SC.testProperty "smallcheck Int"     (f :: wrapper1 Int     -> wrapper2 Int     -> wrapper3 Int     -> bool)
+  , SC.testProperty "smallcheck Word"    (f :: wrapper1 Word    -> wrapper2 Word    -> wrapper3 Word    -> bool)
+  , SC.testProperty "smallcheck Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> wrapper3 Integer -> bool)
+  , SC.testProperty "smallcheck Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> wrapper3 Natural -> bool)
+  , QC.testProperty "quickcheck Int"     (f :: wrapper1 Int     -> wrapper2 Int     -> wrapper3 Int     -> bool)
+  , QC.testProperty "quickcheck Word"    (f :: wrapper1 Word    -> wrapper2 Word    -> wrapper3 Word    -> bool)
+  , QC.testProperty "quickcheck Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> wrapper3 Integer -> bool)
+  , QC.testProperty "quickcheck Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> wrapper3 Natural -> bool)
+  , QC.testProperty "quickcheck Large Int"     (\a b c -> (f :: wrapper1 Int     -> wrapper2 Int     -> wrapper3 Int     -> bool) (getLarge <$> a) (getLarge <$> b) (getLarge <$> c))
+  , QC.testProperty "quickcheck Large Word"    (\a b c -> (f :: wrapper1 Word    -> wrapper2 Word    -> wrapper3 Word    -> bool) (getLarge <$> a) (getLarge <$> b) (getLarge <$> c))
+  , QC.testProperty "quickcheck Huge  Integer" (\a b c -> (f :: wrapper1 Integer -> wrapper2 Integer -> wrapper3 Integer -> bool) (getHuge  <$> a) (getHuge  <$> b) (getHuge  <$> c))
+  , QC.testProperty "quickcheck Huge  Natural" (\a b c -> (f :: wrapper1 Natural -> wrapper2 Natural -> wrapper3 Natural -> bool) (getHuge  <$> a) (getHuge  <$> b) (getHuge  <$> c))
+  ]
+
 testIntegral2Property
   :: forall wrapper1 wrapper2 bool. (TestableIntegral wrapper1, TestableIntegral wrapper2, SC.Testable IO bool, QC.Testable bool)
   => String -> (forall a1 a2. (Integral a1, Integral a2, Bits a1, Bits a2, UniqueFactorisation a1, UniqueFactorisation a2, Show a1, Show a2) => wrapper1 a1 -> wrapper2 a2 -> bool) -> TestTree
@@ -189,32 +225,53 @@
   [ SC.testProperty "smallcheck Int Int"         (f :: wrapper1 Int     -> wrapper2 Int     -> bool)
   , SC.testProperty "smallcheck Int Word"        (f :: wrapper1 Int     -> wrapper2 Word    -> bool)
   , SC.testProperty "smallcheck Int Integer"     (f :: wrapper1 Int     -> wrapper2 Integer -> bool)
+  , SC.testProperty "smallcheck Int Natural"     (f :: wrapper1 Int     -> wrapper2 Natural -> bool)
   , SC.testProperty "smallcheck Word Int"        (f :: wrapper1 Word    -> wrapper2 Int     -> bool)
   , SC.testProperty "smallcheck Word Word"       (f :: wrapper1 Word    -> wrapper2 Word    -> bool)
   , SC.testProperty "smallcheck Word Integer"    (f :: wrapper1 Word    -> wrapper2 Integer -> bool)
+  , SC.testProperty "smallcheck Word Natural"    (f :: wrapper1 Word    -> wrapper2 Natural -> bool)
   , SC.testProperty "smallcheck Integer Int"     (f :: wrapper1 Integer -> wrapper2 Int     -> bool)
   , SC.testProperty "smallcheck Integer Word"    (f :: wrapper1 Integer -> wrapper2 Word    -> bool)
   , SC.testProperty "smallcheck Integer Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> bool)
+  , SC.testProperty "smallcheck Integer Natural" (f :: wrapper1 Integer -> wrapper2 Natural -> bool)
+  , SC.testProperty "smallcheck Natural Int"     (f :: wrapper1 Natural -> wrapper2 Int     -> bool)
+  , SC.testProperty "smallcheck Natural Word"    (f :: wrapper1 Natural -> wrapper2 Word    -> bool)
+  , SC.testProperty "smallcheck Natural Integer" (f :: wrapper1 Natural -> wrapper2 Integer -> bool)
+  , SC.testProperty "smallcheck Natural Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> bool)
 
   , QC.testProperty "quickcheck Int Int"         (f :: wrapper1 Int     -> wrapper2 Int     -> bool)
   , QC.testProperty "quickcheck Int Word"        (f :: wrapper1 Int     -> wrapper2 Word    -> bool)
   , QC.testProperty "quickcheck Int Integer"     (f :: wrapper1 Int     -> wrapper2 Integer -> bool)
+  , QC.testProperty "quickcheck Int Natural"     (f :: wrapper1 Int     -> wrapper2 Natural -> bool)
   , QC.testProperty "quickcheck Word Int"        (f :: wrapper1 Word    -> wrapper2 Int     -> bool)
   , QC.testProperty "quickcheck Word Word"       (f :: wrapper1 Word    -> wrapper2 Word    -> bool)
   , QC.testProperty "quickcheck Word Integer"    (f :: wrapper1 Word    -> wrapper2 Integer -> bool)
+  , QC.testProperty "quickcheck Word Natural"    (f :: wrapper1 Word    -> wrapper2 Natural -> bool)
   , QC.testProperty "quickcheck Integer Int"     (f :: wrapper1 Integer -> wrapper2 Int     -> bool)
   , QC.testProperty "quickcheck Integer Word"    (f :: wrapper1 Integer -> wrapper2 Word    -> bool)
   , QC.testProperty "quickcheck Integer Integer" (f :: wrapper1 Integer -> wrapper2 Integer -> bool)
+  , QC.testProperty "quickcheck Integer Natural" (f :: wrapper1 Integer -> wrapper2 Natural -> bool)
+  , QC.testProperty "quickcheck Natural Int"     (f :: wrapper1 Natural -> wrapper2 Int     -> bool)
+  , QC.testProperty "quickcheck Natural Word"    (f :: wrapper1 Natural -> wrapper2 Word    -> bool)
+  , QC.testProperty "quickcheck Natural Integer" (f :: wrapper1 Natural -> wrapper2 Integer -> bool)
+  , QC.testProperty "quickcheck Natural Natural" (f :: wrapper1 Natural -> wrapper2 Natural -> bool)
 
   , QC.testProperty "quickcheck Large Int Int"         ((f :: wrapper1 Int     -> wrapper2 Int     -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Large Int Word"        ((f :: wrapper1 Int     -> wrapper2 Word    -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Large Int Integer"     ((f :: wrapper1 Int     -> wrapper2 Integer -> bool) . fmap getLarge)
+  , QC.testProperty "quickcheck Large Int Natural"     ((f :: wrapper1 Int     -> wrapper2 Natural -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Large Word Int"        ((f :: wrapper1 Word    -> wrapper2 Int     -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Large Word Word"       ((f :: wrapper1 Word    -> wrapper2 Word    -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Large Word Integer"    ((f :: wrapper1 Word    -> wrapper2 Integer -> bool) . fmap getLarge)
+  , QC.testProperty "quickcheck Large Word Natural"    ((f :: wrapper1 Word    -> wrapper2 Natural -> bool) . fmap getLarge)
   , QC.testProperty "quickcheck Huge  Integer Int"     ((f :: wrapper1 Integer -> wrapper2 Int     -> bool) . fmap getHuge)
   , QC.testProperty "quickcheck Huge  Integer Word"    ((f :: wrapper1 Integer -> wrapper2 Word    -> bool) . fmap getHuge)
   , QC.testProperty "quickcheck Huge  Integer Integer" ((f :: wrapper1 Integer -> wrapper2 Integer -> bool) . fmap getHuge)
+  , QC.testProperty "quickcheck Huge  Integer Natural" ((f :: wrapper1 Integer -> wrapper2 Natural -> bool) . fmap getHuge)
+  , QC.testProperty "quickcheck Huge  Natural Int"     ((f :: wrapper1 Natural -> wrapper2 Int     -> bool) . fmap getHuge)
+  , QC.testProperty "quickcheck Huge  Natural Word"    ((f :: wrapper1 Natural -> wrapper2 Word    -> bool) . fmap getHuge)
+  , QC.testProperty "quickcheck Huge  Natural Integer" ((f :: wrapper1 Natural -> wrapper2 Integer -> bool) . fmap getHuge)
+  , QC.testProperty "quickcheck Huge  Natural Natural" ((f :: wrapper1 Natural -> wrapper2 Natural -> bool) . fmap getHuge)
   ]
 
 testSmallAndQuick
diff --git a/test-suite/Math/NumberTheory/TestUtils/MyCompose.hs b/test-suite/Math/NumberTheory/TestUtils/MyCompose.hs
--- a/test-suite/Math/NumberTheory/TestUtils/MyCompose.hs
+++ b/test-suite/Math/NumberTheory/TestUtils/MyCompose.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016-2017 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Utils to test Math.NumberTheory
 --
diff --git a/test-suite/Math/NumberTheory/TestUtils/Wrappers.hs b/test-suite/Math/NumberTheory/TestUtils/Wrappers.hs
--- a/test-suite/Math/NumberTheory/TestUtils/Wrappers.hs
+++ b/test-suite/Math/NumberTheory/TestUtils/Wrappers.hs
@@ -3,8 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
--- Portability: Non-portable (GHC extensions)
 --
 -- Utils to test Math.NumberTheory
 --
@@ -28,18 +26,20 @@
 module Math.NumberTheory.TestUtils.Wrappers where
 
 import Control.Applicative
+import Data.Coerce
 import Data.Functor.Classes
 
 import Test.Tasty.QuickCheck as QC hiding (Positive, NonNegative, generate, getNonNegative, getPositive)
 import Test.SmallCheck.Series (Positive(..), NonNegative(..), Serial(..), Series)
 
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Euclidean (Euclidean)
+import Math.NumberTheory.Primes (Prime, UniqueFactorisation(..))
 
 -------------------------------------------------------------------------------
 -- AnySign
 
 newtype AnySign a = AnySign { getAnySign :: a }
-  deriving (Eq, Ord, Read, Show, Num, Enum, Bounded, Integral, Real, Functor, Foldable, Traversable, Arbitrary)
+  deriving (Eq, Ord, Read, Show, Num, Enum, Bounded, Integral, Real, Functor, Foldable, Traversable, Arbitrary, Euclidean)
 
 instance (Monad m, Serial m a) => Serial m (AnySign a) where
   series = AnySign <$> series
@@ -57,6 +57,7 @@
 -- Positive from smallcheck
 
 deriving instance Functor Positive
+deriving instance Euclidean a => Euclidean (Positive a)
 
 instance (Num a, Ord a, Arbitrary a) => Arbitrary (Positive a) where
   arbitrary = Positive <$> (arbitrary `suchThat` (> 0))
@@ -79,6 +80,7 @@
 -- NonNegative from smallcheck
 
 deriving instance Functor NonNegative
+deriving instance Euclidean a => Euclidean (NonNegative a)
 
 instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonNegative a) where
   arbitrary = NonNegative <$> (arbitrary `suchThat` (>= 0))
@@ -103,6 +105,10 @@
 instance (Monad m, Num a, Eq a, Serial m a) => Serial m (NonZero a) where
   series = NonZero <$> series `suchThatSerial` (/= 0)
 
+instance (Eq a, Num a, Enum a, Bounded a) => Bounded (NonZero a) where
+  minBound = if minBound == (0 :: a) then NonZero (succ minBound) else NonZero minBound
+  maxBound = if maxBound == (0 :: a) then NonZero (pred maxBound) else NonZero maxBound
+
 -------------------------------------------------------------------------------
 -- Huge
 
@@ -128,13 +134,13 @@
 -- Power
 
 newtype Power a = Power { getPower :: a }
-  deriving (Eq, Ord, Read, Show, Num, Enum, Bounded, Integral, Real, Functor, Foldable, Traversable)
+  deriving (Eq, Ord, Read, Show, Num, Enum, Bounded, Integral, Real, Functor, Foldable, Traversable, Euclidean)
 
 instance (Monad m, Num a, Ord a, Serial m a) => Serial m (Power a) where
   series = Power <$> series `suchThatSerial` (> 0)
 
 instance (Num a, Ord a, Integral a, Arbitrary a) => Arbitrary (Power a) where
-  arbitrary = Power <$> (getSmall <$> arbitrary) `suchThat` (> 0)
+  arbitrary = Power <$> arbitrarySizedNatural `suchThat` (> 0)
   shrink (Power x) = Power <$> filter (> 0) (shrink x)
 
 instance Eq1 Power where
@@ -171,34 +177,22 @@
 -------------------------------------------------------------------------------
 -- Prime
 
-newtype PrimeWrapper a = PrimeWrapper { getPrime :: Prime a }
-
-deriving instance Eq   (Prime a) => Eq   (PrimeWrapper a)
-deriving instance Ord  (Prime a) => Ord  (PrimeWrapper a)
-deriving instance Show (Prime a) => Show (PrimeWrapper a)
-
-instance (Arbitrary a, UniqueFactorisation a) => Arbitrary (PrimeWrapper a) where
-  arbitrary = PrimeWrapper <$> (arbitrary :: Gen a) `suchThatMap` isPrime
+instance (Arbitrary a, UniqueFactorisation a) => Arbitrary (Prime a) where
+  arbitrary = (arbitrary :: Gen a) `suchThatMap` isPrime
 
-instance (Monad m, Serial m a, UniqueFactorisation a) => Serial m (PrimeWrapper a) where
-  series = PrimeWrapper <$> (series :: Series m a) `suchThatMapSerial` isPrime
+instance (Monad m, Serial m a, UniqueFactorisation a) => Serial m (Prime a) where
+  series = (series :: Series m a) `suchThatMapSerial` isPrime
 
 -------------------------------------------------------------------------------
 -- UniqueFactorisation
 
-type instance Prime (Large a) = Prime a
-
 instance UniqueFactorisation a => UniqueFactorisation (Large a) where
-  unPrime p = Large (unPrime p)
-  factorise (Large x) = factorise x
-  isPrime (Large x) = isPrime x
-
-type instance Prime (Huge a) = Prime a
+  factorise (Large x) = coerce $ factorise x
+  isPrime (Large x) = coerce $ isPrime x
 
 instance UniqueFactorisation a => UniqueFactorisation (Huge a) where
-  unPrime p = Huge (unPrime p)
-  factorise (Huge x) = factorise x
-  isPrime (Huge x) = isPrime x
+  factorise (Huge x) = coerce $ factorise x
+  isPrime (Huge x) = coerce $ isPrime x
 
 -------------------------------------------------------------------------------
 -- Utils
diff --git a/test-suite/Math/NumberTheory/UniqueFactorisationTests.hs b/test-suite/Math/NumberTheory/UniqueFactorisationTests.hs
--- a/test-suite/Math/NumberTheory/UniqueFactorisationTests.hs
+++ b/test-suite/Math/NumberTheory/UniqueFactorisationTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.ArithmeticFunctions
 --
@@ -19,8 +18,9 @@
 
 import Test.Tasty
 
-import Math.NumberTheory.Quadratic.GaussianIntegers hiding (factorise)
-import Math.NumberTheory.UniqueFactorisation
+import Math.NumberTheory.Quadratic.EisensteinIntegers
+import Math.NumberTheory.Quadratic.GaussianIntegers
+import Math.NumberTheory.Primes
 import Math.NumberTheory.TestUtils
 
 import Numeric.Natural
@@ -43,5 +43,6 @@
   , testSmallAndQuick "Integer" (testRules :: Integer -> Bool)
   , testSmallAndQuick "Natural" (testRules :: Natural -> Bool)
 
-  , testSmallAndQuick "GaussianInteger" (testRules :: GaussianInteger -> Bool)
+  , testSmallAndQuick "GaussianInteger"   (testRules :: GaussianInteger   -> Bool)
+  , testSmallAndQuick "EisensteinInteger" (testRules :: EisensteinInteger -> Bool)
   ]
diff --git a/test-suite/Math/NumberTheory/Zeta/DirichletTests.hs b/test-suite/Math/NumberTheory/Zeta/DirichletTests.hs
--- a/test-suite/Math/NumberTheory/Zeta/DirichletTests.hs
+++ b/test-suite/Math/NumberTheory/Zeta/DirichletTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2018 Alexandre Rodrigues Baldé
 -- Licence:     MIT
 -- Maintainer:  Alexandre Rodrigues Baldé <alexandrer_b@outlook.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Zeta.Dirichlet
 --
@@ -89,11 +88,11 @@
 
 betasProperty2 :: NonNegative Int -> NonNegative Int -> Bool
 betasProperty2 (NonNegative e1) (NonNegative e2)
-  = maximum (take 10 $ drop 2 $ zipWith ((abs .) . (-)) (betas eps1) (betas eps2)) <= eps1 + eps2
+  = maximum (take 35 $ drop 2 $ zipWith ((abs .) . (-)) (betas eps1) (betas eps2)) <= eps1 + eps2
   where
     eps1, eps2 :: Double
-    eps1 = (1.0 / 2) ^ e1
-    eps2 = (1.0 / 2) ^ e2
+    eps1 = max ((1.0 / 2) ^ e1) ((1.0 / 2) ^ 53)
+    eps2 = max ((1.0 / 2) ^ e2) ((1.0 / 2) ^ 53)
 
 
 testSuite :: TestTree
diff --git a/test-suite/Math/NumberTheory/Zeta/RiemannTests.hs b/test-suite/Math/NumberTheory/Zeta/RiemannTests.hs
--- a/test-suite/Math/NumberTheory/Zeta/RiemannTests.hs
+++ b/test-suite/Math/NumberTheory/Zeta/RiemannTests.hs
@@ -3,7 +3,6 @@
 -- Copyright:   (c) 2016 Andrew Lelechenko
 -- Licence:     MIT
 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
--- Stability:   Provisional
 --
 -- Tests for Math.NumberTheory.Zeta.Riemann
 --
@@ -93,11 +92,11 @@
 -- abs (z1 - z2) < eps1 + eps2.
 zetasProperty2 :: NonNegative Int -> NonNegative Int -> Bool
 zetasProperty2 (NonNegative e1) (NonNegative e2)
-  = maximum (take 25 $ drop 2 $ zipWith ((abs .) . (-)) (zetas eps1) (zetas eps2)) < eps1 + eps2
+  = maximum (take 35 $ drop 2 $ zipWith ((abs .) . (-)) (zetas eps1) (zetas eps2)) < eps1 + eps2
   where
     eps1, eps2 :: Double
-    eps1 = 1.0 / 2 ^ e1
-    eps2 = 1.0 / 2 ^ e2
+    eps1 = max ((1.0 / 2) ^ e1) ((1.0 / 2) ^ 53)
+    eps2 = max ((1.0 / 2) ^ e2) ((1.0 / 2) ^ 53)
 
 testSuite :: TestTree
 testSuite = testGroup "Zeta"
diff --git a/test-suite/Test.hs b/test-suite/Test.hs
--- a/test-suite/Test.hs
+++ b/test-suite/Test.hs
@@ -1,10 +1,10 @@
 import Test.Tasty
 
-import qualified Math.NumberTheory.GCDTests as GCD
+import qualified Math.NumberTheory.EuclideanTests as Euclidean
 
-import qualified Math.NumberTheory.Recurrencies.PentagonalTests as RecurrenciesPentagonal
-import qualified Math.NumberTheory.Recurrencies.BilinearTests as RecurrenciesBilinear
-import qualified Math.NumberTheory.Recurrencies.LinearTests as RecurrenciesLinear
+import qualified Math.NumberTheory.Recurrences.PentagonalTests as RecurrencesPentagonal
+import qualified Math.NumberTheory.Recurrences.BilinearTests as RecurrencesBilinear
+import qualified Math.NumberTheory.Recurrences.LinearTests as RecurrencesLinear
 
 import qualified Math.NumberTheory.Moduli.ChineseTests as ModuliChinese
 import qualified Math.NumberTheory.Moduli.ClassTests as ModuliClass
@@ -28,6 +28,7 @@
 import qualified Math.NumberTheory.PrimesTests as Primes
 import qualified Math.NumberTheory.Primes.CountingTests as Counting
 import qualified Math.NumberTheory.Primes.FactorisationTests as Factorisation
+import qualified Math.NumberTheory.Primes.SequenceTests as Sequence
 import qualified Math.NumberTheory.Primes.SieveTests as Sieve
 import qualified Math.NumberTheory.Primes.TestingTests as Testing
 
@@ -36,6 +37,7 @@
 import qualified Math.NumberTheory.GaussianIntegersTests as Gaussian
 
 import qualified Math.NumberTheory.ArithmeticFunctionsTests as ArithmeticFunctions
+import qualified Math.NumberTheory.ArithmeticFunctions.InverseTests as Inverse
 import qualified Math.NumberTheory.ArithmeticFunctions.MertensTests as Mertens
 import qualified Math.NumberTheory.ArithmeticFunctions.SieveBlockTests as SieveBlock
 import qualified Math.NumberTheory.UniqueFactorisationTests as UniqueFactorisation
@@ -57,11 +59,11 @@
     , Modular.testSuite
     , Squares.testSuite
     ]
-  , GCD.testSuite
-  , testGroup "Recurrencies"
-    [ RecurrenciesPentagonal.testSuite
-    , RecurrenciesLinear.testSuite
-    , RecurrenciesBilinear.testSuite
+  , Euclidean.testSuite
+  , testGroup "Recurrences"
+    [ RecurrencesPentagonal.testSuite
+    , RecurrencesLinear.testSuite
+    , RecurrencesBilinear.testSuite
     ]
   , testGroup "Moduli"
     [ ModuliChinese.testSuite
@@ -81,6 +83,7 @@
     [ Primes.testSuite
     , Counting.testSuite
     , Factorisation.testSuite
+    , Sequence.testSuite
     , Sieve.testSuite
     , Testing.testSuite
     ]
@@ -88,6 +91,7 @@
   , Gaussian.testSuite
   , testGroup "ArithmeticFunctions"
     [ ArithmeticFunctions.testSuite
+    , Inverse.testSuite
     , Mertens.testSuite
     , SieveBlock.testSuite
     ]
