instance Eq LongDouble where
  (==) = cmp ld_eq
  (/=) = cmp ld_ne

instance Ord LongDouble where
  (<=) = cmp ld_le
  (< ) = cmp ld_lt
  (>=) = cmp ld_ge
  (> ) = cmp ld_gt
  min  = f2 ld_min
  max  = f2 ld_max

instance Num LongDouble where
  fromInteger z = encodeFloat z 0
  negate = f1 ld_neg
  (+) = f2 ld_add
  (-) = f2 ld_sub
  (*) = f2 ld_mul
  abs = f1 ld_abs
  signum = f1 ld_sgn

instance Real LongDouble where
  toRational l = case decodeFloat l of
    (m, e)
      | e >= 0 -> m `shiftL` e % 1
      | otherwise -> m % bit (negate e)

instance Fractional LongDouble where
  fromRational q = -- FIXME accuracy?
    let a = fromInteger (numerator q) / fromInteger (denominator q)
        r = q - toRational a
        b = fromInteger (numerator r) / fromInteger (denominator r)
    in  a + b
  (/) = f2 ld_div
  recip = f1 ld_recip

instance RealFrac LongDouble where
  properFraction l
    | l >= 0 = let n' = floor' l
                   f = l - n'
               in  (fromInteger . toInteger' $ n', f)
    
    | l <  0 = let n' = ceiling' l
                   f = l - n'
               in  (fromInteger . toInteger' $ n', f)
    | otherwise = (0, l) -- NaN
  truncate = fromInteger . toInteger' . truncate'
  round    = fromInteger . toInteger' . round'
  ceiling  = fromInteger . toInteger' . ceiling'
  floor    = fromInteger . toInteger' . floor'

toInteger' :: LongDouble -> Integer
toInteger' l = case decodeFloat l of
  (m, e)
    | e >= 0 -> m `shiftL` e
    | otherwise -> m `shiftR` negate e

-- | Alternate versions of RealFrac methods that
--   keep the value as a long double.
truncate', round', ceiling', floor' :: LongDouble -> LongDouble
truncate' = f1 ld_trunc
round'    = f1 ld_round
ceiling'  = f1 ld_ceil
floor'    = f1 ld_floor

instance Floating LongDouble where
  pi = unsafePerformIO $ do
    alloca $ \lp -> do
      ld_pi lp
      peek lp
  exp = f1 ld_exp
  log = f1 ld_log
  sqrt = f1 ld_sqrt
  (**) = f2 ld_pow
  -- logBase
  sin = f1 ld_sin
  cos = f1 ld_cos
  tan = f1 ld_tan
  sinh = f1 ld_sinh
  cosh = f1 ld_cosh
  tanh = f1 ld_tanh
  asin = f1 ld_asin
  acos = f1 ld_acos
  atan = f1 ld_atan
  asinh = f1 ld_asinh
  acosh = f1 ld_acosh
  atanh = f1 ld_atanh

instance RealFloat LongDouble where
  floatRadix _ = 2
  floatDigits _ = 64
  floatRange _ = (-16381,16384) -- FIXME verify?

  decodeFloat l@(LD a b)
    | isNaN l = (0, 0)
    | isInfinite l = (0, 0)
    | l == 0 = (0, 0)
    | isDenormalized l = case decodeFloat (scaleFloat 128 l) of
        (m, e) -> (m, e - 128)
    | otherwise =
        ( (if s then negate else id) (fromIntegral a)
        , fromIntegral e0 - 16383 - 63
        )
    where
      s = b `testBit` 15
      e0 = b .&. (bit 15 - 1)

  encodeFloat m e
    | m == 0 = LD 0 0
    | m <  0 = negate (encodeFloat (negate m) e)
    | b >= bit 15 - 1 = LD (bit 63) (bit 15 - 1) -- overflow to infinity
    | b <= 0 = scaleFloat (b - 128) (encodeFloat m (e - b + 128)) -- denormal
    | otherwise = LD a (fromIntegral b) -- normal
    where
      l = I# (integerLog2# m)
      t = l - 63
      a | t >= 0    = fromInteger (m `shiftR`        t)
        | otherwise = fromInteger (m `shiftL` negate t)
      b = e + t + 16383 + 63

  exponent l@(LD _ b)
    | isNaN l = 0
    | isInfinite l = 0
    | l == 0 = 0
    | isDenormalized l = snd (decodeFloat l) + 64
    | otherwise = fromIntegral e0 - 16383 - 63 + 64
    where
      e0 = b .&. (bit 15 - 1)

  significand l = unsafePerformIO $ do
    with l $ \lp -> alloca $ \ep -> do
      ld_frexp lp lp ep
      peek lp

  scaleFloat e l = unsafePerformIO $ do
    with l $ \lp -> do
      ld_ldexp lp lp (fromIntegral e)
      peek lp

  isNaN = tst ld_isnan
  isInfinite = tst ld_isinf
  isDenormalized = tst ld_isdenorm
  isNegativeZero = tst ld_isnegzero
  isIEEE _= True

  atan2 = f2 ld_atan2

instance Read LongDouble where
  readsPrec _ = readSigned readFloat

instance Show LongDouble where
  showsPrec p x = showParen (p >= 7 && take 1 s == "-") (s ++) -- FIXME: precedence issues?
    where s = showFloat x ""

fromInt :: Int -> LongDouble
fromInt i = unsafePerformIO $ do
  alloca $ \lp -> do
    ld_set_i lp (fromIntegral i)
    peek lp

toInt :: LongDouble -> Int
toInt l = unsafePerformIO $ with l ld_get_i

fromDouble :: Double -> LongDouble
fromDouble i = unsafePerformIO $ do
  alloca $ \lp -> do
    ld_set_d lp i
    peek lp

toDouble :: LongDouble -> Double
toDouble l = unsafePerformIO $ with l ld_get_d

f2 :: F2 -> LongDouble -> LongDouble -> LongDouble
f2 f a b = unsafePerformIO $ do
  with a $ \ap -> with b $ \bp -> alloca $ \rp -> do
    f rp ap bp
    peek rp

type F2 = Ptr LongDouble -> Ptr LongDouble -> Ptr LongDouble -> IO ()

foreign import ccall unsafe "ld_add"   ld_add   :: F2
foreign import ccall unsafe "ld_sub"   ld_sub   :: F2
foreign import ccall unsafe "ld_mul"   ld_mul   :: F2
foreign import ccall unsafe "ld_div"   ld_div   :: F2
foreign import ccall unsafe "ld_pow"   ld_pow   :: F2
foreign import ccall unsafe "ld_min"   ld_min   :: F2
foreign import ccall unsafe "ld_max"   ld_max   :: F2
foreign import ccall unsafe "ld_atan2" ld_atan2 :: F2

f1 :: F1 -> LongDouble -> LongDouble
f1 f a = unsafePerformIO $ do
  with a $ \ap -> alloca $ \rp -> do
    f rp ap
    peek rp

type F1 = Ptr LongDouble -> Ptr LongDouble -> IO ()

foreign import ccall unsafe "ld_abs"   ld_abs   :: F1
foreign import ccall unsafe "ld_sgn"   ld_sgn   :: F1
foreign import ccall unsafe "ld_neg"   ld_neg   :: F1
foreign import ccall unsafe "ld_sqrt"  ld_sqrt  :: F1
foreign import ccall unsafe "ld_recip" ld_recip :: F1
foreign import ccall unsafe "ld_exp"   ld_exp   :: F1
foreign import ccall unsafe "ld_log"   ld_log   :: F1
foreign import ccall unsafe "ld_sin"   ld_sin   :: F1
foreign import ccall unsafe "ld_cos"   ld_cos   :: F1
foreign import ccall unsafe "ld_tan"   ld_tan   :: F1
foreign import ccall unsafe "ld_sinh"  ld_sinh  :: F1
foreign import ccall unsafe "ld_cosh"  ld_cosh  :: F1
foreign import ccall unsafe "ld_tanh"  ld_tanh  :: F1
foreign import ccall unsafe "ld_asin"  ld_asin  :: F1
foreign import ccall unsafe "ld_acos"  ld_acos  :: F1
foreign import ccall unsafe "ld_atan"  ld_atan  :: F1
foreign import ccall unsafe "ld_asinh" ld_asinh :: F1
foreign import ccall unsafe "ld_acosh" ld_acosh :: F1
foreign import ccall unsafe "ld_atanh" ld_atanh :: F1
foreign import ccall unsafe "ld_floor" ld_floor :: F1
foreign import ccall unsafe "ld_ceil"  ld_ceil  :: F1
foreign import ccall unsafe "ld_round" ld_round :: F1
foreign import ccall unsafe "ld_trunc" ld_trunc :: F1

type CMP = Ptr LongDouble -> Ptr LongDouble -> IO CInt

cmp :: CMP -> LongDouble -> LongDouble -> Bool
cmp f a b = unsafePerformIO $ do
  with a $ \ap -> with b $ \bp -> do
    r <- f ap bp
    return (r /= 0)

foreign import ccall unsafe "ld_eq" ld_eq :: CMP
foreign import ccall unsafe "ld_ne" ld_ne :: CMP
foreign import ccall unsafe "ld_lt" ld_lt :: CMP
foreign import ccall unsafe "ld_le" ld_le :: CMP
foreign import ccall unsafe "ld_gt" ld_gt :: CMP
foreign import ccall unsafe "ld_ge" ld_ge :: CMP

type TST = Ptr LongDouble -> IO CInt

tst :: TST -> LongDouble -> Bool
tst f a = unsafePerformIO $ do
  with a $ \ap -> do
    r <- f ap
    return (r /= 0)

foreign import ccall unsafe "ld_isnan" ld_isnan :: TST
foreign import ccall unsafe "ld_isinf" ld_isinf :: TST
foreign import ccall unsafe "ld_isdenorm" ld_isdenorm :: TST
foreign import ccall unsafe "ld_isnegzero" ld_isnegzero :: TST

foreign import ccall unsafe "ld_get_d" ld_get_d :: Ptr LongDouble -> IO Double
foreign import ccall unsafe "ld_get_i" ld_get_i :: Ptr LongDouble -> IO Int

foreign import ccall unsafe "ld_set_d" ld_set_d :: Ptr LongDouble -> Double -> IO ()
foreign import ccall unsafe "ld_set_i" ld_set_i :: Ptr LongDouble -> Int -> IO ()

foreign import ccall unsafe "ld_ldexp" ld_ldexp :: Ptr LongDouble -> Ptr LongDouble -> CInt -> IO ()
foreign import ccall unsafe "ld_frexp" ld_frexp :: Ptr LongDouble -> Ptr LongDouble -> Ptr CInt -> IO ()

foreign import ccall unsafe "ld_pi"    ld_pi    :: Ptr LongDouble -> IO ()