packages feed

connections-0.2.0: src/Data/Connection/Ratio.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Data.Connection.Ratio (
    Ratio (..),
    reduce,
    shiftd,

    -- * Rational
    ratf32,
    ratf64,
    rati08,
    rati16,
    rati32,
    rati64,
    ratixx,
    ratint,

    -- * Positive
    posw08,
    posw16,
    posw32,
    posw64,
    poswxx,
    posnat,
) where

import safe Data.Connection.Conn
import safe qualified Data.Connection.Float as Float
import safe Data.Int
import safe Data.Order
import safe Data.Order.Extended
import safe Data.Order.Syntax
import safe Data.Ratio
import safe Data.Word
import safe GHC.Real (Ratio (..), Rational)
import safe Numeric.Natural
import safe Prelude hiding (Ord (..), until)
import safe qualified Prelude as P

-- | A total version of 'GHC.Real.reduce'.
reduce :: Integral a => Ratio a -> Ratio a
reduce (x :% 0) = x :% 0
reduce (x :% y) = (x `quot` d) :% (y `quot` d) where d = gcd x y

-- | Shift by n 'units of least precision' where the ULP is determined by the denominator
--
-- This is an analog of 'Data.Connection.Float.shift32' for rationals.
shiftd :: Num a => a -> Ratio a -> Ratio a
shiftd n (x :% y) = (n + x) :% y

---------------------------------------------------------------------
-- Ratio Integer
---------------------------------------------------------------------

rati08 :: Conn k Rational (Extended Int8)
rati08 = signedTriple

rati16 :: Conn k Rational (Extended Int16)
rati16 = signedTriple

rati32 :: Conn k Rational (Extended Int32)
rati32 = signedTriple

rati64 :: Conn k Rational (Extended Int64)
rati64 = signedTriple

ratixx :: Conn k Rational (Extended Int)
ratixx = signedTriple

ratint :: Conn k Rational (Extended Integer)
ratint = Conn f g h
  where
    f = liftExtended (~~ ninf) (\x -> x ~~ nan || x ~~ pinf) P.ceiling

    g = extended ninf pinf P.fromIntegral

    h = liftExtended (\x -> x ~~ nan || x ~~ ninf) (~~ pinf) P.floor

ratf32 :: Conn k Rational Float
ratf32 = Conn (toFloating f) (fromFloating g) (toFloating h)
  where
    f x =
        let est = P.fromRational x
         in if fromFloating g est >~ x
                then est
                else ascendf est (fromFloating g) x

    g = flip approxRational 0

    h x =
        let est = P.fromRational x
         in if fromFloating g est <~ x
                then est
                else descendf est (fromFloating g) x

    ascendf z g1 y = Float.until (\x -> g1 x >~ y) (<~) (Float.shift32 1) z

    descendf z f1 x = Float.until (\y -> f1 y <~ x) (>~) (Float.shift32 (-1)) z

ratf64 :: Conn k Rational Double
ratf64 = Conn (toFloating f) (fromFloating g) (toFloating h)
  where
    f x =
        let est = P.fromRational x
         in if fromFloating g est >~ x
                then est
                else ascendf est (fromFloating g) x

    g = flip approxRational 0

    h x =
        let est = P.fromRational x
         in if fromFloating g est <~ x
                then est
                else descendf est (fromFloating g) x

    ascendf z g1 y = Float.until (\x -> g1 x >~ y) (<~) (Float.shift64 1) z

    descendf z f1 x = Float.until (\y -> f1 y <~ x) (>~) (Float.shift64 (-1)) z

---------------------------------------------------------------------
-- Ratio Natural
---------------------------------------------------------------------

posw08 :: Conn k Positive (Lowered Word8)
posw08 = unsignedTriple

posw16 :: Conn k Positive (Lowered Word16)
posw16 = unsignedTriple

posw32 :: Conn k Positive (Lowered Word32)
posw32 = unsignedTriple

posw64 :: Conn k Positive (Lowered Word64)
posw64 = unsignedTriple

poswxx :: Conn k Positive (Lowered Word)
poswxx = unsignedTriple

posnat :: Conn k Positive (Lowered Natural)
posnat = Conn f g h
  where
    f = liftEitherR (\x -> x ~~ nan || x ~~ pinf) P.ceiling

    g = either P.fromIntegral (const pinf)

    h = liftEitherR (~~ pinf) $ \x -> if x ~~ nan then 0 else P.floor x

---------------------------------------------------------------------
-- Internal
---------------------------------------------------------------------

pinf :: Num a => Ratio a
pinf = 1 :% 0

ninf :: Num a => Ratio a
ninf = (-1) :% 0

nan :: Num a => Ratio a
nan = 0 :% 0

unsignedTriple :: forall a k. (Bounded a, Integral a) => Conn k Positive (Lowered a)
unsignedTriple = Conn f g h
  where
    f x
        | x ~~ nan = Right maxBound
        | x > high = Right maxBound
        | otherwise = Left $ P.ceiling x

    g = either P.fromIntegral (const pinf)

    h x
        | x ~~ nan = Left minBound
        | x ~~ pinf = Right maxBound
        | x > high = Left maxBound
        | otherwise = Left $ P.floor x

    high = P.fromIntegral @a maxBound

signedTriple :: forall a k. (Bounded a, Integral a) => Conn k Rational (Extended a)
signedTriple = Conn f g h
  where
    f = liftExtended (~~ ninf) (\x -> x ~~ nan || x > high) $ \x -> if x < low then minBound else P.ceiling x

    g = extended ninf pinf P.fromIntegral

    h = liftExtended (\x -> x ~~ nan || x < low) (~~ pinf) $ \x -> if x > high then maxBound else P.floor x

    high = P.fromIntegral @a maxBound
    low = -1 - high

toFloating :: Fractional a => (Rational -> a) -> Rational -> a
toFloating f x
    | x ~~ nan = 0 / 0
    | x ~~ ninf = (-1) / 0
    | x ~~ pinf = 1 / 0
    | otherwise = f x

fromFloating :: (Order a, Fractional a) => (a -> Rational) -> a -> Rational
fromFloating f x
    | x ~~ 0 / 0 = nan
    | x ~~ (-1) / 0 = ninf
    | x ~~ 1 / 0 = pinf
    | otherwise = f x

{-
pabs :: (Lattice a, Eq a, Num a) => a -> a
pabs x = if 0 <~ x then x else negate x

cancel :: (Lattice a, Eq a, Num a) => Ratio a -> Ratio a
cancel (x :% y) = if x < 0 && y < 0 then (pabs x) :% (pabs y) else x :% y

-- | An exception-safe version of 'nanf' for rationals.
--
nanr :: Integral b => (a -> Ratio b) -> Maybe a -> Ratio b
nanr f = maybe (0 :% 0) f

ratpos :: Conn k Rational Positive
ratpos = Conn k f g h where

  f = liftExtended (~~ ninf) (\x -> x ~~ nan || x ~~ pinf) P.ceiling

  g = extended minBound maxBound P.fromIntegral

  h = liftExtended (\x -> x ~~ nan || x ~~ ninf) (~~ pinf) P.floor
-}