/*
 * Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 8304028
 * @summary Tests for {Math, StrictMath}.IEEEremainder
 */

public class IeeeRemainderTests {
    private IeeeRemainderTests(){}

    public static void main(String... args) {
        int failures = 0;

        failures += testIeeeRemainderSpecials();
        failures += testIeeeRemainderZeroResult();
        failures += testIeeeRemainderOneResult();
        failures += testIeeeRemainderRounding();

        if (failures > 0) {
            System.err.println("Testing IEEEremainder incurred "
                               + failures + " failures.");
            throw new RuntimeException();
        }
    }

    private static final double NaNd      = Double.NaN;
    private static final double MIN_VALUE = Double.MIN_VALUE;
    private static final double MIN_NORM  = Double.MIN_NORMAL;
    private static final double MAX_VALUE = Double.MAX_VALUE;
    private static final double InfinityD = Double.POSITIVE_INFINITY;

    /**
     * Special cases from the spec interspersed with test cases.
     */
    private static int testIeeeRemainderSpecials() {
        int failures = 0;

        /*
         * If either argument is NaN, or the first argument is
         * infinite, or the second argument is positive zero or
         * negative zero, then the result is NaN.
         *
         */
        for(double nan : Tests.NaNs) {
            failures += testIEEEremainderCase(nan, 1.0, NaNd);
            failures += testIEEEremainderCase(1.0, nan, NaNd);
        }


        double [][] nanResultCases = {
            { InfinityD, InfinityD},
            {-InfinityD, InfinityD},

            { InfinityD, 1.0},
            {-InfinityD, 1.0},

            { InfinityD, NaNd},
            {-InfinityD, NaNd},

            { InfinityD,  0.0},
            {-InfinityD,  0.0},
            { InfinityD, -0.0},
            {-InfinityD, -0.0},
        };

        for(double[] testCase : nanResultCases) {
            failures += testIEEEremainderCase(testCase[0], testCase[1], NaNd);
        }

        /*
         * If the first argument is finite and the second argument is
         * infinite, then the result is the same as the first
         * argument.
         *
         */
        double [] specialCases = {
            +0.0,
            +MIN_VALUE,
            +MIN_NORM,
            +MAX_VALUE,

            -0.0,
            -MIN_VALUE,
            -MIN_NORM,
            -MAX_VALUE,
        };

        double [] infinities = {
            +InfinityD,
            -InfinityD
        };

        for (double specialCase : specialCases) {
            for (double infinity: infinities) {
                failures += testIEEEremainderCase(specialCase, infinity, specialCase);
            }
        }

        return failures;
    }

    private static int testIeeeRemainderZeroResult() {
        int failures = 0;

        double [] testCases = {
            +MIN_VALUE,
            +MIN_NORM,
            +MAX_VALUE*0.5,

            -MIN_VALUE,
            -MIN_NORM,
            -MAX_VALUE*0.5,
        };

        for (double testCase : testCases) {
            /*
             * "If the remainder is zero, its sign is the same as the sign of the first argument."
             */
            failures += testIEEEremainderCase(testCase*2.0, +testCase, Math.copySign(0.0, testCase));
            failures += testIEEEremainderCase(testCase*2.0, -testCase, Math.copySign(0.0, testCase));
        }

        return failures;
    }

    /*
     * Construct test cases where the remainder is one.
     */
    private static int testIeeeRemainderOneResult() {
        int failures = 0;

        double [][] testCases = {
            {4.0,                  3.0},

            {10_001.0,             5000.0},

            {15_001.0,             5000.0},

            {10_000.0,             9999.0},

            {0x1.0p52 + 1.0,       0x1.0p52},

            {0x1.fffffffffffffp52, 0x1.ffffffffffffep52},
        };

        for (var testCase : testCases) {
            failures += testIEEEremainderCase(testCase[0], testCase[1], 1.0);
        }

        return failures;
    }

    /*
     * Test cases that differ in rounding between % and IEEEremainder.
     */
    private static int testIeeeRemainderRounding() {
        int failures = 0;

        double [][] testCases = {
            {3.0,                2.0, -1.0},
            {3.0,               -2.0, -1.0},
        };

        for (var testCase : testCases) {
            failures += testIEEEremainderCase(testCase[0], testCase[1], testCase[2]);
        }

        return failures;
    }

    /*
     * For exact cases, built-in % remainder and IEEE remainder should
     * be the same since the rounding mode in the implicit divide
     * doesn't come into play.
     */
    private static double remainder(double a, double b) {
        return a % b;
    }

    private static int testIEEEremainderCase(double input1, double input2, double expected) {
        int failures = 0;
        failures += Tests.test("StrictMath.IEEEremainder", input1, input2, StrictMath::IEEEremainder, expected);
        failures += Tests.test("Math.IEEEremainder",       input1, input2, Math::IEEEremainder,       expected);

        return failures;
    }
}