{-# Language ConstraintKinds #-} {-# Language Safe #-} module Data.Connection.Float ( -- * Connections f32i08 , f32i16 --, f32i32 , min32 , max32 , covers , ulp , shift , within , epsilon ) where import safe Data.Bool import safe Data.Connection.Conn import safe Data.Int import safe Data.Order import safe Data.Order.Extended import safe Data.Order.Syntax import safe Data.Word import safe GHC.Float as F import safe Prelude hiding (Eq(..), Ord(..), until) import safe qualified Prelude as P --------------------------------------------------------------------- -- Float --------------------------------------------------------------------- -- | A /NaN/-handling min32 function. -- -- > min32 x NaN = x -- > min32 NaN y = y -- min32 :: Float -> Float -> Float min32 x y = case (isNaN x, isNaN y) of (False, False) -> if x <= y then x else y (False, True) -> x (True, False) -> y (True, True) -> x -- | A /NaN/-handling max32 function. -- -- > max32 x NaN = x -- > max32 NaN y = y -- max32 :: Float -> Float -> Float max32 x y = case (isNaN x, isNaN y) of (False, False) -> if x >= y then x else y (False, True) -> x (True, False) -> y (True, True) -> x -- | Covering relation on the /N5/ lattice of floats. -- -- < https://en.wikipedia.org/wiki/Covering_relation > -- -- >>> covers 1 (shift 1 1) -- True -- >>> covers 1 (shift 2 1) -- False -- covers :: Float -> Float -> Bool covers x y = x ~~ shift (-1) y -- | Compute the signed distance between two floats in units of least precision. -- -- >>> ulp 1.0 (shift 1 1.0) -- Just (LT,1) -- >>> ulp (0.0/0.0) 1.0 -- Nothing -- ulp :: Float -> Float -> Maybe (Ordering, Word32) ulp x y = fmap f $ pcompare x y where x' = floatInt32 x y' = floatInt32 y f LT = (LT, fromIntegral . abs $ y' - x') f EQ = (EQ, 0) f GT = (GT, fromIntegral . abs $ x' - y') -- | Shift a float by /n/ units of least precision. -- -- >>> shift 1 0 -- 1.0e-45 -- >>> shift 1 $ 0/0 -- NaN -- >>> shift (-1) $ 0/0 -- NaN -- >>> shift 1 $ 1/0 -- Infinity -- shift :: Int32 -> Float -> Float shift n x | x ~~ 0/0 = x | otherwise = int32Float . clamp32 . (+ n) . floatInt32 $ x -- | Compare two floats for approximate equality. -- -- Required accuracy is specified in units of least precision. -- -- See also . -- within :: Word32 -> Float -> Float -> Bool within tol x y = maybe False ((<= tol) . snd) $ ulp x y -- | Difference between 1 and the smallest representable value greater than 1. -- -- > epsilon = shift 1 1 - 1 -- -- >>> epsilon -- 1.1920929e-7 -- epsilon :: Float epsilon = shift 1 1.0 - 1.0 {- -- | Minimal32 positive value. -- -- >>> minimal32 -- 1.0e-45 -- >>> shift (-1) minimal32 -- 0 -- minimal32 :: Float minimal32 = shift 1 0.0 -- | Maximum finite value. -- -- >>> maximal32 -- 3.4028235e38 -- >>> shift 1 maximal32 -- Infinity -- >>> shift (-1) $ negate maximal32 -- -Infinity -- maximal32 :: Float maximal32 = shift (-1) (1/0) -} --------------------------------------------------------------------- -- Float --------------------------------------------------------------------- -- | All 'Data.Int.Int08' values are exactly representable in a 'Float'. f32i08 :: Conn k Float (Extended Int8) f32i08 = signedTriple 127 -- | All 'Data.Int.Int16' values are exactly representable in a 'Float'. f32i16 :: Conn k Float (Extended Int16) f32i16 = signedTriple 32767 {- -- | Exact embedding up to the largest representable 'Int32'. f32i32 :: ConnL Float (Maybe Int32) f32i32 = Conn (nanf f) (nan g) where f x | abs x <~ 2**24-1 = P.ceiling x | otherwise = if x >~ 0 then 2^24 else minBound g i | abs' i <~ 2^24-1 = fromIntegral i | otherwise = if i >~ 0 then 1/0 else -2**24 -- | Exact embedding up to the largest representable 'Int32'. i32f32 :: ConnL (Maybe Int32) Float i32f32 = Conn (nan g) (nanf f) where f x | abs x <~ 2**24-1 = P.floor x | otherwise = if x >~ 0 then maxBound else -2^24 g i | abs i <~ 2^24-1 = fromIntegral i | otherwise = if i >~ 0 then 2**24 else -1/0 -} --------------------------------------------------------------------- -- Internal --------------------------------------------------------------------- signedTriple :: (Bounded a, Integral a) => Float -> Conn k Float (Extended a) signedTriple high = Conn f g h where f = liftExtended (~~ -1/0) (\x -> x ~~ 0/0 || x > high) $ \x -> if x < low then minBound else P.ceiling x g = extended (-1/0) (1/0) P.fromIntegral h = liftExtended (\x -> x ~~ 0/0 || x < low) (~~ 1/0) $ \x -> if x > high then maxBound else P.floor x low = -1 - high -- Non-monotonic function signed32 :: Word32 -> Int32 signed32 x | x < 0x80000000 = fromIntegral x | otherwise = fromIntegral (maxBound - (x - 0x80000000)) -- Non-monotonic function converting from 2s-complement format. unsigned32 :: Int32 -> Word32 unsigned32 x | x >= 0 = fromIntegral x | otherwise = 0x80000000 + (maxBound - (fromIntegral x)) -- Clamp between the int representations of -1/0 and 1/0 clamp32 :: Int32 -> Int32 clamp32 = P.max (-2139095041) . P.min 2139095040 int32Float :: Int32 -> Float int32Float = word32Float . unsigned32 floatInt32 :: Float -> Int32 floatInt32 = signed32 . floatWord32 -- Bit-for-bit conversion. word32Float :: Word32 -> Float word32Float = F.castWord32ToFloat -- Bit-for-bit conversion. floatWord32 :: Float -> Word32 floatWord32 = (+0) . F.castFloatToWord32