{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE Safe #-}
module Data.Connection.Float (
-- * Float
f32w08,
f32w16,
f32w32,
f32w64,
f32wxx,
f32nat,
f32i08,
f32i16,
f32i32,
f32i64,
f32ixx,
f32int,
f32f32,
f64f32,
ratf32,
ulp32,
near32,
shift32,
-- * Double
f64w08,
f64w16,
f64w32,
f64w64,
f64wxx,
f64nat,
f64i08,
f64i16,
f64i32,
f64i64,
f64ixx,
f64int,
f64f64,
ratf64,
ulp64,
near64,
shift64,
) where
import safe Data.Bool
import safe Data.Connection.Cast hiding (ceiling, floor)
import safe Data.Connection.Ratio
import safe Data.Int
import safe Data.Order
import safe Data.Order.Syntax
import safe Data.Ratio (approxRational)
import safe Data.Word
import safe GHC.Float as F
import safe Numeric.Natural
import safe Prelude hiding (Eq (..), Ord (..), until)
import safe qualified Prelude as P
---------------------------------------------------------------------
-- Float
---------------------------------------------------------------------
f32w08 :: Cast k Float (Extended Word8)
f32w08 = fxxext
f32w16 :: Cast k Float (Extended Word16)
f32w16 = fxxext
f32w32 :: Cast 'L Float (Extended Word32)
f32w32 = swapL ratf32 >>> ratw32
f32w64 :: Cast 'L Float (Extended Word64)
f32w64 = swapL ratf32 >>> ratw64
f32wxx :: Cast 'L Float (Extended Word)
f32wxx = swapL ratf32 >>> ratwxx
f32nat :: Cast 'L Float (Extended Natural)
f32nat = swapL ratf32 >>> ratnat
f32i32 :: Cast 'L Float (Extended Int32)
f32i32 = swapL ratf32 >>> rati32
f32i64 :: Cast 'L Float (Extended Int64)
f32i64 = swapL ratf32 >>> rati64
f32ixx :: Cast 'L Float (Extended Int)
f32ixx = swapL ratf32 >>> ratixx
f32int :: Cast 'L Float (Extended Integer)
f32int = swapL ratf32 >>> ratint
f32i08 :: Cast k Float (Extended Int8)
f32i08 = fxxext
f32i16 :: Cast k Float (Extended Int16)
f32i16 = fxxext
f32f32 :: Cast k (Float, Float) Float
f32f32 = fxxfxx
f64f32 :: Cast k Double Float
f64f32 = Cast 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 #-}
ratf32 :: Cast k Rational Float
ratf32 = Cast (toFractional f) (fromFractional g) (toFractional h)
where
f x =
let est = fromRational x
in if fromFractional g est >~ x
then est
else ascendf est (fromFractional g) x
g = flip approxRational 0
h x =
let est = fromRational x
in if fromFractional g est <~ x
then est
else descendf est (fromFractional g) x
ascendf z g1 y = until (\x -> g1 x >~ y) (<~) (shift32 1) z
descendf z f1 x = until (\y -> f1 y <~ x) (>~) (shift32 (-1)) z
-- | 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
---------------------------------------------------------------------
f64w08 :: Cast k Double (Extended Word8)
f64w08 = fxxext
f64w16 :: Cast k Double (Extended Word16)
f64w16 = fxxext
f64w32 :: Cast k Double (Extended Word32)
f64w32 = fxxext
f64w64 :: Cast 'L Double (Extended Word64)
f64w64 = swapL ratf64 >>> ratw64
f64wxx :: Cast 'L Double (Extended Word)
f64wxx = swapL ratf64 >>> ratwxx
f64nat :: Cast 'L Double (Extended Natural)
f64nat = swapL ratf64 >>> ratnat
f64i08 :: Cast k Double (Extended Int8)
f64i08 = fxxext
f64i16 :: Cast k Double (Extended Int16)
f64i16 = fxxext
f64i32 :: Cast k Double (Extended Int32)
f64i32 = fxxext
f64i64 :: Cast 'L Double (Extended Int64)
f64i64 = swapL ratf64 >>> rati64
f64ixx :: Cast 'L Double (Extended Int)
f64ixx = swapL ratf64 >>> ratixx
f64int :: Cast 'L Double (Extended Integer)
f64int = swapL ratf64 >>> ratint
f64f64 :: Cast k (Double, Double) Double
f64f64 = fxxfxx
ratf64 :: Cast k Rational Double
ratf64 = Cast (toFractional f) (fromFractional g) (toFractional h)
where
f x =
let est = fromRational x
in if fromFractional g est >~ x
then est
else ascendf est (fromFractional g) x
g = flip approxRational 0
h x =
let est = fromRational x
in if fromFractional g est <~ x
then est
else descendf est (fromFractional g) x
ascendf z g1 y = until (\x -> g1 x >~ y) (<~) (shift64 1) z
descendf z f1 x = until (\y -> f1 y <~ x) (>~) (shift64 (-1)) z
-- | 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
pinf :: Num a => Ratio a
pinf = 1 :% 0
ninf :: Num a => Ratio a
ninf = (-1) :% 0
nan :: Num a => Ratio a
nan = 0 :% 0
toFractional :: Fractional a => (Rational -> a) -> Rational -> a
toFractional f x
| x ~~ nan = 0 / 0
| x ~~ ninf = (-1) / 0
| x ~~ pinf = 1 / 0
| otherwise = f x
fromFractional :: (Order a, Fractional a) => (a -> Rational) -> a -> Rational
fromFractional f x
| x ~~ 0 / 0 = nan
| x ~~ (-1) / 0 = ninf
| x ~~ 1 / 0 = pinf
| otherwise = 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) => Cast k (a, a) a
fxxfxx = Cast 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) => Cast k a (Extended b)
fxxext = Cast 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 :: Cast 'L Float (Extended Int32)
f32i32 = f32ext
f32i64 :: Cast 'L Float (Extended Int64)
f32i64 = f32ext
f32ixx :: Cast 'L Float (Extended Int)
f32ixx = f32ext
f32int :: Cast 'L Float (Extended Integer)
f32int = f32ext
f64i64 :: Cast 'L Double (Extended Int64)
f64i64 = f64ext
f64ixx :: Cast 'L Double (Extended Int)
f64ixx = f64ext
{-# INLINE f64ext #-}
f64int :: Cast 'L Double (Extended Integer)
f64int = f64ext
f32ext :: Integral a => Cast 'L Float (Extended a)
f32ext = fxxextL 23 -- Float loses integer precision beyond 2^prec
{-# INLINE f32ext #-}
f64ext :: Integral a => Cast 'L Double (Extended a)
f64ext = fxxextL 52 -- Double loses integer precision beyond 2^prec
fxxextL :: (Preorder a, RealFrac a, Integral b) => b -> CastL a (Extended b)
fxxextL prec = CastL 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 #-}
-}