packages feed

integer-types-0.0.0.1: library/Integer/Signed.hs

module Integer.Signed
  (
    {- * Type -} Signed (Zero, NonZero, Plus, Minus, NotPlus, NotMinus),
    {- * Conversion -}
    {- ** Integer -} toInteger, fromInteger,
    {- ** Natural -} toNatural, fromNatural,
    {- ** Positive -} toPositive, fromPositive,
    {- ** Int -} toInt, fromInt,
    {- ** Word -} toWord, fromWord,
  )
  where

import Essentials

import Data.Int (Int)
import Data.Word (Word)
import Integer.Positive.Unsafe (Positive)
import Integer.Sign (Sign (..))
import Numeric.Natural (Natural)
import Prelude (Integer, Integral, Num, Real, seq)

import qualified Control.DeepSeq as DeepSeq
import qualified Data.List as List
import qualified Data.Ord as Ord
import qualified Integer.Positive.Unsafe as Positive.Unsafe
import qualified Integer.Sign as Sign
import qualified Prelude as Bounded (Bounded (..))
import qualified Prelude as Enum (Enum (..))
import qualified Prelude as Num (Integral (..), Num (..), Real (..))
import qualified Text.Show as Show

data Signed = Zero | NonZero Sign Positive
    deriving (Eq)

instance Ord Signed where
    compare Zero Zero = Ord.EQ

    compare Zero (Minus _) = Ord.GT
    compare Zero (Plus _ ) = Ord.LT
    compare (Minus _) Zero = Ord.LT
    compare (Plus  _) Zero = Ord.GT

    compare (Plus  _) (Minus _) = Ord.GT
    compare (Minus _) (Plus  _) = Ord.LT
    compare (Plus  a) (Plus  b) = Ord.compare a b
    compare (Minus a) (Minus b) = Ord.compare b a

instance DeepSeq.NFData Signed where
    rnf Zero          = ()
    rnf (NonZero a b) = a `seq` b `seq` ()

pattern Minus :: Positive -> Signed
pattern Minus x = NonZero MinusSign x
pattern Plus :: Positive -> Signed

pattern Plus x = NonZero PlusSign x

-- | A 'Signed' that is either zero or positive
pattern NotMinus :: Natural -> Signed
pattern NotMinus x <- (toNatural -> Just x)
  where NotMinus = fromNatural

-- | A 'Signed' that is either zero or negative;
-- the 'Natural' gives the magnitude of the negative
pattern NotPlus :: Natural -> Signed
pattern NotPlus x <- ((toNatural . negate) -> Just x)
  where NotPlus = negate . fromNatural

{-# complete Zero, Minus, Plus #-}
{-# complete Plus, NotPlus #-}
{-# complete Minus, NotMinus #-}

fromPositive :: Positive -> Signed
fromPositive = Plus

toPositive :: Signed -> Maybe Positive
toPositive (Plus x) = Just x
toPositive _        = Nothing

fromNatural :: Natural -> Signed
fromNatural 0 = Zero
fromNatural x = Plus $ Positive.Unsafe.fromNatural x

toNatural :: Signed -> Maybe Natural
toNatural (Minus _) = Nothing
toNatural Zero      = Just 0
toNatural (Plus x)  = Just (Positive.Unsafe.toNatural x)

add :: Signed -> Signed -> Signed
add Zero x = x
add x Zero = x
add (NonZero sa a) (NonZero sb b) = case (sa, sb) of
    (PlusSign, PlusSign)   -> Plus  $ a Num.+ b
    (MinusSign, MinusSign) -> Minus $ a Num.+ b

    (MinusSign, PlusSign) -> case Ord.compare a b of
        Ord.EQ -> Zero
        Ord.LT -> Plus  $ Positive.Unsafe.subtract b a
        Ord.GT -> Minus $ Positive.Unsafe.subtract a b

    (PlusSign, MinusSign) -> case Ord.compare a b of
        Ord.EQ -> Zero
        Ord.LT -> Minus $ Positive.Unsafe.subtract b a
        Ord.GT -> Plus  $ Positive.Unsafe.subtract a b

negate :: Signed -> Signed
negate Zero          = Zero
negate (NonZero s x) = NonZero (Sign.negate s) x

multiply :: Signed -> Signed -> Signed
multiply Zero _ = Zero
multiply _ Zero = Zero
multiply (NonZero sa a) (NonZero sb b) =
    NonZero (Sign.multiply sa sb) (a Num.* b)

abs :: Signed -> Signed
abs Zero = Zero
abs x@(NonZero s p) = case s of
    PlusSign  -> x
    MinusSign -> NonZero PlusSign p

signum :: Signed -> Signed
signum Zero          = Zero
signum (NonZero s _) = NonZero s Positive.Unsafe.one

fromInteger :: Integer -> Signed
fromInteger x = case Ord.compare x 0 of
    Ord.EQ -> Zero
    Ord.LT -> Minus $ Positive.Unsafe.fromInteger $ Num.abs x
    Ord.GT -> Plus  $ Positive.Unsafe.fromInteger x

toInteger :: Signed -> Integer
toInteger Zero      = 0
toInteger (Plus x)  = Positive.Unsafe.toInteger x
toInteger (Minus x) = Num.negate $ Positive.Unsafe.toInteger x

toInt :: Signed -> Maybe Int
toInt x = case x of
    Zero -> Just 0
    Plus p -> if ok then Just (Num.fromInteger i) else Nothing
      where
        ok = i Ord.<= Num.toInteger (Bounded.maxBound :: Int)
        i = Positive.Unsafe.toInteger p
    Minus p -> if ok then Just (Num.fromInteger i) else Nothing
      where
        ok = i Ord.>= Num.toInteger (Bounded.minBound :: Int)
        i = Num.negate (Positive.Unsafe.toInteger p)

fromInt :: Int -> Signed
fromInt x = case Ord.compare x 0 of
    Ord.EQ -> Zero
    Ord.GT -> Plus $ Positive.Unsafe.fromInt x
    Ord.LT -> Minus $ Positive.Unsafe.fromInteger $ Num.negate $ Num.toInteger x

toWord :: Signed -> Maybe Word
toWord x = case x of
    Zero -> Just 0
    Plus p -> if ok then Just (Num.fromInteger i) else Nothing
      where
        ok = i Ord.<= Num.toInteger (Bounded.maxBound :: Word)
        i = Positive.Unsafe.toInteger p
    Minus _ -> Nothing

fromWord :: Word -> Signed
fromWord x = case x of
    0 -> Zero
    _ -> Plus $ Positive.Unsafe.fromInteger (Num.toInteger x)

type Div a = a -> a -> (a, a)

divisionOp :: Div Integer -> Div Signed
divisionOp o a b =
    let (q, r) = o (toInteger a) (toInteger b)
    in (fromInteger q, fromInteger r)

instance Num Signed
  where
    (+) = add
    (*) = multiply
    negate = negate
    abs = abs
    signum = signum
    fromInteger = fromInteger

instance Enum Signed
  where
    pred = fromInteger . Enum.pred . toInteger
    succ = fromInteger . Enum.succ . toInteger

    toEnum = fromInteger . Enum.toEnum
    fromEnum = Enum.fromEnum . toInteger

    enumFrom a = List.map fromInteger $ Enum.enumFrom (toInteger a)
    enumFromTo a b = List.map fromInteger $ Enum.enumFromTo (toInteger a) (toInteger b)
    enumFromThen a b = List.map fromInteger $ Enum.enumFromThen (toInteger a) (toInteger b)
    enumFromThenTo a b c = List.map fromInteger $ Enum.enumFromThenTo (toInteger a) (toInteger b) (toInteger c)

instance Real Signed
  where
    toRational = Num.toRational . toInteger

instance Integral Signed
  where
    toInteger = toInteger
    quotRem = divisionOp Num.quotRem
    divMod = divisionOp Num.divMod

instance Show Signed
  where
    show = Show.show . Num.toInteger
    showsPrec i = Show.showsPrec i . Num.toInteger