fptest-0.2.1.0: src/QTrial.hs
{-|
Module : Main
Description : Floating point benchmark described in /IEEE Standard 754 for Binary Floating-Point Arithmetic/ by Prof. W. Kahan
Copyright : (c) John Pavel, 2014
Prof W Kahan, 1997
License : BSD3
Maintainer : jrp@dial.pipex.com
Stability : experimental
Portability : portable
Adapted for Haskell from QTrial, described in /IEEE Standard 754 for Binary Floating-Point Arithmetic/
by
Prof. W. Kahan,
Elect. Eng. & Computer Science,
University of California,
Berkeley CA 94720-1776
<http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF>
This module runs the tests in Float, Double and CFloat and CDouble formats.
It would not be hard to adapt it to run other floating point formats.
-}
module Main where
import Data.Either
import Test.QuickCheck
import Foreign.C.Types
import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck2
{- | 'qdrtc' computes the roots 'x1' and 'x2' of
quadradic of the form @p*x^2 - 2 q*x + r == 0@
as accuractely as they are determined by 'p', 'q', and 'r' -}
qdrtc :: RealFloat a => a -> a -> a -> (a, a)
qdrtc p q r
| ss == 0 = (r / p, r / p)
| otherwise = (r / ss, ss / p)
where
s = sqrt (q * q - p * r) -- NaN if sqrt (< 0)
ss = q + copysign s q
-- Specialisations pf 'qdrtc' for different floating point formats
floatQdrtc :: Float -> Float -> Float -> (Float, Float)
floatQdrtc = qdrtc
doubleQdrtc :: Double -> Double -> Double -> (Double, Double)
doubleQdrtc = qdrtc
cfloatQdrtc :: CFloat -> CFloat -> CFloat -> (CFloat, CFloat)
cfloatQdrtc = qdrtc
cdoubleQdrtc :: CDouble -> CDouble -> CDouble -> (CDouble, CDouble)
cdoubleQdrtc = qdrtc
{- | @copysign(x, y)@ returns 'x' with the sign of 'y'.
Hence, @abs(x) = copysign( x, 1.0)@, even if 'x' is 'NaN'
NB: Haskell's 'signum' returns @signum NaN -> -1.0@ in
GHC 7.8.3, but should be fixed in 7.10 -}
copysign :: RealFloat a => a -> a -> a
copysign x y
| isNaN y = 0 / 0
| signum x == signum y = x
| otherwise = -x
-- | 'log' base 2
log2 :: RealFloat a => a -> a
log2 x = logBase 2 (abs x)
{- | As Haskell's 'min' is not symmetric in its arguments in the
presence of a NaN, define a specific 'min'' -}
min' :: RealFloat a => a -> a -> a
min' x y
| isNaN x || isNaN y = 0 / 0
| otherwise = min x y
{- | 'qtrial' calculates the smaller root of @p*x^2 - 2 q*x + r == 0@, for
a given 'r', which determines 'p' and 'q' -}
qtrial :: (RealFloat a, Show a) => a -> Either String (a, String)
qtrial r
| p <= 0 = Left "qtrail expects r > 2"
| not ((r - q) == 1.0 && (q - p) == 1.0) = Left $ "r =" ++
show r ++ " is too big for qtrail"
| x1 < 1.0 = Left $ "r = " ++ show r ++ " fails: root " ++ show x1 ++
" isn't at least 1"
| otherwise = return (min' e1 e2, -- min' NaN NaN = NaN
"r = " ++ show r ++ " produces " ++ show e1 ++
" and " ++ show e2 ++ " sig. bits" )
where
p = r - 2.0
q = r - 1.0
(x1, x2) = qdrtc p q r
e1 = - log2 (x1 - 1.0) -- Could be NaN
e2 = - log2 ((x2 - 1.0) - 2.0 / p)
{- | 'qtestraw' runs the 'qtrial' against a series of different values of 'r'.
The later values will be too big for 32-bit arithmetic.
The result is either a 'String' reporting an error,
or a '(result, comment)' tuple -}
qtestraw :: (RealFloat a, Show a) => [Either String (a, String)]
qtestraw = map qtrial r
where
r =
[2 ^ 12 + 2.0, -- for 24 sig. bits,
2 ^ 12 + 2.25, -- and 6 hex.
16 ^ 3 + 1.0 + 1.0 / 16 ^ 2, -- 6 hex. IBM.
2 ^ 24 + 2.0, -- 48 bits CRAY -
2 ^ 24 + 2.25, -- rounded;
2 ^ 24 + 3.0, -- 48 bits chopped.
94906267.0, -- 53 sig. bits.
94906267 + 0.25, -- 53 sig. bits.
2 ^ 28 - 5.5, -- PowerPC, i860.
2 ^ 28 - 4.5, -- PowerPC, i860.
2 ^ 28 + 2.0, -- 56 sig. bits,
2 ^ 28 + 2.25, -- and 14 hex.
16 ^ 7 + 1.0 + 1.0 / 16 ^ 6, -- 14 hex. IBM.
2 ^ 32 + 2.0, -- 64 sig. bits.
2 ^ 32 + 2.25] -- 64 sig. bits.
{- | 'qtest' generates a more readable rendering of the raw results of
produced by 'qtrial' -}
qtest :: (RealFloat a, Show a) => [Either String (a, String)] -> String
qtest raw = unlines (map (either id snd) raw) ++
"Worst accuracy is " ++
show (minimum (map fst (rights raw))) ++ " sig. bits"
-- Specialisations of 'qtestraw' for different floating point formats
floatQTest :: [Either String (Float, String)]
floatQTest = qtestraw
doubleQTest :: [Either String (Double, String)]
doubleQTest = qtestraw
cfloatQTest :: [Either String (CFloat, String)]
cfloatQTest = qtestraw
cdoubleQTest :: [Either String (CDouble, String)]
cdoubleQTest = qtestraw
{- 'QuickCheck' testing
Illustrates numerical instability, as need to ignore NaNs to pass -}
{- | '~==' is used for approximate equality, ignoring isNaN.
TODO:: overload for tuples, reduce tolerace to 1e-15 for doubles -}
infix 4 ~==
(~==) :: RealFloat a => a -> a -> Bool
x ~== y
| isNaN x || isNaN y = True -- would otherwise generate false
| otherwise = abs (x - y) < 1.0e-6 * abs x
-- The following test the invariant properties of 'qdrtc' given in the paper
test1 f p = p /= 0 ==> f p 0 0 == (0, 0)
prop_test1f = test1 floatQdrtc
prop_test1d = test1 doubleQdrtc
prop_test1cf = test1 cfloatQdrtc
prop_test1cd = test1 cdoubleQdrtc
test2 f = f 2.0 5.0 12.0 == (2, 3)
prop_test2f = test2 floatQdrtc
prop_test2d = test2 doubleQdrtc
prop_test2cf = test2 cfloatQdrtc
prop_test2cd = test2 cdoubleQdrtc
test3 f = f 2.0e-37 1.0 2.0 == (1.0, 1.0e37)
prop_test3f = test3 floatQdrtc
prop_test3d = test3 doubleQdrtc
prop_test3cf = test3 cfloatQdrtc
prop_test3cd = test3 cdoubleQdrtc
test4 f p q r =
p /= 0 ==> x1 ~== 1.0 / x2' && x2 ~== 1.0 / x1'
where
(x1, x2) = f p q r
(x1', x2') = f r q p
prop_test4f = test4 floatQdrtc
prop_test4d = test4 doubleQdrtc
prop_test4cf = test4 cfloatQdrtc
prop_test4cd = test4 cdoubleQdrtc
-- | TODO:: This test sometimes fails
test5 f mu p q r = (mu * p) /= 0 ==> x1 ~== x1' && x2 ~== x2'
where
(x1, x2) = f p q r
(x1', x2') = f (mu * p) (mu * q) (mu * r)
prop_test5f = test5 floatQdrtc
prop_test5d = test5 doubleQdrtc
prop_test5cf = test5 cfloatQdrtc
prop_test5cd = test5 cdoubleQdrtc
{- (Orphan) instances, as they are defined neither in Foreign.C.Types,
not in Test.QuickCheck -}
instance Arbitrary CFloat where
arbitrary = arbitrarySizedFractional
shrink = shrinkRealFrac
instance Arbitrary CDouble where
arbitrary = arbitrarySizedFractional
shrink = shrinkRealFrac
-- Gather the QuickCheck unit tests
-- | 'unittests' checks that the implemention of 'qdrtc' satisfies
-- certain basic properties
unittests = [
testGroup "test1" [
testProperty "Float" prop_test1f,
testProperty "Double" prop_test1d,
testProperty "CFloat" prop_test1cf,
testProperty "CDouble" prop_test1cd
],
testGroup "test2" [
testProperty "Float" prop_test2f,
testProperty "Double" prop_test2d,
testProperty "CFloat" prop_test2cf,
testProperty "CDouble" prop_test2cd
],
testGroup "test3" [
testProperty "Float" prop_test3f,
testProperty "Double" prop_test3d,
testProperty "CFloat" prop_test3cf,
testProperty "CDouble" prop_test3cd
],
testGroup "test4" [
testProperty "Float" prop_test4f,
testProperty "Double" prop_test4d,
testProperty "CFloat" prop_test4cf,
testProperty "CDouble" prop_test4cd
],
testGroup "test5" [
testProperty "Float" prop_test5f,
testProperty "Double" prop_test5d,
testProperty "CFloat" prop_test5cf,
testProperty "CDouble" prop_test5cd
]
]
-- | run the qtrial tests, followed by checking the properties of 'qdrtc'
main :: IO ()
main = do
putStrLn "Principal Tests:"
putStrLn "\nResults for Float:"
putStrLn $ qtest floatQTest
putStrLn "\nResults for CFloat:"
putStrLn $ qtest cfloatQTest
putStrLn "\nResults for Double:"
putStrLn $ qtest doubleQTest
putStrLn "\nResults for CDouble:"
putStrLn $ qtest cdoubleQTest
putStrLn "\n\nUnit Tests:"
defaultMain unittests