User:NXTBoy/Scripts/Fraction

local Fraction = {}
Fraction.new = function(num, denom)
	return setmetatable({
		numerator = num or 0,
		denominator = denom or 0
	}, Fraction):simplify()
end
Fraction.toFloat = function(self)
	return self.numerator / self.denominator
end
Fraction.simplify = function(self)
	for i = 2, self.q do
		while self.p % i == 0 and self.q % i == 0 do
			self.p = self.p / i
			self.q = self.q / i
		end
	end
	return self
end
-- Metatable bits and pieces
Fraction.__index = function(self, k)
	if k == "p" or k == "top" then
		return self.numerator
	elseif k == "q" or k == "bottom" then
		return self.denominator
	else
		return Fraction[k]
	end
end
Fraction.__tostring = function(self)
	return self.p.."/"..self.q
end
Fraction.__unm = function(self)
	return Fraction.new(
		-self.p,
		self.q
	)
end
Fraction.__pow = function(a, b)
	if getmetatable(a) == Fraction and getmetatable(b) == Fraction then
		return a:toFloat() ^ b:toFloat()
	elseif getmetatable(a) == Fraction and type(b) == "number" then
		return Fraction.new(
			a.p ^ b,
			a.q ^ b
		)
	elseif type(a) == "number" and getmetatable(b) == Fraction then
		return a ^ b:toFloat()
	else
		error(string.format("attempt to raise a %s to the power of a %s", type(a), type(b)), 2)
	end
end
Fraction.__add = function(a, b)
	if getmetatable(a) == Fraction and getmetatable(b) == Fraction then
		return Fraction.new(
			a.p * b.q + a.q * b.p,
			a.q * b.q
		)
	elseif getmetatable(a) == Fraction and type(b) == "number" then
		return Fraction.new(
			a.p + b * a.q,
			a.q
		)
	elseif type(a) == "number" and getmetatable(b) == Fraction then
		return Fraction.new(
			b.p + a * b.q,
			b.q
		)
	else
		error(string.format("attempt to add a %s to a %s", type(a), type(b)), 2)
	end
end
Fraction.__sub = function(a, b)
	if getmetatable(a) == Fraction and getmetatable(b) == Fraction then
		return Fraction.new(
			a.p * b.q - a.q * b.p,
			a.q * b.q
		)
	elseif getmetatable(a) == Fraction and type(b) == "number" then
		return Fraction.new(
			a.p - b * a.q,
			a.q
		)
	elseif type(a) == "number" and getmetatable(b) == Fraction then
		return Fraction.new(
			b.p - a * b.q,
			b.q
		)
	else
		error(string.format("attempt to subtract a %s from a %s", type(a), type(b)), 2)
	end
end
Fraction.__mul = function(a, b)
	if getmetatable(a) == Fraction and getmetatable(b) == Fraction then
		return Fraction.new(
			a.p * b.p,
			a.q * b.q
		)
	elseif getmetatable(a) == Fraction and type(b) == "number"  then
		return Fraction.new(
			a.p * b,
			a.q
		)
	elseif type(a) == "number" and getmetatable(b) == Fraction then
		return Fraction.new(
			a * b.p,
			b.q
		)
	else
		error(string.format("attempt to multiply a %s by a %s", type(a), type(b)), 2)
	end
end
Fraction.__div = function(a, b)
	if getmetatable(a) == Fraction and getmetatable(b) == Fraction then
		return Fraction.new(
			a.p * b.q,
			a.q * b.p
		)
	elseif getmetatable(a) == Fraction and type(b) == "number"  then
		return Fraction.new(
			a.p,
			a.q * b
		)
	elseif type(a) == "number" and getmetatable(b) == Fraction then
		return Fraction.new(
			a * b.q,
			b.p
		)
	else
		error(string.format("attempt to divide a %s by a %s", type(a), type(b)), 2)
	end
end

--Important bit
Fraction.fromFloat = function(input, maxDenominator)
	maxDenominator = maxDenominator or 1e99

	local f0 = Fraction.new(1, 0)
	local f1 = Fraction.new(math.floor(input), 1)
	local f2

	local r = input % 1
	local next_cf

	while math.abs(r) >= 0.0001 do
		r = 1 / r
		next_cf = math.floor(r)
		f2 = Fraction.new(
			next_cf * f1.p + f0.p,
			next_cf * f1.q + f0.q
		)

		-- Limit the denominator
		if f2.denominator > maxDenominator then break end

		-- remember the last two fractions
		f0 = f1
		f1 = f2

		r = r - next_cf
	end
	return f1
end

Using it:

print(Fraction.fromFloat(1/3) + Fraction.fromFloat(1/2)) --5/6
print(Fraction.new(1, 6) + Fraction.new(1, 3)) --1/2

print(Fraction.fromFloat(math.pi, 100)) --22/7