Rational Number Package

From CSWiki
Revision as of 14:04, 15 December 2009 by Rdpoor (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Overview

The Rational class lets you represent numbers as a quotient of two integers. One advantage of rational representation is that precicion is mainatained across arithmatic operations: 2/3 is really 2/3 and not .6666667.

Constructing, destructing operations

  • fun static Rational create(int i) -- create Rational with a numerator of n and denominator of 1
  • fun static Rational create(float v) -- create a Rational with max denominator of 10000 (default)
  • fun static Rational create(float v, int dlimit) -- create a Rational with max denominator of dlimit
  • fun static Rational create(Rational other) -- create a copy of the other Rational
  • fun static Rational create(int n, int d) -- create a normalized Rational from numerator n and denominator d.
  • fun int num() -- return the numerator of the receiver
  • fun int den() -- return the denominator of the receiver.

Conversion to other types

  • fun float toFloat() -- return a floating point approximation of the receiver
  • fun string toString() -- return a string representation of receiver, e.g. "2/3"

Comparison operations

  • fun int signum() -- return -1, 0, +1 if receiver is less than, equal to or greater than zero.
  • fun int cmp(int i) -- return -1, 0, +1 if receiver is less than, equal to or greater than i.
  • fun int cmp(Rational other) -- return -1, 0, +1 if receiver is less than, equal to, or greater than other.

=== Integer approximation

  • fun int trunc() -- return the largest integer no greater in magnitude than the receiver ("round towards zero")
  • fun int floor() -- return the largest integer no greater than the receiver ("round towards minus infinity")
  • fun int ceil() -- return the smallest integer no less than the receiver ("round towards positive infinity")
  • fun int round() -- return the closest integer to the receiver, rounding to even when the receiver is halfway between two integers.

div and mod

  • fun int div(Rational other) -- returns receiver divided by other using truncating division
  • fun int div(int i) -- returns receiver divided by i using truncating division
  • fun int mod(Rational other) -- return receiver modulus other using truncating division
  • fun int mod(int i) -- returns receiver modulus i using truncating division

unary operations

  • fun Rational abs() -- returns a new Rational that is the absolute value of receiver
  • fun Rational abs_() -- destructively modifies receiver to its absolute value and returns it
  • fun Rational abs(Rational result) -- sets result to the absolute value of receiver and returns it
  • fun Rational negate() -- returns a new Rational that is the negative of the receiver
  • fun Rational negate_() -- destructively modifies the receiver to its negative and returns is
  • fun Rational negate(Rational result) -- sets result to the negative of the receiver and returns it.
  • fun Rational reciprocal() -- returns a new Rational set to 1/receiver
  • fun Rational reciprocal_() { return reciprocal(this); }
  • fun Rational reciprocal(Rational result) {


  signum(), cmp()

Arithmetic operations

  div(), mod(), abs(), negate(), reciprocal(), add(), sub(), mul(),
  quo()

Destructive operations

Most operations are non-destructive -- they create a new rational rather than modify the receiver -- but a number of operations include versions that explicitly modify the receiver. These are indicated by a trailing '_' in the method name:

  set_(), norm_(), abs_(), negate_(), reciprocal_(), add_(), sub_(),
  mul_(), quo_()

Rational Approximation

Of special note is the internal method that converts a floating point value to a rational. We use a modified version of Farey's technique, which is efficient and numerically stable. The method allows you to specify the largest permissable denominator used to approximate the floating point value. Even limiting the denominator to less than 1000 produces rational approximations that are very close to their irrational counterparts, as can be seen in this example:

 // file: rational_eg.ck
 
 fun void test(float v, int dlimit) {
   Rational.create(v, dlimit) @=> Rational @ r;
   "Rational.create("+v+","+dlimit+")" => string label;
   <<< label, "~=", r.toString(), "~=", r.toFloat(), "err=", (r.toFloat()-v)*100/v, "%" >>>;
 }
 
 test(pi, 100);
 test(pi, 1000);
 test(pi, 10000);
 
 Math.sqrt(10.0) => float SQRT_10;
 test(SQRT_10, 100);
 test(SQRT_10, 1000);
 test(SQRT_10, 10000);
 
 Math.pow(2.0, 1/12.0) => float SEMITONE;
 test(SEMITONE, 100);
 test(SEMITONE, 1000);
 test(SEMITONE, 10000);

...which produces the following:

 bash-3.2$ chuck rational.ck rational_eg.ck
 Rational.create(3.1416,100) ~= 22/7 ~= 3.142857 err= 0.040250 % 
 Rational.create(3.1416,1000) ~= 355/113 ~= 3.141593 err= 0.000008 % 
 Rational.create(3.1416,10000) ~= 355/113 ~= 3.141593 err= 0.000008 % 
 Rational.create(3.1623,100) ~= 117/37 ~= 3.162162 err= -0.003652 % 
 Rational.create(3.1623,1000) ~= 721/228 ~= 3.162281 err= 0.000096 % 
 Rational.create(3.1623,10000) ~= 27379/8658 ~= 3.162278 err= 0.000000 % 
 Rational.create(1.0595,100) ~= 89/84 ~= 1.059524 err= 0.005731 % 
 Rational.create(1.0595,1000) ~= 196/185 ~= 1.059459 err= -0.000343 % 
 Rational.create(1.0595,10000) ~= 7893/7450 ~= 1.059463 err= -0.000001 % 

Links to the code, example file and test file

Comments, questions are welcome

rdpoor (at) gmail (dot) com