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

require 'flt/math'
require 'bigdecimal/math'

if RUBY_VERSION>="1.9.2"
  BgMth = BigMath
else
  module BgMth
    extend BigMath
  end
end

class TestRationalizer < Minitest::Test

  def setup
    initialize_context
    Flt::DecNum.context = Flt::DecNum::DefaultContext
    @data = float_data
  end

  R = Flt::Support::Rationalizer

  def test_basic_rationalizer
    # basic Rationalizer tests
    r = R.new
    assert_equal [13,10], r.rationalize(1.3)
    assert_equal [13,10], R.max_denominator(1.3,10)
    assert_equal [13,10], R.max_denominator(BigDecimal('1.3'),10)
    assert_equal [1,3], R.max_denominator(1.0/3,10)
    assert_equal [1,3], R.max_denominator(BigDecimal('1')/3,10)
    assert_equal [13,10], R.max_denominator(Flt.DecNum('1.3'),10)
    assert_equal [1,3], R.max_denominator(Flt.DecNum('1')/3,10)

    # basic tests of Float.context.rationalize
    assert_equal Rational(1,3), Float.context.rationalize(1.0/3.0)
    assert_equal Rational(2,3), Float.context.rationalize(2.0/3.0)
    assert_equal Rational(1237,1234), Float.context.rationalize(1237.0/1234.0)
    assert_equal Rational(89,217), Float.context.rationalize(89.0/217.0)

    # rationalization of Floats using a tolerance
    t = Flt.Tolerance(1e-15/2,:floating)
    assert_equal Rational(540429, 12500), Float.context.rationalize(43.23432, t, :strict)
    assert_equal Rational(6636649, 206596193), Float.context.rationalize(0.032123772, t, :strict)
    assert_equal Rational(280943, 2500000), Float.context.rationalize(0.1123772, t, :strict)
    assert_equal Rational(39152929, 12500), Float.context.rationalize(3132.23432, t, :strict)
    assert_equal Rational(24166771439, 104063), Float.context.rationalize(232232.123223432, t, :strict)
    assert_equal Rational(792766404965, 637), Float.context.rationalize(1244531247.98273123, t, :strict)

    # @data.each do |x|
    #  assert t.eq?(x, Float.context.rationalize(x,t).to_f), "out of tolerance: #{x.inspect}"
    # end

    # rationalization with maximum denominator
    assert_equal Rational(9441014047197, 7586), Float.context.rationalize(1244531247.98273123, 10000)
    assert_equal Rational(11747130449709, 9439), BigDecimal.context.rationalize(BigDecimal('1244531247.982731230'), 10000)

    assert_equal Rational(11747130449709, 9439), Flt::DecNum.context.rationalize(Flt::DecNum('1244531247.982731230'), 10000)

    # approximate a value in [0.671,0.672];
    #  Float
    assert_equal [43,64], R[Flt.Tolerance(0.0005)].rationalize(0.6715)
    assert_equal [43,64], R[Rational(5,10000)].rationalize(0.6715)
    # BinNum
    assert_equal [43,64], R[Flt.Tolerance(Flt.BinNum('0.0005'))].rationalize(Flt::BinNum('0.6715'))
    assert_equal [43,64], R[Flt.Tolerance(Rational(5,10000))].rationalize(Flt::BinNum('0.6715'))
    #  BigDecimal
    assert_equal [43,64], R[Flt.Tolerance('0.0005')].rationalize(BigDecimal('0.6715'))
    assert_equal [43,64], R[Rational(5,10000)].rationalize(BigDecimal('0.6715'))
    # DecNum
    assert_equal [43,64], R[Flt.Tolerance(Flt.DecNum('0.0005'))].rationalize(Flt::DecNum('0.6715'))
    assert_equal [43,64], R[Flt.Tolerance(Rational(5,10000))].rationalize(Flt::DecNum('0.6715'))
    #
    assert_equal Rational(43,64), Float.context.rationalize(0.6715, 0.0005)
    assert_equal Rational(43,64), Float.context.rationalize(0.6715, Rational(5,10000))
    assert_equal Rational(47,70), Float.context.rationalize(0.6715, 70)
    assert_equal Rational(45,67), Float.context.rationalize(0.6715, 69)
    assert_equal Rational(2,3), Float.context.rationalize(0.6715, 10)

    # some PI tests
    assert_equal Rational(899125804609,286200632530), BigDecimal.context.rationalize(BgMth.PI(64), Flt.Tolerance(Flt.DecNum('261E-24')))
    assert_equal Rational(899125804609,286200632530), BigDecimal.context.rationalize(BgMth.PI(64), Flt.Tolerance(Flt.DecNum('261E-24')))
    assert_equal Rational(899125804609,286200632530), BigDecimal.context.rationalize(BgMth.PI(64), Flt.DecNum('261E-24'))
    assert_equal Rational(899125804609,286200632530), BigDecimal.context.rationalize(BgMth.PI(64), 261E-24)

    assert_equal Rational(899125804609,286200632530), Flt::DecNum.context.rationalize(Flt::DecNum::Math.pi(64), Flt.Tolerance(Flt.DecNum('261E-24')))
    assert_equal Rational(899125804609,286200632530), Flt::DecNum.context.rationalize(Flt::DecNum::Math.pi(64), Flt.Tolerance(Flt.DecNum('261E-24')))
    assert_equal Rational(899125804609,286200632530), Flt::DecNum.context.rationalize(Flt::DecNum::Math.pi(64), Flt.DecNum('261E-24'))
    assert_equal Rational(899125804609,286200632530), Flt::DecNum.context.rationalize(Flt::DecNum::Math.pi(64), 261E-24)

    # DecNum tests
    #t = Flt.Tolerance(Flt.DecNum('1e-15'),:floating)
    t = Flt.Tolerance(20, :sig_decimals)
    @data.each do |x|
      x = Flt.BinNum(x).to_decimal_exact
      next if x.special?
      q = x.rationalize(t)
      assert t.eq?(x, Flt.DecNum(q)), "out of tolerance: #{x.inspect} #{Flt.DecNum(q)}"
    end

    # Flt tests
    #t = Flt.Tolerance(Flt.DecNum('1e-15'),:floating)
    t = Flt.Tolerance(20,:sig_decimals)
    @data.each do |x|
      x = Flt.BinNum(x)
      next if x.special?
      q = x.rationalize(t)
      assert t.eq?(x, Flt.BinNum(q)), "out of tolerance: #{x.inspect} #{Flt.BinNum(q)}"
    end
  end

  def test_compare_algorithms
    r = R[Flt.Tolerance(1e-5,:floating)]
    @data.each do |x|
      q1 = r.rationalize_Knuth(x)
      q2 = r.rationalize_Horn(x)
      q3 = r.rationalize_HornHutchins(x)
      #q4 = r.rationalize_KnuthB(x)
      q1 = [-q1[0],-q1[1]] if q1[1] < 0
      q2 = [-q2[0],-q2[1]] if q2[1] < 0
      q3 = [-q3[0],-q3[1]] if q3[1] < 0
      assert_equal q1, q2
      assert_equal q1, q3
      #assert_equal q1, q4
    end
    r = R[Flt.Tolerance(:epsilon)]
    @data.each do |x|
      q1 = r.rationalize_Knuth(x)
      q2 = r.rationalize_Horn(x)
      q3 = r.rationalize_HornHutchins(x)
      q1 = [-q1[0],-q1[1]] if q1[1] < 0
      q2 = [-q2[0],-q2[1]] if q2[1] < 0
      q3 = [-q3[0],-q3[1]] if q3[1] < 0
      #q4 = r.rationalize_KnuthB(x)
      assert_equal q1, q2
      assert_equal q1, q3
      #assert_equal q1, q4
    end
  end

end
