require "spec"
require "spec/helpers/string"

describe "Float" do
  describe "**" do
    it { (2.5_f32 ** 2_i32).should be_close(6.25_f32, 0.0001) }
    it { (2.5_f32 ** 2).should be_close(6.25_f32, 0.0001) }
    it { (2.5_f32 ** 2.5_f32).should be_close(9.882117688026186_f32, 0.0001) }
    it { (2.5_f32 ** 2.5).should be_close(9.882117688026186_f32, 0.0001) }
    it { (2.5_f64 ** 2_i32).should be_close(6.25_f64, 0.0001) }
    it { (2.5_f64 ** 2).should be_close(6.25_f64, 0.0001) }
    it { (2.5_f64 ** 2.5_f64).should be_close(9.882117688026186_f64, 0.0001) }
    it { (2.5_f64 ** 2.5).should be_close(9.882117688026186_f64, 0.001) }
  end

  describe "%" do
    it "uses modulo behavior, not remainder behavior" do
      ((-11.5) % 4.0).should eq(0.5)
    end
  end

  describe "modulo" do
    it "raises when mods by zero" do
      expect_raises(DivisionByZeroError) { 1.2.modulo 0.0 }
    end

    it { (13.0.modulo 4.0).should eq(1.0) }
    it { (13.0.modulo -4.0).should eq(-3.0) }
    it { (-13.0.modulo 4.0).should eq(3.0) }
    it { (-13.0.modulo -4.0).should eq(-1.0) }
    it { (11.5.modulo 4.0).should eq(3.5) }
    it { (11.5.modulo -4.0).should eq(-0.5) }
    it { (-11.5.modulo 4.0).should eq(0.5) }
    it { (-11.5.modulo -4.0).should eq(-3.5) }
  end

  describe "remainder" do
    it "raises when mods by zero" do
      expect_raises(DivisionByZeroError) { 1.2.remainder 0.0 }
    end

    it { (13.0.remainder 4.0).should eq(1.0) }
    it { (13.0.remainder -4.0).should eq(1.0) }
    it { (-13.0.remainder 4.0).should eq(-1.0) }
    it { (-13.0.remainder -4.0).should eq(-1.0) }
    it { (11.5.remainder 4.0).should eq(3.5) }
    it { (11.5.remainder -4.0).should eq(3.5) }
    it { (-11.5.remainder 4.0).should eq(-3.5) }
    it { (-11.5.remainder -4.0).should eq(-3.5) }

    it "preserves type" do
      r = 1.5_f32.remainder(1)
      typeof(r).should eq(Float32)
    end
  end

  describe "round" do
    it { 2.5.round.should eq(2) }
    it { 3.5.round.should eq(4) }
    it { 2.4.round.should eq(2) }
  end

  describe "floor" do
    it { 2.1.floor.should eq(2) }
    it { 2.9.floor.should eq(2) }
  end

  describe "ceil" do
    it { 2.0_f32.ceil.should eq(2) }
    it { 2.0.ceil.should eq(2) }

    it { 2.1_f32.ceil.should eq(3_f32) }
    it { 2.1.ceil.should eq(3) }

    it { 2.9_f32.ceil.should eq(3) }
    it { 2.9.ceil.should eq(3) }
  end

  describe "#integer?" do
    it { 1.0_f32.integer?.should be_true }
    it { 1.0_f64.integer?.should be_true }

    it { 1.2_f32.integer?.should be_false }
    it { 1.2_f64.integer?.should be_false }

    it { Float32::MAX.integer?.should be_true }
    it { Float64::MAX.integer?.should be_true }

    it { Float32::INFINITY.integer?.should be_false }
    it { Float64::INFINITY.integer?.should be_false }

    it { Float32::NAN.integer?.should be_false }
    it { Float64::NAN.integer?.should be_false }
  end

  describe "fdiv" do
    it { 1.0.fdiv(1).should eq 1.0 }
    it { 1.0.fdiv(2).should eq 0.5 }
    it { 1.0.fdiv(0.5).should eq 2.0 }
    it { 0.0.fdiv(1).should eq 0.0 }
    it { 1.0.fdiv(0).should eq 1.0/0.0 }
  end

  describe "divmod" do
    it { 1.2.divmod(0.3)[0].should eq(4) }
    it { 1.2.divmod(0.3)[1].should be_close(0.0, 0.00001) }

    it { 1.3.divmod(0.3)[0].should eq(4) }
    it { 1.3.divmod(0.3)[1].should be_close(0.1, 0.00001) }

    it { 1.4.divmod(0.3)[0].should eq(4) }
    it { 1.4.divmod(0.3)[1].should be_close(0.2, 0.00001) }

    it { -1.2.divmod(0.3)[0].should eq(-4) }
    it { -1.2.divmod(0.3)[1].should be_close(0.0, 0.00001) }

    it { -1.3.divmod(0.3)[0].should eq(-5) }
    it { -1.3.divmod(0.3)[1].should be_close(0.2, 0.00001) }

    it { -1.4.divmod(0.3)[0].should eq(-5) }
    it { -1.4.divmod(0.3)[1].should be_close(0.1, 0.00001) }

    it { 1.2.divmod(-0.3)[0].should eq(-4) }
    it { 1.2.divmod(-0.3)[1].should be_close(0.0, 0.00001) }

    it { 1.3.divmod(-0.3)[0].should eq(-5) }
    it { 1.3.divmod(-0.3)[1].should be_close(-0.2, 0.00001) }

    it { 1.4.divmod(-0.3)[0].should eq(-5) }
    it { 1.4.divmod(-0.3)[1].should be_close(-0.1, 0.00001) }

    it { -1.2.divmod(-0.3)[0].should eq(4) }
    it { -1.2.divmod(-0.3)[1].should be_close(0.0, 0.00001) }

    it { -1.3.divmod(-0.3)[0].should eq(4) }
    it { -1.3.divmod(-0.3)[1].should be_close(-0.1, 0.00001) }

    it { -1.4.divmod(-0.3)[0].should eq(4) }
    it { -1.4.divmod(-0.3)[1].should be_close(-0.2, 0.00001) }
  end

  describe "floor division //" do
    it "preserves type of lhs" do
      (7.0 // 2).should be_a(Float64)
      (7.0 // 2i32).should be_a(Float64)
      (7.0 // 2.0).should be_a(Float64)
      (7.0_f32 // 2.0_f64).should be_a(Float32)
      (7.0_f32 // 2.0_f32).should be_a(Float32)
    end

    it "applies floor" do
      (7.0 // 2.0).should eq(3.0)
      (-7.0 // 2.0).should eq(-4.0)

      (6.0 // 2.0).should eq(3.0)
      (-6.0 // 2.0).should eq(-3.0)

      (30.3 // 3.9).should eq(7.0)
    end
  end

  describe "#to_s" do
    it "does to_s for f64" do
      assert_prints 12.34.to_s, "12.34"
      assert_prints 1.2.to_s, "1.2"
      assert_prints 1.23.to_s, "1.23"
      assert_prints 1.234.to_s, "1.234"
      assert_prints 0.65000000000000002.to_s, "0.65"
      assert_prints 1.234001.to_s, "1.234001"
      assert_prints 1.23499.to_s, "1.23499"
      assert_prints 1.23499999999999999.to_s, "1.235"
      assert_prints 1.2345.to_s, "1.2345"
      assert_prints 1.23456.to_s, "1.23456"
      assert_prints 1.234567.to_s, "1.234567"
      assert_prints 1.2345678.to_s, "1.2345678"
      assert_prints 1.23456789.to_s, "1.23456789"
      assert_prints 1.234567891.to_s, "1.234567891"
      assert_prints 1.2345678911.to_s, "1.2345678911"
      assert_prints 1.2345678912.to_s, "1.2345678912"
      assert_prints 1.23456789123.to_s, "1.23456789123"
      assert_prints 9525365.25.to_s, "9525365.25"
      assert_prints 12.9999.to_s, "12.9999"
      assert_prints 12.9999999999999999.to_s, "13.0"
      assert_prints 1.0.to_s, "1.0"
      assert_prints 2e20.to_s, "2.0e+20"
      assert_prints 1e-10.to_s, "1.0e-10"
      assert_prints 1464132168.65.to_s, "1464132168.65"
      assert_prints 146413216.865.to_s, "146413216.865"
      assert_prints 14641321.6865.to_s, "14641321.6865"
      assert_prints 1464132.16865.to_s, "1464132.16865"
      assert_prints 654329382.1.to_s, "654329382.1"
      assert_prints 6543293824.1.to_s, "6543293824.1"
      assert_prints 65432938242.1.to_s, "65432938242.1"
      assert_prints 654329382423.1.to_s, "654329382423.1"
      assert_prints 6543293824234.1.to_s, "6543293824234.1"
      assert_prints 65432938242345.1.to_s, "65432938242345.1"
      assert_prints 65432.123e20.to_s, "6.5432123e+24"
      assert_prints 65432.123e200.to_s, "6.5432123e+204"
      assert_prints -65432.123e200.to_s, "-6.5432123e+204"
      assert_prints 65432.123456e20.to_s, "6.5432123456e+24"
      assert_prints 65432.1234567e20.to_s, "6.54321234567e+24"
      assert_prints 65432.12345678e20.to_s, "6.543212345678e+24"
      assert_prints 65432.1234567891e20.to_s, "6.54321234567891e+24"
      assert_prints (1.0/0.0).to_s, "Infinity"
      assert_prints (-1.0/0.0).to_s, "-Infinity"
      assert_prints (0.999999999999999989).to_s, "1.0"
    end

    it "does to_s for f32" do
      assert_prints 12.34_f32.to_s, "12.34"
      assert_prints 1.2_f32.to_s, "1.2"
      assert_prints 1.23_f32.to_s, "1.23"
      assert_prints 1.234_f32.to_s, "1.234"
      assert_prints 0.65000000000000002_f32.to_s, "0.65"
      # assert_prints 1.234001_f32.to_s, "1.234001"
      assert_prints 1.23499_f32.to_s, "1.23499"
      assert_prints 1.23499999999999_f32.to_s, "1.235"
      assert_prints 1.2345_f32.to_s, "1.2345"
      assert_prints 1.23456_f32.to_s, "1.23456"
      # assert_prints 9525365.25_f32.to_s, "9525365.25"
      assert_prints (1.0_f32/0.0_f32).to_s, "Infinity"
      assert_prints (-1.0_f32/0.0_f32).to_s, "-Infinity"
    end
  end

  describe "#next_float" do
    it "does for f64" do
      0.0.next_float.should eq(Float64::MIN_SUBNORMAL)
      1.0.next_float.should eq(1.0000000000000002)
      (-1.0).next_float.should eq(-0.9999999999999999)
      Float64::MAX.next_float.should eq(Float64::INFINITY)
      Float64::INFINITY.next_float.should eq(Float64::INFINITY)
      (-Float64::INFINITY).next_float.should eq(Float64::MIN)
      Float64::NAN.next_float.nan?.should be_true
    end

    it "does for f32" do
      0.0_f32.next_float.should eq(Float32::MIN_SUBNORMAL)
      1.0_f32.next_float.should eq(1.0000001_f32)
      (-1.0_f32).next_float.should eq(-0.99999994_f32)
      Float32::MAX.next_float.should eq(Float32::INFINITY)
      Float32::INFINITY.next_float.should eq(Float32::INFINITY)
      (-Float32::INFINITY).next_float.should eq(Float32::MIN)
      Float32::NAN.next_float.nan?.should be_true
    end
  end

  describe "#prev_float" do
    it "does for f64" do
      0.0.prev_float.should eq(-Float64::MIN_SUBNORMAL)
      1.0.prev_float.should eq(0.9999999999999999)
      (-1.0).prev_float.should eq(-1.0000000000000002)
      Float64::MIN.prev_float.should eq(-Float64::INFINITY)
      Float64::INFINITY.prev_float.should eq(Float64::MAX)
      (-Float64::INFINITY).prev_float.should eq(-Float64::INFINITY)
      Float64::NAN.prev_float.nan?.should be_true
    end

    it "does for f32" do
      0.0_f32.prev_float.should eq(-Float32::MIN_SUBNORMAL)
      1.0_f32.prev_float.should eq(0.99999994_f32)
      (-1.0_f32).prev_float.should eq(-1.0000001_f32)
      Float32::MIN.prev_float.should eq(-Float32::INFINITY)
      Float32::INFINITY.prev_float.should eq(Float32::MAX)
      (-Float32::INFINITY).prev_float.should eq(-Float32::INFINITY)
      Float32::NAN.prev_float.nan?.should be_true
    end
  end

  describe "#inspect" do
    it "does inspect for f64" do
      3.2.inspect.should eq("3.2")
    end

    it "does inspect for f32" do
      3.2_f32.inspect.should eq("3.2")
    end

    it "does inspect for f64 with IO" do
      str = String.build { |io| 3.2.inspect(io) }
      str.should eq("3.2")
    end

    it "does inspect for f32" do
      str = String.build { |io| 3.2_f32.inspect(io) }
      str.should eq("3.2")
    end
  end

  describe "hash" do
    it "does for Float32" do
      1.2_f32.hash.should eq(1.2_f32.hash)
    end

    it "does for Float64" do
      1.2.hash.should eq(1.2.hash)
    end
  end

  describe ".new" do
    it "String overload" do
      Float32.new("1").should be_a(Float32)
      Float32.new("1").should eq(1)
      expect_raises ArgumentError, %(Invalid Float32: " 1 ") do
        Float32.new(" 1 ", whitespace: false)
      end

      Float64.new("1").should be_a(Float64)
      Float64.new("1").should eq(1)
      expect_raises ArgumentError, %(Invalid Float64: " 1 ") do
        Float64.new(" 1 ", whitespace: false)
      end
    end

    it "fallback overload" do
      Float32.new(1_f64).should be_a(Float32)
      Float32.new(1_f64).should eq(1)

      Float64.new(1_f32).should be_a(Float64)
      Float64.new(1_f32).should eq(1)
    end
  end

  it "does nan?" do
    1.5.nan?.should be_false
    (0.0 / 0.0).nan?.should be_true
  end

  it "does infinite?" do
    (0.0).infinite?.should be_nil
    (-1.0/0.0).infinite?.should eq(-1)
    (1.0/0.0).infinite?.should eq(1)

    (0.0_f32).infinite?.should be_nil
    (-1.0_f32/0.0_f32).infinite?.should eq(-1)
    (1.0_f32/0.0_f32).infinite?.should eq(1)
  end

  it "does finite?" do
    0.0.finite?.should be_true
    1.5.finite?.should be_true
    (1.0/0.0).finite?.should be_false
    (-1.0/0.0).finite?.should be_false
    (-0.0/0.0).finite?.should be_false
  end

  {% if compare_versions(Crystal::VERSION, "0.36.1") > 0 %}
    it "converts infinity" do
      Float32::INFINITY.to_f64.infinite?.should eq 1
      Float32::INFINITY.to_f32.infinite?.should eq 1
      expect_raises(OverflowError) { Float32::INFINITY.to_i }
      (-Float32::INFINITY).to_f64.infinite?.should eq -1
      (-Float32::INFINITY).to_f32.infinite?.should eq -1
      expect_raises(OverflowError) { (-Float32::INFINITY).to_i }

      Float64::INFINITY.to_f64.infinite?.should eq 1
      Float64::INFINITY.to_f32.infinite?.should eq 1
      expect_raises(OverflowError) { Float64::INFINITY.to_i }
      (-Float64::INFINITY).to_f64.infinite?.should eq -1
      (-Float64::INFINITY).to_f32.infinite?.should eq -1
      expect_raises(OverflowError) { (-Float64::INFINITY).to_i }
    end
  {% else %}
    pending "converts infinity"
  {% end %}

  it "does unary -" do
    f = -(1.5)
    f.should eq(-1.5)
    f.should be_a(Float64)

    f = -(1.5_f32)
    f.should eq(-1.5_f32)
    f.should be_a(Float32)

    (-(0.0)).sign_bit.should eq(-1)
    (-(-0.0)).sign_bit.should eq(1)

    (-(0.0_f32)).sign_bit.should eq(-1)
    (-(-0.0_f32)).sign_bit.should eq(1)

    (-Math.copysign(Float64::NAN, 1.0)).sign_bit.should eq(-1)
    (-Math.copysign(Float64::NAN, -1.0)).sign_bit.should eq(1)
  end

  it "clones" do
    1.0.clone.should eq(1.0)
    1.0_f32.clone.should eq(1.0_f32)
  end

  it "constants have right binary value" do
    Float32::MIN.unsafe_as(UInt32).should eq 0xff7fffff_u32
    Float32::MAX.unsafe_as(UInt32).should eq 0x7f7fffff_u32
    Float32::EPSILON.unsafe_as(UInt32).should eq 0x34000000_u32
    Float32::MIN_POSITIVE.unsafe_as(UInt32).should eq 0x00800000_u32

    Float64::MIN.unsafe_as(UInt64).should eq 0xffefffffffffffff_u64
    Float64::MAX.unsafe_as(UInt64).should eq 0x7fefffffffffffff_u64
    Float64::EPSILON.unsafe_as(UInt64).should eq 0x3cb0000000000000_u64
    Float64::MIN_POSITIVE.unsafe_as(UInt64).should eq 0x0010000000000000_u64
  end

  it "returns nil in <=> for NaN values (Float32)" do
    nan = Float32::NAN

    (1_f32 <=> nan).should be_nil
    (1_f64 <=> nan).should be_nil

    (1_u8 <=> nan).should be_nil
    (1_u16 <=> nan).should be_nil
    (1_u32 <=> nan).should be_nil
    (1_u64 <=> nan).should be_nil
    (1_i8 <=> nan).should be_nil
    (1_i16 <=> nan).should be_nil
    (1_i32 <=> nan).should be_nil
    (1_i64 <=> nan).should be_nil

    (nan <=> 1_u8).should be_nil
    (nan <=> 1_u16).should be_nil
    (nan <=> 1_u32).should be_nil
    (nan <=> 1_u64).should be_nil
    (nan <=> 1_i8).should be_nil
    (nan <=> 1_i16).should be_nil
    (nan <=> 1_i32).should be_nil
    (nan <=> 1_i64).should be_nil
  end

  it "returns nil in <=> for NaN values (Float64)" do
    nan = Float64::NAN

    (1_f32 <=> nan).should be_nil
    (1_f64 <=> nan).should be_nil

    (1_u8 <=> nan).should be_nil
    (1_u16 <=> nan).should be_nil
    (1_u32 <=> nan).should be_nil
    (1_u64 <=> nan).should be_nil
    (1_i8 <=> nan).should be_nil
    (1_i16 <=> nan).should be_nil
    (1_i32 <=> nan).should be_nil
    (1_i64 <=> nan).should be_nil

    (nan <=> 1_u8).should be_nil
    (nan <=> 1_u16).should be_nil
    (nan <=> 1_u32).should be_nil
    (nan <=> 1_u64).should be_nil
    (nan <=> 1_i8).should be_nil
    (nan <=> 1_i16).should be_nil
    (nan <=> 1_i32).should be_nil
    (nan <=> 1_i64).should be_nil
  end

  it "#abs" do
    0.0_f64.abs.sign_bit.should eq 1
    -0.0_f64.abs.sign_bit.should eq 1

    0.1_f64.abs.should eq 0.1_f64
    -0.1_f64.abs.should eq 0.1_f64

    0.0_f32.abs.sign_bit.should eq 1_f32
    -0.0_f32.abs.sign_bit.should eq 1_f32

    0.1_f32.abs.should eq 0.1_f32
    -0.1_f32.abs.should eq 0.1_f32

    Float64::MAX.abs.should eq Float64::MAX
    Float64::MIN.abs.should eq -Float64::MIN
    Float64::INFINITY.abs.should eq Float64::INFINITY
    (-Float64::INFINITY).abs.should eq Float64::INFINITY

    Float32::MAX.abs.should eq Float32::MAX
    Float32::MIN.abs.should eq -Float32::MIN
    Float32::INFINITY.abs.should eq Float32::INFINITY
    (-Float32::INFINITY).abs.should eq Float32::INFINITY
  end

  it "#sign_bit" do
    1.2_f64.sign_bit.should eq(1)
    -1.2_f64.sign_bit.should eq(-1)
    0.0_f64.sign_bit.should eq(1)
    -0.0_f64.sign_bit.should eq(-1)
    Float64::INFINITY.sign_bit.should eq(1)
    (-Float64::INFINITY).sign_bit.should eq(-1)
    0x7ff0_0000_0000_0001_u64.unsafe_as(Float64).sign_bit.should eq(1)
    0xfff0_0000_0000_0001_u64.unsafe_as(Float64).sign_bit.should eq(-1)

    1.2_f32.sign_bit.should eq(1)
    -1.2_f32.sign_bit.should eq(-1)
    0.0_f32.sign_bit.should eq(1)
    -0.0_f32.sign_bit.should eq(-1)
    Float32::INFINITY.sign_bit.should eq(1)
    (-Float32::INFINITY).sign_bit.should eq(-1)
    0x7f80_0001_u32.unsafe_as(Float32).sign_bit.should eq(1)
    0xff80_0001_u32.unsafe_as(Float32).sign_bit.should eq(-1)
  end
end
