Flint2-0.1.0.0: src/Data/Number/Flint/Support/D/Interval/FFI.hsc
{-|
module : Data.Number.Flint.Support.D.Interval.FFI
copyright : (c) 2022 Hartmut Monien
license : GNU GPL, version 2 or above (see LICENSE)
maintainer : hmonien@uni-bonn.de
-}
module Data.Number.Flint.Support.D.Interval.FFI (
-- * Double-precision interval arithmetic and helpers
Di (..)
, CDi (..)
-- * Basic manipulation
, di_interval
, arb_get_di
, arb_set_di
, di_print
, di_randtest2
, di_randtest
-- * Arithmetic
, di_neg
-- * Fast arithmetic
, di_fast_add
, di_fast_sub
, di_fast_mul
, di_fast_div
, di_fast_sqr
, di_fast_add_d
, di_fast_sub_d
, di_fast_mul_d
, di_fast_div_d
, di_fast_log_nonnegative
, di_fast_mid
, di_fast_ubound_radius
) where
-- Double-precision interval arithmetic and helpers ----------------------------
import System.IO.Unsafe
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C.Types
import Foreign.C.String
import Foreign.Storable
import Text.Printf
import Data.Number.Flint.Flint
import Data.Number.Flint.Arb
import Data.Number.Flint.Arb.Types
import Data.Number.Flint.Arb.Arf
import Data.Number.Flint.Arb.Mag
import Data.Number.Flint.Support.D.Extras
#include <flint/double_interval.h>
#include <flint/double_extras.h>
d_inf = 1/0 :: CDouble
-- di_t ------------------------------------------------------------------------
data Di = Di {-# UNPACK #-} !(ForeignPtr CDi)
data CDi = CDi CDouble CDouble deriving Show
instance Storable CDi where
sizeOf _ = #{size di_t}
alignment _ = #{alignment di_t}
peek ptr = CDi
<$> #{peek di_t, a} ptr
<*> #{peek di_t, b} ptr
poke ptr (CDi a b) = do
#{poke di_t, a} ptr a
#{poke di_t, b} ptr b
-- Basic manipulation ----------------------------------------------------------
-- | /di_interval/ /a/ /b/
--
-- Returns the interval \([a, b]\). We require that the endpoints are
-- ordered and not NaN.
di_interval :: CDouble -> CDouble -> CDi
di_interval a b =
if a <= b
then CDi a b
else error $ printf "di_interval endpoints %g, %g not ordered.\n"
(realToFrac a :: Double)
(realToFrac b :: Double)
_di_below x =
if x <= 1e300 then
x - (1e-300 + if x < 0 then -x else x) * 4.440892098500626e-16
else
if x /= x then -d_inf else 1e300
_di_above x =
if x >= -1e300 then
x + (1e-300 + if x < 0 then -x else x) * 4.440892098500626e-16
else
if x /= x then d_inf else -1e300
-- | /arb_get_di/ /x/
--
-- Returns the ball /x/ converted to a double-precision interval.
arb_get_di :: Ptr CArb -> IO CDi
arb_get_di x = do
(_, result) <- withNewArf $ \t -> do
arb_get_lbound_arf t x 53
a <- arf_get_d t arf_rnd_floor
arb_get_ubound_arf t x 53
b <- arf_get_d t arf_rnd_ceil
return $ CDi a b
return result
-- | /arb_set_di/ /res/ /x/ /prec/
--
-- Sets the ball /res/ to the double-precision interval /x/, rounded to
-- /prec/ bits.
arb_set_di :: Ptr CArb -> CDi -> CLong -> IO ()
arb_set_di res (CDi a b) prec = do
withNewArf $ \t -> do
withNewArf $ \u -> do
arf_set_d t a
arf_set_d u b
arb_set_interval_arf res t u prec
return ()
-- | /di_print/ /x/
--
-- Prints /x/ to standard output. This simply prints decimal
-- representations of the floating-point endpoints; the decimals are not
-- guaranteed to be rounded outward.
di_print :: CDi -> IO ()
di_print (CDi a b) = do
putStr $ printf "[%.17g, %.17g]" (realToFrac a :: Double)
(realToFrac b :: Double)
-- | /d_randtest2/ /state/
--
-- Returns a random non-NaN @double@ with any exponent. The value can be
-- infinite or subnormal.
di_randtest2 :: Ptr CFRandState -> IO CDouble
di_randtest2 state = do
x <- d_randtest state
return x
-- | /di_randtest/ /state/
--
-- Returns an interval with random endpoints.
di_randtest :: Ptr CFRandState -> IO CDi
di_randtest state = do
a <- d_randtest state
b <- d_randtest state
return $ if a > b then CDi b a else CDi a b
-- Arithmetic ------------------------------------------------------------------
-- | /di_neg/ /x/
--
-- Returns the exact negation of /x/.
di_neg :: CDi -> CDi
di_neg (CDi a b) = CDi (-b) a
-- Fast arithmetic -------------------------------------------------------------
-- The following methods perform fast but sloppy interval arithmetic: we
-- manipulate the endpoints with default rounding and then add or subtract
-- generic perturbations regardless of whether the operations were exact.
-- It is currently assumed that the CPU rounding mode is to nearest.
--
-- | /di_fast_add/ /x/ /y/
di_fast_add :: CDi -> CDi -> CDi
di_fast_add (CDi a b) (CDi a' b') = CDi (_di_below (a+a')) (_di_above (b+b'))
-- | /di_fast_sub/ /x/ /y/
di_fast_sub :: CDi -> CDi -> CDi
di_fast_sub (CDi a b) (CDi a' b') = CDi (_di_below (a-b')) (_di_above (b-a'))
-- | /di_fast_mul/ /x/ /y/
di_fast_mul :: CDi -> CDi -> CDi
di_fast_mul (CDi xa xb) (CDi ya yb) = CDi (_di_below u) (_di_above v) where
(u, v)
| xa > 0 && ya > 0 = (xa*ya, xb*yb)
| xa > 0 && yb < 0 = (xb*ya, xa*yb)
| xb < 0 && ya > 0 = (xa*yb, xb*ya)
| xb < 0 && yb < 0 = (xb*yb, xa*ya)
| a /= a || b /= b || c /= c || d /= d = (-d_inf, d_inf)
| otherwise = (min (min a b) (min c d), max (max a b) (max c d))
where
a = xa * ya
b = xa * yb
c = xb * ya
d = xb * yb
-- | /di_fast_div/ /x/ /y/
--
-- Returns the sum, difference, product or quotient of /x/ and /y/.
-- Division by zero is currently defined to return \([-\infty, +\infty]\).
di_fast_div :: CDi -> CDi -> CDi
di_fast_div (CDi xa xb) (CDi ya yb) = CDi (_di_below u) (_di_above v) where
(u, v)
| ya > 0 && xa >= 0 = (xa/yb, xb/ya)
| ya > 0 && xb <= 0 = (xa/ya, xb/yb)
| ya > 0 = (xa/ya, xb/ya)
| yb < 0 && xa >= 0 = (xb/yb, xa/ya)
| yb < 0 && xb <= 0 = (xb/ya, xa/yb)
| yb <0 = (xb/yb, xa/yb)
| otherwise = (-d_inf, d_inf)
-- | /di_fast_sqr/ /x/
--
-- Returns the square of /x/. The output is clamped to be nonnegative.
di_fast_sqr :: CDi -> CDi
di_fast_sqr (CDi a b) =
CDi (if a /= 0 then _di_below u else u) (_di_above b) where
(u, v)
| a >= 0 = (a*a, b*b)
| b <= 0 = (b*b, a*a)
| otherwise = (0, max (a*a) (b*b))
-- | /di_fast_add_d/ /x/ /y/
di_fast_add_d :: CDi -> CDouble -> CDi
di_fast_add_d x y = di_fast_add x (di_interval y y)
-- -- | /di_fast_sub_d/ /x/ /y/
di_fast_sub_d :: CDi -> CDouble -> CDi
di_fast_sub_d x y = di_fast_sub x (di_interval y y)
-- | /di_fast_mul_d/ /x/ /y/
di_fast_mul_d :: CDi -> CDouble -> CDi
di_fast_mul_d x y = di_fast_mul x (di_interval y y)
-- | /di_fast_div_d/ /x/ /y/
-- Arithmetic with an exact @double@ operand.
di_fast_div_d :: CDi -> CDouble -> CDi
di_fast_div_d x y = di_fast_div x (di_interval y y)
-- | /di_fast_log_nonnegative/ /x/
--
-- Returns an enclosure of \(\log(x)\). The lower endpoint of /x/ is
-- rounded up to 0 if it is negative.
di_fast_log_nonnegative :: CDi -> CDi
di_fast_log_nonnegative (CDi a b) = CDi a' b' where
a' = if a <= 0 then (-d_inf) else mag_d_log_lower_bound a
b' = mag_d_log_upper_bound b
-- | /di_fast_mid/ /x/
--
-- Returns an enclosure of the midpoint of /x/.
di_fast_mid :: CDi -> CDi
di_fast_mid (CDi a b)
| a == -d_inf || b == d_inf = di_interval (-d_inf) d_inf
| otherwise = di_fast_mul_d (di_fast_add (di_interval a a)
(di_interval b b)) 0.5
-- | /di_fast_ubound_radius/ /x/
--
-- Returns an upper bound for the radius of /x/.
di_fast_ubound_radius :: CDi -> CDouble
di_fast_ubound_radius (CDi a b) = _di_above (0.5 * (b -a))