/*
 * Copyright (c) 2015, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */

// This file is available under and governed by the GNU General Public
// License version 2 only, as published by the Free Software Foundation.
// However, the following notice accompanied the original version of this
// file:
//
// Copyright 2006-2008 the V8 project authors. All rights reserved.

package jdk.nashorn.internal.runtime.doubleconv.test;

import org.testng.annotations.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
 * IeeeDouble tests
 *
 * @test
 * @modules jdk.scripting.nashorn/jdk.nashorn.internal.runtime.doubleconv:open
 * @run testng jdk.nashorn.internal.runtime.doubleconv.test.IeeeDoubleTest
 */
@SuppressWarnings({"unchecked", "javadoc"})
public class IeeeDoubleTest {

    static final Method asDiyFp;
    static final Method asNormalizedDiyFp;
    static final Method doubleToLong;
    static final Method longToDouble;
    static final Method isDenormal;
    static final Method isSpecial;
    static final Method isInfinite;
    static final Method isNaN;
    static final Method value;
    static final Method sign;
    static final Method nextDouble;
    static final Method previousDouble;
    static final Method normalizedBoundaries;
    static final Method Infinity;
    static final Method NaN;
    static final Method f;
    static final Method e;
    static final Constructor<?> DiyFpCtor;

    static {
        try {
            final Class<?> IeeeDouble = Class.forName("jdk.nashorn.internal.runtime.doubleconv.IeeeDouble");
            final Class DiyFp = Class.forName("jdk.nashorn.internal.runtime.doubleconv.DiyFp");
            asDiyFp = method(IeeeDouble, "asDiyFp", long.class);
            asNormalizedDiyFp = method(IeeeDouble, "asNormalizedDiyFp", long.class);
            doubleToLong = method(IeeeDouble, "doubleToLong", double.class);
            longToDouble = method(IeeeDouble, "longToDouble", long.class);
            isDenormal = method(IeeeDouble, "isDenormal", long.class);
            isSpecial = method(IeeeDouble, "isSpecial", long.class);
            isInfinite = method(IeeeDouble, "isInfinite", long.class);
            isNaN = method(IeeeDouble, "isNaN", long.class);
            value = method(IeeeDouble, "value", long.class);
            sign = method(IeeeDouble, "sign", long.class);
            nextDouble = method(IeeeDouble, "nextDouble", long.class);
            previousDouble = method(IeeeDouble, "previousDouble", long.class);
            Infinity = method(IeeeDouble, "Infinity");
            NaN = method(IeeeDouble, "NaN");
            normalizedBoundaries = method(IeeeDouble, "normalizedBoundaries", long.class, DiyFp, DiyFp);
            DiyFpCtor = DiyFp.getDeclaredConstructor();
            DiyFpCtor.setAccessible(true);
            f = method(DiyFp, "f");
            e = method(DiyFp, "e");
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Method method(final Class<?> clazz, final String name, final Class<?>... params) throws NoSuchMethodException {
        final Method m = clazz.getDeclaredMethod(name, params);
        m.setAccessible(true);
        return m;
    }

    @Test
    public void testUint64Conversions() throws Exception {
        // Start by checking the byte-order.
        final long ordered = 0x0123456789ABCDEFL;
        assertEquals(3512700564088504e-318, value.invoke(null, ordered));

        final long min_double64 = 0x0000000000000001L;
        assertEquals(5e-324, value.invoke(null, min_double64));

        final long max_double64 = 0x7fefffffffffffffL;
        assertEquals(1.7976931348623157e308, value.invoke(null, max_double64));
    }


    @Test
    public void testDoubleAsDiyFp() throws Exception {
        final long ordered = 0x0123456789ABCDEFL;
        Object diy_fp = asDiyFp.invoke(null, ordered);
        assertEquals(0x12 - 0x3FF - 52, e.invoke(diy_fp));
        // The 52 mantissa bits, plus the implicit 1 in bit 52 as a UINT64.
        assertTrue(0x0013456789ABCDEFL == (long) f.invoke(diy_fp));

        final long min_double64 = 0x0000000000000001L;
        diy_fp = asDiyFp.invoke(null, min_double64);
        assertEquals(-0x3FF - 52 + 1, e.invoke(diy_fp));
        // This is a denormal; so no hidden bit.
        assertTrue(1L == (long) f.invoke(diy_fp));

        final long max_double64 = 0x7fefffffffffffffL;
        diy_fp = asDiyFp.invoke(null, max_double64);
        assertEquals(0x7FE - 0x3FF - 52, e.invoke(diy_fp));
        assertTrue(0x001fffffffffffffL == (long) f.invoke(diy_fp));
    }


    @Test
    public void testAsNormalizedDiyFp() throws Exception {
        final long ordered = 0x0123456789ABCDEFL;
        Object diy_fp = asNormalizedDiyFp.invoke(null, ordered);
        assertEquals(0x12 - 0x3FF - 52 - 11, (int) e.invoke(diy_fp));
        assertTrue((0x0013456789ABCDEFL << 11) == (long) f.invoke(diy_fp));

        final long min_double64 = 0x0000000000000001L;
        diy_fp = asNormalizedDiyFp.invoke(null, min_double64);
        assertEquals(-0x3FF - 52 + 1 - 63, e.invoke(diy_fp));
        // This is a denormal; so no hidden bit.
        assertTrue(0x8000000000000000L == (long) f.invoke(diy_fp));

        final long max_double64 = 0x7fefffffffffffffL;
        diy_fp = asNormalizedDiyFp.invoke(null, max_double64);
        assertEquals(0x7FE - 0x3FF - 52 - 11, e.invoke(diy_fp));
        assertTrue((0x001fffffffffffffL << 11) == (long) f.invoke(diy_fp));
    }


    @Test
    public void testIsDenormal() throws Exception {
        final long min_double64 = 0x0000000000000001L;
        assertTrue((boolean) isDenormal.invoke(null, min_double64));
        long bits = 0x000FFFFFFFFFFFFFL;
        assertTrue((boolean) isDenormal.invoke(null, bits));
        bits = 0x0010000000000000L;
        assertTrue(!(boolean) isDenormal.invoke(null, bits));
    }

    @Test
    public void testIsSpecial() throws Exception {
        assertTrue((boolean) isSpecial.invoke(null, doubleToLong.invoke(null, Infinity.invoke(null))));
        assertTrue((boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -(double) Infinity.invoke(null))));
        assertTrue((boolean) isSpecial.invoke(null, doubleToLong.invoke(null, NaN.invoke(null))));
        final long bits = 0xFFF1234500000000L;
        assertTrue((boolean) isSpecial.invoke(null, bits));
        // Denormals are not special:
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 5e-324)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -5e-324)));
        // And some random numbers:
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -0.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 1.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -1.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 1000000.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -1000000.0)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 1e23)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -1e23)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, 1.7976931348623157e308)));
        assertTrue(!(boolean) isSpecial.invoke(null, doubleToLong.invoke(null, -1.7976931348623157e308)));
    }

        @Test
    public void testIsInfinite() throws Exception {
        assertTrue((boolean) isInfinite.invoke(null, doubleToLong.invoke(null, Infinity.invoke(null))));
        assertTrue((boolean) isInfinite.invoke(null, doubleToLong.invoke(null, -(double) Infinity.invoke(null))));
        assertTrue(!(boolean) isInfinite.invoke(null, doubleToLong.invoke(null, NaN.invoke(null))));
        assertTrue(!(boolean) isInfinite.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertTrue(!(boolean) isInfinite.invoke(null, doubleToLong.invoke(null, -0.0)));
        assertTrue(!(boolean) isInfinite.invoke(null, doubleToLong.invoke(null, 1.0)));
        assertTrue(!(boolean) isInfinite.invoke(null, doubleToLong.invoke(null, -1.0)));
        final long min_double64 = 0x0000000000000001L;
        assertTrue(!(boolean) isInfinite.invoke(null, min_double64));
    }

        @Test
    public void testIsNan() throws Exception {
        assertTrue((boolean) isNaN.invoke(null, doubleToLong.invoke(null, NaN.invoke(null))));
        final long other_nan = 0xFFFFFFFF00000001L;
        assertTrue((boolean) isNaN.invoke(null, other_nan));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, Infinity.invoke(null))));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, -(double) Infinity.invoke(null))));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, -0.0)));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, 1.0)));
        assertTrue(!(boolean) isNaN.invoke(null, doubleToLong.invoke(null, -1.0)));
        final long min_double64 = 0x0000000000000001L;
        assertTrue(!(boolean) isNaN.invoke(null, min_double64));
    }

    @Test
    public void testSign() throws Exception {
        assertEquals(1, (int) sign.invoke(null, doubleToLong.invoke(null, 1.0)));
        assertEquals(1, (int) sign.invoke(null, doubleToLong.invoke(null, Infinity.invoke(null))));
        assertEquals(-1, (int) sign.invoke(null, doubleToLong.invoke(null, -(double) Infinity.invoke(null))));
        assertEquals(1, (int) sign.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertEquals(-1, (int) sign.invoke(null, doubleToLong.invoke(null, -0.0)));
        final long min_double64 = 0x0000000000000001L;
        assertEquals(1, (int) sign.invoke(null, min_double64));
    }

    @Test
    public void testNormalizedBoundaries() throws Exception {
        final Object boundary_plus = DiyFpCtor.newInstance();
        final Object boundary_minus = DiyFpCtor.newInstance();
        Object diy_fp = asNormalizedDiyFp.invoke(null, doubleToLong.invoke(null, 1.5));
        normalizedBoundaries.invoke(null, doubleToLong.invoke(null, 1.5), boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_plus));
        // 1.5 does not have a significand of the form 2^p (for some p).
        // Therefore its boundaries are at the same distance.
        assertTrue((long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));
        assertTrue((1 << 10) == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));

        diy_fp =asNormalizedDiyFp.invoke(null, doubleToLong.invoke(null, 1.0));
        normalizedBoundaries.invoke(null, doubleToLong.invoke(null, 1.0), boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_plus));
        // 1.0 does have a significand of the form 2^p (for some p).
        // Therefore its lower boundary is twice as close as the upper boundary.
        assertTrue((long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp) > (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));
        assertTrue((1L << 9) == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));
        assertTrue((1L << 10) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));

        final long min_double64 = 0x0000000000000001L;
        diy_fp = asNormalizedDiyFp.invoke(null, min_double64);
        normalizedBoundaries.invoke(null, min_double64, boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_plus));
        // min-value does not have a significand of the form 2^p (for some p).
        // Therefore its boundaries are at the same distance.
        assertTrue((long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));
        // Denormals have their boundaries much closer.
        assertTrue(1L << 62 == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));

        final long smallest_normal64 = 0x0010000000000000L;
        diy_fp = asNormalizedDiyFp.invoke(null, smallest_normal64);
        normalizedBoundaries.invoke(null, smallest_normal64, boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp), e.invoke(boundary_plus));
        // Even though the significand is of the form 2^p (for some p), its boundaries
        // are at the same distance. (This is the only exception).
        assertTrue((long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));
        assertTrue(1L << 10 == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));

        final long largest_denormal64 = 0x000FFFFFFFFFFFFFL;
        diy_fp = asNormalizedDiyFp.invoke(null, largest_denormal64);
        normalizedBoundaries.invoke(null, largest_denormal64, boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp),  e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp),  e.invoke(boundary_plus));
        assertTrue((long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));
        assertTrue(1L << 11 == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));

        final long max_double64 = 0x7fefffffffffffffL;
        diy_fp = asNormalizedDiyFp.invoke(null, max_double64);
        normalizedBoundaries.invoke(null, max_double64, boundary_minus, boundary_plus);
        assertEquals(e.invoke(diy_fp),  e.invoke(boundary_minus));
        assertEquals(e.invoke(diy_fp),  e.invoke(boundary_plus));
        // max-value does not have a significand of the form 2^p (for some p).
        // Therefore its boundaries are at the same distance.
        assertTrue((long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus) == (long) f.invoke(boundary_plus) - (long) f.invoke(diy_fp));
        assertTrue(1L << 10 == (long) f.invoke(diy_fp) - (long) f.invoke(boundary_minus));
    }

    @Test
    public void testNextDouble() throws Exception {
        assertEquals(4e-324, (double) nextDouble.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertEquals(0.0, (double) nextDouble.invoke(null, doubleToLong.invoke(null, -0.0)));
        assertEquals(-0.0, (double) nextDouble.invoke(null, doubleToLong.invoke(null, -4e-324)));
        assertTrue((int) sign.invoke(null, doubleToLong.invoke(null, nextDouble.invoke(null, doubleToLong.invoke(null, -0.0)))) > 0);
        assertTrue((int) sign.invoke(null, doubleToLong.invoke(null, nextDouble.invoke(null, doubleToLong.invoke(null, -4e-324)))) < 0);
        final long d0 = (long) doubleToLong.invoke(null, -4e-324);
        final long d1 = (long) doubleToLong.invoke(null, nextDouble.invoke(null, d0));
        final long d2 = (long) doubleToLong.invoke(null, nextDouble.invoke(null, d1));
        assertEquals(-0.0, value.invoke(null, d1));
        assertTrue((int) sign.invoke(null, d1) < 0);
        assertEquals(0.0, value.invoke(null, d2));
        assertTrue((int) sign.invoke(null, d2) > 0);
        assertEquals(4e-324, (double) nextDouble.invoke(null, d2));
        assertEquals(-1.7976931348623157e308, (double) nextDouble.invoke(null, doubleToLong.invoke(null, -(double) Infinity.invoke(null))));
        assertEquals(Infinity.invoke(null), (double) nextDouble.invoke(null, 0x7fefffffffffffffL));
    }

    @Test
    public void testPreviousDouble() throws Exception {
        assertEquals(0.0, (double) previousDouble.invoke(null, doubleToLong.invoke(null, 4e-324)));
        assertEquals(-0.0, (double) previousDouble.invoke(null, doubleToLong.invoke(null, 0.0)));
        assertTrue((int) sign.invoke(null, doubleToLong.invoke(null, previousDouble.invoke(null, doubleToLong.invoke(null, 0.0)))) < 0);
        assertEquals(-4e-324, previousDouble.invoke(null, doubleToLong.invoke(null, -0.0)));
        final long d0 = (long) doubleToLong.invoke(null, 4e-324);
        final long d1 = (long) doubleToLong.invoke(null, previousDouble.invoke(null, d0));
        final long d2 = (long) doubleToLong.invoke(null, previousDouble.invoke(null, d1));
        assertEquals(0.0, value.invoke(null, d1));
        assertTrue((int) sign.invoke(null, d1) > 0);
        assertEquals(-0.0, value.invoke(null, d2));
        assertTrue((int) sign.invoke(null, d2) < 0);
        assertEquals(-4e-324, (double) previousDouble.invoke(null, d2));
        assertEquals(1.7976931348623157e308, (double) previousDouble.invoke(null, doubleToLong.invoke(null, Infinity.invoke(null))));
        assertEquals(-(double) Infinity.invoke(null), (double) previousDouble.invoke(null, 0xffefffffffffffffL));
    }

}
