4

Given an arbitrary large (or small) Rational number that has a finite decimal representation, e.g.:

r = Rational(1, 2**15)
#=> (1/32768)

How can I get its full decimal value as a string?

The expected output for the above number is:

"0.000030517578125"

to_f apparently doesn't work:

r.to_f
#=> 3.0517578125e-05

And sprintf requires me to specify the number of digits:

sprintf('%.30f', r)
#=> "0.000030517578125000000000000000"
Stefan
  • 109,145
  • 14
  • 143
  • 218

3 Answers3

1
a = sprintf('%.30f', r)
a.gsub(/0*\z/,'')

That's all :) (or should :P) It's not the best way, if the value have more than 30 decimals, you need to add more than 30 zeroes in sprintf. I think there is a better way to do it, but this way it works

Edited

require 'bigdecimal'
require 'bigdecimal/util'
b = BigDecimal.new(r, (r.denominator * r.numerator))
b.to_digits

Note about this solution. (r.denominator * r.numerator) It's the precision, the precision will never be bigger than denominator * numerator (I think, but a mathematician can tell you this)

Edit 2

r = BigDecimal("1") / (BigDecimal("2") ** BigDecimal("99"))
r.to_digits
# Example
r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("99"))
r.to_digits
# "0.000000000000000000000000000001577721810442023610823457130565572459346412870218046009540557861328125"

But reeeeelly big numbers, like:

r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("999999999999"))
# RangeError: integer 999999999999 too big to convert to `int'

If you need something better, I think you need to got with your own implementation of of "string divisions".

Andrés
  • 624
  • 7
  • 12
  • Problem is that I don't know the number of decimal places in advance. – Stefan Nov 25 '16 at 17:18
  • `r.denominator * r.numerator` would be `32768` using my example value. That's way to much. It will even result in an error once you reach `2**31`: ``RangeError: integer 2147483648 too big to convert to `int'`` – Stefan Nov 25 '16 at 18:21
  • How much decimals do you think you will have? Float numbers have a limit of precision, you can't go more than that, because that, there is no simple way to solve this. You will have more than 1000 decimals? Do you need more precision than that? – Andrés Nov 25 '16 at 19:01
  • I need this to _print_ the rational numbers, i.e. I want a string, not a float. And I can't use a hard limit, because the numbers are user supplied. – Stefan Nov 25 '16 at 19:06
  • Not that in my last example (Edit 2), if you do this: `r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("99999999"))` you have 30.103.999 decimals. – Andrés Nov 25 '16 at 19:23
  • Unfortunately it's not that easy: `BigDecimal#/` truncates the result (I've opened a [separate question](http://stackoverflow.com/q/40820119/477037) regarding this). `BigDecimal#div` would work, but it requires an explicit precision. – Stefan Nov 26 '16 at 15:54
1

Bigdecimal to_s has an "F" option. It takes some converting to kick this rational into shape however.

require "bigdecimal"
r = Rational(1, 2**15)
p   BigDecimal.new(r.to_f.to_s).to_s("F") # => "0.000030517578125"
steenslag
  • 79,051
  • 16
  • 138
  • 171
  • Unfortunately, this is limited to floating point precision (try `2**30`). You could avoid this by converting directly via `r.to_d` (you have to `require "bigdecimal/util"`), but `BigDecimal`'s [`Rational#to_d`](http://ruby-doc.org/stdlib-2.3.2/libdoc/bigdecimal/rdoc/Rational.html#method-i-to_d) again requires an explicit precision. – Stefan Nov 26 '16 at 08:52
1

Most ten-year-olds know how to do that: use long division!1

Code

def finite_long_division(n,d)
  return nil if d.zero?
  sign = n*d >= 0 ? '' : '-'
  n, d = n.abs, d.abs
  pwr =
  case n <=> d
  when 1 then power(n,d)
  when 0 then 0
  else        -power(d,n)-1
  end            
  n *= 10**(-pwr) if pwr < 0
  d *= 10**(pwr)  if pwr >= 0
  s = ld(n,d)
  t = s.size == 1 ? '0' : s[1..-1]
  "%s%s.%s x 10^%d" % [sign, s[0], t, pwr]
end

def power(n, d)
  # n > d
  ns = n.to_s
  ds = d.to_s
  pwr = ns.size - ds.size - 1
  pwr += 1 if ns[0, ds.size].to_i >= ds.to_i
  pwr
end

def ld(n,d)
  s = ''
  loop do # .with_object('') do |s|
    m,n = n.divmod(d)
    s << m.to_s
    return s if n.zero?
    n *= 10
  end
end

Examples2

finite_long_division(1, 2**15)
  #=> "3.0517578125 x 10^-5"
finite_long_division(-1, 2**15)
  #=> "-3.0517578125 x 10^-5"
finite_long_division(-1, -2**15)
  #=> "3.0517578125 x 10^-5"

finite_long_division(143, 16777216)
  #=> "8.523464202880859375 x 10^-6"
143/16777216.0
  #=> 8.52346420288086e-06 

finite_long_division(8671,
  803469022129495137770981046170581301261101496891396417650688)
  #=> "1.079195309486679194852923588206549145803161531099624\
  #      804222395643336829571798416196370119711226461255452\
  #      67714596064934085006825625896453857421875 x 10^-56"      

Recall that every rational number has either a decimal representation or contains an infinitely-repeating sequence of digits (e.g., 1/3 #=> 0.33333..., 3227/555 #=> 5.8144144144... and 1/9967 #=> 0.00010033109260559848...3). This method would therefore never terminate if the rational was of the repeating sequence variety. Since one generally doesn't know in advance which type a rational number is, it might be useful to modify the method to first determine if the rational number has a finite decimal representation. It is known that a rational number n/d which cannot be reduced (by removing common factors) has this property if and only if d is divisible by 2 or by 5 and is not divisible by any other prime number.4 We could easily construct a method to determine if an already-reduced rational number has that property.

require 'prime'

def decimal_representation?(n, d)
  primes = Prime.prime_division(d).map(&:first)
  (primes & [2,5]).any? && (primes - [2, 5]).empty?
end

1 At least that was true when I was a kid.

2 See here for a partial list of rational numbers that have finite decimal representations.

3 This rational number's repeating sequence contains 9,966 digits.

4 Reference.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • I've used long division, too. Fortunately, all my numbers do have a finite decimal representation. – Stefan Jan 25 '17 at 17:18