packages feed

data-binary-ieee754-0.4.3: tests/Tests.hs

-----------------------------------------------------------------------------
-- |
-- Module: Tests
-- Copyright: 2010 John Millikin
-- License: MIT
--
-- Maintainer: jmillikin@gmail.com
-- Portability: portable
--
-----------------------------------------------------------------------------
module Main (tests, main) where

import qualified Data.ByteString.Lazy as B
import           Data.Word (Word8)
import           Data.Binary.Get (Get, runGetState)
import           Data.Binary.Put (Put, runPut)

import          Test.Chell
import          Test.Chell.QuickCheck
import          Test.QuickCheck hiding (label, property)

import          Data.Binary.IEEE754

tests :: [Suite]
tests =
	[ test_Parsing
	, test_Serialising
	, test_Passthrough
	, test_Passthrough_NaN
	]

main :: IO ()
main = Test.Chell.defaultMain tests

test_Parsing :: Suite
test_Parsing = suite "parsing"
	(props_GetFloat16 "16")
	(props_GetFloat32 "32")
	(props_GetFloat64 "64")

test_Serialising :: Suite
test_Serialising = suite "serialising"
	(props_PutFloat32 "32")
	(props_PutFloat64 "64")

test_Passthrough :: Suite
test_Passthrough = suite "passthrough"
	(testPassthrough "32-le" putFloat32le getFloat32le)
	(testPassthrough "32-be" putFloat32be getFloat32be)
	(testPassthrough "64-le" putFloat64le getFloat64le)
	(testPassthrough "64-be" putFloat64be getFloat64be)

test_Passthrough_NaN :: Suite
test_Passthrough_NaN = suite "passthrough-nan"
	(testPassthroughNaN "32-le" putFloat32le getFloat32le)
	(testPassthroughNaN "32-be" putFloat32be getFloat32be)
	(testPassthroughNaN "64-le" putFloat64le getFloat64le)
	(testPassthroughNaN "64-be" putFloat64be getFloat64be)

props_GetFloat16 :: String -> Suite
props_GetFloat16 label =
	let check = checkGet getFloat16be getFloat16le in
	suite label
	
	(check [0, 0]    ((== 0.0) .&& (not . isNegativeZero)))
	(check [0x80, 0] isNegativeZero)
	
	-- Normalised
	(check [0x3C, 0] (==  1.0))
	(check [0xBC, 0] (== -1.0))
	
	-- Denormalised
	(check [0x03, 0xFF] (==  6.097555e-5))
	(check [0x83, 0xFF] (== -6.097555e-5))
	
	-- Infinity
	(check [0x7C, 0] (==  inf32))
	(check [0xFC, 0] (== -inf32))
	
	-- NaN
	(check [0x7E, 0] (isNaN .&& (not . isNegativeNaN)))
	(check [0xFE, 0] isNegativeNaN)

props_GetFloat32 :: String -> Suite
props_GetFloat32 label =
	let check = checkGet getFloat32be getFloat32le in
	suite label
	
	(check [0, 0, 0, 0]    ((== 0.0) .&& (not . isNegativeZero)))
	(check [0x80, 0, 0, 0] isNegativeZero)
	
	-- Normalised
	(check [0x3F, 0x80, 0, 0] (==  1.0))
	(check [0xBF, 0x80, 0, 0] (== -1.0))
	
	-- Denormalised
	(check [0x00, 0x7F, 0xFF, 0xFF] (==  1.1754942106924411e-38))
	(check [0x80, 0x7F, 0xFF, 0xFF] (== -1.1754942106924411e-38))
	
	-- Infinity
	(check [0x7F, 0x80, 0, 0] (==  inf32))
	(check [0xFF, 0x80, 0, 0] (== -inf32))
	
	-- NaN and negative NaN
	(check [0x7F, 0xC0, 0, 0] (isNaN .&& (not . isNegativeNaN)))
	(check [0xFF, 0xC0, 0, 0] isNegativeNaN)

props_GetFloat64 :: String -> Suite
props_GetFloat64 label =
	let check = checkGet getFloat64be getFloat64le in
	suite label
	
	(check [0, 0, 0, 0, 0, 0, 0, 0]    ((== 0.0) .&& (not . isNegativeZero)))
	(check [0x80, 0, 0, 0, 0, 0, 0, 0] isNegativeZero)
	
	-- Normalised
	(check [0x3F, 0xF0, 0, 0, 0, 0, 0, 0] (==  1.0))
	(check [0xBF, 0xF0, 0, 0, 0, 0, 0, 0] (== -1.0))
	
	-- Denormalised
	(check [0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] (==  2.2250738585072009e-308))
	(check [0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] (== -2.2250738585072009e-308))
	
	-- Infinity
	(check [0x7F, 0xF0, 0, 0, 0, 0, 0, 0] (==  inf64))
	(check [0xFF, 0xF0, 0, 0, 0, 0, 0, 0] (== -inf64))
	
	-- NaN
	(check [0x7F, 0xF8, 0, 0, 0, 0, 0, 0] (isNaN .&& (not . isNegativeNaN)))
	(check [0xFF, 0xF8, 0, 0, 0, 0, 0, 0] isNegativeNaN)

props_PutFloat32 :: String -> Suite
props_PutFloat32 label =
	let check = checkPut putFloat32be putFloat32le in
	suite label
	
	(check [0, 0, 0, 0]   0.0)
	(check [0x80, 0, 0, 0] (-0.0))
	
	-- Normalised
	(check [0x3F, 0x80, 0, 0]   1.0)
	(check [0xBF, 0x80, 0, 0] (-1.0))
	
	-- Denormalised
	(check [0x00, 0x7F, 0xFF, 0xFF]   1.1754942106924411e-38)
	(check [0x80, 0x7F, 0xFF, 0xFF] (-1.1754942106924411e-38))
	
	-- Infinity
	(check [0x7F, 0x80, 0, 0]   inf32)
	(check [0xFF, 0x80, 0, 0] (-inf32))
	
	-- NaN
	(check [0x7F, 0xC0, 0, 0]   nan32)
	(check [0xFF, 0xC0, 0, 0] (-nan32))

props_PutFloat64 :: String -> Suite
props_PutFloat64 label =
	let check = checkPut putFloat64be putFloat64le in
	suite label
	
	(check [0, 0, 0, 0, 0, 0, 0, 0]      0.0)
	(check [0x80, 0, 0, 0, 0, 0, 0, 0] (-0.0))
	
	-- Normalised
	(check [0x3F, 0xF0, 0, 0, 0, 0, 0, 0]   1.0)
	(check [0xBF, 0xF0, 0, 0, 0, 0, 0, 0] (-1.0))
	
	-- Denormalised
	(check [0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]   2.2250738585072009e-308)
	(check [0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] (-2.2250738585072009e-308))
	
	-- Infinity
	(check [0x7F, 0xF0, 0, 0, 0, 0, 0, 0]   inf64)
	(check [0xFF, 0xF0, 0, 0, 0, 0, 0, 0] (-inf64))
	
	-- NaN
	(check [0x7F, 0xF8, 0, 0, 0, 0, 0, 0]   nan64)
	(check [0xFF, 0xF8, 0, 0, 0, 0, 0, 0] (-nan64))

checkGet :: (Show a, Eq a, RealFloat a)
         => Get a -- ^ big endian
         -> Get a -- ^ little endian
         -> [Word8] -- ^ big-endian bytes
         -> (a -> Bool) -- ^ verify result
         -> Test
checkGet getBE getLE bytes f = property "get" $ forAll (return bytes) (const valid) where
	valid = B.null remainingBE && B.null remainingLE && f xBE && f xLE
	(xBE, remainingBE, _) = runGetState getBE (B.pack bytes) 0
	(xLE, remainingLE, _) = runGetState getLE (B.pack (reverse bytes)) 0

checkPut :: Show a
         => (a -> Put) -- ^ big endian
         -> (a -> Put) -- ^ little endian
         -> [Word8] -- ^ expected big-endian bytes
         -> a
         -> Test
checkPut putBE putLE bytes x = property "put" $ forAll (return x) (const valid) where
	valid = sameResult && bytes == B.unpack bytesBE
	sameResult = bytesBE == B.reverse bytesLE
	bytesBE = runPut (putBE x)
	bytesLE = runPut (putLE x)

(.&&) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
(.&&) f g x = f x && g x

isNegativeNaN :: RealFloat a => a -> Bool
isNegativeNaN x = isNaN x && frac < 0 where
	(frac, _) = decodeFloat x

-- Verify that the given put and get functions are inverses.
testPassthrough :: (Arbitrary a, Show a, Eq a)
                => String
                -> (a -> Put)
                -> Get a
                -> Test
testPassthrough name put get = property name $ \x -> let
	bytes = runPut (put x)
	(x', remaining, _) = runGetState get bytes 0
	in x == x' && B.null remaining

testPassthroughNaN :: (Arbitrary a, RealFloat a, Read a)
                    => String
                    -> (a -> Put)
                    -> Get a
                    -> Test
testPassthroughNaN name put get = property name valid where
	nan = read "NaN"
	check x = decodeFloat x == decodeFloat x' && B.null remaining where
		bytes = runPut (put x)
		(x', remaining, _) = runGetState get bytes 0
	valid = check nan && check (- nan)

-- Pseudo-literals for special values
inf32 :: Float
inf32 = read "Infinity"

inf64 :: Double
inf64 = read "Infinity"

nan32 :: Float
nan32 = - (read "NaN")

nan64 :: Double
nan64 = - (read "NaN")