packages feed

decimal-arithmetic-0.5.0.0: test/Numeric/Decimal/OperationSpec.hs

module Numeric.Decimal.OperationSpec (spec) where

import Test.Hspec

import Numeric.Decimal
import Numeric.Decimal.Arithmetic

import qualified Numeric.Decimal.Operation as Op

spec :: Spec
spec = do
  describe "Special values" $ do
    it "Infinity + 1 = Infinity" $
      op2 Op.add "Infinity" "1" `shouldBe` "Infinity"
    it "NaN + 1 = NaN" $
      op2 Op.add "NaN" "1" `shouldBe` "NaN"
    it "NaN + Infinity = NaN" $
      op2 Op.add "NaN" "Infinity" `shouldBe` "NaN"
    it "1 - Infinity = -Infinity" $
      op2 Op.subtract "1" "Infinity" `shouldBe` "-Infinity"
    it "-1 * Infinity = -Infinity" $
      op2 Op.multiply "-1" "Infinity" `shouldBe` "-Infinity"
    it "-0 - 0 = -0" $
      op2 Op.subtract "-0" "0" `shouldBe` "-0"
    it "-1 * 0 = -0" $
      op2 Op.multiply "-1" "0" `shouldBe` "-0"
    it "1 / 0 = Infinity" $
      op2 Op.divide "1" "0" `shouldBe` "Infinity"
    it "1 / -0 = -Infinity" $
      op2 Op.divide "1" "-0" `shouldBe` "-Infinity"
    it "-1 / 0 = -Infinity" $
      op2 Op.divide "-1" "0" `shouldBe` "-Infinity"

  describe "add" $ do
    it "add('12', '7.00')        ==>  '19.00'" $
      op2 Op.add "12" "7.00" `shouldBe` "19.00"
    it "add('1E+2', '1E+4')      ==>  '1.01E+4'" $
      op2 Op.add "1E+2" "1E+4" `shouldBe` "1.01E+4"

  describe "subtract" $ do
    it "subtract('1.3', '1.07')  ==>  '0.23'" $
      op2 Op.subtract "1.3" "1.07" `shouldBe` "0.23"
    it "subtract('1.3', '1.30')  ==>  '0.00'" $
      op2 Op.subtract "1.3" "1.30" `shouldBe` "0.00"
    it "subtract('1.3', '2.07')  ==>  '-0.77'" $
      op2 Op.subtract "1.3" "2.07" `shouldBe` "-0.77"

  describe "minus" $ do
    it "minus('1.3')   ==>  '-1.3'" $
      op1 Op.minus "1.3" `shouldBe` "-1.3"
    it "minus('-1.3')  ==>  '1.3'" $
      op1 Op.minus "-1.3" `shouldBe` "1.3"

  describe "plus" $ do
    it "plus('1.3')    ==>  '1.3'" $
      op1 Op.plus "1.3" `shouldBe` "1.3"
    it "plus('-1.3')   ==>  '-1.3'" $
      op1 Op.plus "-1.3" `shouldBe` "-1.3"

  describe "multiply" $ do
    it "multiply('1.20', '3')         ==>  '3.60'" $
      op2 Op.multiply "1.20" "3" `shouldBe` "3.60"
    it "multiply('7', '3')            ==>  '21'" $
      op2 Op.multiply "7" "3" `shouldBe` "21"
    it "multiply('0.9', '0.8')        ==> '0.72'" $
      op2 Op.multiply "0.9" "0.8" `shouldBe` "0.72"
    it "multiply('0.9', '-0')         ==> '-0.0'" $
      op2 Op.multiply "0.9" "-0" `shouldBe` "-0.0"
    it "multiply('654321', '654321')  ==>  '4.28135971E+11'" $
      op2 Op.multiply "654321" "654321" `shouldBe` "4.28135971E+11"

  describe "exp" $ do
    it "exp('-Infinity')    ==> '0'" $
      op1 Op.exp "-Infinity" `shouldBe` "0"
    it "exp('-1')           ==> '0.367879441'" $
      op1 Op.exp "-1" `shouldBe` "0.367879441"
    it "exp('0')            ==> '1'" $
      op1 Op.exp "0" `shouldBe` "1"
    it "exp('1')            ==> '2.71828183'" $
      op1 Op.exp "1" `shouldBe` "2.71828183"
    it "exp('0.693147181')  ==> '2.00000000'" $
      op1 Op.exp "0.693147181" `shouldBe` "2.00000000"
    it "exp('+Infinity')    ==> 'Infinity'" $
      op1 Op.exp "+Infinity" `shouldBe` "Infinity"

  describe "fusedMultiplyAdd" $ do
    it ("fused-multiply-add('3', '5', '7')                             ==>  " ++
        "'22'") $
      op3 Op.fusedMultiplyAdd "3" "5" "7" `shouldBe` "22"
    it ("fused-multiply-add('3', '-5', '7')                            ==>  " ++
        "'-8'") $
      op3 Op.fusedMultiplyAdd "3" "-5" "7" `shouldBe` "-8"
    it ("fused-multiply-add('888565290', '1557.96930', '-86087.7578')  ==>  " ++
        "'1.38435736E+12'") $
      op3 Op.fusedMultiplyAdd "888565290" "1557.96930" "-86087.7578" `shouldBe`
      "1.38435736E+12"

  describe "ln" $ do
    it "ln('0')           ==> '-Infinity'" $
      op1 Op.ln "0" `shouldBe` "-Infinity"
    it "ln('1.000')       ==> '0'" $
      op1 Op.ln "1.000" `shouldBe` "0"
    it "ln('2.71828183')  ==> '1.00000000'" $
      op1 Op.ln "2.71828183" `shouldBe` "1.00000000"
    it "ln('10')          ==> '2.30258509'" $
      op1 Op.ln "10" `shouldBe` "2.30258509"
    it "ln('+Infinity')   ==> 'Infinity'" $
      op1 Op.ln "+Infinity" `shouldBe` "Infinity"

  describe "log10" $ do
    it "log10('0')          ==>  '-Infinity'" $
      op1 Op.log10 "0" `shouldBe` "-Infinity"
    it "log10('0.001')      ==>  '-3'" $
      op1 Op.log10 "0.001" `shouldBe` "-3"
    it "log10('1.000')      ==>  '0'" $
      op1 Op.log10 "1.000" `shouldBe` "0"
    it "log10('2')          ==>  '0.301029996'" $
      op1 Op.log10 "2" `shouldBe` "0.301029996"
    it "log10('10')         ==>  '1'" $
      op1 Op.log10 "10" `shouldBe` "1"
    it "log10('70')         ==>  '1.84509804'" $
      op1 Op.log10 "70" `shouldBe` "1.84509804"
    it "log10('+Infinity')  ==>  'Infinity'" $
      op1 Op.log10 "+Infinity" `shouldBe` "Infinity"

  describe "divide" $ do
    it "divide('1', '3'  )      ==>  '0.333333333'" $
      op2 Op.divide "1" "3" `shouldBe` "0.333333333"
    it "divide('2', '3'  )      ==>  '0.666666667'" $
      op2 Op.divide "2" "3" `shouldBe` "0.666666667"
    it "divide('5', '2'  )      ==>  '2.5'" $
      op2 Op.divide "5" "2" `shouldBe` "2.5"
    it "divide('1', '10' )      ==>  '0.1'" $
      op2 Op.divide "1" "10" `shouldBe` "0.1"
    it "divide('12', '12')      ==>  '1'" $
      op2 Op.divide "12" "12" `shouldBe` "1"
    it "divide('8.00', '2')     ==>  '4.00'" $
      op2 Op.divide "8.00" "2" `shouldBe` "4.00"
    it "divide('2.400', '2.0')  ==>  '1.20'" $
      op2 Op.divide "2.400" "2.0" `shouldBe` "1.20"
    it "divide('1000', '100')   ==>  '10'" $
      op2 Op.divide "1000" "100" `shouldBe` "10"
    it "divide('1000', '1')     ==>  '1000'" $
      op2 Op.divide "1000" "1" `shouldBe` "1000"
    it "divide('2.40E+6', '2')  ==>  '1.20E+6'" $
      op2 Op.divide "2.40E+6" "2" `shouldBe` "1.20E+6"

  describe "abs" $ do
    it "abs('2.1')    ==>  '2.1'" $
      op1 Op.abs "2.1" `shouldBe` "2.1"
    it "abs('-100')   ==>  '100'" $
      op1 Op.abs "-100" `shouldBe` "100"
    it "abs('101.5')  ==>  '101.5'" $
      op1 Op.abs "101.5" `shouldBe` "101.5"
    it "abs('-101.5') ==>  '101.5'" $
      op1 Op.abs "-101.5" `shouldBe` "101.5"

  describe "compare" $ do
    it "compare('2.1', '3')     ==>  '-1'" $
      op2 compare' "2.1" "3" `shouldBe` "-1"
    it "compare('2.1', '2.1')   ==>  '0'" $
      op2 compare' "2.1" "2.1" `shouldBe` "0"
    it "compare('2.1', '2.10')  ==>  '0'" $
      op2 compare' "2.1" "2.10" `shouldBe` "0"
    it "compare('3', '2.1')     ==>  '1'" $
      op2 compare' "3" "2.1" `shouldBe` "1"
    it "compare('2.1', '-3')    ==>  '1'" $
      op2 compare' "2.1" "-3" `shouldBe` "1"
    it "compare('-3', '2.1')    ==>  '-1'" $
      op2 compare' "-3" "2.1" `shouldBe` "-1"

  describe "max" $ do
    it "max('3', '2')    ==>  '3'" $
      op2 Op.max "3" "2" `shouldBe` "3"
    it "max('-10', '3')  ==>  '3'" $
      op2 Op.max "-10" "3" `shouldBe` "3"
    it "max('1.0', '1')  ==>  '1'" $
      op2 Op.max "1.0" "1" `shouldBe` "1"
    it "max('7', 'NaN')  ==>  '7'" $
      op2 Op.max "7" "NaN" `shouldBe` "7"

  describe "min" $ do
    it "min('3', '2')    ==>  '2'" $
      op2 Op.min "3" "2" `shouldBe` "2"
    it "min('-10', '3')  ==>  '-10'" $
      op2 Op.min "-10" "3" `shouldBe` "-10"
    it "min('1.0', '1')  ==>  '1.0'" $
      op2 Op.min "1.0" "1" `shouldBe` "1.0"
    it "min('7', 'NaN')  ==>  '7'" $
      op2 Op.min "7" "NaN" `shouldBe` "7"

  describe "power" $ do
    it "power('2', '3')             ==>  '8'" $
      op2 Op.power "2" "3" `shouldBe` "8"
    it "power('-2', '3')            ==>  '-8'" $
      op2 Op.power "-2" "3" `shouldBe` "-8"
    it "power('2', '-3')            ==>  '0.125'" $
      op2 Op.power "2" "-3" `shouldBe` "0.125"
    it "power('1.7', '8')           ==>  '69.7575744'" $
      op2 Op.power "1.7" "8" `shouldBe` "69.7575744"
    it "power('10', '0.301029996')  ==>  '2.00000000'" $
      op2 Op.power "10" "0.301029996" `shouldBe` "2.00000000"
    it "power('Infinity', '-1')     ==>  '0'" $
      op2 Op.power "Infinity" "-1" `shouldBe` "0"
    it "power('Infinity', '0')      ==>  '1'" $
      op2 Op.power "Infinity" "0" `shouldBe` "1"
    it "power('Infinity', '1')      ==>  'Infinity'" $
      op2 Op.power "Infinity" "1" `shouldBe` "Infinity"
    it "power('-Infinity', '-1')    ==>  '-0'" $
      op2 Op.power "-Infinity" "-1" `shouldBe` "-0"
    it "power('-Infinity', '0')     ==>  '1'" $
      op2 Op.power "-Infinity" "0" `shouldBe` "1"
    it "power('-Infinity', '1')     ==>  '-Infinity'" $
      op2 Op.power "-Infinity" "1" `shouldBe` "-Infinity"
    it "power('-Infinity', '2')     ==>  'Infinity'" $
      op2 Op.power "-Infinity" "2" `shouldBe` "Infinity"
    it "power('0', '0')             ==>  'NaN'" $
      op2 Op.power "0" "0" `shouldBe` "NaN"

  describe "quantize" $ do
    it "quantize('2.17', '0.001')        ==>  '2.170'" $
      op2 Op.quantize "2.17" "0.001" `shouldBe` "2.170"
    it "quantize('2.17', '0.01')         ==>  '2.17'" $
      op2 Op.quantize "2.17" "0.01" `shouldBe` "2.17"
    it "quantize('2.17', '0.1')          ==>  '2.2'" $
      op2 Op.quantize "2.17" "0.1" `shouldBe` "2.2"
    it "quantize('2.17', '1e+0')         ==>  '2'" $
      op2 Op.quantize "2.17" "1e+0" `shouldBe` "2"
    it "quantize('2.17', '1e+1')         ==>  '0E+1'" $
      op2 Op.quantize "2.17" "1e+1" `shouldBe` "0E+1"
    it "quantize('-Inf'  'Infinity')     ==>  '-Infinity'" $
      op2 Op.quantize "-Inf" "Infinity" `shouldBe` "-Infinity"
    it "quantize('2',    'Infinity')     ==>  'NaN'" $
      op2 Op.quantize "2" "Infinity" `shouldBe` "NaN"
    it "quantize('-0.1', '1'  )          ==>  '-0'" $
      op2 Op.quantize "-0.1" "1" `shouldBe` "-0"
    it "quantize('-0',   '1e+5')         ==>  '-0E+5'" $
      op2 Op.quantize "-0" "1e+5" `shouldBe` "-0E+5"
    it "quantize('+35236450.6', '1e-2')  ==>  'NaN'" $
      op2 Op.quantize "+35236450.6" "1e-2" `shouldBe` "NaN"
    it "quantize('-35236450.6', '1e-2')  ==>  'NaN'" $
      op2 Op.quantize "-35236450.6" "1e-2" `shouldBe` "NaN"
    it "quantize('217',  '1e-1')         ==>  '217.0'" $
      op2 Op.quantize "217" "1e-1" `shouldBe` "217.0"
    it "quantize('217',  '1e+0')         ==>  '217'" $
      op2 Op.quantize "217" "1e+0" `shouldBe` "217"
    it "quantize('217',  '1e+1')         ==>  '2.2E+2'" $
      op2 Op.quantize "217" "1e+1" `shouldBe` "2.2E+2"
    it "quantize('217',  '1e+2')         ==>  '2E+2'" $
      op2 Op.quantize "217" "1e+2" `shouldBe` "2E+2"

  describe "reduce" $ do
    it "reduce('2.1')     ==>  '2.1'" $
      op1 Op.reduce "2.1" `shouldBe` "2.1"
    it "reduce('-2.0')    ==>  '-2'" $
      op1 Op.reduce "-2.0" `shouldBe` "-2"
    it "reduce('1.200')   ==>  '1.2'" $
      op1 Op.reduce "1.200" `shouldBe` "1.2"
    it "reduce('-120')    ==>  '-1.2E+2'" $
      op1 Op.reduce "-120" `shouldBe` "-1.2E+2"
    it "reduce('120.00')  ==>  '1.2E+2'" $
      op1 Op.reduce "120.00" `shouldBe` "1.2E+2"
    it "reduce('0.00')    ==>  '0'" $
      op1 Op.reduce "0.00" `shouldBe` "0"

  describe "roundToIntegralExact" $ do
    it "round-to-integral-exact('2.1')      ==>  '2'" $
      op1 Op.roundToIntegralExact "2.1" `shouldBe` "2"
    it "round-to-integral-exact('100')      ==>  '100'" $
      op1 Op.roundToIntegralExact "100" `shouldBe` "100"
    it "round-to-integral-exact('100.0')    ==>  '100'" $
      op1 Op.roundToIntegralExact "100.0" `shouldBe` "100"
    it "round-to-integral-exact('101.5')    ==>  '102'" $
      op1 Op.roundToIntegralExact "101.5" `shouldBe` "102"
    it "round-to-integral-exact('-101.5')   ==>  '-102'" $
      op1 Op.roundToIntegralExact "-101.5" `shouldBe` "-102"
    it "round-to-integral-exact('10E+5')    ==>  '1.0E+6'" $
      op1 Op.roundToIntegralExact "10E+5" `shouldBe` "1.0E+6"
    it "round-to-integral-exact('7.89E+77') ==>  '7.89E+77'" $
      op1 Op.roundToIntegralExact "7.89E+77" `shouldBe` "7.89E+77"
    it "round-to-integral-exact('-Inf')     ==>  '-Infinity'" $
      op1 Op.roundToIntegralExact "-Inf" `shouldBe` "-Infinity"

  describe "squareRoot" $ do
    it "square-root('0')     ==> '0'" $
      op1 Op.squareRoot "0" `shouldBe` "0"
    it "square-root('-0')    ==> '-0'" $
      op1 Op.squareRoot "-0" `shouldBe` "-0"
    -- The following example is a corrected version of that found in the
    -- specification; confirmed with Mike Cowlishaw on 2016-08-02.
    it "square-root('0.39')  ==> '0.624499800'" $
      op1 Op.squareRoot "0.39" `shouldBe` "0.624499800"
    it "square-root('100')   ==> '10'" $
      op1 Op.squareRoot "100" `shouldBe` "10"
    it "square-root('1')     ==> '1'" $
      op1 Op.squareRoot "1" `shouldBe` "1"
    it "square-root('1.0')   ==> '1.0'" $
      op1 Op.squareRoot "1.0" `shouldBe` "1.0"
    it "square-root('1.00')  ==> '1.0'" $
      op1 Op.squareRoot "1.00" `shouldBe` "1.0"
    it "square-root('7')     ==> '2.64575131'" $
      op1 Op.squareRoot "7" `shouldBe` "2.64575131"
    it "square-root('10')    ==> '3.16227766'" $
      op1 Op.squareRoot "10" `shouldBe` "3.16227766"

  describe "and" $ do
    it "and('0', '0')        ==>  '0'" $
      op2 Op.and "0" "0" `shouldBe` "0"
    it "and('0', '1')        ==>  '0'" $
      op2 Op.and "0" "1" `shouldBe` "0"
    it "and('1', '0')        ==>  '0'" $
      op2 Op.and "1" "0" `shouldBe` "0"
    it "and('1', '1')        ==>  '1'" $
      op2 Op.and "1" "1" `shouldBe` "1"
    it "and('1100', '1010')  ==>  '1000'" $
      op2 Op.and "1100" "1010" `shouldBe` "1000"
    it "and('1111', '10')    ==>  '10'" $
      op2 Op.and "1111" "10" `shouldBe` "10"

  describe "or" $ do
    it "or('0', '0')        ==>  '0'" $
      op2 Op.or "0" "0" `shouldBe` "0"
    it "or('0', '1')        ==>  '1'" $
      op2 Op.or "0" "1" `shouldBe` "1"
    it "or('1', '0')        ==>  '1'" $
      op2 Op.or "1" "0" `shouldBe` "1"
    it "or('1', '1')        ==>  '1'" $
      op2 Op.or "1" "1" `shouldBe` "1"
    it "or('1100', '1010')  ==>  '1110'" $
      op2 Op.or "1100" "1010" `shouldBe` "1110"
    it "or('1110', '10')    ==>  '1110'" $
      op2 Op.or "1110" "10" `shouldBe` "1110"

  describe "xor" $ do
    it "xor('0', '0')        ==>  '0'" $
      op2 Op.xor "0" "0" `shouldBe` "0"
    it "xor('0', '1')        ==>  '1'" $
      op2 Op.xor "0" "1" `shouldBe` "1"
    it "xor('1', '0')        ==>  '1'" $
      op2 Op.xor "1" "0" `shouldBe` "1"
    it "xor('1', '1')        ==>  '0'" $
      op2 Op.xor "1" "1" `shouldBe` "0"
    it "xor('1100', '1010')  ==>  '110'" $
      op2 Op.xor "1100" "1010" `shouldBe` "110"
    it "xor('1111', '10')    ==>  '1101'" $
      op2 Op.xor "1111" "10" `shouldBe` "1101"

  describe "invert" $ do
    it "invert('0')          ==>  '111111111'" $
      op1 Op.invert "0" `shouldBe` "111111111"
    it "invert('1')          ==>  '111111110'" $
      op1 Op.invert "1" `shouldBe` "111111110"
    it "invert('111111111')  ==>  '0'" $
      op1 Op.invert "111111111" `shouldBe` "0"
    it "invert('101010101')  ==>  '10101010'" $
      op1 Op.invert "101010101" `shouldBe` "10101010"

  describe "canonical" $
    it "canonical('2.50')  ==>  '2.50'" $
      op1 Op.canonical "2.50" `shouldBe` "2.50"

  describe "class'" $ do
    it "class('Infinity')   ==>  \"+Infinity\"" $
      op1 Op.class' "Infinity" `shouldBe` "+Infinity"
    it "class('1E-10')      ==>  \"+Normal\"" $
      op1 Op.class' "1E-10" `shouldBe` "+Normal"
    it "class('2.50')       ==>  \"+Normal\"" $
      op1 Op.class' "2.50" `shouldBe` "+Normal"
    it "class('0.1E-999')   ==>  \"+Subnormal\"" $
      op1 Op.class' "0.1E-999" `shouldBe` "+Subnormal"
    it "class('0')          ==>  \"+Zero\"" $
      op1 Op.class' "0" `shouldBe` "+Zero"
    it "class('-0')         ==>  \"-Zero\"" $
      op1 Op.class' "-0" `shouldBe` "-Zero"
    it "class('-0.1E-999')  ==>  \"-Subnormal\"" $
      op1 Op.class' "-0.1E-999" `shouldBe` "-Subnormal"
    it "class('-1E-10')     ==>  \"-Normal\"" $
      op1 Op.class' "-1E-10" `shouldBe` "-Normal"
    it "class('-2.50')      ==>  \"-Normal\"" $
      op1 Op.class' "-2.50" `shouldBe` "-Normal"
    it "class('-Infinity')  ==>  \"-Infinity\"" $
      op1 Op.class' "-Infinity" `shouldBe` "-Infinity"
    it "class('NaN')        ==>  \"NaN\"" $
      op1 Op.class' "NaN" `shouldBe` "NaN"
    it "class('-NaN')       ==>  \"NaN\"" $
      op1 Op.class' "-NaN" `shouldBe` "NaN"
    it "class('sNaN')       ==>  \"sNaN\"" $
      op1 Op.class' "sNaN" `shouldBe` "sNaN"

  describe "compareTotal" $ do
    it "compare-total('12.73', '127.9')   ==>  '-1'" $
      op2 compareTotal' "12.73" "127.9" `shouldBe` "-1"
    it "compare-total('-127',  '12')      ==>  '-1'" $
      op2 compareTotal' "-127" "12" `shouldBe` "-1"
    it "compare-total('12.30', '12.3')    ==>  '-1'" $
      op2 compareTotal' "12.30" "12.3" `shouldBe` "-1"
    it "compare-total('12.30', '12.30')   ==>  '0'" $
      op2 compareTotal' "12.30" "12.30" `shouldBe` "0"
    it "compare-total('12.3',  '12.300')  ==>  '1'" $
      op2 compareTotal' "12.3" "12.300" `shouldBe` "1"
    it "compare-total('12.3',  'NaN')     ==>  '-1'" $
      op2 compareTotal' "12.3" "NaN" `shouldBe` "-1"

  describe "copy" $ do
    it "copy('2.1')    ==>  '2.1'" $
      op1 Op.copy "2.1" `shouldBe` "2.1"
    it "copy('-1.00')  ==>  '-1.00'" $
      op1 Op.copy "-1.00" `shouldBe` "-1.00"

  describe "copyAbs" $ do
    it "copy-abs('2.1')   ==>  '2.1'" $
      op1 Op.copyAbs "2.1" `shouldBe` "2.1"
    it "copy-abs('-100')  ==>  '100'" $
      op1 Op.copyAbs "-100" `shouldBe` "100"

  describe "copyNegate" $ do
    it "copy-negate('101.5')   ==>  '-101.5'" $
      op1 Op.copyNegate "101.5" `shouldBe` "-101.5"
    it "copy-negate('-101.5')  ==>  '101.5'" $
      op1 Op.copyNegate "-101.5" `shouldBe` "101.5"

  describe "copySign" $ do
    it "copy-sign( '1.50',  '7.33')  ==>  '1.50'" $
      op2 Op.copySign  "1.50"  "7.33" `shouldBe` "1.50"
    it "copy-sign('-1.50',  '7.33')  ==>  '1.50'" $
      op2 Op.copySign "-1.50"  "7.33" `shouldBe` "1.50"
    it "copy-sign( '1.50', '-7.33')  ==>  '-1.50'" $
      op2 Op.copySign  "1.50" "-7.33" `shouldBe` "-1.50"
    it "copy-sign('-1.50', '-7.33')  ==>  '-1.50'" $
      op2 Op.copySign "-1.50" "-7.33" `shouldBe` "-1.50"

  describe "isCanonical" $
    it "is-canonical('2.50')  ==>  '1'" $
      pred1 Op.isCanonical "2.50" `shouldBe` "1"

  describe "isFinite" $ do
    it "is-finite('2.50')  ==>  '1'" $
      pred1 Op.isFinite "2.50" `shouldBe` "1"
    it "is-finite('-0.3')  ==>  '1'" $
      pred1 Op.isFinite "-0.3" `shouldBe` "1"
    it "is-finite('0')     ==>  '1'" $
      pred1 Op.isFinite "0" `shouldBe` "1"
    it "is-finite('Inf')   ==>  '0'" $
      pred1 Op.isFinite "Inf" `shouldBe` "0"
    it "is-finite('NaN')   ==>  '0'" $
      pred1 Op.isFinite "NaN" `shouldBe` "0"

  describe "isInfinite" $ do
    it "is-infinite('2.50')  ==>  '0'" $
      pred1 Op.isInfinite "2.50" `shouldBe` "0"
    it "is-infinite('-Inf')  ==>  '1'" $
      pred1 Op.isInfinite "-Inf" `shouldBe` "1"
    it "is-infinite('NaN')   ==>  '0'" $
      pred1 Op.isInfinite "NaN" `shouldBe` "0"

  describe "isNaN" $ do
    it "is-NaN('2.50')   ==>  '0'" $
      pred1 Op.isNaN "2.50" `shouldBe` "0"
    it "is-NaN('NaN')    ==>  '1'" $
      pred1 Op.isNaN "NaN" `shouldBe` "1"
    it "is-NaN('-sNaN')  ==>  '1'" $
      pred1 Op.isNaN "-sNaN" `shouldBe` "1"

  describe "isNormal" $ do
    it "is-normal('2.50')      ==>  '1'" $
      pred1 Op.isNormal "2.50" `shouldBe` "1"
    it "is-normal('0.1E-999')  ==>  '0'" $
      pred1 Op.isNormal "0.1E-999" `shouldBe` "0"
    it "is-normal('0.00')      ==>  '0'" $
      pred1 Op.isNormal "0.00" `shouldBe` "0"
    it "is-normal('-Inf')      ==>  '0'" $
      pred1 Op.isNormal "-Inf" `shouldBe` "0"
    it "is-normal('NaN')       ==>  '0'" $
      pred1 Op.isNormal "NaN" `shouldBe` "0"

  describe "isQNaN" $ do
    it "is-qNaN('2.50')  ==>  '0'" $
      pred1 Op.isQNaN "2.50" `shouldBe` "0"
    it "is-qNaN('NaN')   ==>  '1'" $
      pred1 Op.isQNaN "NaN" `shouldBe` "1"
    it "is-qNaN('sNaN')  ==>  '0'" $
      pred1 Op.isQNaN "sNaN" `shouldBe` "0"

  describe "isSigned" $ do
    it "is-signed('2.50')  ==>  '0'" $
      pred1 Op.isSigned "2.50" `shouldBe` "0"
    it "is-signed('-12')   ==>  '1'" $
      pred1 Op.isSigned "-12" `shouldBe` "1"
    it "is-signed('-0')    ==>  '1'" $
      pred1 Op.isSigned "-0" `shouldBe` "1"

  describe "isSNaN" $ do
    it "is-sNaN('2.50')  ==>  '0'" $
      pred1 Op.isSNaN "2.50" `shouldBe` "0"
    it "is-sNaN('NaN')   ==>  '0'" $
      pred1 Op.isSNaN "NaN" `shouldBe` "0"
    it "is-sNaN('sNaN')  ==>  '1'" $
      pred1 Op.isSNaN "sNaN" `shouldBe` "1"

  describe "isSubnormal" $ do
    it "is-subnormal('2.50')      ==>  '0'" $
      pred1 Op.isSubnormal "2.50" `shouldBe` "0"
    it "is-subnormal('0.1E-999')  ==>  '1'" $
      pred1 Op.isSubnormal "0.1E-999" `shouldBe` "1"
    it "is-subnormal('0.00')      ==>  '0'" $
      pred1 Op.isSubnormal "0.00" `shouldBe` "0"
    it "is-subnormal('-Inf')      ==>  '0'" $
      pred1 Op.isSubnormal "-Inf" `shouldBe` "0"
    it "is-subnormal('NaN')       ==>  '0'" $
      pred1 Op.isSubnormal "NaN" `shouldBe` "0"

  describe "isZero" $ do
    it "is-zero('0')      ==>  '1'" $
      pred1 Op.isZero "0" `shouldBe` "1"
    it "is-zero('2.50')   ==>  '0'" $
      pred1 Op.isZero "2.50" `shouldBe` "0"
    it "is-zero('-0E+2')  ==>  '1'" $
      pred1 Op.isZero "-0E+2" `shouldBe` "1"

  describe "logb" $ do
    it "logb('250')   ==>  '2'" $
      op1 Op.logb "250" `shouldBe` "2"
    it "logb('2.50')  ==>  '0'" $
      op1 Op.logb "2.50" `shouldBe` "0"
    it "logb('0.03')  ==>  '-2'" $
      op1 Op.logb "0.03" `shouldBe` "-2"
    it "logb('0')     ==>  '-Infinity'" $
      op1 Op.logb "0" `shouldBe` "-Infinity"

  describe "scaleb" $ do
    it "scaleb('7.50', '-2')  ==>  '0.0750'" $
      op2 Op.scaleb "7.50" "-2" `shouldBe` "0.0750"
    it "scaleb('7.50', '0')   ==>  '7.50'" $
      op2 Op.scaleb "7.50" "0" `shouldBe` "7.50"
    it "scaleb('7.50', '3')   ==>  '7.50E+3'" $
      op2 Op.scaleb "7.50" "3" `shouldBe` "7.50E+3"

  describe "radix" $
    it "radix()  ==>  '10'" $
      op0 Op.radix `shouldBe` "10"

  describe "sameQuantum" $ do
    it "samequantum('2.17', '0.001')  ==>  '0'" $
      pred2 Op.sameQuantum "2.17" "0.001" `shouldBe` "0"
    it "samequantum('2.17', '0.01')   ==>  '1'" $
      pred2 Op.sameQuantum "2.17" "0.01" `shouldBe` "1"
    it "samequantum('2.17', '0.1')    ==>  '0'" $
      pred2 Op.sameQuantum "2.17" "0.1" `shouldBe` "0"
    it "samequantum('2.17', '1')      ==>  '0'" $
      pred2 Op.sameQuantum "2.17" "1" `shouldBe` "0"
    it "samequantum('Inf', '-Inf')    ==>  '1'" $
      pred2 Op.sameQuantum "Inf" "-Inf" `shouldBe` "1"
    it "samequantum('NaN', 'NaN')     ==>  '1'" $
      pred2 Op.sameQuantum "NaN" "NaN" `shouldBe` "1"

  describe "shift" $ do
    it "shift('34', '8')          ==>  '400000000'" $
      op2 Op.shift "34" "8" `shouldBe` "400000000"
    it "shift('12', '9')          ==>  '0'" $
      op2 Op.shift "12" "9" `shouldBe` "0"
    it "shift('123456789', '-2')  ==>  '1234567'" $
      op2 Op.shift "123456789" "-2" `shouldBe` "1234567"
    it "shift('123456789', '0')   ==>  '123456789'" $
      op2 Op.shift "123456789" "0" `shouldBe` "123456789"
    it "shift('123456789', '+2')  ==>  '345678900'" $
      op2 Op.shift "123456789" "+2" `shouldBe` "345678900"

  describe "rotate" $ do
    it "rotate('34', '8')          ==>  '400000003'" $
      op2 Op.rotate "34" "8" `shouldBe` "400000003"
    it "rotate('12', '9')          ==>  '12'" $
      op2 Op.rotate "12" "9" `shouldBe` "12"
    it "rotate('123456789', '-2')  ==>  '891234567'" $
      op2 Op.rotate "123456789" "-2" `shouldBe` "891234567"
    it "rotate('123456789', '0')   ==>  '123456789'" $
      op2 Op.rotate "123456789" "0" `shouldBe` "123456789"
    it "rotate('123456789', '+2')  ==>  '345678912'" $
      op2 Op.rotate "123456789" "+2" `shouldBe` "345678912"

exceptionError :: Exception p r -> a
exceptionError = error . show . exceptionSignal

type BasicArith = Arith P9 RoundHalfUp

pred1 :: (BasicDecimal -> BasicArith Bool) -> String -> String
pred1 op x = either exceptionError (show . fromBool) $
  evalArith arith newContext
  where arith = op (read x)

pred2 :: (BasicDecimal -> BasicDecimal -> BasicArith Bool) -> String -> String
      -> String
pred2 op x y = either exceptionError (show . fromBool) $
  evalArith arith newContext
  where arith = op (read x) (read y)

op0 :: Show a => BasicArith a -> String
op0 op = either exceptionError show $ evalArith op newContext

op1 :: Show a => (BasicDecimal -> BasicArith a) -> String -> String
op1 op x = either exceptionError show $ evalArith arith newContext
  where arith = op (read x)

op2 :: Show a => (BasicDecimal -> BasicDecimal -> BasicArith a) -> String
    -> String -> String
op2 op x y = either exceptionError show $ evalArith arith newContext
  where arith = read x `op` read y

op3 :: Show a => (BasicDecimal -> BasicDecimal -> BasicDecimal -> BasicArith a)
    -> String -> String -> String -> String
op3 op x y z = either exceptionError show $ evalArith arith newContext
  where arith = op (read x) (read y) (read z)

compare' :: Decimal a b -> Decimal c d -> Arith p r (Decimal p r)
compare' x y = either id fromOrdering <$> Op.compare x y

compareTotal' :: Decimal a b -> Decimal c d -> Arith p r (Decimal p r)
compareTotal' x y = fromOrdering <$> Op.compareTotal x y