/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math3.dfp;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class DfpMathTest {

    private DfpField factory;
    private Dfp pinf;
    private Dfp ninf;
    private Dfp nan;
    private Dfp qnan;

    @Before
    public void setUp() {
        // Some basic setup.  Define some constants and clear the status flags
        factory = new DfpField(20);
        pinf = factory.newDfp("1").divide(factory.newDfp("0"));
        ninf = factory.newDfp("-1").divide(factory.newDfp("0"));
        nan = factory.newDfp("0").divide(factory.newDfp("0"));
        qnan = factory.newDfp((byte)1, Dfp.QNAN);
        ninf.getField().clearIEEEFlags();

        // force loading of dfpmath
        Dfp pi = factory.getPi();
        pi.getField().clearIEEEFlags();
    }

    @After
    public void tearDown() {
        pinf = null;
        ninf = null;
        nan  = null;
        qnan = null;
    }

    // Generic test function.  Takes params x and y and tests them for
    // equality.  Then checks the status flags against the flags argument.
    // If the test fail, it prints the desc string
    private void test(Dfp x, Dfp y, int flags, String desc)
    {
        boolean b = x.equals(y);

        if (!x.equals(y) && !x.unequal(y))  // NaNs involved
            b = (x.toString().equals(y.toString()));

        if (x.equals(factory.newDfp("0")))  // distinguish +/- zero
            b = (b && (x.toString().equals(y.toString())));

        b = (b && x.getField().getIEEEFlags() == flags);

        if (!b)
            Assert.assertTrue("assersion failed "+desc+" x = "+x.toString()+" flags = "+x.getField().getIEEEFlags(), b);

        x.getField().clearIEEEFlags();
    }

    @Test
    public void testPow()
    {
        // Test special cases  exponent of zero
        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("0")),
             factory.newDfp("1"),
             0, "pow #1");

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-0")),
             factory.newDfp("1"),
             0, "pow #2");

        test(DfpMath.pow(factory.newDfp("2"), factory.newDfp("0")),
             factory.newDfp("1"),
             0, "pow #3");

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-0")),
             factory.newDfp("1"),
             0, "pow #4");

        test(DfpMath.pow(pinf, factory.newDfp("-0")),
             factory.newDfp("1"),
             0, "pow #5");

        test(DfpMath.pow(pinf, factory.newDfp("0")),
             factory.newDfp("1"),
             0, "pow #6");

        test(DfpMath.pow(ninf, factory.newDfp("-0")),
             factory.newDfp("1"),
             0, "pow #7");

        test(DfpMath.pow(ninf, factory.newDfp("0")),
             factory.newDfp("1"),
             0, "pow #8");

        test(DfpMath.pow(qnan, factory.newDfp("0")),
             factory.newDfp("1"),
             0, "pow #8");

        // exponent of one
        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1")),
             factory.newDfp("0"),
             0, "pow #9");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1")),
             factory.newDfp("-0"),
             0, "pow #10");

        test(DfpMath.pow(factory.newDfp("2"), factory.newDfp("1")),
             factory.newDfp("2"),
             0, "pow #11");

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("1")),
             factory.newDfp("-2"),
             0, "pow #12");

        test(DfpMath.pow(pinf, factory.newDfp("1")),
             pinf,
             0, "pow #13");

        test(DfpMath.pow(ninf, factory.newDfp("1")),
             ninf,
             0, "pow #14");

        test(DfpMath.pow(qnan, factory.newDfp("1")),
             qnan,
             DfpField.FLAG_INVALID, "pow #14.1");

        // exponent of NaN
        test(DfpMath.pow(factory.newDfp("0"), qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #15");

        test(DfpMath.pow(factory.newDfp("-0"), qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #16");

        test(DfpMath.pow(factory.newDfp("2"), qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #17");

        test(DfpMath.pow(factory.newDfp("-2"), qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #18");

        test(DfpMath.pow(pinf, qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #19");

        test(DfpMath.pow(ninf, qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #20");

        test(DfpMath.pow(qnan, qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #21");

        // radix of NaN
        test(DfpMath.pow(qnan, factory.newDfp("1")),
             qnan,
             DfpField.FLAG_INVALID, "pow #22");

        test(DfpMath.pow(qnan, factory.newDfp("-1")),
             qnan,
             DfpField.FLAG_INVALID, "pow #23");

        test(DfpMath.pow(qnan, pinf),
             qnan,
             DfpField.FLAG_INVALID, "pow #24");

        test(DfpMath.pow(qnan, ninf),
             qnan,
             DfpField.FLAG_INVALID, "pow #25");

        test(DfpMath.pow(qnan, qnan),
             qnan,
             DfpField.FLAG_INVALID, "pow #26");

        // (x > 1) ^ pinf = pinf,    (x < -1) ^ pinf = pinf
        test(DfpMath.pow(factory.newDfp("2"), pinf),
             pinf,
             0, "pow #27");

        test(DfpMath.pow(factory.newDfp("-2"), pinf),
             pinf,
             0, "pow #28");

        test(DfpMath.pow(pinf, pinf),
             pinf,
             0, "pow #29");

        test(DfpMath.pow(ninf, pinf),
             pinf,
             0, "pow #30");

        // (x > 1) ^ ninf = +0,    (x < -1) ^ ninf = +0
        test(DfpMath.pow(factory.newDfp("2"), ninf),
             factory.getZero(),
             0, "pow #31");

        test(DfpMath.pow(factory.newDfp("-2"), ninf),
             factory.getZero(),
             0, "pow #32");

        test(DfpMath.pow(pinf, ninf),
             factory.getZero(),
             0, "pow #33");

        test(DfpMath.pow(ninf, ninf),
             factory.getZero(),
             0, "pow #34");

        // (-1 < x < 1) ^ pinf = 0
        test(DfpMath.pow(factory.newDfp("0.5"), pinf),
             factory.getZero(),
             0, "pow #35");

        test(DfpMath.pow(factory.newDfp("-0.5"), pinf),
             factory.getZero(),
             0, "pow #36");

        // (-1 < x < 1) ^ ninf = pinf
        test(DfpMath.pow(factory.newDfp("0.5"), ninf),
             pinf,
             0, "pow #37");

        test(DfpMath.pow(factory.newDfp("-0.5"), ninf),
             pinf,
             0, "pow #38");

        // +/- 1  ^ +/-inf  = NaN
        test(DfpMath.pow(factory.getOne(), pinf),
             qnan,
             DfpField.FLAG_INVALID, "pow #39");

        test(DfpMath.pow(factory.getOne(), ninf),
             qnan,
             DfpField.FLAG_INVALID, "pow #40");

        test(DfpMath.pow(factory.newDfp("-1"), pinf),
             qnan,
             DfpField.FLAG_INVALID, "pow #41");

        test(DfpMath.pow(factory.getOne().negate(), ninf),
             qnan,
             DfpField.FLAG_INVALID, "pow #42");

        // +0  ^ +anything except 0, NAN  = +0

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1")),
             factory.newDfp("0"),
             0, "pow #43");

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1e30")),
             factory.newDfp("0"),
             0, "pow #44");

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1e-30")),
             factory.newDfp("0"),
             0, "pow #45");

        test(DfpMath.pow(factory.newDfp("0"), pinf),
             factory.newDfp("0"),
             0, "pow #46");

        // -0  ^ +anything except 0, NAN, odd integer  = +0

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("2")),
             factory.newDfp("0"),
             0, "pow #47");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1e30")),
             factory.newDfp("0"),
             0, "pow #48");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1e-30")),
             factory.newDfp("0"),
             DfpField.FLAG_INEXACT, "pow #49");

        test(DfpMath.pow(factory.newDfp("-0"), pinf),
             factory.newDfp("0"),
             0, "pow #50");

        // +0  ^ -anything except 0, NAN  = +INF

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1")),
             pinf,
             0, "pow #51");

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1e30")),
             pinf,
             0, "pow #52");

        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1e-30")),
             pinf,
             0, "pow #53");

        test(DfpMath.pow(factory.newDfp("0"), ninf),
             pinf,
             0, "pow #54");

        // -0  ^ -anything except 0, NAN, odd integer  = +INF

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-2")),
             pinf,
             0, "pow #55");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1e30")),
             pinf,
             0, "pow #56");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1e-30")),
             pinf,
             DfpField.FLAG_INEXACT, "pow #57");

        test(DfpMath.pow(factory.newDfp("-0"), ninf),
             pinf,
             0, "pow #58");

        // -0  ^ -odd integer   =  -INF
        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1")),
             ninf,
             DfpField.FLAG_INEXACT, "pow #59");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-12345")),
             ninf,
             DfpField.FLAG_INEXACT, "pow #60");

        // -0  ^ +odd integer   =  -0
        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("3")),
             factory.newDfp("-0"),
             DfpField.FLAG_INEXACT, "pow #61");

        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("12345")),
             factory.newDfp("-0"),
             DfpField.FLAG_INEXACT, "pow #62");

        // pinf  ^ +anything   = pinf
        test(DfpMath.pow(pinf, factory.newDfp("3")),
             pinf,
             0, "pow #63");

        test(DfpMath.pow(pinf, factory.newDfp("1e30")),
             pinf,
             0, "pow #64");

        test(DfpMath.pow(pinf, factory.newDfp("1e-30")),
             pinf,
             0, "pow #65");

        test(DfpMath.pow(pinf, pinf),
             pinf,
             0, "pow #66");

        // pinf  ^ -anything   = +0

        test(DfpMath.pow(pinf, factory.newDfp("-3")),
             factory.getZero(),
             0, "pow #67");

        test(DfpMath.pow(pinf, factory.newDfp("-1e30")),
             factory.getZero(),
             0, "pow #68");

        test(DfpMath.pow(pinf, factory.newDfp("-1e-30")),
             factory.getZero(),
             0, "pow #69");

        test(DfpMath.pow(pinf, ninf),
             factory.getZero(),
             0, "pow #70");

        // ninf  ^ anything   = -0 ^ -anything
        // ninf  ^ -anything except 0, NAN, odd integer  = +0

        test(DfpMath.pow(ninf, factory.newDfp("-2")),
             factory.newDfp("0"),
             0, "pow #71");

        test(DfpMath.pow(ninf, factory.newDfp("-1e30")),
             factory.newDfp("0"),
             0, "pow #72");

        test(DfpMath.pow(ninf, factory.newDfp("-1e-30")),
             factory.newDfp("0"),
             DfpField.FLAG_INEXACT, "pow #73");

        test(DfpMath.pow(ninf, ninf),
             factory.newDfp("0"),
             0, "pow #74");

        // ninf  ^ +anything except 0, NAN, odd integer  = +INF

        test(DfpMath.pow(ninf, factory.newDfp("2")),
             pinf,
             0, "pow #75");

        test(DfpMath.pow(ninf, factory.newDfp("1e30")),
             pinf,
             0, "pow #76");

        test(DfpMath.pow(ninf, factory.newDfp("1e-30")),
             pinf,
             DfpField.FLAG_INEXACT, "pow #77");

        test(DfpMath.pow(ninf, pinf),
             pinf,
             0, "pow #78");

        // ninf  ^ +odd integer   =  -INF
        test(DfpMath.pow(ninf, factory.newDfp("3")),
             ninf,
             DfpField.FLAG_INEXACT, "pow #79");

        test(DfpMath.pow(ninf, factory.newDfp("12345")),
             ninf,
             DfpField.FLAG_INEXACT, "pow #80");

        // ninf  ^ -odd integer   =  -0
        test(DfpMath.pow(ninf, factory.newDfp("-3")),
             factory.newDfp("-0"),
             DfpField.FLAG_INEXACT, "pow #81");

        test(DfpMath.pow(ninf, factory.newDfp("-12345")),
             factory.newDfp("-0"),
             DfpField.FLAG_INEXACT, "pow #82");

        // -anything ^ integer
        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("3")),
             factory.newDfp("-8"),
             DfpField.FLAG_INEXACT, "pow #83");

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("16")),
             factory.newDfp("65536"),
             0, "pow #84");

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-3")),
             factory.newDfp("-0.125"),
             DfpField.FLAG_INEXACT, "pow #85");

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-4")),
             factory.newDfp("0.0625"),
             0, "pow #86");

        // -anything ^ noninteger = NaN

        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-4.1")),
             qnan,
             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "pow #87");

        // Some fractional cases.
        test(DfpMath.pow(factory.newDfp("2"),factory.newDfp("1.5")),
             factory.newDfp("2.8284271247461901"),
             DfpField.FLAG_INEXACT, "pow #88");
    }

    @Test
    public void testSin()
    {
        test(DfpMath.sin(pinf),
             nan,
             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "sin #1");

        test(DfpMath.sin(nan),
             nan,
             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "sin #2");

        test(DfpMath.sin(factory.getZero()),
             factory.getZero(),
             DfpField.FLAG_INEXACT, "sin #3");

        test(DfpMath.sin(factory.getPi()),
             factory.getZero(),
             DfpField.FLAG_INEXACT, "sin #4");

        test(DfpMath.sin(factory.getPi().negate()),
             factory.newDfp("-0"),
             DfpField.FLAG_INEXACT, "sin #5");

        test(DfpMath.sin(factory.getPi().multiply(2)),
             factory.getZero(),
             DfpField.FLAG_INEXACT, "sin #6");

        test(DfpMath.sin(factory.getPi().divide(2)),
             factory.getOne(),
             DfpField.FLAG_INEXACT, "sin #7");

        test(DfpMath.sin(factory.getPi().divide(2).negate()),
             factory.getOne().negate(),
             DfpField.FLAG_INEXACT, "sin #8");

        test(DfpMath.sin(DfpMath.atan(factory.getOne())),  // pi/4
             factory.newDfp("0.5").sqrt(),
             DfpField.FLAG_INEXACT, "sin #9");

        test(DfpMath.sin(DfpMath.atan(factory.getOne())).negate(),  // -pi/4
             factory.newDfp("0.5").sqrt().negate(),
             DfpField.FLAG_INEXACT, "sin #10");

        test(DfpMath.sin(DfpMath.atan(factory.getOne())).negate(),  // -pi/4
             factory.newDfp("0.5").sqrt().negate(),
             DfpField.FLAG_INEXACT, "sin #11");

        test(DfpMath.sin(factory.newDfp("0.1")),
             factory.newDfp("0.0998334166468281523"),
             DfpField.FLAG_INEXACT, "sin #12");

        test(DfpMath.sin(factory.newDfp("0.2")),
             factory.newDfp("0.19866933079506121546"),
             DfpField.FLAG_INEXACT, "sin #13");

        test(DfpMath.sin(factory.newDfp("0.3")),
             factory.newDfp("0.2955202066613395751"),
             DfpField.FLAG_INEXACT, "sin #14");

        test(DfpMath.sin(factory.newDfp("0.4")),
             factory.newDfp("0.38941834230865049166"),
             DfpField.FLAG_INEXACT, "sin #15");

        test(DfpMath.sin(factory.newDfp("0.5")),
             factory.newDfp("0.47942553860420300026"),  // off by one ULP
             DfpField.FLAG_INEXACT, "sin #16");

        test(DfpMath.sin(factory.newDfp("0.6")),
             factory.newDfp("0.56464247339503535721"),  // off by one ULP
             DfpField.FLAG_INEXACT, "sin #17");

        test(DfpMath.sin(factory.newDfp("0.7")),
             factory.newDfp("0.64421768723769105367"),
             DfpField.FLAG_INEXACT, "sin #18");

        test(DfpMath.sin(factory.newDfp("0.8")),
             factory.newDfp("0.71735609089952276163"),
             DfpField.FLAG_INEXACT, "sin #19");

        test(DfpMath.sin(factory.newDfp("0.9")),        // off by one ULP
             factory.newDfp("0.78332690962748338847"),
             DfpField.FLAG_INEXACT, "sin #20");

        test(DfpMath.sin(factory.newDfp("1.0")),
             factory.newDfp("0.84147098480789650666"),
             DfpField.FLAG_INEXACT, "sin #21");

        test(DfpMath.sin(factory.newDfp("1.1")),
             factory.newDfp("0.89120736006143533995"),
             DfpField.FLAG_INEXACT, "sin #22");

        test(DfpMath.sin(factory.newDfp("1.2")),
             factory.newDfp("0.93203908596722634968"),
             DfpField.FLAG_INEXACT, "sin #23");

        test(DfpMath.sin(factory.newDfp("1.3")),
             factory.newDfp("0.9635581854171929647"),
             DfpField.FLAG_INEXACT, "sin #24");

        test(DfpMath.sin(factory.newDfp("1.4")),
             factory.newDfp("0.98544972998846018066"),
             DfpField.FLAG_INEXACT, "sin #25");

        test(DfpMath.sin(factory.newDfp("1.5")),
             factory.newDfp("0.99749498660405443096"),
             DfpField.FLAG_INEXACT, "sin #26");

        test(DfpMath.sin(factory.newDfp("1.6")),
             factory.newDfp("0.99957360304150516323"),
             DfpField.FLAG_INEXACT, "sin #27");
    }


}
