Rational - Original Numbers in Ruby

Pushing rational numbers

Your usage of Rational is wrong. Should be

mine << Rational(22, 7)

return fraction when dividing with Ruby

Here's a hint:

(8**-2).class #=> Rational

So this already exists in Ruby via the Rational class, which you can call as a method to create rational numbers.

Rational(5, 3)  #=> (5/3)
Rational('5/3') #=> (5/3)
Rational('0.3') #=> (3/10)

Check out the documentation here

Why am I getting rounding errors when using Ruby Rational?

It's not clear to me whether in this expression: current_freq * 2 ** Rational('1/12') that Ruby is keeping the entire calculation in the Rational realm. Within Ruby, you get:

2.0.0p195 :001 > current_freq = 440
=> 440
2.0.0p195 :002 > current_freq * 2 ** Rational('1/12')
=> 466.1637615180899

The calculation produces a float, not a Rational. If we kept it Rational, it would look like:

2.0.0p195 :005 > Rational( current_freq * 2 ** Rational('1/12'))
=> (4100419809895505/8796093022208)

Even if you do this:

2.0.0p195 :010 > Rational(2) ** Rational(1,12)
=> 1.0594630943592953

Ruby goes from Rational to float. The Ruby doc on Rational doesn't describe this clearly, but the examples given show this when taking a rational to a fractional exponent that's not an integer. This makes sense, since when you take a rational number to a rational (fractional, non-integer) exponent, chances are you're going to get an irrational number. 2**(1/12) is one of those cases.

So to keep accuracy, you'd need to keep everything in the Rational realm throughout which isn't really possible once you hit an irrational number. You might, as Scott Hunter suggests, be able to narrow the field with some custom functions to control the inaccuracy. It's unclear whether that would be worth the effort in this situation.

Matrix multiplication results in fraction

Your problem has been identified, but your real question has not been answered, namely, when multiplying matrix objects, when is an element of the product an integer, a rational number or a float?

If a and b are matrix objects, each element of a*b will be:

  • an integer if all elements of a and b used in its calculation are integers;
  • a rational number if, among the elements of a and b used in its calculation there is at least one rational number and the remainder are integers or rationals; and
  • a float if at least one of the elements of a and b used in its calculation is a float.

I will give a few examples, but first let's consider how Ruby expresses rational numbers. A rational number is a number that can be expressed as the ratio of two integers. For example, 1.5 is a rational number since it can be expressed 3/2. We can't write it that way in Ruby, however, since 3/2 will be replaced by 1, the result of integer division. Instead, we create an instance of the class Rational:

r = Rational(3,2)
#=> (3/2)

and use that in the calculations. (Note the parentheses in the return value.) We can extract its numerator and denominator, or convert it to an integer (rounding down or up) or a float:

r.numerator
#=> 3
r.denominator
#=> 2
r.to_i
#=> 1
r.ceil
#=> 2
r.to_f
#=> 1.5

Now let's look at some examples.

require 'matrix'

Matrix[[Rational(-1,2)]] * Matrix[[Rational(1,2)]]
#=> Matrix[[(-1/4)]]
Matrix[[-1]] * Matrix[[Rational(1,2)]]
# => Matrix[[(-1/2)]]
Matrix[[-0.5]] * Matrix[[Rational(1,2)]]
#=> Matrix[[-0.25]]
Matrix[[Rational(-1,2), Rational(1,2)]].transpose * Matrix[[Rational(1,2), 0.5]]
#=> Matrix[[(-1/4), -0.25], [(1/4), 0.25]]

Now let's consider the inverse of a matrix:

Matrix[[Rational(-1,2), 1],[2, Rational(2,3)]].inverse
#=> Matrix[[(-2/7), (3/7)], [(6/7), (3/14)]]
Matrix[[Rational(-1,2), 1.0],[2, Rational(2,3)]].inverse
#=> Matrix[[-0.2857142857142857, 0.4285714285714286],
# [0.8571428571428571, 0.21428571428571427]]

In calculating the inverse of a matrix with n rows and columns there are n "pivoting" steps. If, as in my latter example, the matrix contains a mix of integers, rationals and floats, when each pivot operation is performed:

  • each integer will be converted to a float if at least one float is used in its calculation, else it will be converted to a rational if at least one rational is used in its calculation; else it will remain an integer;
  • each rational will be converted to a float if there is at least one float used in its calculation, else it will remain a rational; and
  • each float will remain a float.

As "once a float always a float", it won't be long before all elements of the computed matrix are floats. (I believe it can be proven that the inverse will contain all floats if the original matrix contains at least one float.)

Why not use an extended rational instead of floats/doubles?

There are three main reasons "why not". If none of them apply, a rational number system may be useful for your application:

  1. Compactness. It only takes 8 bytes to store a double, 4 bytes for a float. The is not important if you are dealing with one or two of them. It does matter for matrices with thousands of rows and columns.
  2. Performance. As Pascal Cuoq pointed out in a comment on the question, many important floating point operations are very fast on modern computers.
  3. Need to approximate irrational numbers such as pi and the values of functions such as square root and the trig functions. If you need to deal with rounding anyway, general rational numbers lose a lot of their attraction. Floating point numbers are, of course, rational, but with carefully designed rounding implemented in fast hardware.

If you think your Rational class has usable compactness and performance for scientific and engineering work, I think you should use it to run the LINPACK benchmarks, especially LINPACK 1000, and post your results for comparison to results for similar systems using floating point arithmetic. LINPACK 1000 times solving a system of 1000 simultaneous linear equations

What does the quo method do?

quo is a method defined on the Numeric class (and redefined in the Float class), which calculates the quotient of the receiver with the given argument. In other words, x.quo(y) is roughly equivalent to x / y, but more precise.

The difference here comes in when x and y are Fixnums (ie. an integer value):

> (1 / 2)
=> 0
> (1 / 2).class
=> Fixnum
> 1.quo(2)
=> (1/2)
> 1.quo(2).class
=> Rational
> 1.quo(2.5)
=> 0.4
> 1.quo(2.5).class
=> Float

Basically, quo ensures that the result of the division is expressed accurately by returing a Rational or Float, depending on the receiver and argument.

In Javascript, there isn't a distinction between different types of numbers, and division returns a floating point number already if necessary, so the first method can be expressed as:

obs * Math.pow(1 - 1 / (9 * obs) - z_value / (3 * Math.sqrt(obs)), 3)


Related Topics



Leave a reply



Submit