diff --git a/test/Test/Data/WideWord/Int128.hs b/test/Test/Data/WideWord/Int128.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/WideWord/Int128.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Test.Data.WideWord.Int128
+  ( testInt128
+  ) where
+
+import Control.Exception (evaluate)
+
+import Data.Bits ((.&.), (.|.), bit, complement, countLeadingZeros, countTrailingZeros, popCount, rotateL, rotateR, shiftL, shiftR, testBit, xor)
+import Data.Int (Int16)
+import Data.Word (Word32, Word64)
+import Data.WideWord
+
+import Foreign (allocaBytes)
+import Foreign.Storable (Storable (..))
+
+import Test.Hspec (Spec, describe, errorCall, it, shouldBe, shouldThrow)
+import Test.Hspec.QuickCheck (prop)
+import Test.QuickCheck.Modifiers (NonZero (..))
+
+
+testInt128 :: Spec
+testInt128 = describe "Int128:" $ do
+  prop "constructor and accessors" $ \ (h, l) ->
+    let i128 = Int128 h l in
+    (int128Hi64 i128, int128Lo64 i128) `shouldBe` (h, l)
+
+  prop "byte swap" $ \ (h, l) ->
+    let i128 = byteSwapInt128 $ byteSwapInt128 (Int128 h l) in
+    (int128Hi64 i128, int128Lo64 i128) `shouldBe` (h, l)
+
+  prop "derivied Eq instance" $ \ (a1, a0, b1, b0) ->
+    (Int128 a1 a0 == Int128 b1 b0) `shouldBe` (a1 == b1 && a0 == b0)
+
+  prop "toInteger" $ \ (a1, a0) ->
+    toInteger (Int128 a1 a0) `shouldBe` mkInteger a1 a0
+
+  prop "negate" $ \ (a1, a0) ->
+    toInteger (negate (Int128 a1 a0)) `shouldBe` negate (mkInteger a1 a0)
+
+  prop "fromInteger" $ \ (a1, a0) -> do
+    let i128 = fromInteger $ mkInteger a1 a0
+    (int128Hi64 i128, int128Lo64 i128) `shouldBe` (a1, a0)
+
+  prop "Ord instance" $ \ (a1, a0, b1, b0) ->
+    compare (Int128 a1 a0) (Int128 b1 b0) `shouldBe` compare (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "show / read" $ \ (a1, a0) ->
+    toInteger (read (show $ Int128 a1 a0) :: Int128) `shouldBe` mkInteger a1 a0
+
+  prop "succ" $ \ (a1, a0) -> do
+    let i128 = Int128 a1 a0
+    if i128 == maxBound
+      then evaluate (succ i128) `shouldThrow` errorCall "Enum.succ{Int128}: tried to take `succ' of maxBound"
+      else toInteger128 (succ i128) `shouldBe` succ (mkInteger a1 a0)
+
+  prop "pred" $ \ (a1, a0) -> do
+    let i128 = Int128 a1 a0
+    if i128 == minBound
+      then evaluate (pred i128) `shouldThrow` errorCall "Enum.pred{Int128}: tried to take `pred' of minBound"
+      else toInteger128 (pred i128) `shouldBe` pred (mkInteger a1 a0)
+
+  it "succ maxBound throws error" $
+    evaluate (succ (maxBound :: Int128)) `shouldThrow` errorCall "Enum.succ{Int128}: tried to take `succ' of maxBound"
+
+  it "pred minBount throws error" $
+    evaluate (pred (minBound :: Int128)) `shouldThrow` errorCall "Enum.pred{Int128}: tried to take `pred' of minBound"
+
+  prop "toEnum / fromEnum" $ \ (a0 :: Word32) -> do
+    let i128 = Int128 0 (fromIntegral a0)
+        e128 = fromEnum i128
+    toInteger e128 `shouldBe` toInteger a0
+    toInteger (toEnum e128 :: Int128) `shouldBe` toInteger a0
+
+  prop "complement" $ \ (a1, a0) ->
+    toInteger (complement $ Int128 a1 a0) `shouldBe` mkInteger (complement a1) (complement a0)
+
+  prop "negate" $ \ (a1, a0) ->
+    toInteger (negate (Int128 a1 a0)) `shouldBe` negate (mkInteger a1 a0)
+
+  prop "abs" $ \ (a1, a0) ->
+    toInteger (abs (Int128 a1 a0)) `shouldBe` abs (mkInteger a1 a0)
+
+  prop "signum" $ \ (a1, a0) ->
+    toInteger (signum $ Int128 a1 a0) `shouldBe` signum (mkInteger a1 a0)
+
+  prop "logical and/or/xor" $ \ (a1, a0, b1, b0) -> do
+    toInteger (Int128 a1 a0 .&. Int128 b1 b0) `shouldBe` (mkInteger a1 a0 .&. mkInteger b1 b0)
+    toInteger (Int128 a1 a0 .|. Int128 b1 b0) `shouldBe` (mkInteger a1 a0 .|. mkInteger b1 b0)
+    toInteger (xor (Int128 a1 a0) (Int128 b1 b0)) `shouldBe` xor (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "testBit" $ \ (a1, a0) (b :: Int16) -> do
+    let idx = fromIntegral b
+        expected
+          | idx < 0 = False
+          | idx >= 128 = False
+          | otherwise = testBit (mkInteger a1 a0) idx
+    testBit (Int128 a1 a0) idx `shouldBe` expected
+
+  prop "bit" $ \ (b :: Int16) -> do
+    let idx = fromIntegral b
+        expected
+          | idx < 0 = 0
+          | idx >= 128 = 0
+          | idx == 127 = toInteger128 (minBound :: Int128)
+          | otherwise = bit idx
+    toInteger (bit idx :: Int128) `shouldBe` expected
+
+  prop "popCount" $ \ (a1, a0) ->
+    popCount (Int128 a1 a0) `shouldBe` popCount a1 + popCount a0
+
+  prop "countLeadingZeros" $ \ (a1, a0) -> do
+    let expected = if a1 == 0
+                    then 64 + countLeadingZeros a0
+                    else countLeadingZeros a1
+    countLeadingZeros (Int128 a1 a0) `shouldBe` expected
+
+  prop "countTrailingZeros" $ \ (a1, a0) -> do
+    let expected = if a0 == 0
+                    then 64 + countTrailingZeros a1
+                    else countTrailingZeros a0
+    countTrailingZeros (Int128 a1 a0) `shouldBe` expected
+
+
+  prop "addition" $ \ (a1, a0, b1, b0) ->
+    toInteger (Int128 a1 a0 + Int128 b1 b0) `shouldBe` correctInt128 (mkInteger a1 a0 + mkInteger b1 b0)
+
+  prop "subtraction" $ \ (a1, a0, b1, b0) ->
+    toInteger (Int128 a1 a0 - Int128 b1 b0) `shouldBe` correctInt128 (mkInteger a1 a0 - mkInteger b1 b0)
+
+  prop "multiplication" $ \ (a1, a0, b1, b0) ->
+    toInteger (Int128 a1 a0 * Int128 b1 b0) `shouldBe` correctInt128 (mkInteger a1 a0 * mkInteger b1 b0)
+
+  prop "logical shiftL" $ \ (a1, a0) shift ->
+    let safeShift = if shift < 0 then 128 - (abs shift `mod` 128) else shift in
+    toInteger (shiftL (Int128 a1 a0) shift) `shouldBe` correctInt128 (shiftL (mkInteger a1 a0) safeShift)
+
+  prop "logical shiftR" $ \ (a1, a0) shift ->
+    let expected = if shift < 0 then 0 else correctInt128 (shiftR (mkInteger a1 a0) shift) in
+    toInteger (shiftR (Int128 a1 a0) shift) `shouldBe` expected
+
+  -- Use `Int16` here to force a uniform distribution across the `Int16` range
+  -- (standard QuickCkeck generator for `Int` doesn't give an even distribution).
+  prop "logical rotateL" $ \ (a1, a0) (r :: Int16) -> do
+    let rot = fromIntegral r
+    toInteger (rotateL (Int128 a1 a0) rot) `shouldBe` correctInt128 (toInteger $ rotateL (Word128 a1 a0) rot)
+
+  prop "logical rotateR" $ \ (a1, a0) (r :: Int16) -> do
+    let rot = fromIntegral r
+    toInteger (rotateR (Int128 a1 a0) rot) `shouldBe` correctInt128 (toInteger $ rotateR (Word128 a1 a0) rot)
+
+  prop "quotRem" $ \ (a1, a0, NonZero b1, b0) -> do
+    let (aq128, ar128) = quotRem (Int128 a1 a0) (Int128 b1 b0)
+    (toInteger aq128, toInteger ar128) `shouldBe` quotRem (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "divMod" $ \ (a1, a0, NonZero b1, b0) -> do
+    let (aq128, ar128) = divMod (Int128 a1 a0) (Int128 b1 b0)
+    (toInteger aq128, toInteger ar128) `shouldBe` divMod (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "peek / poke" $ \ (a1, a0) -> do
+    ar <- allocaBytes (sizeOf zeroInt128) $ \ ptr -> do
+                    poke ptr $ Int128 a1 a0
+                    peek ptr
+    toInteger128 ar `shouldBe` mkInteger a1 a0
+
+  prop "peekElemOff / pokeElemOff" $ \ (a1, a0, b1, b0) -> do
+    (ar, br) <- allocaBytes (2 * sizeOf zeroInt128) $ \ ptr -> do
+                    pokeElemOff ptr 0 $ Int128 a1 a0
+                    pokeElemOff ptr 1 $ Int128 b1 b0
+                    (,) <$> peekElemOff ptr 0 <*>  peekElemOff ptr 1
+    (toInteger128 ar, toInteger128 br) `shouldBe` (mkInteger a1 a0, mkInteger b1 b0)
+
+
+
+-- -----------------------------------------------------------------------------
+
+-- Convert an `Integer` to the `Integer` with the same bit pattern as the
+-- corresponding `Int128`.
+correctInt128 :: Integer -> Integer
+correctInt128 x
+  | x >= minBoundInt128 && x <= maxBoundInt128 = x
+  | otherwise = toInteger (fromIntegral x :: Int128)
+  where
+    minBoundInt128 = fromIntegral (minBound :: Int128)
+    maxBoundInt128 = fromIntegral (maxBound :: Int128)
+
+mkInteger :: Word64 -> Word64 -> Integer
+mkInteger a1 a0
+  | testBit a1 63 = negate (fromIntegral (complement a1) `shiftL` 64 + fromIntegral (complement a0) + 1)
+  | otherwise = fromIntegral a1 `shiftL` 64 + fromIntegral a0
+
+
+toInteger128 :: Int128 -> Integer
+toInteger128 = toInteger
diff --git a/test/Test/Data/WideWord/Word128.hs b/test/Test/Data/WideWord/Word128.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/WideWord/Word128.hs
@@ -0,0 +1,208 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Test.Data.WideWord.Word128
+  ( testWord128
+  ) where
+
+import Control.Exception (evaluate)
+
+import Data.Bits ((.&.), (.|.), bit, complement, countLeadingZeros, countTrailingZeros, popCount, rotateL, rotateR, shiftL, shiftR, testBit, xor)
+import Data.Int (Int16)
+import Data.Word (Word32, Word64)
+import Data.WideWord
+
+import Foreign (allocaBytes)
+import Foreign.Storable (Storable (..))
+
+import Test.Hspec (Spec, describe, errorCall, it, shouldBe, shouldThrow)
+import Test.Hspec.QuickCheck (prop)
+import Test.QuickCheck.Modifiers (NonZero (..))
+
+
+testWord128 :: Spec
+testWord128 = describe "Word128:" $ do
+  prop "constructor and accessors" $ \ (h, l) ->
+    let w128 = Word128 h l in
+    (word128Hi64 w128, word128Lo64 w128) `shouldBe` (h, l)
+
+  prop "byte swap" $ \ (h, l) ->
+    let w128 = byteSwapWord128 $ byteSwapWord128 (Word128 h l) in
+    (word128Hi64 w128, word128Lo64 w128) `shouldBe` (h, l)
+
+  prop "derivied Eq instance" $ \ (a1, a0, b1, b0) ->
+    (Word128 a1 a0 == Word128 b1 b0) `shouldBe` (a1 == b1 && a0 == b0)
+
+  prop "Ord instance" $ \ (a1, a0, b1, b0) ->
+    compare (Word128 a1 a0) (Word128 b1 b0) `shouldBe` compare (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "show" $ \ (a1, a0) ->
+    show (Word128 a1 a0) `shouldBe` show (mkInteger a1 a0)
+
+  prop "read" $ \ (a1, a0) ->
+    read (show $ Word128 a1 a0) `shouldBe` Word128 a1 a0
+
+  prop "succ" $ \ (a1, a0) ->
+    if a1 == maxBound && a0 == maxBound
+      then evaluate (succ $ Word128 a1 a0) `shouldThrow` errorCall "Enum.succ{Word128}: tried to take `succ' of maxBound"
+      else toInteger128 (succ $ Word128 a1 a0) `shouldBe` succ (mkInteger a1 a0)
+
+  prop "pred" $ \ (a1, a0) ->
+    if a1 == 0 && a0 == 0
+      then evaluate (pred $ Word128 a1 a0) `shouldThrow` errorCall "Enum.pred{Word128}: tried to take `pred' of minBound"
+      else toInteger128 (pred $ Word128 a1 a0) `shouldBe` pred (mkInteger a1 a0)
+
+  it "succ maxBound throws error" $
+    evaluate (succ $ Word128 maxBound maxBound) `shouldThrow` errorCall "Enum.succ{Word128}: tried to take `succ' of maxBound"
+
+  it "pred minBount throws error" $
+    evaluate (pred $ Word128 0 0) `shouldThrow` errorCall "Enum.pred{Word128}: tried to take `pred' of minBound"
+
+  prop "toEnum / fromEnum" $ \ (a0 :: Word32) -> do
+    let w128 = Word128 0 (fromIntegral a0)
+        e128 = fromEnum w128
+    toInteger e128 `shouldBe` toInteger a0
+    toInteger128 (toEnum e128 :: Word128) `shouldBe` toInteger a0
+
+  prop "addition" $ \ (a1, a0, b1, b0) ->
+    toInteger128 (Word128 a1 a0 + Word128 b1 b0) `shouldBe` correctWord128 (mkInteger a1 a0 + mkInteger b1 b0)
+
+  prop "subtraction" $ \ (a1, a0, b1, b0) -> do
+    let ai = mkInteger a1 a0
+        bi = mkInteger b1 b0
+        expected = ai + (1 `shiftL` 128) - bi
+    toInteger128 (Word128 a1 a0 - Word128 b1 b0) `shouldBe` correctWord128 expected
+
+  prop "multiplication" $ \ (a1, a0, b1, b0) ->
+    toInteger128 (Word128 a1 a0 * Word128 b1 b0) `shouldBe` correctWord128 (mkInteger a1 a0 * mkInteger b1 b0)
+
+  prop "negate" $ \ (a1, a0) ->
+    toInteger128 (negate (Word128 a1 a0)) `shouldBe` correctWord128 (negate $ mkInteger a1 a0)
+
+  prop "abs" $ \ (a1, a0) ->
+    toInteger128 (abs (Word128 a1 a0)) `shouldBe` correctWord128 (abs $ mkInteger a1 a0)
+
+  prop "signum" $ \ (a1, a0) ->
+    toInteger128 (signum $ Word128 a1 a0) `shouldBe` signum (mkInteger a1 a0)
+
+  prop "fromInteger" $ \ (a1, a0) -> do
+    let w128 = fromInteger $ mkInteger a1 a0
+    (word128Hi64 w128, word128Lo64 w128) `shouldBe` (a1, a0)
+
+  prop "logical and/or/xor" $ \ (a1, a0, b1, b0) -> do
+    toInteger128 (Word128 a1 a0 .&. Word128 b1 b0) `shouldBe` (mkInteger a1 a0 .&. mkInteger b1 b0)
+    toInteger128 (Word128 a1 a0 .|. Word128 b1 b0) `shouldBe` (mkInteger a1 a0 .|. mkInteger b1 b0)
+    toInteger128 (xor (Word128 a1 a0) (Word128 b1 b0)) `shouldBe` xor (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "complement" $ \ (a1, a0) ->
+    toInteger128 (complement $ Word128 a1 a0) `shouldBe` mkInteger (complement a1) (complement a0)
+
+  prop "logical shiftL" $ \ (a1, a0) shift ->
+    let safeShift = if shift < 0 then 128 - (abs shift `mod` 128) else shift in
+    toInteger128 (shiftL (Word128 a1 a0) shift) `shouldBe` correctWord128 (shiftL (mkInteger a1 a0) safeShift)
+
+  prop "logical shiftR" $ \ (a1, a0) shift ->
+    let expected = if shift < 0 then 0 else correctWord128 (shiftR (mkInteger a1 a0) shift) in
+    toInteger128 (shiftR (Word128 a1 a0) shift) `shouldBe` expected
+
+  -- Use `Int16` here to force a uniform distribution across the `Int16` range
+  -- (standard QuickCkeck generator for `Int` doesn't give an even distribution).
+  prop "logical rotateL" $ \ (a1, a0) (r :: Int16) -> do
+    let rot = fromIntegral r
+        i128 = mkInteger a1 a0
+        expected
+          | rot < 0 = 0
+          | otherwise =
+              correctWord128 (i128 `shiftL` erot + i128 `shiftR` (128 - (erot `mod` 128)))
+              where
+                erot
+                  | rot < 0 = 128 - (abs rot `mod` 128)
+                  | otherwise = rot `mod` 128
+    toInteger128 (rotateL (Word128 a1 a0) rot) `shouldBe` expected
+
+  prop "logical rotateR" $ \ (a1, a0) (r :: Int16) -> do
+    let rot = fromIntegral r
+        i128 = mkInteger a1 a0
+        expected =
+          correctWord128 $ i128 `shiftR` erot + i128 `shiftL` (128 - erot)
+          where
+            erot
+              | rot < 0 = 128 - (abs rot `mod` 128)
+              | otherwise = rot `mod` 128
+    toInteger128 (rotateR (Word128 a1 a0) rot) `shouldBe` expected
+
+  prop "testBit" $ \ (a1, a0) (b :: Int16) -> do
+    let idx = fromIntegral b
+        expected
+          | idx < 0 = False
+          | idx >= 128 = False
+          | otherwise = testBit (mkInteger a1 a0) idx
+    testBit (Word128 a1 a0) idx `shouldBe` expected
+
+  prop "bit" $ \ (b :: Int16) -> do
+    let idx = fromIntegral b
+        expected
+          | idx < 0 = 0
+          | idx >= 128 = 0
+          | otherwise = bit idx
+    toInteger128 (bit idx :: Word128) `shouldBe` expected
+
+  prop "popCount" $ \ (a1, a0) ->
+    popCount (Word128 a1 a0) `shouldBe` popCount (mkInteger a1 a0)
+
+  prop "countLeadingZeros" $ \ (a1, a0) -> do
+    let expected = if a1 == 0
+                    then 64 + countLeadingZeros a0
+                    else countLeadingZeros a1
+    countLeadingZeros (Word128 a1 a0) `shouldBe` expected
+
+  prop "countTrailingZeros" $ \ (a1, a0) -> do
+    let expected = if a0 == 0
+                    then 64 + countTrailingZeros a1
+                    else countTrailingZeros a0
+    countTrailingZeros (Word128 a1 a0) `shouldBe` expected
+
+  prop "quotRem (both upper words zero)" $ \ (a0, NonZero b0) -> do
+    let (aq128, ar128) = quotRem (Word128 0 a0) (Word128 0 b0)
+    (toInteger128 aq128, toInteger128 ar128) `shouldBe` quotRem (mkInteger 0 a0) (mkInteger 0 b0)
+
+  prop "quotRem (denominator upper word zero)" $ \ (NonZero a1, a0, NonZero b0) -> do
+    let (aq128, ar128) = quotRem (Word128 a1 a0) (Word128 0 b0)
+    (toInteger128 aq128, toInteger128 ar128) `shouldBe` quotRem (mkInteger a1 a0) (mkInteger 0 b0)
+
+  -- Don't need to test `quot` or `rem` because they are implemented by applying
+  -- `fst` or `snd` to the output of `quotRem`.
+  prop "quotRem (full)" $ \ (a1, a0, NonZero b1, b0) -> do
+    let (aq128, ar128) = quotRem (Word128 a1 a0) (Word128 b1 b0)
+    (toInteger128 aq128, toInteger128 ar128) `shouldBe` quotRem (mkInteger a1 a0) (mkInteger b1 b0)
+
+  -- For unsigned values `quotRem` and `divMod` should give the same results.
+  prop "divMod (full)" $ \ (a1, a0, NonZero b1, b0) -> do
+    let (aq128, ar128) = divMod (Word128 a1 a0) (Word128 b1 b0)
+    (toInteger128 aq128, toInteger128 ar128) `shouldBe` divMod (mkInteger a1 a0) (mkInteger b1 b0)
+
+  prop "peek / poke" $ \ (a1, a0) -> do
+    ar <- allocaBytes (sizeOf zeroWord128) $ \ ptr -> do
+                    poke ptr $ Word128 a1 a0
+                    peek ptr
+    toInteger128 ar `shouldBe` mkInteger a1 a0
+
+  prop "peekElemOff / pokeElemOff" $ \ (a1, a0, b1, b0) -> do
+    (ar, br) <- allocaBytes (2 * sizeOf zeroWord128) $ \ ptr -> do
+                    pokeElemOff ptr 0 $ Word128 a1 a0
+                    pokeElemOff ptr 1 $ Word128 b1 b0
+                    (,) <$> peekElemOff ptr 0 <*>  peekElemOff ptr 1
+    (toInteger128 ar, toInteger128 br) `shouldBe` (mkInteger a1 a0, mkInteger b1 b0)
+
+-- -----------------------------------------------------------------------------
+
+mkInteger :: Word64 -> Word64 -> Integer
+mkInteger a1 a0 = fromIntegral a1 `shiftL` 64 + fromIntegral a0
+
+correctWord128 :: Integer -> Integer
+correctWord128 i
+  | i >= 0 && i <= maxWord128 = i
+  | otherwise = i .&. maxWord128
+  where
+    maxWord128 = (1 `shiftL` 128) - 1
+
+toInteger128 :: Word128 -> Integer
+toInteger128 = toInteger
diff --git a/wide-word.cabal b/wide-word.cabal
--- a/wide-word.cabal
+++ b/wide-word.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                wide-word
-version:             0.1.0.4
+version:             0.1.0.5
 synopsis:            Data types for large but fixed width signed and unsigned integers
 description:
   A library to provide data types for large (ie > 64 bits) but fixed width signed
@@ -46,6 +46,9 @@
 
   main-is:            test.hs
   hs-source-dirs:     test
+
+  other-modules:      Test.Data.WideWord.Int128
+                    , Test.Data.WideWord.Word128
 
   build-depends:       base                          >= 4.8         && < 5.0
                      , bytestring                    >= 0.10
