{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE Safe #-}
module Data.Connection.Float (
-- * Float
f32w08,
f32w16,
f32i08,
f32i16,
f32f32,
ulp32,
near32,
shift32,
-- * Double
f64f64,
f64w08,
f64w16,
f64w32,
f64i08,
f64i16,
f64i32,
f64f32,
ulp64,
near64,
shift64,
until,
) where
import safe Data.Bool
import safe Data.Connection.Conn hiding (ceiling, floor)
import safe Data.Int
import safe Data.Order
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
---------------------------------------------------------------------
f32f32 :: Conn k (Float, Float) Float
f32f32 = fxxfxx
f32w08 :: Conn k Float (Extended Word8)
f32w08 = fxxext
f32w16 :: Conn k Float (Extended Word16)
f32w16 = fxxext
f32i08 :: Conn k Float (Extended Int8)
f32i08 = fxxext
f32i16 :: Conn k Float (Extended Int16)
f32i16 = fxxext
-- | Compute the signed distance between two floats in units of least precision.
--
-- >>> ulp32 1.0 (shift32 1 1.0)
-- Just (LT,1)
-- >>> ulp32 (0.0/0.0) 1.0
-- Nothing
ulp32 :: Float -> Float -> Maybe (Ordering, Word32)
ulp32 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')
-- | Compare two floats for approximate equality.
--
-- Required accuracy is specified in units of least precision.
--
-- See .
near32 :: Word32 -> Float -> Float -> Bool
near32 tol x y = maybe False ((<= tol) . snd) $ ulp32 x y
-- | Shift a float by /n/ units of least precision.
--
-- >>> shift32 1 0
-- 1.0e-45
-- >>> shift32 1 1 - 1
-- 1.1920929e-7
-- >>> shift32 1 $ 0/0
-- Infinity
-- >>> shift32 (-1) $ 0/0
-- -Infinity
-- >>> shift32 1 $ 1/0
-- Infinity
shift32 :: Int32 -> Float -> Float
shift32 n x =
if isNaN x == True
then case signum n of
-1 -> -1 / 0
1 -> 1 / 0
_ -> 0 / 0
else int32Float . clamp32 . (+ n) . floatInt32 $ x
---------------------------------------------------------------------
-- Double
---------------------------------------------------------------------
f64f64 :: Conn k (Double, Double) Double
f64f64 = fxxfxx
f64w08 :: Conn k Double (Extended Word8)
f64w08 = fxxext
f64w16 :: Conn k Double (Extended Word16)
f64w16 = fxxext
f64w32 :: Conn k Double (Extended Word32)
f64w32 = fxxext
f64i08 :: Conn k Double (Extended Int8)
f64i08 = fxxext
f64i16 :: Conn k Double (Extended Int16)
f64i16 = fxxext
f64i32 :: Conn k Double (Extended Int32)
f64i32 = fxxext
f64f32 :: Conn k Double Float
f64f32 = Conn f g h
where
f x =
let est = double2Float x
in if g est >~ x
then est
else ascend32 est g x
g = float2Double
h x =
let est = double2Float x
in if g est <~ x
then est
else descend32 est g x
ascend32 z g1 y = until (\x -> g1 x >~ y) (<~) (shift32 1) z
descend32 z h1 x = until (\y -> h1 y <~ x) (>~) (shift32 (-1)) z
{-# INLINE f64f32 #-}
-- | Compute the signed distance between two doubles in units of least precision.
--
-- >>> ulp64 1.0 (shift64 1 1.0)
-- Just (LT,1)
-- >>> ulp64 (0.0/0.0) 1.0
-- Nothing
ulp64 :: Double -> Double -> Maybe (Ordering, Word64)
ulp64 x y = fmap f $ pcompare x y
where
x' = doubleInt64 x
y' = doubleInt64 y
f LT = (LT, fromIntegral . abs $ y' - x')
f EQ = (EQ, 0)
f GT = (GT, fromIntegral . abs $ x' - y')
-- | Compare two double-precision floats for approximate equality.
--
-- Required accuracy is specified in units of least precision.
--
-- See also .
near64 :: Word64 -> Double -> Double -> Bool
near64 tol x y = maybe False ((<= tol) . snd) $ ulp64 x y
-- | Shift by /n/ units of least precision.
--
-- >>> shift64 1 0
-- 5.0e-324
-- >>> shift64 1 1 - 1
-- 2.220446049250313e-16
-- >>> shift64 1 $ 0/0
-- Infinity
-- >>> shift64 (-1) $ 0/0
-- -Infinity
-- >>> shift64 1 $ 1/0
-- Infinity
shift64 :: Int64 -> Double -> Double
shift64 n x =
if isNaN x == True
then case signum n of
-1 -> -1 / 0
1 -> 1 / 0
_ -> 0 / 0
else int64Double . clamp64 . (+ n) . doubleInt64 $ x
---------------------------------------------------------------------
-- Internal
---------------------------------------------------------------------
{-# INLINE until #-}
until :: (a -> Bool) -> (a -> a -> Bool) -> (a -> a) -> a -> a
until pre rel f seed = go seed
where
go x
| x' `rel` x = x
| pre x = x
| otherwise = go x'
where
x' = f x
-- Non-monotonic function
signed32 :: Word32 -> Int32
signed32 x
| x < 0x80000000 = fromIntegral x
| otherwise = fromIntegral (maxBound - (x - 0x80000000))
-- Non-monotonic function
signed64 :: Word64 -> Int64
signed64 x
| x < 0x8000000000000000 = fromIntegral x
| otherwise = fromIntegral (maxBound - (x - 0x8000000000000000))
-- Non-monotonic function converting from 2s-complement format.
unsigned32 :: Int32 -> Word32
unsigned32 x
| x >= 0 = fromIntegral x
| otherwise = 0x80000000 + (maxBound - (fromIntegral x))
-- Non-monotonic function converting from 2s-complement format.
unsigned64 :: Int64 -> Word64
unsigned64 x
| x >= 0 = fromIntegral x
| otherwise = 0x8000000000000000 + (maxBound - (fromIntegral x))
int32Float :: Int32 -> Float
int32Float = castWord32ToFloat . unsigned32
-- NB: I needed these zeros to avoid some error
floatInt32 :: Float -> Int32
floatInt32 = signed32 . (+ 0) . castFloatToWord32
int64Double :: Int64 -> Double
int64Double = castWord64ToDouble . unsigned64
doubleInt64 :: Double -> Int64
doubleInt64 = signed64 . (+ 0) . castDoubleToWord64
-- Clamp between the int representations of -1/0 and 1/0
clamp32 :: Int32 -> Int32
clamp32 = P.max (-2139095041) . P.min 2139095040
-- Clamp between the int representations of -1/0 and 1/0
clamp64 :: Int64 -> Int64
clamp64 = P.max (-9218868437227405313) . P.min 9218868437227405312
fxxfxx :: (Total a, Fractional a) => Conn k (a, a) a
fxxfxx = Conn f g h
where
-- join
f (x, y) = maybe (1 / 0) (bool y x . (>= EQ)) (pcompare x y)
g x = (x, x)
-- meet
h (x, y) = maybe (-1 / 0) (bool y x . (<= EQ)) (pcompare x y)
fxxext :: forall a b k. (RealFrac a, Preorder a, Bounded b, Integral b) => Conn k a (Extended b)
fxxext = Conn f g h
where
f = extend (~~ -1 / 0) (\x -> x ~~ 0 / 0 || x > high) $ \x -> if x < low then minBound else ceiling x
g = extended (-1 / 0) (1 / 0) fromIntegral
h = extend (\x -> x ~~ 0 / 0 || x < low) (~~ 1 / 0) $ \x -> if x > high then maxBound else floor x
low = fromIntegral $ minBound @b
high = fromIntegral $ maxBound @b
{-# INLINE fxxext #-}
{-
f32i32 :: Conn 'L Float (Extended Int32)
f32i32 = f32ext
f32i64 :: Conn 'L Float (Extended Int64)
f32i64 = f32ext
f32ixx :: Conn 'L Float (Extended Int)
f32ixx = f32ext
f32int :: Conn 'L Float (Extended Integer)
f32int = f32ext
f64i64 :: Conn 'L Double (Extended Int64)
f64i64 = f64ext
f64ixx :: Conn 'L Double (Extended Int)
f64ixx = f64ext
{-# INLINE f64ext #-}
f64int :: Conn 'L Double (Extended Integer)
f64int = f64ext
f32ext :: Integral a => Conn 'L Float (Extended a)
f32ext = fxxextL 23 -- Float loses integer precision beyond 2^prec
{-# INLINE f32ext #-}
f64ext :: Integral a => Conn 'L Double (Extended a)
f64ext = fxxextL 52 -- Double loses integer precision beyond 2^prec
fxxextL :: (Preorder a, RealFrac a, Integral b) => b -> ConnL a (Extended b)
fxxextL prec = ConnL f g
where
f x
| abs x <= 2 ^^ prec -1 = Finite (ceiling x)
| otherwise = case pcompare x 0 of
Just LT -> NegInf
_ -> Finite (2 ^ prec)
g NegInf = -2 ^^ prec
g PosInf = 1 / 0
g (Finite i)
| abs i P.<= 2 ^ prec -1 = fromIntegral i
| otherwise = if i P.>= 0 then 1 / 0 else -2 ^^ prec
{-# INLINE fxxextL #-}
-}