# Licensed to the .NET Foundation under one or more agreements.
# The .NET Foundation licenses this file to you under the Apache 2.0 License.
# See the LICENSE file in the project root for more information.

import sys
import itertools
import math
import unittest

from iptest import IronPythonTestCase, is_cli, big, run_test, skipUnlessIronPython

if is_cli:
    from System import Int64, Byte, Int16

class MathTest(IronPythonTestCase):

    def test_nonnumeric_multiply(self):
        self.assertTrue("pypypypypy" == 5 * "py")
        self.assertTrue("pypypypypy" == "py" * 5)
        self.assertTrue("pypypypy" == 2 * "py" * 2)

        self.assertTrue(['py', 'py', 'py'] == ['py'] * 3)
        self.assertTrue(['py', 'py', 'py'] == 3 * ['py'])

        self.assertTrue(['py', 'py', 'py', 'py', 'py', 'py', 'py', 'py', 'py'] == 3 * ['py'] * 3)

    def test_misc(self):
        self.assertTrue(3782452410 > 0)

    def test_complex(self):
        # Complex tests
        self.assertTrue((2+4j)/(1+1j) == (3+1j))
        self.assertTrue((2+10j)/4.0 == (0.5+2.5j))
        self.assertEqual(1j ** 2, (-1.0+0j))
        self.assertEqual(pow(1j, 2), (-1.0+0j))
        self.assertEqual(1+0j, 1)
        self.assertEqual(1+0j, 1.0)
        self.assertEqual(1+0j, big(1))
        self.assertEqual((1+1j)/big(1), (1+1j))
        self.assertEqual((1j) + big(1), (1+1j))
        self.assertEqual(0j ** 0, 1)
        self.assertEqual(0j ** 0j, 1)
        self.assertEqual(pow(0j, 0), 1)
        self.assertEqual(pow(0j, 0j), 1)

        if is_cli: self.assertEqual((1j) + Int64(), 1j)

        self.assertRaises(TypeError, (lambda:(1+1j)+[]))
        self.assertRaises(TypeError, lambda : type(2j).__dict__['real'].__set__, 2j, 0)

    def test_floor_divide(self):
        self.assertRaises(TypeError, lambda: 12j // 5j)
        self.assertRaises(TypeError, lambda: (12.0+0j) // (5.0+0j))
        self.assertRaises(TypeError, lambda: (12+0j) // (0+3j))
        self.assertRaises(TypeError, lambda: (0+12j) // (3+0j))
        self.assertRaises(TypeError, lambda: 2j // "astring")
        self.assertRaises(TypeError, lambda: 3.0 // (0j))
        self.assertRaises(TypeError, lambda: 3.0 // (2j))
        self.assertRaises(TypeError, lambda: 12 // (3j))
        self.assertRaises(TypeError, lambda: 25 % (5+3j))

    def test_more_complex(self):
        self.assertEqual((12+3j)/big(3), (4+1j))
        self.assertEqual(3j - big(5), -5+3j)
        if is_cli: self.assertEqual(3j - Int64(), 3j)
        self.assertRaises(TypeError, (lambda:3j-[]))
        if is_cli: self.assertEqual(pow(5j, Int64()), (1+0j))
        self.assertEqual(pow(5j, big(0)), (1+0j))
        self.assertRaises(TypeError, (lambda:pow(5j, [])))
        if is_cli: self.assertEqual(5j * Int64(), 0)
        self.assertEqual(5j * big(3), 15j)
        self.assertRaises(TypeError, (lambda:(5j*[])))

    def test_erf(self):
        table = [
            (0.0,  0.0000000),
            (0.05, 0.0563720),
            (0.1,  0.1124629),
            (0.15, 0.1679960),
            (0.2,  0.2227026),
            (0.25, 0.2763264),
            (0.3,  0.3286268),
            (0.35, 0.3793821),
            (0.4,  0.4283924),
            (0.45, 0.4754817),
            (0.5,  0.5204999),
            (0.55, 0.5633234),
            (0.6,  0.6038561),
            (0.65, 0.6420293),
            (0.7,  0.6778012),
            (0.75, 0.7111556),
            (0.8,  0.7421010),
            (0.85, 0.7706681),
            (0.9,  0.7969082),
            (0.95, 0.8208908),
            (1.0,  0.8427008),
            (1.1,  0.8802051),
            (1.2,  0.9103140),
            (1.3,  0.9340079),
            (1.4,  0.9522851),
            (1.5,  0.9661051),
            (1.6,  0.9763484),
            (1.7,  0.9837905),
            (1.8,  0.9890905),
            (1.9,  0.9927904),
            (2.0,  0.9953223),
            (2.1,  0.9970205),
            (2.2,  0.9981372),
            (2.3,  0.9988568),
            (2.4,  0.9993115),
            (2.5,  0.9995930),
            (2.6,  0.9997640),
            (2.7,  0.9998657),
            (2.8,  0.9999250),
            (2.9,  0.9999589),
            (3.0,  0.9999779),
            (3.1,  0.9999884),
            (3.2,  0.9999940),
            (3.3,  0.9999969),
            (3.4,  0.9999985),
            (3.5,  0.9999993),
            (4.0,  1.0000000),
        ]

        for x, y in table:
            self.assertAlmostEqual(y, math.erf(x), places=7)
            self.assertAlmostEqual(-y, math.erf(-x), places=7)

    def test_erfc(self):
        table = [
            (0.0,  1.0000000),
            (0.05, 0.9436280),
            (0.1,  0.8875371),
            (0.15, 0.8320040),
            (0.2,  0.7772974),
            (0.25, 0.7236736),
            (0.3,  0.6713732),
            (0.35, 0.6206179),
            (0.4,  0.5716076),
            (0.45, 0.5245183),
            (0.5,  0.4795001),
            (0.55, 0.4366766),
            (0.6,  0.3961439),
            (0.65, 0.3579707),
            (0.7,  0.3221988),
            (0.75, 0.2888444),
            (0.8,  0.2578990),
            (0.85, 0.2293319),
            (0.9,  0.2030918),
            (0.95, 0.1791092),
            (1.0,  0.1572992),
            (1.1,  0.1197949),
            (1.2,  0.0896860),
            (1.3,  0.0659921),
            (1.4,  0.0477149),
            (1.5,  0.0338949),
            (1.6,  0.0236516),
            (1.7,  0.0162095),
            (1.8,  0.0109095),
            (1.9,  0.0072096),
            (2.0,  0.0046777),
            (2.1,  0.0029795),
            (2.2,  0.0018628),
            (2.3,  0.0011432),
            (2.4,  0.0006885),
            (2.5,  0.0004070),
            (2.6,  0.0002360),
            (2.7,  0.0001343),
            (2.8,  0.0000750),
            (2.9,  0.0000411),
            (3.0,  0.0000221),
            (3.1,  0.0000116),
            (3.2,  0.0000060),
            (3.3,  0.0000031),
            (3.4,  0.0000015),
            (3.5,  0.0000007),
            (4.0,  0.0000000),
        ]

        for x, y in table:
            self.assertAlmostEqual(y, math.erfc(x), places=7)
            self.assertAlmostEqual(2.0 - y, math.erfc(-x), places=7)

    def test_erf_erfc(self):
        tolerance = 15
        for x in itertools.count(0.0, 0.001):
            if x > 5.0:
                break
            self.assertAlmostEqual(math.erf(x), -math.erf(-x), places=tolerance)
            self.assertAlmostEqual(math.erfc(x), 2.0 - math.erfc(-x), places=tolerance)

            self.assertAlmostEqual(1.0 - math.erf(x), math.erfc(x), places=tolerance)
            self.assertAlmostEqual(1.0 - math.erf(-x), math.erfc(-x), places=tolerance)
            self.assertAlmostEqual(1.0 - math.erfc(x), math.erf(x), places=tolerance)
            self.assertAlmostEqual(1.0 - math.erfc(-x), math.erf(-x), places=tolerance)

    def test_gamma(self):
        self.assertAlmostEqual(math.gamma(0.5), math.sqrt(math.pi), places=15)
        for i in range(1, 20):
            self.assertEqual(math.factorial(i-1), math.gamma(i))
        self.assertEqual(math.gamma(float('inf')), float('inf'))
        self.assertRaises(ValueError, math.gamma, float('-inf'))
        self.assertTrue(math.isnan(math.gamma(float('nan'))))
        for i in range(0, -1001, -1):
            self.assertRaises(ValueError, math.gamma, i)

    def test_lgamma(self):
        tolerance = 14
        self.assertAlmostEqual(math.lgamma(0.5), 0.5 * math.log(math.pi), places=15)
        for i in range(1, 20):
            if i > 14:
                tolerance = 13
            self.assertAlmostEqual(math.log(math.factorial(i-1)), math.lgamma(i), places=tolerance)
        self.assertEqual(math.lgamma(float('inf')), float('inf'))
        self.assertEqual(math.lgamma(float('-inf')), float('inf'))
        self.assertTrue(math.isnan(math.lgamma(float('nan'))))
        for i in range(0, -1001, -1):
            self.assertRaises(ValueError, math.lgamma, i)

    def test_gamma_lgamma(self):
        tolerance = 13
        for x in itertools.count(0.001, 0.001):
            if x > 5.0:
                break
            self.assertAlmostEqual(math.lgamma(x), math.log(math.gamma(x)), places=tolerance)
            self.assertAlmostEqual(math.lgamma(x*x), math.log(math.gamma(x*x)), places=tolerance)
            self.assertAlmostEqual(math.lgamma(2.0**x), math.log(math.gamma(2.0**x)), places=tolerance)

            # Test negative values too, but not integers
            if x % 1.0 != 0.0:
                self.assertAlmostEqual(math.lgamma(-x), math.log(abs(math.gamma(-x))), places=tolerance)
                self.assertAlmostEqual(math.lgamma(-x*x), math.log(abs(math.gamma(-x*x))), places=tolerance)
                self.assertAlmostEqual(math.lgamma(-2.0**x), math.log(abs(math.gamma(-2.0**x))), places=tolerance)

    def test_pow(self):
        self.assertEqual(pow(2, 1000000000, 2147483647 + 10), 511677409)
        self.assertEqual(pow(2, 2147483647*2147483647, 2147483647*2147483647), 297528129805479806)
        nums = [3, 2.3, (2+1j), (2-1j), 1j, (-1j), 1]
        for x in nums:
            for y in nums:
                z = x ** y

    def test_mod_pow(self):
        for i in range(-100, 100, 7):
            l = big(i)
            self.assertTrue(type(i) == int)
            self.assertTrue(type(l) == int)
            for exp in [1, 17, 2863, 234857, 1435435, 234636554, 2147483647]:
                lexp = big(exp)
                self.assertTrue(type(exp) == int)
                self.assertTrue(type(lexp) == int)
                for mod in [-7, -5293, -2147483647, 7, 5293, 23745, 232474276, 534634665, 2147483647]:
                    lmod = big(mod)
                    self.assertTrue(type(mod) == int)
                    self.assertTrue(type(lmod) == int)

                    ir = pow(i, exp, mod)
                    lr = pow(l, lexp, lmod)

                    self.assertEqual(ir, lr)

                    for zero in [0, big(0)]:
                        ir = pow(i, zero, mod)
                        lr = pow(l, zero, lmod)

                        if mod > 0:
                            self.assertEqual(ir, 1)
                            self.assertEqual(lr, 1)
                        else:
                            self.assertEqual(ir, mod+1)
                            self.assertEqual(lr, mod+1)
                self.assertRaises(ValueError, pow, i, exp, 0)
                self.assertRaises(ValueError, pow, l, lexp, big(0))


            for exp in [0, big(0)]:
                for mod in [-1,1,-big(1),big(1)]:
                    ir = pow(i, exp, mod)
                    lr = pow(l, exp, mod)
                    self.assertEqual(ir, 0)
                    self.assertEqual(lr, 0)

    def test_user_ops(self):
        class powtest:
            def __pow__(self, exp, mod = None):
                return ("powtest.__pow__", exp, mod)
            def __rpow__(self, exp):
                return ("powtest.__rpow__", exp)

        self.assertEqual(pow(powtest(), 1, 2), ("powtest.__pow__", 1, 2))
        self.assertEqual(pow(powtest(), 3), ("powtest.__pow__", 3, None))
        self.assertEqual(powtest() ** 4, ("powtest.__pow__", 4, None))
        self.assertEqual(5 ** powtest(), ("powtest.__rpow__", 5))
        self.assertEqual(pow(7, powtest()), ("powtest.__rpow__", 7))
        self.assertRaises(TypeError, pow, 1, powtest(), 7)

        # Extensible Float tests
        class XFloat(float): pass

        self.assertEqual(XFloat(3.14), 3.14)
        self.assertTrue(XFloat(3.14) < 4.0)
        self.assertTrue(XFloat(3.14) > 3.0)
        self.assertTrue(XFloat(3.14) < XFloat(4.0))
        self.assertTrue(XFloat(3.14) > XFloat(3.0))

        self.assertTrue(0xabcdef01 + (0xabcdef01<<32)+(0xabcdef01<<64) == 0xabcdef01abcdef01abcdef01)

    def assertEqualAndCheckType(self, actual, expected, expectedType):
        self.assertEqual(actual, expected)
        self.assertIsInstance(actual, expectedType, msg="Type: {0}".format(type(actual)))

    def test_rounding(self):
        self.assertEqual(round(-5.5489), -6)
        self.assertEqual(round(5.5519), 6)
        self.assertEqual(round(-5.5), -6)
        self.assertEqual(round(-5.0), -5)

        self.assertEqual(round(-4.5), -4)
        self.assertEqual(round(-2.5), -2)
        self.assertEqual(round(-0.5), -0)
        self.assertEqual(round(0.5), 0)
        self.assertEqual(round(2.5), 2)
        self.assertEqual(round(4.5), 4)

        self.assertEqual(round(-4.0), -4)
        self.assertEqual(round(-3.5), -4)
        self.assertEqual(round(-3.0), -3)
        self.assertEqual(round(-2.0), -2)
        self.assertEqual(round(-1.5), -2)
        self.assertEqual(round(-1.0), -1)
        self.assertEqual(round(0.0), 0)
        self.assertEqual(round(1.0), 1)
        self.assertEqual(round(1.5), 2)
        self.assertEqual(round(2.0), 2)
        self.assertEqual(round(3.0), 3)
        self.assertEqual(round(3.5), 4)
        self.assertEqual(round(4.0), 4)
        self.assertEqual(round(5.0), 5)

        # two parameter round overload
        self.assertEqual(round(-4.0, 0), -4.0)
        self.assertEqual(round(-3.5, 0), -4.0)
        self.assertEqual(round(-3.0, 0), -3.0)
        self.assertEqual(round(-2.0, 0), -2.0)
        self.assertEqual(round(-1.5, 0), -2.0)
        self.assertEqual(round(-1.0, 0), -1.0)
        self.assertEqual(round(0.0, 0), 0.0)
        self.assertEqual(round(1.0, 0), 1.0)
        self.assertEqual(round(1.5, 0), 2.0)
        self.assertEqual(round(2.0, 0), 2.0)
        self.assertEqual(round(3.0, 0), 3.0)
        self.assertEqual(round(3.5, 0), 4.0)
        self.assertEqual(round(4.0, 0), 4.0)
        self.assertEqual(round(5.0, 0), 5.0)
        self.assertEqual(round(123.41526375, 1), 123.4)
        self.assertEqual(round(123.41526375, 2), 123.42)
        self.assertEqual(round(123.41526375, 3), 123.415)
        self.assertEqual(round(123.41526375, 4), 123.4153)
        self.assertEqual(round(123.41526375, 5), 123.41526)
        self.assertEqual(round(123.41526375, 6), 123.415264)
        if is_cli: # https://github.com/IronLanguages/main/issues/856
            self.assertEqual(round(123.41526375, 7), 123.4152638)
        else:
            self.assertEqual(round(123.41526375, 7), 123.4152637)
        self.assertEqual(round(-123.41526375, 1), -123.4)
        self.assertEqual(round(-123.41526375, 2), -123.42)
        self.assertEqual(round(-123.41526375, 3), -123.415)
        self.assertEqual(round(-123.41526375, 4), -123.4153)
        self.assertEqual(round(-123.41526375, 5), -123.41526)
        self.assertEqual(round(-123.41526375, 6), -123.415264)
        if is_cli: # https://github.com/IronLanguages/main/issues/856
            self.assertEqual(round(-123.41526375, 7), -123.4152638)
        else:
            self.assertEqual(round(-123.41526375, 7), -123.4152637)
        for i in range(8, 307):
            if is_cli and i >= 17: # https://github.com/IronLanguages/ironpython2/issues/517
                self.assertAlmostEqual(round(123.41526375, i), 123.41526375, 10)
                self.assertAlmostEqual(round(-123.41526375, i), -123.41526375, 10)
            else:
                self.assertEqual(round(123.41526375, i), 123.41526375)
                self.assertEqual(round(-123.41526375, i), -123.41526375)

        self.assertEqual(round(7182930456.0, -1), 7182930460.0)
        self.assertEqual(round(7182930456.0, -2), 7182930500.0)
        self.assertEqual(round(7182930456.0, -3), 7182930000.0)
        self.assertEqual(round(7182930456.0, -4), 7182930000.0)
        self.assertEqual(round(7182930456.0, -5), 7182900000.0)
        self.assertEqual(round(7182930456.0, -6), 7183000000.0)
        self.assertEqual(round(7182930456.0, -7), 7180000000.0)
        self.assertEqual(round(7182930456.0, -8), 7200000000.0)
        self.assertEqual(round(7182930456.0, -9), 7000000000.0)
        self.assertEqual(round(7182930456.0, -10), 10000000000.0)
        self.assertEqual(round(7182930456.0, -11), 0.0)
        self.assertEqual(round(-7182930456.0, -1), -7182930460.0)
        self.assertEqual(round(-7182930456.0, -2), -7182930500.0)
        self.assertEqual(round(-7182930456.0, -3), -7182930000.0)
        self.assertEqual(round(-7182930456.0, -4), -7182930000.0)
        self.assertEqual(round(-7182930456.0, -5), -7182900000.0)
        self.assertEqual(round(-7182930456.0, -6), -7183000000.0)
        self.assertEqual(round(-7182930456.0, -7), -7180000000.0)
        self.assertEqual(round(-7182930456.0, -8), -7200000000.0)
        self.assertEqual(round(-7182930456.0, -9), -7000000000.0)
        self.assertEqual(round(-7182930456.0, -10), -10000000000.0)
        self.assertEqual(round(-7182930456.0, -11), 0.0)
        for i in range(-12, -309, -1):
            self.assertEqual(round(7182930456.0, i), 0.0)
            self.assertEqual(round(-7182930456.0, i), 0.0)

        self.assertEqualAndCheckType((2.4).__round__(), 2, int)
        self.assertEqualAndCheckType((2.5).__round__(), 2, int)
        self.assertEqualAndCheckType((2.6).__round__(), 3, int)
        self.assertEqualAndCheckType((-2.4).__round__(), -2, int)
        self.assertEqualAndCheckType((-2.5).__round__(), -2, int)
        self.assertEqualAndCheckType((-2.6).__round__(), -3, int)
        self.assertEqualAndCheckType(sys.float_info.max.__round__(), int(sys.float_info.max), int)
        self.assertEqualAndCheckType(-sys.float_info.max.__round__(), int(-sys.float_info.max), int)
        self.assertEqualAndCheckType(round(88.3), 88, int)
        self.assertEqualAndCheckType(round(-88.3), -88, int)
        self.assertEqualAndCheckType(round(2.1123E25), 21122999999999999263899648, int)
        self.assertEqualAndCheckType(round(-2.1123E25), -21122999999999999263899648, int)

        self.assertEqualAndCheckType((2.54).__round__(1), 2.5, float)
        if is_cli: # https://github.com/IronLanguages/ironpython2/issues/517
            self.assertEqualAndCheckType((2.55).__round__(1), 2.6, float)
        else:
            self.assertEqualAndCheckType((2.55).__round__(1), 2.5, float)
        self.assertEqualAndCheckType((2.56).__round__(1), 2.6, float)
        self.assertEqualAndCheckType((24.0).__round__(-1), 20.0, float)
        self.assertEqualAndCheckType((25.0).__round__(-1), 20.0, float)
        self.assertEqualAndCheckType((26.0).__round__(-1), 30.0, float)

        self.assertEqualAndCheckType((1).__round__(), 1, int)
        self.assertEqualAndCheckType((1).__round__(1), 1, int)
        self.assertEqualAndCheckType((1).__round__(-1), 0, int)

        self.assertEqualAndCheckType(big(1).__round__(), 1, int)
        self.assertEqualAndCheckType(big(1).__round__(1), 1, int)
        self.assertEqualAndCheckType(big(1).__round__(-1), 0, int)

    def test_other(self):
        x = ('a', 'b', 'c')
        y = x
        y *= 3
        z = x
        z += x
        z += x
        self.assertTrue(y == z)

        self.assertTrue(1 << 32 == 4294967296)
        self.assertTrue(2 << 32 == (1 << 32) << 1)
        self.assertTrue(((1 << 16) << 16) << 16 == 1 << 48)
        self.assertTrue(((1 << 16) << 16) << 16 == 281474976710656)

        for i in [1, 10, 42, 1000000000, 34141235135135135, 13523525234523452345235235234523, 100000000000000000000000000000000000000]:
            self.assertTrue(~i == -i - 1)

        self.assertTrue(7 ** 5 == 7*7*7*7*7)
        self.assertTrue(big(7) ** big(5) == big(7)*big(7)*big(7)*big(7)*big(7))
        self.assertTrue(7 ** big(5) == 7*7*7*7*7)
        self.assertTrue(big(7) ** 5 == big(7)*big(7)*big(7)*big(7)*big(7))
        self.assertTrue(1 ** 735293857239475 == 1)
        self.assertTrue(0 ** 735293857239475 == 0)

        self.assertTrue(2 ** 3.0 == 8.0)
        self.assertTrue(2.0 ** 3 == 8.0)
        self.assertTrue(4 ** 0.5 == 2.0)

    def test_divmod(self):
        self.assertEqual(7.1//2.1, 3.0)
        self.assertEqual(divmod(5.0, 2), (2.0,1.0))
        self.assertEqual(divmod(5,2), (2,1))
        self.assertEqual(divmod(-5,2), (-3,1))

    def test_boolean(self):
        self.assertEqual(True | False, True)
        self.assertEqual(True | 4, 5)
        self.assertEqual(True & 3, 1)
        self.assertEqual(True + 3, 4)
        self.assertEqual(True - 10, -9)
        self.assertEqual(True * 8, 8)
        self.assertEqual(True // 3, 0)
        self.assertEqual(True ** 5, 1)
        self.assertEqual(True % 2, 1)
        self.assertEqual(True << 4, 1 << 4)
        self.assertEqual(True >> 2, 0)
        self.assertEqual(True ^ 3, 2)

    @skipUnlessIronPython()
    def test_byte(self):
        a = Byte()
        self.assertEqual(type(a), Byte)
        self.assertEqual(a, 0)

        b = a + Byte(1)
        self.assertEqual(b, 1)
        self.assertEqual(type(b), Byte)

        bprime = b * Byte(10)
        self.assertEqual(type(bprime), Byte)

        d = a + Byte(255)
        self.assertEqual(type(d), Byte)

        c = b + Byte(255)
        self.assertEqual(c, 256)
        self.assertEqual(type(c), Int16)

    def test_negated_comparisons(self):
        self.assertTrue(not (20 == False))
        self.assertTrue(not (20 == None))
        self.assertTrue(not (False == 20))
        self.assertTrue(not (None == 20))
        self.assertTrue(not (20 == 'a'))
        self.assertTrue(not ('a' == 20))
        self.assertTrue(not (2.5 == None))
        self.assertTrue(not (20 == (2,3)))

        self.assertEqual(big(1234793454934), 1234793454934)
        self.assertEqual(4 ** -2, 0.0625)
        self.assertEqual(big(4) ** -2, 0.0625)

    def test_zero_division(self):
        self.assertRaises(ZeroDivisionError, (lambda: (0 ** -1)))
        self.assertRaises(ZeroDivisionError, (lambda: (0.0 ** -1)))
        self.assertRaises(ZeroDivisionError, (lambda: (0 ** -1.0)))
        self.assertRaises(ZeroDivisionError, (lambda: (0.0 ** -1.0)))
        self.assertRaises(ZeroDivisionError, (lambda: (False ** -1)))
        self.assertRaises(ZeroDivisionError, (lambda: (big(0) ** -(2 ** 65))))
        self.assertRaises(ZeroDivisionError, (lambda: (0j ** -1)))
        self.assertRaises(ZeroDivisionError, (lambda: (0j ** 1j)))

    def test_extensible_math(self):
        operators = ['__add__', '__sub__', '__pow__', '__mul__', '__div__', '__floordiv__', '__truediv__', '__mod__']
        opSymbol  = ['+',       '-',       '**',      '*',       '/',       '//',           '/',           '%']

        types = []
        for baseType in [(int, (100,2)), (int, (big(100), big(2))), (float, (100.0, 2.0))]:
        # (complex, (100+0j, 2+0j)) - cpython doesn't call reverse ops for complex ?
            class prototype(baseType[0]):
                for op in operators:
                    exec('''def %s(self, other):
    global opCalled
    opCalled.append('%s')
    return super(self.__class__, self).%s(other)

def %s(self, other):
    global opCalled
    opCalled.append('%s')
    return super(self.__class__, self).%s(other)''' % (op, op, op, op[:2] + 'r' + op[2:], op[:2] + 'r' + op[2:], op[:2] + 'r' + op[2:]))

            types.append( (prototype, baseType[1]) )

        global opCalled
        opCalled = []
        for op in opSymbol:
                for typeInfo in types:
                    ex = typeInfo[0](typeInfo[1][0])
                    ey = typeInfo[0](typeInfo[1][1])
                    nx = typeInfo[0].__bases__[0](typeInfo[1][0])
                    ny = typeInfo[0].__bases__[0](typeInfo[1][1])

                    #print('nx %s ey' % op, type(nx), type(ey))
                    res1 = eval('nx %s ey' % op)
                    res2 = eval('nx %s ny' % op)
                    self.assertEqual(res1, res2)
                    self.assertEqual(len(opCalled), 1)
                    opCalled = []

                    #print('ex %s ny' % op, type(ex), type(ny))
                    res1 = eval('ex %s ny' % op)
                    res2 = eval('nx %s ny' % op)
                    self.assertEqual(res1, res2)
                    self.assertEqual(len(opCalled), 1)
                    opCalled = []


    def test_nan(self):
        x  = 1e66666
        self.assertTrue(x==x)
        self.assertTrue(x<=x)
        self.assertTrue(x>=x)
        self.assertTrue(not x!=x)
        self.assertTrue(not x<x)
        self.assertTrue(not x>x)

        y = x/x
        self.assertEqual(y == y, False)
        self.assertEqual(y >= y, False)
        self.assertEqual(y <= y, False)
        self.assertEqual(y != y, True)
        self.assertEqual(y > y, False)
        self.assertEqual(y < y, False)

        self.assertTrue(not x==y)
        self.assertTrue(x!=y)
        self.assertTrue(not x>=y)
        self.assertTrue(not x<=y)
        self.assertTrue(not x<y)
        self.assertTrue(not x>y)

    def test_long_log(self):
        """logon big ints should work"""
        self.assertEqual(round(math.log10(10 ** 1000), 5), 1000.0)
        self.assertEqual(round(math.log(10 ** 1000), 5), 2302.58509)

        self.assertEqual(round(math.log10(18446744073709551615), 5),  19.26592)
        self.assertEqual(round(math.log(18446744073709551615), 5), 44.36142)

        self.assertEqual(round(math.log10(18446744073709551616), 5),  19.26592)
        self.assertEqual(round(math.log(18446744073709551616), 5), 44.36142)

        self.assertEqual(round(math.log10(18446744073709551614), 5),  19.26592)
        self.assertEqual(round(math.log(18446744073709551614), 5), 44.36142)

        # log in a new base
        self.assertEqual(round(math.log(2 ** 1000, 2), 5), 1000.0)

        self.assertRaises(ValueError, math.log, big(0))
        self.assertRaises(ValueError, math.log, big(-1))
        self.assertEqual(math.log(big(2), 1e666), 0.0)
        self.assertRaises(ValueError, math.log, big(2), -1e666)
        self.assertRaises(ZeroDivisionError, math.log, big(2), 1.0)

        #Make sure that an object is converted to float before being passed into log funcs
        class N(object):
            def __float__(self):
                return 10.0
            def __int__(self):
                return 100

        self.assertEqual(round(math.log10(N()), 5),1.0)
        self.assertEqual(round(math.log(N()), 5),2.30259)

    def test_log_neg(self):
        for x in [[2,0], [0,2.0], [0], [big(0)], [big(0), 3.14]]:
            self.assertRaisesMessage(ValueError, "math domain error",
                                math.log, *x)

    def test_math_subclass(self):
        """verify subtypes of float/int work w/ math functions"""
        import math
        class myfloat(float): pass
        class myint(int): pass

        mf = myfloat(1)
        mi = myint(1)

        for x in math.log, math.log10, math.log1p, math.asinh, math.acosh, math.atanh, math.factorial, math.trunc, math.isinf:
            try:
                resf = x(mf)
            except ValueError:
                resf = None
            try:
                resl = x(mi)
            except ValueError:
                resl = None
            self.assertEqual(resf, resl)

    def test_float_26(self):
        from_hex_tests = [('1.fffffffffffff7', 1.9999999999999998),
                        ('1.fffffffffffff8', 2.0),
                        ('-1.1fffffffffffffffffffffffffffffffff', -1.125),
                        ('-1.ffffffffffffffffffffffffffffffffff', -2),
                        ('10.4', 16.25),
                        ('1.fffffffffffffp1023', 1.7976931348623157e+308),
                        ('1p1', 2.0),
                        ('-1.0p1', -2.0),
                        ('+1.0p1', 2.0),
                        ('-0x1.0p1', -2.0),
                        ('1.0p1023', 8.9884656743115795e+307),
                        ('1.0p-1023', 1.1125369292536007e-308),
                        ('1.1234p1', 2.1422119140625),
                        ('1.' + 'f'*1000+ 'p1', 4.0),
                        ('1.ap1', 3.25),
                        ('1.Ap1', 3.25),
                        ('1.0p-1', 0.5),
                        ('1.0p-1', 0.5),
                        ('1.0p-1074', 4.9406564584124654e-324),
                        ('2.0p-1075', 4.9406564584124654e-324),
                        ('1.0p-1075', 0),
                        ('10.0p-1075', 3.9525251667299724e-323),
                        ('0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a', 1.634661910256948e+55),
                        ('0x1.Ap1', 3.25),
                        ('0x1.Ap2', 6.5),
                        ('0x1.Ap3', 13),
                        ('  0x1.Ap1   ', 3.25),
                        ]

        for string, value in from_hex_tests:
            #print string, value
            self.assertEqual(float.fromhex(string), value)

        from_hex_errors = [(OverflowError, '1.0p1024'),
                        (OverflowError, '1.0p1025'),
                        (OverflowError, '10.0p1023'),
                        (OverflowError, '1.ffffffffffffffp1023'),
                        (ValueError, 'xxxx'),
                        (OverflowError, '1.0p99999999999999999999999')]
        for excep, error in from_hex_errors:
            self.assertRaises(excep, float.fromhex, error)


    def test_float_subclass(self):
        global calledCount
        calledCount = 0
        class MyFloat(float):
            def __float__(self):
                global calledCount
                calledCount += 1
                return 42

        self.assertRaisesMessage(TypeError, "__float__ returned non-float (type int)", float, MyFloat(1.1))
        self.assertEqual(calledCount, 1)

    def test_integer_ratio(self):
        int_ratio_tests = [ (2.5, (5, 2)), (1.3, (5854679515581645, 4503599627370496))]

        for flt, res in int_ratio_tests:
            self.assertEqual(flt.as_integer_ratio(), res)

    def test_degrees(self):
        # check that IronPython is doing the same conversion as CPython
        self.assertNotEqual(0.06825994771674652 / (math.pi / 180), 0.06825994771674652 * (180 / math.pi))
        self.assertEqual(0.06825994771674652 * (180 / math.pi), 3.911006913953236)
        self.assertEqual(math.degrees(0.06825994771674652), 3.911006913953236)

run_test(__name__)
