# Test cases for floats (compile and run)

[case testFloatOps]
from __future__ import annotations
from typing import Any, cast
from typing_extensions import Final
from testutil import assertRaises, float_vals, FLOAT_MAGIC
import math

def test_arithmetic() -> None:
    zero = float(0.0)
    one = zero + 1.0
    x = one + one / 2.0
    assert x == 1.5
    assert x - one == 0.5
    assert x * x == 2.25
    assert x / 2.0 == 0.75
    assert x * (-0.5) == -0.75
    assert -x == -1.5
    for x in float_vals:
        assert repr(-x) == repr(getattr(x, "__neg__")())

        for y in float_vals:
            assert repr(x + y) == repr(getattr(x, "__add__")(y))
            assert repr(x - y) == repr(getattr(x, "__sub__")(y))
            assert repr(x * y) == repr(getattr(x, "__mul__")(y))
            if y != 0:
                assert repr(x / y) == repr(getattr(x, "__truediv__")(y))

def test_mod() -> None:
    zero = float(0.0)
    one = zero + 1.0
    x = one + one / 2.0
    assert x % 0.4 == 0.29999999999999993
    assert (-x) % 0.4 == 0.10000000000000009
    assert x % -0.4 == -0.10000000000000009
    assert (-x) % -0.4 == -0.29999999999999993
    for x in float_vals:
        for y in float_vals:
            if y != 0:
                assert repr(x % y) == repr(getattr(x, "__mod__")(y))

def test_floor_div() -> None:
    for x in float_vals:
        for y in float_vals:
            if y != 0:
                assert repr(x // y) == repr(getattr(x, "__floordiv__")(y))
            else:
                with assertRaises(ZeroDivisionError, "float floor division by zero"):
                    x // y

def test_mixed_arithmetic() -> None:
    zf = float(0.0)
    zn = int()
    assert (zf + 5.5) + (zn + 1) == 6.5
    assert (zn - 2) - (zf - 5.5) == 3.5
    x = zf + 3.4
    x += zn + 2
    assert x == 5.4

def test_arithmetic_errors() -> None:
    zero = float(0.0)
    one = zero + 1.0
    with assertRaises(ZeroDivisionError, "float division by zero"):
        print(one / zero)
    with assertRaises(ZeroDivisionError, "float modulo"):
        print(one % zero)

def test_comparisons() -> None:
    zero = float(0.0)
    one = zero + 1.0
    x = one + one / 2.0
    assert x < (1.51 + zero)
    assert not (x < (1.49 + zero))
    assert x > (1.49 + zero)
    assert not (x > (1.51 + zero))
    assert x <= (1.5 + zero)
    assert not (x <= (1.49 + zero))
    assert x >= (1.5 + zero)
    assert not (x >= (1.51 + zero))
    for x in float_vals:
        for y in float_vals:
            assert (x <= y) == getattr(x, "__le__")(y)
            assert (x < y) == getattr(x, "__lt__")(y)
            assert (x >= y) == getattr(x, "__ge__")(y)
            assert (x > y) == getattr(x, "__gt__")(y)
            assert (x == y) == getattr(x, "__eq__")(y)
            assert (x != y) == getattr(x, "__ne__")(y)

def test_mixed_comparisons() -> None:
    zf = float(0.0)
    zn = int()
    if (zf + 1.0) == (zn + 1):
        assert True
    else:
        assert False
    if (zf + 1.1) == (zn + 1):
        assert False
    else:
        assert True
    assert (zf + 1.1) != (zn + 1)
    assert (zf + 1.1) > (zn + 1)
    assert not (zf + 0.9) > (zn + 1)
    assert (zn + 1) < (zf + 1.1)

def test_boxing_and_unboxing() -> None:
    x = 1.5
    boxed: Any = x
    assert repr(boxed) == "1.5"
    assert type(boxed) is float
    y: float = boxed
    assert y == x
    boxed_int: Any = 5
    assert [type(boxed_int)] == [int]  # Avoid mypy type narrowing
    z: float = boxed_int
    assert z == 5.0
    for xx in float_vals:
        bb: Any = xx
        yy: float = bb
        assert repr(xx) == repr(bb)
        assert repr(xx) == repr(yy)
    for b in True, False:
        boxed_bool: Any = b
        assert type(boxed_bool) is bool
        zz: float = boxed_bool
        assert zz == int(b)

def test_unboxing_failure() -> None:
    boxed: Any = '1.5'
    with assertRaises(TypeError):
        x: float = boxed

def identity(x: float) -> float:
    return x

def test_coerce_from_int_literal() -> None:
    assert identity(34) == 34.0
    assert identity(-1) == -1.0

def test_coerce_from_short_tagged_int() -> None:
    n = int() - 17
    assert identity(n) == -17.0
    for i in range(-300, 300):
        assert identity(i) == float(i)

def test_coerce_from_long_tagged_int() -> None:
    n = int() + 2**100
    x = identity(n)
    assert repr(x) == '1.2676506002282294e+30'
    n = int() - 2**100
    y = identity(n)
    assert repr(y) == '-1.2676506002282294e+30'

def test_coerce_from_very_long_tagged_int() -> None:
    n = int() + 10**1000
    with assertRaises(OverflowError, "int too large to convert to float"):
        identity(n)
    with assertRaises(OverflowError, "int too large to convert to float"):
        identity(int(n))
    n = int() - 10**1000
    with assertRaises(OverflowError, "int too large to convert to float"):
        identity(n)
    with assertRaises(OverflowError, "int too large to convert to float"):
        identity(int(n))

def test_explicit_conversion_from_int() -> None:
    float_any: Any = float
    a = [0, 1, 2, 3, -1, -2, 13257, -928745]
    for n in range(1, 100):
        for delta in -1, 0, 1, 2342345:
            a.append(2**n + delta)
            a.append(-2**n + delta)
    for x in a:
        assert repr(float(x)) == repr(float_any(x))

def test_explicit_conversion_to_int() -> None:
    int_any: Any = int
    for x in float_vals:
        if math.isinf(x):
            with assertRaises(OverflowError, "cannot convert float infinity to integer"):
                int(x)
        elif math.isnan(x):
            with assertRaises(ValueError, "cannot convert float NaN to integer"):
                int(x)
        else:
            assert repr(int(x)) == repr(int_any(x))

    # Test some edge cases
    assert 2**30 == int(2.0**30 + int())
    assert 2**30 - 1 == int(1073741823.9999999 + int())  # math.nextafter(2.0**30, 0))
    assert -2**30 - 1 == int(-2.0**30 - 1 + int())
    assert -2**30 == int(-1073741824.9999998 + int())  # math.nextafter(-2.0**30 - 1, 0)
    assert 2**62 == int(2.0**62 + int())
    assert 2**62 == int(2.0**62 - 1 + int())
    assert -2**62 == int(-2.0**62 + int())
    assert -2**62 == int(-2.0**62 - 1 + int())

def str_to_float(x: str) -> float:
    return float(x)

def test_str_to_float() -> None:
    assert str_to_float("1") == 1.0
    assert str_to_float("1.234567") == 1.234567
    assert str_to_float("44324") == 44324.0
    assert str_to_float("23.4") == 23.4
    assert str_to_float("-43.44e-4") == -43.44e-4
    assert str_to_float("-43.44e-4") == -43.44e-4
    assert math.isinf(str_to_float("inf"))
    assert math.isinf(str_to_float("-inf"))
    assert str_to_float("inf") > 0.0
    assert str_to_float("-inf") < 0.0
    assert math.isnan(str_to_float("nan"))
    assert math.isnan(str_to_float("NaN"))
    assert repr(str_to_float("-0.0")) == "-0.0"

def test_abs() -> None:
    assert abs(0.0) == 0.0
    assert abs(-1.234567) == 1.234567
    assert abs(44324.732) == 44324.732
    assert abs(-23.4) == 23.4
    assert abs(-43.44e-4) == 43.44e-4
    abs_any: Any = abs
    for x in float_vals:
        assert repr(abs(x)) == repr(abs_any(x))

def test_float_min_max() -> None:
    for x in float_vals:
        for y in float_vals:
            min_any: Any = min
            assert repr(min(x, y)) == repr(min_any(x, y))
            max_any: Any = max
            assert repr(max(x, y)) == repr(max_any(x, y))

def default(x: float = 2) -> float:
    return x + 1

def test_float_default_value() -> None:
    assert default(1.2) == 2.2
    for i in range(-200, 200):
        assert default(float(i)) == i + 1
    assert default() == 3.0

def test_float_default_value_wrapper() -> None:
    f: Any = default
    assert f(1.2) == 2.2
    for i in range(-200, 200):
        assert f(float(i)) == i + 1
    assert f() == 3.0

class C:
    def __init__(self, x: float) -> None:
        self.x = x

def test_float_attr() -> None:
    for i in range(-200, 200):
        f = float(i)
        c = C(f)
        assert c.x == f
        a: Any = c
        assert a.x == f
        c.x = FLOAT_MAGIC
        assert c.x == FLOAT_MAGIC
        assert a.x == FLOAT_MAGIC
        a.x = 1.0
        assert a.x == 1.0
        a.x = FLOAT_MAGIC
        assert a.x == FLOAT_MAGIC

class D:
    def __init__(self, x: float) -> None:
        if x:
            self.x = x

def test_float_attr_maybe_undefned() -> None:
    for i in range(-200, 200):
        if i == 0:
            d = D(0.0)
            with assertRaises(AttributeError):
                d.x
            a: Any = d
            with assertRaises(AttributeError):
                a.x
            d.x = FLOAT_MAGIC
            assert d.x == FLOAT_MAGIC
            assert a.x == FLOAT_MAGIC
            d.x = 0.0
            assert d.x == 0.0
            assert a.x == 0.0
            a.x = FLOAT_MAGIC
            assert a.x == FLOAT_MAGIC
            d = D(0.0)
            a = cast(Any, d)
            a.x = FLOAT_MAGIC
            assert d.x == FLOAT_MAGIC
        else:
            f = float(i)
            d = D(f)
            assert d.x == f
            a2: Any = d
            assert a2.x == f

def f(x: float) -> float:
    return x + 1

def test_return_values() -> None:
    a: Any = f
    for i in range(-200, 200):
        x = float(i)
        assert f(x) == x + 1
        assert a(x) == x + 1
    for x in float_vals:
        if not math.isnan(x):
            assert f(x) == x + 1
        else:
            assert math.isnan(f(x))

def exc() -> float:
    raise IndexError('x')

def test_exception() -> None:
    with assertRaises(IndexError):
        exc()
    a: Any = exc
    with assertRaises(IndexError):
        a()

def test_undefined_local_var() -> None:
    if not int():
        x = -113.0
    assert x == -113.0
    if int():
        y = -113.0
    with assertRaises(UnboundLocalError, 'local variable "y" referenced before assignment'):
        print(y)
    if not int():
        x2 = -1.0
    assert x2 == -1.0
    if int():
        y2 = -1.0
    with assertRaises(UnboundLocalError, 'local variable "y2" referenced before assignment'):
        print(y2)

def test_tuples() -> None:
    t1: tuple[float, float] = (1.5, 2.5)
    assert t1 == tuple([1.5, 2.5])
    n = int() + 5
    t2: tuple[float, float, float, float] = (n, 1.5, -7, -113)
    assert t2 == tuple([5.0, 1.5, -7.0, -113.0])

[case testFloatGlueMethodsAndInheritance]
from typing import Any
from typing_extensions import Final

from mypy_extensions import trait

from testutil import assertRaises

MAGIC: Final = -113.0

class Base:
    def foo(self) -> float:
        return 5.0

    def bar(self, x: float = 2.0) -> float:
        return x + 1

    def hoho(self, x: float) -> float:
        return x - 1

class Derived(Base):
    def foo(self, x: float = 5.0) -> float:
        return x + 10

    def bar(self, x: float = 3, y: float = 20) -> float:
        return x + y + 2

    def hoho(self, x: float = 7) -> float:
        return x - 2

def test_derived_adds_bitmap() -> None:
    b: Base = Derived()
    assert b.foo() == 15

def test_derived_adds_another_default_arg() -> None:
    b: Base = Derived()
    assert b.bar() == 25
    assert b.bar(1) == 23
    assert b.bar(MAGIC) == MAGIC + 22

def test_derived_switches_arg_to_have_default() -> None:
    b: Base = Derived()
    assert b.hoho(5) == 3
    assert b.hoho(MAGIC) == MAGIC - 2

@trait
class T:
    @property
    def x(self) -> float: ...
    @property
    def y(self) -> float: ...

class C(T):
    x: float = 1.0
    y: float = 4

def test_read_only_property_in_trait_implemented_as_attribute() -> None:
    c = C()
    c.x = 5.5
    assert c.x == 5.5
    c.x = MAGIC
    assert c.x == MAGIC
    assert c.y == 4
    c.y = 6.5
    assert c.y == 6.5
    t: T = C()
    assert t.y == 4
    t = c
    assert t.x == MAGIC
    c.x = 55.5
    assert t.x == 55.5
    assert t.y == 6.5
    a: Any = c
    assert a.x == 55.5
    assert a.y == 6.5
    a.x = 7.0
    a.y = 8.0
    assert a.x == 7
    assert a.y == 8

class D(T):
    xx: float

    @property
    def x(self) -> float:
        return self.xx

    @property
    def y(self) -> float:
        raise TypeError

def test_read_only_property_in_trait_implemented_as_property() -> None:
    d = D()
    d.xx = 5.0
    assert d.x == 5
    d.xx = MAGIC
    assert d.x == MAGIC
    with assertRaises(TypeError):
        d.y
    t: T = d
    assert t.x == MAGIC
    d.xx = 6.0
    assert t.x == 6
    with assertRaises(TypeError):
        t.y

@trait
class T2:
    x: float
    y: float

class C2(T2):
    pass

def test_inherit_trait_attribute() -> None:
    c = C2()
    c.x = 5.0
    assert c.x == 5
    c.x = MAGIC
    assert c.x == MAGIC
    with assertRaises(AttributeError):
        c.y
    c.y = 6.0
    assert c.y == 6.0
    t: T2 = C2()
    with assertRaises(AttributeError):
        t.y
    t = c
    assert t.x == MAGIC
    c.x = 55.0
    assert t.x == 55
    assert t.y == 6
    a: Any = c
    assert a.x == 55
    assert a.y == 6
    a.x = 7.0
    a.y = 8.0
    assert a.x == 7
    assert a.y == 8

class D2(T2):
    x: float
    y: float = 4

def test_implement_trait_attribute() -> None:
    d = D2()
    d.x = 5.0
    assert d.x == 5
    d.x = MAGIC
    assert d.x == MAGIC
    assert d.y == 4
    d.y = 6.0
    assert d.y == 6
    t: T2 = D2()
    assert t.y == 4
    t = d
    assert t.x == MAGIC
    d.x = 55.0
    assert t.x == 55
    assert t.y == 6
    a: Any = d
    assert a.x == 55
    assert a.y == 6
    a.x = 7.0
    a.y = 8.0
    assert a.x == 7
    assert a.y == 8
