{-# OPTIONS -cpp #-}
-- !!! Testing IEEE Float and Double extremity predicates.
module Main(main) where

import Data.Char
import Control.Monad.ST
import Data.Word
import Data.Array.Unsafe ( castSTUArray )
import Data.Array.ST

#include "ghcconfig.h"

reverse_if_bigendian :: [a] -> [a]
#ifdef WORDS_BIGENDIAN
reverse_if_bigendian = reverse
#else
reverse_if_bigendian = id
#endif


main :: IO ()
main = do
 sequence_ (map putStrLn double_tests)
 sequence_ (map putStrLn float_tests)
  where
   double_tests = run_tests double_numbers
   float_tests  = run_tests float_numbers

   run_tests nums =
    map ($ nums)
        [ denorm
        , pos_inf
        , neg_inf
        , nan
        , neg_zero
        , pos_zero
        ]

-------------
double_numbers :: [Double]
double_numbers =
      [ 0
      , encodeFloat 0 0     -- 0 using encodeFloat method
      , mkDouble (reverse_if_bigendian [0,0,0,0,0,0, 0xf0, 0x7f])  -- +inf
      , encodeFloat 1 2047  -- +Inf
      , encodeFloat 1 2048
      , encodeFloat 1  2047               -- signalling NaN
      , encodeFloat 0xf000000000000 2047  -- quiet NaN
      , 0/(0::Double)
        -- misc
      , 1.82173691287639817263897126389712638972163e-300
      , 1.82173691287639817263897126389712638972163e+300
      , 4.9406564558412465e-324  -- smallest possible denorm number
                                 -- (as reported by enquire running
                                 --  on a i686-pc-linux.)
      , 2.2250738585072014e-308
      , 0.11
      , 0.100
      , -3.4
        -- smallest
      , let (l, _) = floatRange x
            x = encodeFloat 1 (l-1)
        in x
        -- largest
      , let (_, u) = floatRange x
            d = floatDigits x
            x = encodeFloat (floatRadix x ^ d - 1) (u - d)
        in x
      ]

float_numbers :: [Float]
float_numbers =
      [ 0
      , encodeFloat 0 0     -- 0 using encodeFloat method
      , encodeFloat 1 255  -- +Inf
      , encodeFloat 1 256
      , encodeFloat 11 255        -- signalling NaN
      , encodeFloat 0xf00000 255  -- quiet NaN
      , 0/(0::Float)
        -- misc
      , 1.82173691287639817263897126389712638972163e-300
      , 1.82173691287639817263897126389712638972163e+300
      , 1.40129846e-45
      , 1.17549435e-38
      , 2.98023259e-08
      , 0.11
      , 0.100
      , -3.4
        -- smallest
      , let (l, _) = floatRange x
            x = encodeFloat 1 (l-1)
        in x
        -- largest
      , let (_, u) = floatRange x
            d = floatDigits x
            x = encodeFloat (floatRadix x ^ d - 1) (u - d)
        in x
      ]

-------------

denorm :: (Show a, RealFloat a) => [a] -> String
denorm numbers =
  unlines
     ( ""
     : "*********************************"
     : ("Denormalised numbers: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (isDenormalized) "isDenormalised"

pos_inf :: (Show a, RealFloat a) => [a] -> String
pos_inf numbers =
  unlines
     ( ""
     : "*********************************"
     : ("Positive Infinity: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (isInfinite) "isInfinite"

neg_inf :: (Show a, RealFloat a) => [a] -> String
neg_inf numbers =
  unlines
     ( ""
     : "*********************************"
     : ("Negative Infinity: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (\ x -> isInfinite x && x < 0) "isNegInfinite"

nan :: (Show a, RealFloat a) => [a] -> String
nan numbers =
  unlines
     ( ""
     : "*********************************"
     : ("NaN: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (isNaN) "isNaN"

pos_zero :: (Show a, RealFloat a) => [a] -> String
pos_zero numbers =
  unlines
     ( ""
     : "*********************************"
     : ("Positive zero: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (==0) "isPosZero"

neg_zero :: (Show a, RealFloat a) => [a] -> String
neg_zero numbers =
  unlines
     ( ""
     : "*********************************"
     : ("Negative zero: " ++ doubleOrFloat numbers)
     : ""
     : map showPerform numbers)
 where
   showPerform = showAndPerform (isNegativeZero) "isNegativeZero"

-- what a hack.
doubleOrFloat :: RealFloat a => [a] -> String
doubleOrFloat ls
 | (floatDigits atType) == (floatDigits (0::Double)) = "Double"
 | (floatDigits atType) == (floatDigits (0::Float))  = "Float"
 | otherwise = "unknown RealFloat type"
 where
   atType = undefined `asTypeOf` (head ls)

-- make a double from a list of 8 bytes
-- (caller deals with byte ordering.)
mkDouble :: [Word8] -> Double
mkDouble ls =
 runST (( do
   arr <- newArray_ (0,7)
   sequence (zipWith (writeArray arr) [(0::Int)..] (take 8 ls))
   arr' <- castSTUArray arr
   readArray arr' 0
 ) :: ST s Double )

showAndPerform :: (Show a, Show b)
               => (a -> b)
               -> String
               -> a
               -> String
showAndPerform fun name_fun val =
  name_fun ++ ' ':show val ++ " = " ++ show (fun val)


