require "spec"

describe "Math" do
  describe "Mathematical constants" do
    it "E" do
      Math::E.should be_close(2.718281828459045, 1e-7)
    end

    it "LOG2" do
      Math::LOG2.should be_close(0.6931471805599453, 1e-7)
    end

    it "LOG10" do
      Math::LOG10.should be_close(2.302585092994046, 1e-7)
    end
  end

  describe "Functions manipulating signs" do
    it "copysign" do
      Math.copysign(6.9, -0.2).should eq(-6.9)
    end
  end

  describe "Order-related functions" do
    it { Math.min(2.1, 2.11).should eq(2.1) }
    it { Math.max(3.2, 3.11).should eq(3.2) }
  end

  pending "Functions for computing quotient and remainder" do
  end

  describe "Roots" do
    it "cbrt" do
      Math.cbrt(6.5_f32).should be_close(1.866255578408624, 1e-7)
      Math.cbrt(6.5).should be_close(1.866255578408624, 1e-7)
    end

    it "sqrt" do
      Math.sqrt(5.2_f32).should be_close(2.280350850198276, 1e-7)
      Math.sqrt(5.2).should be_close(2.280350850198276, 1e-7)
      Math.sqrt(4_f32).should eq(2)
      Math.sqrt(4).should eq(2)
    end

    it "isqrt" do
      Math.isqrt(9).should eq(3)
      Math.isqrt(8).should eq(2)
      Math.isqrt(4).should eq(2)
      {% for type in [UInt8, UInt16, UInt32, UInt64, UInt128, Int8, Int16, Int32, Int64, Int128] %}
        %val = {{type}}.new 42
        %exp = {{type}}.new 6
        Math.isqrt(%val).should eq(%exp)
      {% end %}
    end
  end

  describe "Exponents" do
    it "exp" do
      Math.exp(0.211_f32).should be_close(1.2349123550613943, 1e-7)
      Math.exp(0.211).should be_close(1.2349123550613943, 1e-7)
    end

    it "exp2" do
      Math.exp2(0.41_f32).should be_close(1.3286858140965117, 1e-7)
      Math.exp2(0.41).should be_close(1.3286858140965117, 1e-7)
    end

    it "expm1" do
      Math.expm1(0.99_f32).should be_close(1.6912344723492623, 1e-6)
      Math.expm1(0.99).should be_close(1.6912344723492623, 1e-7)
    end

    it "ilogb" do
      Math.ilogb(0.5_f32).should eq(-1)
      Math.ilogb(0.5).should eq(-1)
    end

    it "ldexp" do
      Math.ldexp(0.27_f32, 2).should be_close(1.08, 1e-7)
      Math.ldexp(0.27, 2).should be_close(1.08, 1e-7)
    end

    it "logb" do
      Math.logb(10_f32).should be_close(3.0, 1e-7)
      Math.logb(10.0).should be_close(3.0, 1e-7)
    end

    it "scalbn" do
      Math.scalbn(0.2_f32, 3).should be_close(1.6, 1e-7)
      Math.scalbn(0.2, 3).should be_close(1.6, 1e-7)
    end

    it "scalbln" do
      Math.scalbln(0.11_f32, 2).should be_close(0.44, 1e-7)
      Math.scalbln(0.11, 2).should be_close(0.44, 1e-7)
    end

    it "frexp" do
      Math.frexp(0.2_f32).should eq({0.8_f32, -2})
      Math.frexp(0.2).should eq({0.8, -2})
    end
  end

  describe "Logarithms" do
    it "log" do
      Math.log(3.24_f32).should be_close(1.1755733298042381, 1e-7)
      Math.log(3.24).should be_close(1.1755733298042381, 1e-7)
      Math.log(0.3_f32, 3).should be_close(-1.0959032742893848, 1e-7)
      Math.log(0.3, 3).should be_close(-1.0959032742893848, 1e-7)
    end

    it "log2" do
      Math.log2(1.2_f32).should be_close(0.2630344058337938, 1e-7)
      Math.log2(1.2).should be_close(0.2630344058337938, 1e-7)
    end

    it "log10" do
      Math.log10(0.5_f32).should be_close(-0.3010299956639812, 1e-7)
      Math.log10(0.5).should be_close(-0.3010299956639812, 1e-7)
    end

    it "log1p" do
      Math.log1p(0.67_f32).should be_close(0.5128236264286637, 1e-7)
      Math.log1p(0.67).should be_close(0.5128236264286637, 1e-7)
    end
  end

  describe "Trigonometric functions" do
    it "cos" do
      Math.cos(2.23_f32).should be_close(-0.6124875656583851, 1e-7)
      Math.cos(2.23).should be_close(-0.6124875656583851, 1e-7)
    end

    it "sin" do
      Math.sin(3.3_f32).should be_close(-0.1577456941432482, 1e-7)
      Math.sin(3.3).should be_close(-0.1577456941432482, 1e-7)
    end

    it "tan" do
      Math.tan(0.91_f32).should be_close(1.2863693807208076, 1e-7)
      Math.tan(0.91).should be_close(1.2863693807208076, 1e-7)
    end

    it "hypot" do
      Math.hypot(2.1_f32, 1.5_f32).should be_close(2.5806975801127883, 1e-7)
      Math.hypot(2.1, 1.5).should be_close(2.5806975801127883, 1e-7)
    end
  end

  describe "Inverse trigonometric functions" do
    it "acos" do
      Math.acos(0.125_f32).should be_close(1.445468495626831, 1e-7)
      Math.acos(0.125).should be_close(1.445468495626831, 1e-7)
    end

    it "asin" do
      Math.asin(-0.4_f32).should be_close(-0.41151684606748806, 1e-7)
      Math.asin(-0.4).should be_close(-0.41151684606748806, 1e-7)
    end

    it "atan" do
      Math.atan(4.3_f32).should be_close(1.3422996875030344, 1e-7)
      Math.atan(4.3).should be_close(1.3422996875030344, 1e-7)
    end

    it "atan2" do
      Math.atan2(3.5_f32, 2.1_f32).should be_close(1.0303768265243125, 1e-7)
      Math.atan2(3.5, 2.1).should be_close(1.0303768265243125, 1e-7)
      Math.atan2(1, 0).should eq(Math.atan2(1.0, 0.0))
    end
  end

  describe "Hyperbolic functions" do
    it "cosh" do
      Math.cosh(0.79_f32).should be_close(1.3286206107691463, 1e-7)
      Math.cosh(0.79).should be_close(1.3286206107691463, 1e-7)
    end

    it "sinh" do
      Math.sinh(0.12_f32).should be_close(0.12028820743110909, 1e-7)
      Math.sinh(0.12).should be_close(0.12028820743110909, 1e-7)
    end

    it "tanh" do
      Math.tanh(4.1_f32).should be_close(0.9994508436877974, 1e-7)
      Math.tanh(4.1).should be_close(0.9994508436877974, 1e-7)
    end
  end

  describe "Inverse hyperbolic functions" do
    it "acosh" do
      Math.acosh(1.1_f32).should be_close(0.4435682543851154, 1e-7)
      Math.acosh(1.1).should be_close(0.4435682543851154, 1e-7)
    end

    it "asinh" do
      Math.asinh(-2.3_f32).should be_close(-1.570278543484978, 1e-7)
      Math.asinh(-2.3).should be_close(-1.570278543484978, 1e-7)
    end

    it "atanh" do
      Math.atanh(0.13_f32).should be_close(0.13073985002887845, 1e-7)
      Math.atanh(0.13).should be_close(0.13073985002887845, 1e-7)
    end
  end

  describe "Gamma functions" do
    it "gamma" do
      Math.gamma(3.2_f32).should be_close(2.4239654799353683, 1e-6)
      Math.gamma(3.2).should be_close(2.4239654799353683, 1e-7)
      Math.gamma(5).should eq 24
      Math.gamma(5_i8).should eq 24
    end

    it "lgamma" do
      Math.lgamma(2.96_f32).should be_close(0.6565534110944214, 1e-7)
      Math.lgamma(2.96).should be_close(0.6565534110944214, 1e-7)
      Math.lgamma(3).should be_close(0.6931471805599454, 1e-7)
      Math.lgamma(3_i8).should be_close(0.6931471805599454, 1e-7)
    end
  end

  describe "Bessel functions" do
    it "besselj0" do
      Math.besselj0(9.1_f32).should be_close(-0.11423923268319867, 1e-7)
      Math.besselj0(9.1).should be_close(-0.11423923268319867, 1e-7)
    end

    it "besselj1" do
      Math.besselj1(-2.1_f32).should be_close(-0.5682921357570385, 1e-7)
      Math.besselj1(-2.1).should be_close(-0.5682921357570385, 1e-7)
    end

    it "besselj" do
      Math.besselj(4, -6.4_f32).should be_close(0.2945338623574655, 1e-7)
      Math.besselj(4, -6.4).should be_close(0.2945338623574655, 1e-7)
    end

    it "bessely0" do
      Math.bessely0(2.14_f32).should be_close(0.5199289108068015, 1e-7)
      Math.bessely0(2.14).should be_close(0.5199289108068015, 1e-7)
    end

    it "bessely1" do
      Math.bessely1(7.7_f32).should be_close(-0.2243184743430081, 1e-7)
      Math.bessely1(7.7).should be_close(-0.2243184743430081, 1e-7)
    end

    it "bessely" do
      Math.bessely(3, 2.7_f32).should be_close(-0.6600575162477298, 1e-7)
      Math.bessely(3, 2.7).should be_close(-0.6600575162477298, 1e-7)
    end
  end

  describe "Gauss error functions" do
    it "erf" do
      Math.erf(0.72_f32).should be_close(0.6914331231387512, 1e-7)
      Math.erf(0.72).should be_close(0.6914331231387512, 1e-7)
    end

    it "erfc" do
      Math.erfc(-0.66_f32).should be_close(1.6493766879629543, 1e-7)
      Math.erfc(-0.66).should be_close(1.6493766879629543, 1e-7)
    end
  end

  # div rem

  it "fma" do
    x = Math.fma(0.1, 10.0, -1.0)
    x.should be_close(5.551115123125783e-17, 1e-25)
    x.should_not eq(0.0)

    x = Math.fma(0.1_f32, 10.0_f32, -1.0_f32)
    x.should be_close(1.4901161e-8_f32, 1e-16_f32)
    x.should_not eq(0.0_f32)
  end

  describe ".pw2ceil" do
    {% for int in %w(Int8 Int16 Int32 Int64 Int128) %}
      it {{ int }} do
        Math.pw2ceil({{ int.id }}::MIN).should eq 1
        Math.pw2ceil({{ int.id }}::MIN + 1).should eq 1
        Math.pw2ceil({{ int.id }}.new(-11)).should eq 1
        Math.pw2ceil({{ int.id }}.new(-1)).should eq 1
        Math.pw2ceil({{ int.id }}.new(0)).should eq 1
        Math.pw2ceil({{ int.id }}.new(1)).should eq 1
        Math.pw2ceil({{ int.id }}.new(2)).should eq 2
        Math.pw2ceil({{ int.id }}.new(3)).should eq 4
        Math.pw2ceil({{ int.id }}.new(4)).should eq 4
        Math.pw2ceil({{ int.id }}.new(5)).should eq 8
        Math.pw2ceil({{ int.id }}.new(32)).should eq(32)
        Math.pw2ceil({{ int.id }}.new(33)).should eq(64)
        Math.pw2ceil({{ int.id }}.new(64)).should eq(64)

        Math.pw2ceil({{ int.id }}::MAX // 2).should eq({{ int.id }}::MAX // 2 + 1)
        Math.pw2ceil({{ int.id }}::MAX // 2 + 1).should eq({{ int.id }}::MAX // 2 + 1)
        expect_raises(OverflowError) { Math.pw2ceil({{ int.id }}::MAX // 2 + 2) }
        expect_raises(OverflowError) { Math.pw2ceil({{ int.id }}::MAX) }
      end
    {% end %}

    {% for uint in %w(UInt8 UInt16 UInt32 UInt64 UInt128) %}
      it {{ uint }} do
        Math.pw2ceil({{ uint.id }}.new(0)).should eq 1
        Math.pw2ceil({{ uint.id }}.new(1)).should eq 1
        Math.pw2ceil({{ uint.id }}.new(2)).should eq 2
        Math.pw2ceil({{ uint.id }}.new(3)).should eq 4
        Math.pw2ceil({{ uint.id }}.new(4)).should eq 4
        Math.pw2ceil({{ uint.id }}.new(5)).should eq 8
        Math.pw2ceil({{ uint.id }}.new(32)).should eq(32)
        Math.pw2ceil({{ uint.id }}.new(33)).should eq(64)
        Math.pw2ceil({{ uint.id }}.new(64)).should eq(64)

        Math.pw2ceil({{ uint.id }}::MAX // 2).should eq({{ uint.id }}::MAX // 2 + 1)
        Math.pw2ceil({{ uint.id }}::MAX // 2 + 1).should eq({{ uint.id }}::MAX // 2 + 1)
        expect_raises(OverflowError) { Math.pw2ceil({{ uint.id }}::MAX // 2 + 2) }
        expect_raises(OverflowError) { Math.pw2ceil({{ uint.id }}::MAX) }
      end
    {% end %}
  end

  # ** (float and int)
end
