{-# LANGUAGE DataKinds #-}
{-# LANGUAGE HexFloatLiterals #-}
{-# LANGUAGE NumericUnderscores #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
module Conversion (benchmark) where
import           Data.Bits
import           Data.Functor.Product
import           Data.Int
import           Data.Ratio
import           Data.Word
import           Numeric.Floating.IEEE
import qualified Numeric.Floating.IEEE.Internal as IEEE.Internal
import           Numeric.Rounded.Hardware
import qualified Numeric.Rounded.Hardware.Backend.C as C
import           Numeric.Rounded.Hardware.Class
import           Numeric.Rounded.Hardware.Interval
import           Test.Tasty.Bench

word64ToDouble :: RoundingMode -> Word64 -> Double
word64ToDouble ToNearest x
  | x >= 0xFFFF_FFFF_FFFF_FC00 = 0x1p64
  | otherwise = let z = countLeadingZeros x
                    y = if x .&. (0x0000_0000_0000_0800 `unsafeShiftR` z) == 0
                        then x + (0x0000_0000_0000_03FF `unsafeShiftR` z)
                        else x + (0x0000_0000_0000_0400 `unsafeShiftR` z)
                in fromIntegral (y .&. (0xFFFF_FFFF_FFFF_F800 `unsafeShiftR` z))
word64ToDouble TowardInf x
  | x >= 0xFFFF_FFFF_FFFF_F800 = 0x1p64
  | otherwise = let z = countLeadingZeros x
                    y = x + (0x0000_0000_0000_07FF `unsafeShiftR` z)
                in fromIntegral (y .&. (0xFFFF_FFFF_FFFF_F800 `unsafeShiftR` z))
word64ToDouble TowardNegInf x = let z = countLeadingZeros x
                                in fromIntegral (x .&. (0xFFFF_FFFF_FFFF_F800 `unsafeShiftR` z))
word64ToDouble TowardZero x = let z = countLeadingZeros x
                              in fromIntegral (x .&. (0xFFFF_FFFF_FFFF_F800 `unsafeShiftR` z))

int64ToDouble :: RoundingMode -> Int64 -> Double
int64ToDouble r x | x >= 0 = word64ToDouble r (fromIntegral x)
                  | r == TowardInf = - word64ToDouble TowardNegInf (fromIntegral (-x))
                  | r == TowardNegInf = - word64ToDouble TowardInf (fromIntegral (-x))
                  | otherwise = - word64ToDouble r (fromIntegral (-x))

benchmark :: Benchmark
benchmark = bgroup "Conversion"
  [ bgroup "fromInteger/to Double"
    [ bgroup name $ map ($ value)
      [ bench "plain" . nf (fromInteger :: Integer -> Double)
      , bench "Rounded/ToNearest" . nf (fromInteger :: Integer -> Rounded 'ToNearest Double)
      , bench "Rounded/TowardInf" . nf (fromInteger :: Integer -> Rounded 'TowardInf Double)
      , bench "roundedFromInteger/ToNearest" . nf (roundedFromInteger ToNearest :: Integer -> Double)
      , bench "roundedFromInteger/TowardInf" . nf (roundedFromInteger TowardInf :: Integer -> Double)
      , bench "fp-ieee/ToNearest" . nf (fromIntegerTiesToEven :: Integer -> Double)
      , bench "fp-ieee/TowardInf" . nf (fromIntegerTowardPositive :: Integer -> Double)
      , bench "Interval/default" . nf (fromInteger :: Integer -> Interval Double)
      , bench "Interval/individual" . nf (\n -> (fromIntegerTowardNegative n, fromIntegerTowardPositive n) :: (Double, Double))
      , bench "Interval/fromIntegerR" . nf (\n -> case IEEE.Internal.fromIntegerR n of
                                                    Pair (IEEE.Internal.RoundTowardNegative x) (IEEE.Internal.RoundTowardPositive y) -> (x, y) :: (Double, Double)
                                           )
      ]
    | (name, value) <- [ ("small", -2^50 + 2^13 + 127)
                       , ("medium", -2^60 + 42 * 2^53 - 137 * 2^24 + 3)
                       , ("large",  -2^100 - 37 * 2^80 + 2^13 + 127)
                       ] :: [(String, Integer)]
    ]
  , bgroup "fromIntegral/Int64->Double"
    [ bgroup name $ map ($ value)
      [ bench "plain" . nf (fromIntegral :: Int64 -> Double)
      , bench "Rounded/ToNearest" . nf (fromIntegral :: Int64 -> Rounded 'ToNearest Double)
      , bench "Rounded/TowardInf" . nf (fromIntegral :: Int64 -> Rounded 'TowardInf Double)
      , bench "roundedFromInteger/ToNearest" . nf (roundedFromInteger ToNearest . fromIntegral :: Int64 -> Double)
      , bench "roundedFromInteger/TowardInf" . nf (roundedFromInteger TowardInf . fromIntegral :: Int64 -> Double)
      , bench "fp-ieee/ToNearest" . nf (fromIntegralTiesToEven :: Int64 -> Double)
      , bench "fp-ieee/TowardInf" . nf (fromIntegralTowardPositive :: Int64 -> Double)
      , bench "int64ToDouble/ToNearest" . nf (int64ToDouble ToNearest :: Int64 -> Double)
      , bench "int64ToDouble/TowardInf" . nf (int64ToDouble TowardInf :: Int64 -> Double)
      , bench "Interval/default" . nf (fromIntegral :: Int64 -> Interval Double)
      , bench "Interval/individual" . nf (\n -> (fromIntegralTowardNegative n, fromIntegralTowardPositive n) :: (Double, Double))
      , bench "Interval/fromIntegralR" . nf (\n -> case IEEE.Internal.fromIntegralR n of
                                                Pair (IEEE.Internal.RoundTowardNegative x) (IEEE.Internal.RoundTowardPositive y) -> (x, y) :: (Double, Double)
                                            )
      , bench "Interval/individual/C" . nf (\n -> (C.roundedDoubleFromInt64 TowardNegInf n, C.roundedDoubleFromInt64 TowardInf n))
      ]
    | (name, value) <- [ ("small", -2^50 + 2^13 + 127)
                       , ("medium", -2^60 + 42 * 2^53 - 137 * 2^24 + 3)
                       ] :: [(String, Int64)]
    ]
  , bgroup "fromIntegral/Word64->Double"
    [ bgroup name $ map ($ value)
      [ bench "plain" . nf (fromIntegral :: Word64 -> Double)
      , bench "Rounded/ToNearest" . nf (fromIntegral :: Word64 -> Rounded 'ToNearest Double)
      , bench "Rounded/TowardInf" . nf (fromIntegral :: Word64 -> Rounded 'TowardInf Double)
      , bench "roundedFromInteger/ToNearest" . nf (roundedFromInteger ToNearest . fromIntegral :: Word64 -> Double)
      , bench "roundedFromInteger/TowardInf" . nf (roundedFromInteger TowardInf . fromIntegral :: Word64 -> Double)
      , bench "fp-ieee/ToNearest" . nf (fromIntegralTiesToEven :: Word64 -> Double)
      , bench "fp-ieee/TowardInf" . nf (fromIntegralTowardPositive :: Word64 -> Double)
      , bench "word64ToDouble/ToNearest" . nf (word64ToDouble ToNearest :: Word64 -> Double)
      , bench "word64ToDouble/TowardInf" . nf (word64ToDouble TowardInf :: Word64 -> Double)
      , bench "Interval/default" . nf (fromIntegral :: Word64 -> Interval Double)
      , bench "Interval/individual" . nf (\n -> (fromIntegralTowardNegative n, fromIntegralTowardPositive n) :: (Double, Double))
      , bench "Interval/fromIntegralR" . nf (\n -> case IEEE.Internal.fromIntegralR n of
                                                Pair (IEEE.Internal.RoundTowardNegative x) (IEEE.Internal.RoundTowardPositive y) -> (x, y) :: (Double, Double)
                                            )
      , bench "Interval/individual/C" . nf (\n -> (C.roundedDoubleFromWord64 TowardNegInf n, C.roundedDoubleFromWord64 TowardInf n))
      ]
    | (name, value) <- [ ("small", 2^50 + 2^13 + 127)
                       , ("medium", 2^63 + 42 * 2^53 - 137 * 2^24 + 3)
                       ] :: [(String, Word64)]
    ]
  , bgroup "fromRational/to Double"
    [ bgroup name $ map ($ value)
      [ bench "plain" . nf (fromRational :: Rational -> Double)
      , bench "Rounded/ToNearest" . nf (fromRational :: Rational -> Rounded 'ToNearest Double)
      , bench "Rounded/TowardInf" . nf (fromRational :: Rational -> Rounded 'TowardInf Double)
      , bench "fp-ieee/ToNearest" . nf (fromRationalTiesToEven :: Rational -> Double)
      , bench "fp-ieee/TowardInf" . nf (fromRationalTowardPositive :: Rational -> Double)
      , bench "Interval/default" . nf (fromRational :: Rational -> Interval Double)
      , bench "Interval/individual" . nf (\x -> (fromRationalTowardNegative x :: Double, fromRationalTowardPositive x :: Double))
      , bench "Interval/fromRationalR" . nf (\x -> case IEEE.Internal.fromRationalR x of
                                                     Pair (IEEE.Internal.RoundTowardNegative a) (IEEE.Internal.RoundTowardPositive b) -> (a, b) :: (Double, Double)
                                            )
      ]
    | (name, value) <- [ ("decimal", 3.14159265358979323846264338327950)
                       , ("binary", 0xcafec0ffeecafec0ffeep-177)
                       , ("small", 22 % 7)
                       , ("large", 78326489123342523452342137498719847192 % 348912374981749170413424213275017)
                       ] :: [(String, Rational)]
    ]
  ]
