packages feed

wide-word-0.1.0.5: test/Test/Data/WideWord/Int128.hs

{-# 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