require File.expand_path(File.join(File.dirname(__FILE__),'helper.rb'))

# These tests assume that Float arithmetic is correctly rounded
# Random tests using Float as a reference
class TestBinArithmetic < Minitest::Test

  def setup
    initialize_context
    srand 93831
    @test_float_data ||= Array.new(1000){random_num(Float)} + singular_nums(Float) # + special_nums(Float)
  end

  # TODO: some of these tests may fail under some OSs due to the rounding of subnormal results.
  #       On OSX, linux, etc. the rounding of subnormal Float results seems to be as performed by BinNum:
  #       as if the exact result is rounded to the number of bits of the subnormal result.
  #       But on Windows OSs (even under Cygwin) subnormal results seem to be rounded first to the
  #       full Float precision (53 bits).
  #       Consider avoid testing with subnormal results, at least on Winddows, or preparing test data,
  #       including results, in a reliable machine.

  def test_addition
    float_emulation_context
    each_pair(@test_float_data) do |x, y|
      z = x + y
      assert_equal z, (BinNum(x)+BinNum(y)).to_f
    end
  end

  def test_subtraction
    float_emulation_context
    each_pair(@test_float_data) do |x, y|
      z = x - y
      assert_equal z, (BinNum(x)-BinNum(y)).to_f
    end
  end

  def test_multiplication
    float_emulation_context
    each_pair(@test_float_data) do |x, y|
      z = x * y
      assert_equal z, (BinNum(x)*BinNum(y)).to_f
    end
  end

  def test_division
    float_emulation_context
    each_pair(@test_float_data) do |x, y|
      # next if y.abs < Float::EPSILON*x.abs
      next if y.zero?
      z = x / y
      # if z != (BinNum(x)/BinNum(y)).to_f
      #   puts "x=#{float_split(x).inspect}"
      #   puts "y=#{float_split(y).inspect}"
      #   puts "z=#{float_split(z).inspect}"
      #   puts "->#{(BinNum(x)/BinNum(y)).split.inspect}"
      # end
      assert_equal z, (BinNum(x)/BinNum(y)).to_f
    end
  end

  def test_power
    float_emulation_context
    each_pair(@test_float_data) do |x, y|
      next if x.zero? && y.zero?
      x = x.abs
      xx = BinNum(x)
      yy = BinNum(y)
      z = x**y
      zz = nil
      begin
        zz = xx**yy
      rescue=>err
        if err.is_a?(Num::Overflow)
          zz = BinNum.infinity
        else
          zz = err.to_s
        end
      end
      ok = true
      zzz = nil
      if zz != z
        # Math.power may not be accurate enough
        zzz = +BinNum.context(:precision=>512) { xx**yy }
        if zzz != zz
          ok = false
        end
      end
      assert ok, "#{x}**#{y} (#{Float.context.split(x).inspect}**#{Float.context.split(y).inspect}) Incorrect: #{zz.split.inspect} instead of #{zzz && zzz.split.inspect}"
    end
  end

  def test_sqrt
    float_emulation_context
    @test_float_data.each do |x|
      context = x.class.context
      x = context.abs(x)
      z = context.sqrt(x)
      assert_equal z, BinNum(x).sqrt.to_f
    end
  end

end
