decimal-arithmetic-0.5.0.0: test/Numeric/Decimal/NumberSpec.hs
module Numeric.Decimal.NumberSpec (spec) where
import Arbitrary ()
import Test.Hspec
import Test.QuickCheck
import Numeric.Decimal
spec :: Spec
spec = do
describe "Read" $ do
it "reads with Just correctly (positive)" $
(read "Just 123" :: Maybe GeneralDecimal) `shouldBe` Just 123
it "reads with Just correctly (negative)" $
(read "Just (-12.0)" :: Maybe GeneralDecimal) `shouldBe` Just (-12)
describe "Ord" $ do
it "satisfies (>) invariant" $
property $ \x y ->
x > y ==> max x y == x && max y x == (x :: BasicDecimal)
it "satisfies (<) invariant" $
property $ \x y ->
x < y ==> min x y == x && min y x == (x :: BasicDecimal)
it "satisfies `max` invariant with respect to 1st arg" $
property $ \x y ->
max x y == x ==> x >= (y :: BasicDecimal)
it "satisfies `max` invariant with respect to 2nd arg" $
property $ \x y ->
max x y == y ==> y >= (x :: BasicDecimal)
it "satisfies `min` invariant with respect to 1st arg" $
property $ \x y ->
min x y == x ==> x <= (y :: BasicDecimal)
it "satisfies `min` invariant with respect to 2nd arg" $
property $ \x y ->
min x y == y ==> y <= (x :: BasicDecimal)
describe "Enum" $ do
it "enumerates `enumFromTo` precisely" $
([1.7 .. 5.7] :: [BasicDecimal]) `shouldBe` [1.7, 2.7, 3.7, 4.7, 5.7]
it "enumerates `enumFromThenTo` precisely (ascending)" $
([0, 0.1 .. 2] :: [BasicDecimal]) `shouldBe`
[ 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
it "enumerates `enumFromThenTo` precisely (descending)" $
([2, 1.9 .. 0] :: [BasicDecimal]) `shouldBe`
[ 2, 1.9, 1.8, 1.7, 1.6, 1.5, 1.4, 1.3, 1.2, 1.1,
1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]
describe "Num" $ do
it "satisfies relation between (+) and (*)" $
property $ \x ->
x + x == x * (2 :: GeneralDecimal)
it "satisfies relation between (-) and 0" $
property $ \x ->
isFinite x ==> x - x == (0 :: BasicDecimal)
it "satisfies relation between (+) and `negate`" $
property $ \x ->
isFinite x ==> x + negate x == (0 :: BasicDecimal)
it "satisfies relation between `abs` and 0" $
property $ \x ->
abs x >= (0 :: GeneralDecimal)
it "satisfies relation between `abs` and `signum`" $
property $ \x ->
abs x * signum x == (x :: GeneralDecimal)
describe "Fractional" $ do
context "RoundHalfUp" $ do
it "properly rounds (down)" $
(4.14 :: Decimal P2 RoundHalfUp) `shouldBe` 4.1
it "properly rounds (up)" $
(4.15 :: Decimal P2 RoundHalfUp) `shouldBe` 4.2
context "RoundHalfDown" $
it "properly rounds" $
(4.15 :: Decimal P2 RoundHalfDown) `shouldBe` 4.1
context "RoundHalfEven" $ do
it "properly rounds 1 (up)" $
(4.15 :: Decimal P2 RoundHalfEven) `shouldBe` 4.2
it "properly rounds 1 (down)" $
(4.25 :: Decimal P2 RoundHalfEven) `shouldBe` 4.2
it "properly rounds 2 (up)" $
(4.35 :: Decimal P2 RoundHalfEven) `shouldBe` 4.4
it "properly rounds 2 (down)" $
(4.45 :: Decimal P2 RoundHalfEven) `shouldBe` 4.4
describe "RealFrac" $ do
it "satisfies `properFraction` invariant 1" $
property $ \x ->
let (n,f) = properFraction (x :: BasicDecimal) :: (Integer, BasicDecimal)
in x == fromIntegral n + f
it "satisfies `properFraction` invariant 2" $
property $ \x ->
let (n,_) = properFraction (x :: BasicDecimal) :: (Integer, BasicDecimal)
in (x < 0 && n <= 0) || (x >= 0 && n >= 0)
it "satisfies `properFraction` invariant 3" $
property $ \x ->
let (_,f) = properFraction (x :: BasicDecimal) :: (Integer, BasicDecimal)
in (x < 0 && f <= 0) || (x >= 0 && f >= 0)
it "satisfies `properFraction` invariant 4" $
property $ \x ->
let (_,f) = properFraction (x :: BasicDecimal) :: (Integer, BasicDecimal)
in isFinite f ==> abs f < 1
describe "Floating" $ do
it "produces same `pi` as Double" $
realToFrac (pi :: ExtendedDecimal P16) `shouldBe` (pi :: Double)
it "satisfies relation between (**) and (^)" $
property $ \x y ->
y >= 0 ==> (x :: BasicDecimal) ** fromInteger y == x ^ y
it "computes `sqrt` correctly" $
property $ \x ->
isFinite x && x >= 0 ==>
sqrt (x * x) `shouldBe` (x :: ExtendedDecimal P32)
-- coefficient (sqrt (x * x) - (x :: ExtendedDecimal P16)) <= 1
describe "RealFloat" $ do
it "satisfies `decodeFloat` invariant 1" $
property $ \x ->
isFinite x ==> let b = floatRadix (x :: BasicDecimal)
(m, n) = decodeFloat x
in x == fromInteger m * fromInteger b ^^ n
it "satisfies `decodeFloat` invariant 2 (zero)" $
decodeFloat (0 :: BasicDecimal) `shouldBe` (0,0)
it "satisfies `decodeFloat` invariant 2 (negative zero)" $
decodeFloat (read "-0" :: BasicDecimal) `shouldBe` (0,0)
it "satisfies `decodeFloat` invariant 2 (nonzero)" $
property $ \x ->
isFinite x && x /= 0 ==> let b = floatRadix (x :: BasicDecimal)
(m, _) = decodeFloat x
d = floatDigits x
am = abs m
in b^(d-1) <= am && am < b^d
it "satisfies relation between `encodeFloat` and `decodeFloat`" $
property $ \x ->
not (isNegativeZero x) ==>
uncurry encodeFloat (decodeFloat x) == (x :: BasicDecimal)
it "satisfies `exponent` invariant 1 (zero)" $
exponent (0 :: BasicDecimal) `shouldBe` 0
it "satisfies `exponent` invariant 1 (nonzero)" $
property $ \x ->
isFinite x && x /= 0 ==>
exponent (x :: BasicDecimal) == snd (decodeFloat x) + floatDigits x
it "satisfies `exponent` invariant 2" $
property $ \x ->
isFinite x ==> let b = floatRadix (x :: BasicDecimal)
in x == significand x * fromInteger b ^^ exponent x
it "satisfies `significand` invariant" $
property $ \x ->
isFinite x ==> let s = significand (x :: BasicDecimal)
b = floatRadix x
in s == 0 ||
(s > -1 && s < 1 && abs s >= 1 / fromInteger b)
it "detects negative zero" $
isNegativeZero (read "-0" :: BasicDecimal) `shouldBe` True
it "does not detect normal zero as negative" $
isNegativeZero (read "+0" :: BasicDecimal) `shouldBe` False
it "does not detect nonzero numbers as negative zero" $
property $ \x ->
x /= 0 ==> isNegativeZero (x :: BasicDecimal) == False
isFinite :: (FinitePrecision p, Rounding r) => Decimal p r -> Bool
isFinite x = not (isNaN x || isInfinite x)