/*
 * Copyright (c) 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 8301833 8302026 8301444 8302028 8302040 8302027 8304028
 * @build Tests
 * @build FdlibmTranslit
 * @build ExhaustingTests
 * @run main ExhaustingTests
 * @summary Compare StrictMath.foo and FdlibmTranslit.foo for many inputs.
 */

/*
 * Note on usage: for more exhaustive testing to help validate changes
 * to StrictMath, the DEFAULT_SHIFT setting should be set to 0. This
 * will test all float values against the unary methods. Running all
 * the float values for a single method takes on the order of a minute
 * or two. The default setting is a shift of 10, meaning every 1024th
 * float value is tested and the overall test runs within the typical
 * time expectations of a tier 1 test.
 */

import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleUnaryOperator;

public class ExhaustingTests {
    public static void main(String... args) {
        long failures = 0;

        failures += testUnaryMethods();
        failures += testBinaryMethods();

        if (failures > 0) {
            System.err.println("Comparing StrictMath and FdlibmTranslit"
                               + " incurred " + failures + " failures.");
            throw new RuntimeException();
        }
    }

    private static final int DEFAULT_SHIFT = 10;

    /**
     * Test the unary (one-argument) StrictMath methods from FDLIBM.
     */
    private static long testUnaryMethods() {
        long failures = 0;
        UnaryTestCase[] testCases = {
            // Since sqrt is correctly rounded and thus for each input
            // there is one well-defined correct result, additional
            // comparison of the transliteration sqrt or StrictMath
            // sqrt could be made against Math::sqrt.
            new UnaryTestCase("sqrt",  FdlibmTranslit::sqrt,  StrictMath::sqrt,  DEFAULT_SHIFT),
            new UnaryTestCase("cbrt",  FdlibmTranslit::cbrt,  StrictMath::cbrt,  DEFAULT_SHIFT),

            new UnaryTestCase("log",   FdlibmTranslit::log,   StrictMath::log,   DEFAULT_SHIFT),
            new UnaryTestCase("log10", FdlibmTranslit::log10, StrictMath::log10, DEFAULT_SHIFT),
            new UnaryTestCase("log1p", FdlibmTranslit::log1p, StrictMath::log1p, DEFAULT_SHIFT),

            new UnaryTestCase("exp",   FdlibmTranslit::exp,   StrictMath::exp,   DEFAULT_SHIFT),
            new UnaryTestCase("expm1", FdlibmTranslit::expm1, StrictMath::expm1, DEFAULT_SHIFT),

            new UnaryTestCase("sinh",  FdlibmTranslit::sinh,  StrictMath::sinh,  DEFAULT_SHIFT),
            new UnaryTestCase("cosh",  FdlibmTranslit::cosh,  StrictMath::cosh,  DEFAULT_SHIFT),
            new UnaryTestCase("tanh",  FdlibmTranslit::tanh,  StrictMath::tanh,  DEFAULT_SHIFT),

            new UnaryTestCase("sin",   FdlibmTranslit::sin,   StrictMath::sin,   DEFAULT_SHIFT),
            new UnaryTestCase("cos",   FdlibmTranslit::cos,   StrictMath::cos,   DEFAULT_SHIFT),
            new UnaryTestCase("tan",   FdlibmTranslit::tan,   StrictMath::tan,   DEFAULT_SHIFT),

            new UnaryTestCase("asin",  FdlibmTranslit::asin,  StrictMath::asin,  DEFAULT_SHIFT),
            new UnaryTestCase("acos",  FdlibmTranslit::acos,  StrictMath::acos,  DEFAULT_SHIFT),
            new UnaryTestCase("atan",  FdlibmTranslit::atan,  StrictMath::atan,  DEFAULT_SHIFT),
        };

        for (var testCase : testCases) {
            System.out.println("Testing " + testCase.name());
            System.out.flush();
            int i = Integer.MAX_VALUE; // overflow to Integer.MIN_VALUE at start of loop
            int increment = 1 << testCase.shiftDistance;
            do {
                i += increment;
                double input = (double)Float.intBitsToFloat(i);
                failures += Tests.test(testCase.name(),
                                       input,
                                       testCase.strictMath,
                                       testCase.translit.applyAsDouble(input));
            } while (i != Integer.MAX_VALUE);
        }
        return failures;
    }

    private static record UnaryTestCase(String name,
                                        DoubleUnaryOperator translit,
                                        DoubleUnaryOperator strictMath,
                                        int shiftDistance) {
        UnaryTestCase {
            if (shiftDistance < 0 || shiftDistance >= 31) {
                throw new IllegalArgumentException("Shift out of range");
            }
        }
    }

    /**
     * Test the binary (two-argument) StrictMath methods from FDLIBM.
     */
    private static long testBinaryMethods() {
        long failures = 0;
        // Note: pow does _not_ have a transliteration port.

        // Shift of 16 for a binary method gives comparable running
        // time to exhaustive testing of a unary method (testing every
        // 2^16 floating point values over two arguments is 2^32
        // probes).
        BinaryTestCase[] testCases = {
            new BinaryTestCase("hypot", FdlibmTranslit::hypot, StrictMath::hypot, 20, 20),
            new BinaryTestCase("atan2", FdlibmTranslit::atan2, StrictMath::atan2, 20, 20),
            new BinaryTestCase("IEEEremainder", FdlibmTranslit::IEEEremainder, StrictMath::IEEEremainder, 20, 20),
        };

        for (var testCase : testCases) {
            System.out.println("Testing " + testCase.name());
            System.out.flush();

            int iIncrement = 1 << testCase.xShift;
            int jIncrement = 1 << testCase.yShift;

            for (long i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i += iIncrement) {
                for (long j = Integer.MIN_VALUE; j <= Integer.MAX_VALUE; j += jIncrement) {
                    double input1 = (double)Float.intBitsToFloat((int)i);
                    double input2 = (double)Float.intBitsToFloat((int)j);
                    failures += Tests.test(testCase.name(),
                                           input1, input2,
                                           testCase.strictMath,
                                           testCase.translit.applyAsDouble(input1, input2));
                }
            }
        }
        return failures;
    }

    private static record BinaryTestCase(String name,
                                         DoubleBinaryOperator translit,
                                         DoubleBinaryOperator strictMath,
                                         int xShift,
                                         int yShift) {
        BinaryTestCase {
            if (xShift < 0 || xShift >= 31 ||
                yShift < 0 || yShift >= 31 ) {
                throw new IllegalArgumentException("Shift out of range");
            }
        }
    }
}