Skip to content

Commit

Permalink
Make BigRational.new(BigFloat) exact (crystal-lang#13295)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored and straight-shoota committed Apr 13, 2023
1 parent 2bf6c5c commit 884a48e
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 6 deletions.
8 changes: 8 additions & 0 deletions spec/std/big/big_rational_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ describe BigRational do
end
end

it "initializes from BigFloat with high precision" do
(0..12).each do |i|
bf = BigFloat.new(2.0, precision: 64) ** 64 + BigFloat.new(2.0, precision: 64) ** i
br = BigRational.new(bf)
br.should eq(bf)
end
end

it "#numerator" do
br(10, 3).numerator.should eq(BigInt.new(10))
end
Expand Down
26 changes: 20 additions & 6 deletions src/big/big_rational.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ struct BigRational < Number
include Comparable(Int)
include Comparable(Float)

private MANTISSA_BITS = 53
private MANTISSA_SHIFT = (1_i64 << MANTISSA_BITS).to_f64

# Creates a new `BigRational`.
#
# If *denominator* is 0, this will raise an exception.
Expand All @@ -47,12 +44,29 @@ struct BigRational < Number
end

# Creates a exact representation of float as rational.
def initialize(num : Float)
def initialize(num : Float::Primitive)
# It ensures that `BigRational.new(f) == f`
# It relies on fact, that mantissa is at most 53 bits
frac, exp = Math.frexp num
ifrac = (frac.to_f64 * MANTISSA_SHIFT).to_i64
exp -= MANTISSA_BITS
ifrac = Math.ldexp(frac.to_f64, Float64::MANT_DIGITS).to_i64
exp -= Float64::MANT_DIGITS
initialize ifrac, 1
if exp >= 0
LibGMP.mpq_mul_2exp(out @mpq, self, exp)
else
LibGMP.mpq_div_2exp(out @mpq, self, -exp)
end
end

# :ditto:
def initialize(num : BigFloat)
frac, exp = Math.frexp num
prec = LibGMP.mpf_get_prec(frac)
# the mantissa has at most `prec + 1` bits, because the first fractional bit
# of `frac` is always 1, and `prec` variable bits follow
# TODO: use `Math.ldexp` after #11007
ifrac = BigFloat.new { |mpf| LibGMP.mpf_mul_2exp(mpf, frac, prec + 1) }.to_big_i
exp -= prec + 1
initialize ifrac, 1
if exp >= 0
LibGMP.mpq_mul_2exp(out @mpq, self, exp)
Expand Down
1 change: 1 addition & 0 deletions src/big/lib_gmp.cr
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ lib LibGMP
# # Precision
fun mpf_set_default_prec = __gmpf_set_default_prec(prec : BitcntT)
fun mpf_get_default_prec = __gmpf_get_default_prec : BitcntT
fun mpf_get_prec = __gmpf_get_prec(op : MPF*) : BitcntT

# # Conversion
fun mpf_get_str = __gmpf_get_str(str : UInt8*, expptr : MpExp*, base : Int, n_digits : LibC::SizeT, op : MPF*) : UInt8*
Expand Down

0 comments on commit 884a48e

Please sign in to comment.