{-# LANGUAGE HexFloatLiterals #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Numeric.Rounded.Hardware.Internal.Conversion
  ( fromInt
  , fromIntF
  , intervalFromInteger_default
  , fromRatio
  , fromRatioF
  , intervalFromRational_default
  ) where
import Numeric.Rounded.Hardware.Internal.Rounding
import Numeric.Rounded.Hardware.Internal.RoundedResult
import Numeric.Rounded.Hardware.Internal.FloatUtil
import Data.Bits
import Data.Functor.Product
import Math.NumberTheory.Logarithms (integerLog2')
import Data.Ratio
import Control.Exception (assert)
-- import GHC.Integer.Logarithms.Internals (integerLog2IsPowerOf2#)
-- integerLog2IsPowerOf2# :: Integer -> (# Int#, Int# #)

intervalFromInteger_default :: RealFloat a => Integer -> (Rounded 'TowardNegInf a, Rounded 'TowardInf a)
intervalFromInteger_default x = case fromIntF x of Pair a b -> (a, b)
{-# SPECIALIZE intervalFromInteger_default :: Integer -> (Rounded 'TowardNegInf Float, Rounded 'TowardInf Float) #-}
{-# SPECIALIZE intervalFromInteger_default :: Integer -> (Rounded 'TowardNegInf Double, Rounded 'TowardInf Double) #-}

intervalFromRational_default :: RealFloat a => Rational -> (Rounded 'TowardNegInf a, Rounded 'TowardInf a)
intervalFromRational_default x = case fromRatioF (numerator x) (denominator x) of Pair a b -> (a, b)
{-# SPECIALIZE intervalFromRational_default :: Rational -> (Rounded 'TowardNegInf Float, Rounded 'TowardInf Float) #-}
{-# SPECIALIZE intervalFromRational_default :: Rational -> (Rounded 'TowardNegInf Double, Rounded 'TowardInf Double) #-}

fromInt :: RealFloat a => RoundingMode -> Integer -> a
fromInt r n = withRoundingMode (fromIntF n) r
{-# SPECIALIZE fromInt :: RoundingMode -> Integer -> Float #-}
{-# SPECIALIZE fromInt :: RoundingMode -> Integer -> Double #-}

fromIntF :: forall a f. (RealFloat a, Result f) => Integer -> f a
fromIntF !_ | floatRadix (undefined :: a) /= 2 = error "radix other than 2 is not supported"
fromIntF 0 = exact 0
fromIntF n | n < 0 = negate <$> withOppositeRoundingMode (fromPositiveIntF (- n))
           | otherwise = fromPositiveIntF n
{-# INLINE fromIntF #-}

-- n > 0
fromPositiveIntF :: forall a f. (RealFloat a, Result f) => Integer -> f a
fromPositiveIntF !n
  = let !k = integerLog2' n -- floor (log2 n)
        -- 2^k <= n < 2^(k+1)
        !fDigits = floatDigits (undefined :: a) -- 53 for Double
    in if k < fDigits
       then exact (fromInteger n)
       else let e = k - (fDigits - 1)
                  -- (!q, !r) = n `quotRem` (1 `unsafeShiftL` e)
                q = n `unsafeShiftR` e
                r = n .&. ((1 `unsafeShiftL` e) - 1)
                    -- 2^52 <= q < 2^53, 0 <= r < 2^(k-52)
                (_expMin, !expMax) = floatRange (undefined :: a) -- (-1021, 1024) for Double
            in if k >= expMax
               then
                 -- infinity
                 inexact (1 / 0) -- ToNearest
                         (1 / 0) -- TowardInf
                         maxFinite_ieee -- TowardNegInf
                         maxFinite_ieee -- TowardZero
               else
                 if r == 0
                 then exact $ encodeFloat q e -- exact
                 else
                   -- inexact
                   let down = encodeFloat q e
                       up = encodeFloat (q + 1) e
                       toNearest = case compare r (1 `unsafeShiftL` (e-1)) of
                         LT -> down
                         EQ | even q -> down
                            | otherwise -> up
                         GT -> up
                   in inexact toNearest up down down
{-# SPECIALIZE fromPositiveIntF :: Integer -> DynamicRoundingMode Float #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> OppositeRoundingMode DynamicRoundingMode Float #-}
{-# SPECIALIZE fromPositiveIntF :: Rounding r => Integer -> Rounded r Float #-}
{-# SPECIALIZE fromPositiveIntF :: Rounding r => Integer -> OppositeRoundingMode (Rounded r) Float #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> Product (Rounded 'TowardNegInf) (Rounded 'TowardInf) Float #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> OppositeRoundingMode (Product (Rounded 'TowardNegInf) (Rounded 'TowardInf)) Float #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> DynamicRoundingMode Double #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> OppositeRoundingMode DynamicRoundingMode Double #-}
{-# SPECIALIZE fromPositiveIntF :: Rounding r => Integer -> Rounded r Double #-}
{-# SPECIALIZE fromPositiveIntF :: Rounding r => Integer -> OppositeRoundingMode (Rounded r) Double #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> Product (Rounded 'TowardNegInf) (Rounded 'TowardInf) Double #-}
{-# SPECIALIZE fromPositiveIntF :: Integer -> OppositeRoundingMode (Product (Rounded 'TowardNegInf) (Rounded 'TowardInf)) Double #-}

fromRatio :: (RealFloat a)
          => RoundingMode
          -> Integer -- ^ numerator
          -> Integer -- ^ denominator
          -> a
fromRatio r n d = withRoundingMode (fromRatioF n d) r
{-# SPECIALIZE fromRatio :: RoundingMode -> Integer -> Integer -> Float #-}
{-# SPECIALIZE fromRatio :: RoundingMode -> Integer -> Integer -> Double #-}

fromRatioF :: forall a f. (RealFloat a, Result f)
           => Integer -- ^ numerator
           -> Integer -- ^ denominator
           -> f a
fromRatioF !_ !_ | floatRadix (undefined :: a) /= 2 = error "radix other than 2 is not supported"
fromRatioF 0 _ = exact 0
fromRatioF n 0 | n > 0 = exact (1 / 0) -- positive infinity
               | otherwise = exact (- 1 / 0) -- negative infinity
fromRatioF n d | d < 0 = error "fromRatio: negative denominator"
               | n < 0 = negate <$> withOppositeRoundingMode (fromPositiveRatioF (- n) d)
               | otherwise = fromPositiveRatioF n d
{-# INLINE fromRatioF #-}

-- n > 0, d > 0
fromPositiveRatioF :: forall a f. (RealFloat a, Result f)
                   => Integer -> Integer -> f a
fromPositiveRatioF !n !d
  = let ln, ld, e :: Int
        ln = integerLog2' n
        ld = integerLog2' d
        e = ln - ld - fDigits
        q, r, d_ :: Integer
        d_ | e >= 0 = d `unsafeShiftL` e
           | otherwise = d
        (!q, !r) | e >= 0 = n `quotRem` d_
                 | otherwise = (n `unsafeShiftL` (-e)) `quotRem` d
        -- e >= 0: n = q * (d * 2^e) + r, 0 <= r < d * 2^e
        -- e <= 0: n * 2^(-e) = q * d + r, 0 <= r < d
        -- n / d * 2^^(-e) = q + r / d_
        -- 52 <= log2 q < 54
        q', r', d' :: Integer
        e' :: Int
        (!q', !r', !d', !e') | q < (1 `unsafeShiftL` fDigits) = (q, r, d_, e)
                             | otherwise = let (q'', r'') = q `quotRem` 2
                                           in (q'', r'' * d_ + r, 2 * d_, e + 1)
        -- n / d * 2^^(-e') = q' + r' / d', 2^52 <= q' < 2^53, 0 <= r' < d'
        -- q' * 2^^e' <= n/d < (q'+1) * 2^^e', 2^52 <= q' < 2^53
        -- (q'/2^53) * 2^^(e'+53) <= n/d < (q'+1)/2^53 * 2^^(e'+53), 1/2 <= q'/2^53 < 1
        -- normal: 0x1p-1022 <= x <= 0x1.fffffffffffffp+1023
    in assert (n % d * 2^^(-e) == fromInteger q + r % d_) $
       assert (n % d * 2^^(-e') == fromInteger q' + r' % d') $
       if expMin <= e' + fDigits && e' + fDigits <= expMax
       then
         -- normal
         if r' == 0
         then
           exact $ encodeFloat q' e' -- exact
         else
           -- inexact
           let down = encodeFloat q' e'
               up = encodeFloat (q' + 1) e' -- may be infinity
               toNearest = case compare (2 * r') d' of
                 LT -> down
                 EQ | even q' -> down
                    | otherwise -> up -- q' + 1 is even
                 GT -> up
           in inexact toNearest up down down
       else
         -- infinity or subnormal
         if expMax <= e' + fDigits
         then
           -- infinity
           inexact (1 / 0) -- ToNearest
                   (1 / 0) -- TowardInf
                   maxFinite_ieee -- TowardNegInf
                   maxFinite_ieee -- TowardZero
         else
           -- subnormal
           -- e' + fDigits < expMin (or, e' < expMin - fDigits = -1074)
           -- 0 <= rounded(n/d) <= 2^(expMin - 1) = 0x1p-1022, minimum (positive) subnormal: 0x1p-1074
           let (!q'', !r'') = q' `quotRem` (1 `unsafeShiftL` (expMin - fDigits - e'))
               -- q' = q'' * 2^(expMin - fDigits - e') + r'', 0 <= r'' < 2^(expMin - fDigits - e')
               -- 2^(fDigits-1) <= q' = q'' * 2^(expMin - fDigits - e') + r'' < 2^fDigits
               -- n / d * 2^^(-e') = q' + r' / d' = q'' * 2^(expMin - fDigits - e') + r'' + r' / d'
               -- n / d = q'' * 2^^(expMin - fDigits) + (r'' + r' / d') * 2^^e'
               -- 0 <= r'' < 2^(expMin - fDigits - e')
           in if r' == 0 && r'' == 0
              then exact $ encodeFloat q'' (expMin - fDigits) -- exact
              else let down = encodeFloat q'' (expMin - fDigits)
                       up = encodeFloat (q'' + 1) (expMin - fDigits)
                       toNearest = case compare r'' (1 `unsafeShiftL` (expMin - fDigits - e' - 1)) of
                         LT -> down
                         GT -> up
                         EQ | r' /= 0   -> up
                            | even q'   -> down
                            | otherwise -> up
                   in inexact toNearest up down down
  where
    !fDigits = floatDigits (undefined :: a) -- 53 for Double
    (!expMin, !expMax) = floatRange (undefined :: a) -- (-1021, 1024) for Double
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> DynamicRoundingMode Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> OppositeRoundingMode DynamicRoundingMode Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Rounding r => Integer -> Integer -> Rounded r Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Rounding r => Integer -> Integer -> OppositeRoundingMode (Rounded r) Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> Product (Rounded 'TowardNegInf) (Rounded 'TowardInf) Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> OppositeRoundingMode (Product (Rounded 'TowardNegInf) (Rounded 'TowardInf)) Float #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> DynamicRoundingMode Double #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> OppositeRoundingMode DynamicRoundingMode Double #-}
{-# SPECIALIZE fromPositiveRatioF :: Rounding r => Integer -> Integer -> Rounded r Double #-}
{-# SPECIALIZE fromPositiveRatioF :: Rounding r => Integer -> Integer -> OppositeRoundingMode (Rounded r) Double #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> Product (Rounded 'TowardNegInf) (Rounded 'TowardInf) Double #-}
{-# SPECIALIZE fromPositiveRatioF :: Integer -> Integer -> OppositeRoundingMode (Product (Rounded 'TowardNegInf) (Rounded 'TowardInf)) Double #-}