Skip to content

Instantly share code, notes, and snippets.

@will
Last active April 16, 2016 06:54
Show Gist options
  • Save will/3b5e2d8e33d90436a2174a5c3b1e9dd9 to your computer and use it in GitHub Desktop.
Save will/3b5e2d8e33d90436a2174a5c3b1e9dd9 to your computer and use it in GitHub Desktop.
# https://github.com/wbhart/mpir/blob/master/gmp-h.in#L262-L266
# https://gmplib.org/manual/Rational-Arithmetic.html#Rational-Arithmetic
require "spec"
require "big_int"
lib LibGMP
struct MPQ
_mp_num : MPZ
_mp_den : MPZ
end
fun mpq_init = __gmpq_init(x : MPQ*)
fun mpq_get_str = __gmpq_get_str(str : UInt8*, base : Int, op : MPQ*) : UInt8*
fun mpq_set_num = __gmpq_set_num(x : MPQ*, num : MPZ*)
fun mpq_set_den = __gmpq_set_den(x : MPQ*, den : MPZ*)
fun mpq_get_num = __gmpq_get_num(rop : MPZ*, op : MPQ*)
fun mpq_get_den = __gmpq_get_den(rop : MPZ*, op : MPQ*)
fun mpq_denref = __gmpq_numref(op : MPQ*) : MPZ*
fun mpq_canonicalize = __gmpq_canonicalize(x : MPQ*)
fun mpq_get_d = __gmpq_get_d(x : MPQ*) : Float64
# compare
fun mpq_cmp = __gmpq_cmp(x : MPQ*, o : MPQ*) : Int32
# math
fun mpq_add = __gmpq_add(rop : MPQ*, op1 : MPQ*, op2 : MPQ*)
fun mpq_sub = __gmpq_sub(rop : MPQ*, op1 : MPQ*, op2 : MPQ*)
fun mpq_mul = __gmpq_mul(rop : MPQ*, op1 : MPQ*, op2 : MPQ*)
fun mpq_div = __gmpq_div(rop : MPQ*, op1 : MPQ*, op2 : MPQ*)
fun mpq_inv = __gmpq_inv(rop : MPQ*, op1 : MPQ*)
fun mpq_neg = __gmpq_neg(rop : MPQ*, op1 : MPQ*)
fun mpq_abs = __gmpq_abs(rop : MPQ*, op1 : MPQ*)
fun mpq_div_2exp = __gmpq_div_2exp(q : MPQ*, n : MPQ*, b : BitcntT)
fun mpq_mul_2exp = __gmpq_mul_2exp(rop : MPQ*, op1 : MPQ*, op2 : BitcntT)
end
class BigRational
include Comparable(BigRational)
include Comparable(Int)
def initialize(numerator : Int, denominator : Int)
check_division_by_zero denominator
initialize BigInt.new(numerator), BigInt.new(denominator)
end
def initialize(numerator : BigInt, denominator : BigInt)
check_division_by_zero denominator
LibGMP.mpq_init(out @mpq)
LibGMP.mpq_set_num(mpq, numerator.to_unsafe)
LibGMP.mpq_set_den(mpq, denominator.to_unsafe)
LibGMP.mpq_canonicalize(mpq)
end
# :nodoc:
def initialize(@mpq : LibGMP::MPQ)
end
# :nodoc:
def self.new
LibGMP.mpq_init(out mpq)
yield pointerof(mpq)
new(mpq)
end
def numerator
BigInt.new { |mpz| LibGMP.mpq_get_num(mpz, self) }
end
def denominator
BigInt.new { |mpz| LibGMP.mpq_get_den(mpz, self) }
end
def <=>(other : BigRational)
LibGMP.mpq_cmp(mpq, other)
end
def <=>(other : BigRational::Coercible)
LibGMP.mpq_cmp(mpq, other.to_big_r)
end
def +(other : BigRational)
BigRational.new { |mpq| LibGMP.mpq_add(mpq, self, other) }
end
def +(other : BigRational::Coercible)
self + other.to_big_r
end
def -(other : BigRational)
BigRational.new { |mpq| LibGMP.mpq_sub(mpq, self, other) }
end
def -(other : BigRational::Coercible)
self - other.to_big_r
end
def *(other : BigRational)
BigRational.new { |mpq| LibGMP.mpq_mul(mpq, self, other) }
end
def *(other : BigRational::Coercible)
self * other.to_big_r
end
def /(other : BigRational)
check_division_by_zero other
BigRational.new { |mpq| LibGMP.mpq_div(mpq, self, other) }
end
def /(other : BigRational::Coercible)
self / other.to_big_r
end
def >>(other : Int)
BigRational.new { |mpq| LibGMP.mpq_div_2exp(mpq, self, other) }
end
def <<(other : Int)
BigRational.new { |mpq| LibGMP.mpq_mul_2exp(mpq, self, other) }
end
def -
BigRational.new { |mpq| LibGMP.mpq_neg(mpq, self) }
end
def inv
check_division_by_zero self
BigRational.new { |mpq| LibGMP.mpq_inv(mpq, self) }
end
def abs
BigRational.new { |mpq| LibGMP.mpq_abs(mpq, self) }
end
def to_f
to_f64
end
def to_f32
to_f64.to_f32
end
def to_f64
LibGMP.mpq_get_d(mpq)
end
def to_s
String.new(to_cstr)
end
def to_s(io)
str = to_cstr
io.write_utf8 Slice.new(str, LibC.strlen(str))
end
def inspect
to_s
end
def inspect(io)
to_s io
end
private def mpq
pointerof(@mpq)
end
def to_unsafe
mpq
end
private def to_cstr
LibGMP.mpq_get_str(nil, 10, mpq)
end
private def check_division_by_zero(value)
# TODO why does swapping value == 0 fail
raise DivisionByZero.new if 0 == value
end
module Coercible
include Comparable(BigRational)
abstract def to_big_r : BigRational
def <=>(other : BigRational)
-(other <=> self)
end
def +(other : BigRational)
other + self
end
def -(other : BigRational)
self.to_big_r - other
end
def /(other : BigRational)
self.to_big_r / other
end
def *(other : BigRational)
other * self
end
end
end
struct Int
include BigRational::Coercible
def to_big_r
BigRational.new(self, 1)
end
end
require "./bigq"
def b(n, d)
BigRational.new(n, d)
end
describe BigRational do
it "initialize" do
BigRational.new(BigInt.new(10), BigInt.new(3))
.should eq(BigRational.new(10, 3))
expect_raises(DivisionByZero) do
BigRational.new(BigInt.new(2), BigInt.new(0))
end
expect_raises(DivisionByZero) do
BigRational.new(2, 0)
end
end
it "#numerator" do
b(10, 3).numerator.should eq(BigInt.new(10))
end
it "#denominator" do
b(10, 3).denominator.should eq(BigInt.new(3))
end
it "#to_s" do
BigRational.new(10, 3).to_s.should eq("10/3")
BigRational.new(90, 3).to_s.should eq("30")
BigRational.new(1, 98).to_s.should eq("1/98")
end
it "#to_f64" do
r = BigRational.new(10, 3)
f = 10.to_f64 / 3.to_f64
r.to_f64.should be_close(f, 0.001)
end
it "#to_f" do
r = BigRational.new(10, 3)
f = 10.to_f64 / 3.to_f64
r.to_f.should be_close(f, 0.001)
end
it "#to_f" do
r = BigRational.new(10, 3)
f = 10.to_f64 / 3.to_f64
r.to_f32.should be_close(f, 0.001)
end
it "Int#to_big_r" do
3.to_big_r.should eq(b(3, 1))
end
it "#<=>(:BigRational) and Compareable" do
a = BigRational.new(11, 3)
b = BigRational.new(12, 3)
e = a
l = BigRational.new(10, 3)
# sainty check things aren't swapped
[b, e, l].each { |o| (a <=> o).should eq(a.to_f <=> o.to_f) }
(a < b).should eq(true)
(a <=> b).should eq(-1)
(a == e).should eq(true)
(a <=> e).should eq(0)
(a > l).should eq(true)
(a <=> l).should eq(1)
end
it "#<=>(:Int) and Compareable" do
a = BigRational.new(10, 2)
(a < 6).should eq(true)
(a <=> 6).should eq(-1)
# (a == 5).should eq(true) #fails
(5 == a).should eq(true)
(a <=> 5).should eq(0)
(a > 4).should eq(true)
(a <=> 4).should eq(1)
end
it "#+" do
(b(10, 7) + b(3, 7)).should eq(b(13, 7))
(0 + b(10, 7) + 3).should eq(b(31, 7))
end
it "#-" do
(b(10, 7) - b(3, 7)).should eq(b(7, 7))
(b(10, 7) - 3).should eq(b(-11, 7))
(0 - b(10, 7)).should eq(b(-10, 7))
end
it "#*" do
(b(10, 7) * b(3, 7)).should eq(b(30, 49))
(1 * b(10, 7) * 3).should eq(b(30, 7))
end
it "#/" do
(b(10, 7) / b(3, 7)).should eq(b(10, 3))
expect_raises(DivisionByZero) { b(10, 7) / b(0, 10) }
(b(10, 7) / 3).should eq(b(10, 21))
(1 / b(10, 7)).should eq(b(7, 10))
end
it "#- (negation)" do
(-b(10, 3)).should eq(b(-10, 3))
end
it "#inv" do
(b(10, 3).inv).should eq(b(3, 10))
expect_raises(DivisionByZero) { b(0, 3).inv }
end
it "#abs" do
(b(-10, 3).abs).should eq(b(10, 3))
end
it "#<<" do
(b(10, 3) << 2).should eq(b(40, 3))
end
it "#>>" do
(b(10, 3) >> 2).should eq(b(5, 6))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment