packages feed

integer-types-0.1.2.0: 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 Control.DeepSeq qualified as DeepSeq
import Data.Hashable (Hashable (hashWithSalt))
import Data.Int (Int)
import Data.List qualified as List
import Data.Ord qualified as Ord
import Data.Word (Word)
import Essentials
import Integer.Positive.Unsafe (Positive)
import Integer.Positive.Unsafe qualified as Positive.Unsafe
import Integer.Sign (Sign (..))
import Integer.Sign qualified as Sign
import Numeric.Natural (Natural)
import Text.Show qualified as Show
import Prelude (Integer, Integral, Num, Real, seq)
import Prelude qualified as Bounded (Bounded (..))
import Prelude qualified as Enum (Enum (..))
import Prelude qualified as Num (Integral (..), Num (..), Real (..))

data Signed = Zero | NonZero Sign Positive
  deriving stock (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` ()

instance Hashable Signed where
  hashWithSalt s Zero = s `hashWithSalt` (0 :: Int)
  hashWithSalt s (Plus x) = s `hashWithSalt` (1 :: Int) `hashWithSalt` x
  hashWithSalt s (Minus x) = s `hashWithSalt` (-1 :: Int) `hashWithSalt` x

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