#
# Tests for
# An extension to the standard library's Rational
#
# Includes conversions from String and Float, inversion, reduction methods
# (trim and approximate), and a to_s with a base conversion parameter.
#
# A bunch of this code is taken from the Python project:
#   http://cvs.sourceforge.net/viewcvs.py/python/python/nondist/sandbox
#   /rational/Rational.py?rev=1.3&view=markup
#
# Author: Dave Burt <dave at burt.id.au>
# Created: 5 May 2005
# Last modified: 8 May 2005
#

require 'test/unit'
require 'test/unit/ui/console/testrunner'

class TC_Rational < Test::Unit::TestCase
	def setup
		@r1 = Rational(1)
		@rs = [
			Rational(2, 2),
			Rational(1, 2),
			Rational(0, 2),
			Rational(-1, 2),
			Rational(2, -2)
		]
	end
	def assert_rat(rat, n, d, message = "")
		assert_equal(Rational, rat.class, message)
		assert_equal(n, rat.numerator, message)
		assert_equal(d, rat.denominator, message)
	end
	def test_new_from_int
		#	Rationals can be created easily:
		assert_rat(Rational(1), 1, 1)
	end
	def test_to_s
		#	And are printed in friendly (though misleading ways)
		assert_equal("1", @r1.to_s)
		assert_equal("Rational(1, 1)", @r1.inspect)
		assert_equal("1/2", (@r1 / 2).to_s)
		assert_equal("Rational(1, 2)", (@r1 / 2).inspect)
	end
	def test_new_from_2_ints
		#	There are many ways to build rationals:
		#
		#	From numerators and denominators:
		assert_rat(Rational(1, 2), 1, 2)
	end
	def test_new_from_float
		#	From floats:
		assert_rat(Rational(1.3), 5854679515581645, 4503599627370496)
		assert_in_delta(1.3, Rational(1.3).to_f, 1e-13)
	end
		#	Not from complex numbers, even if they have 0 imaginary part:
		#
		#>>> rational(1j)
		#Traceback (most recent call last):
		#  File "<stdin>", line 1, in ?
		#  File "Rational.py", line 418, in rational
		#    raise TypeError('cannot convert arguments')
		#TypeError: cannot convert arguments
	def test_new_from_string
		#	But there is no problem with using floating point literals -- just
		#	protect them by quotes.
		
		assert_rat(Rational("1.3"), 13, 10)
		assert_rat(Rational("1.2e-3"), 3, 2500)
		
		#	From ``rational'' literals
		assert_rat(Rational("1/2"), 1, 2)
		
		#	Or even mix the two
		assert_rat(Rational("1.5/2.3"), 15, 23)
	end
	def test_new_from_2_strings
		#	Or give them as seperate arguments:
		assert_rat(Rational("1.5", "2.3"), 15, 23)
	end
	def test_z_big_rationals
		#	*Note*: The following calculation takes some time
		p = (1..1000).inject(0) do |m, i|
			m += Rational(1) / i
		end
		
		#	And p is *very* large. Rationals explode quickly in term of space
		#	and (as you have noticed if you tried the above calculation) time.
		assert_rat(p,
			53362913282294785045591045624042980409652472280384260097101349248456268889497101757506097901985035691409088731550468098378442172117885009464302344326566022502100278425632852081405544941210442510142672770294774712708917963967779610453224692426866468888281582071984897105110796873249319155529397017508931564519976085734473014183284011724412280649074307703736683170055800293659235088589360235285852808160759574737836655413175508131522517,
			7128865274665093053166384155714272920668358861885893040452001991154324087581111499476444151913871586911717817019575256512980264067621009251465871004305131072686268143200196609974862745937188343705015434452523739745298963145674982128236956232823794011068809262317708861979540791247754558049326475737829923352751796735248042463638051137034331214781746850878453485678021888075373249921995672056932029099390891687487672697950931603520000)
		
		#	printing p is not that user friendly, either.
		assert_equal(
			"53362913282294785045591045624042980409652472280384260097101349248456268889497101757506097901985035691409088731550468098378442172117885009464302344326566022502100278425632852081405544941210442510142672770294774712708917963967779610453224692426866468888281582071984897105110796873249319155529397017508931564519976085734473014183284011724412280649074307703736683170055800293659235088589360235285852808160759574737836655413175508131522517/7128865274665093053166384155714272920668358861885893040452001991154324087581111499476444151913871586911717817019575256512980264067621009251465871004305131072686268143200196609974862745937188343705015434452523739745298963145674982128236956232823794011068809262317708861979540791247754558049326475737829923352751796735248042463638051137034331214781746850878453485678021888075373249921995672056932029099390891687487672697950931603520000",
			p.to_s)
		
		#	We can convert p to float. Float conversion always works:
		assert_in_delta(7.48547086055035, p.to_f, 1e-13, "Rational#to_f")
		
		#	even though naive conversion (e.g. stdlib Rational) wouldn't:
		assert_equal("NaN", (p.numerator.to_f / p.denominator.to_f).to_s)
		
		#	To conserve time, we can trim p: find the closest rational with 
		#	a bounded denominator
		assert_rat(p.trim(1 << 32), 5466869645, 730330763, "Rational#trim")
		
		#	Subtracting them gives unhelpful results :(
		assert_rat(p.trim(1 << 32) - p,
			2577788201482067066737007241662322009004442191323142940957793410960773544195656189405713838657873990794707120563414218257216295235776725961380581853243676529101802869319854943132085885976475008351302459480401130337393807751564961514389284506570878030840913462101938626328125070941767634509281662396253877243099231501640465588462092742122865407143955103028387230105708261249863249616117628465334447491104829810234506275473,
			71320953635210438068287738443752270577287136952862135385886307932565161118251945010968569287591037621277475163624668260617062954252700483307577101904571817275753022912958806190588423562473724837435481769458741782517775581058762874626392603976928864721352245397944650158649037211802689156395786255639867340910229285743487833516783255380045214702551644603765865129523825997928602015415353282959410250017758257005112733624503731706295549120000)
		
		#	But we can measure the error as a floating point number to get a rough
		#	estimate:
		assert_in_delta(3.61434903782531e-020, (p.trim(1 << 32) - p).to_f, 1e-13)
		
		#	We can also approximate p: find the closest rational which is closer then
		#	the bound (shifting rationals is the same as multiplying by 2 to the correct
		#	power. Right shifting means negative powers, of course...)
		assert_rat(p.approximate(2e-10), 231069, 30869, "Rational#approximate")
		assert_in_delta(7.48547086073407, p.approximate(2e-10).to_f, 1e-13)
		assert_in_delta(1.83725035185489e-010, p.approximate(2e-10).to_f - p, 1e-13)
	end
	def test_x_bitconjurer_order
		@rs.inject do |a, b|
			assert_operator(a, :>, b)
			b
		end
	end
	def test_x_bitconjurer_eq
		assert_equal(Rational(0), Rational(0, 2))
		assert_equal(Rational(-1, 1), Rational(2, -2))
		assert_equal(Rational(6, 9), Rational(-2, -3))
	end
	def test_x_bitconjurer_hash
		assert_equal(Rational(0, 2).hash, 0.hash)
		assert_equal(Rational(1, 1).hash, 1.hash)
		assert_equal(Rational(-1, 1).hash, -1.hash)
	end
	def test_x_bitconjurer_unary_neg
		assert_equal(-@rs[2], @rs[2])
		assert_equal(-@rs[0], @rs[4])
		assert_equal(--@rs[0], @rs[0])
	end
	def test_x_bitconjurer_compare_int
		assert_operator(@rs[0], :==, 1)
		assert(@rs[1] != 1)  # Ruby language uses == operator - can't send
		                     # "!=" to assert_operator
		assert_operator(@rs[0], :<, 2)
		assert_operator(@rs[0], :>, 0)
		assert_operator(@rs[1], :<, 1)
		assert_operator(@rs[1], :>, 0)
	end
	def test_x_bitconjurer_math
		assert_equal(@rs[1] - 1, @rs[3])
		assert_equal(1 - @rs[1], @rs[1])
		assert_equal(@rs[1] + @rs[3], 0)
		assert_equal(@rs[1] * 2, 1)
		assert_equal(@rs[1] * @rs[3], Rational(1, -4))
		assert_equal(@rs[1] / @rs[3], -1)
		assert_equal(2 / @rs[1], 4)
		assert_equal(@rs[1] / 2, Rational(1, 4))
	end
end

Test::Unit::UI::Console::TestRunner.run(TC_Rational)
