2013-04-07

$\frac{p}{r}$: just the fracts, ma'am

This is the first in a series of posts in which I plan to document myself attempting to wring some mathematics out of Javascript. With Mathjax we can now display complex formulae and equations easily; with Raphaël, we have at least a glimmer of a hope of displaying graphs. I have a number of ideas in mind involving both these technologies, but I thought it would be better to start out small.



The Fractionizer

Improper fraction
Integer + Proper Fraction


Yeah, I gave one of my doohickeys a name ending in -izer. So unlike me. Unfortunately the usual suffix* would have resulted in The Fractioninator, which even I concede would perhaps have been a step too far.

Why?


Fractions are hard. Especially in Javascript, where there is no native number type for storing fractions. Everything is a float, which frankly can be a bit of a chore. $0.\overline{666}$ for example, is obviously $\frac{2}{3}$, but what of $1.21\overline{21}$ or $0.5476190\overline{476190}$?

[They're $\frac{40}{33}$ and $\frac{23}{42}$, respectively, FYI, but don't take my word for it - fractionize those mothers!]

There are two reasons I made this. Firstly, I want to do some linear equation graphing with Raphaël at some point, and I don't want my slopes of the line to be ugly decimals if I can help it. Secondly, I was bored at work and had a few minutes to spare.

How's it work?


Like this:

/* Returns an object containing the parts of the fraction.
 * @param Number frac
 *   the number to try and convert to a fraction
 * @param Boolean wholenum
 *   true: returns integer + proper fraction
 *   false: returns improper (possibly top-heavy) fraction
 */
Math.fractionize = function(frac, wholenum) {
  //If the argument is not convertible to a number, end here.
  if (isNaN(frac)) return null;
  
  //Run through fractions from 1/2 to 1/100000 (arbitrary range)
  for (var i = 2; i < 100000; i++) {
    
    //Attempt to find a fraction which, when multiplied 
    // by frac, approximates an integer, e.g. 2.333 * 3 ≈ 7
    var testnum = frac * i;
    
    //Potential numerator value, if everything checks out.
    // e.g. Math.round(2.333 * 3) = 7
    var pnum = Math.round(testnum);
    
    //If the difference between the pre-rounded number and 
    // the rounded one is so small as to be negligible, we 
    // assume that if done with a more accurate computer it 
    // would be equal. Thus we assume it is rational.
    if ( Math.abs(testnum - pnum) < 0.0000001 ) {
      
      //If wholenum is set to true, we want to return an 
      // integer followed by a fraction whose numerator is 
      // smaller than its denominator, e.g. 7/3 = 2 & 1/3
      if (wholenum) {
        return {
         //Numerator, e.g. 7 % 3 = [1]
          n : pnum % i,
          //Denominator, e.g. [3]
          d : i,
          //Whole number, e.g. Math.floor(7 / 3) = [2]
          w : Math.floor(pnum / i)
        }
      }
      else {
        return {
          //Numerator, e.g. [7]
          n : pnum,
          //Denominator, e.g. [3]
          d : i
        }
      }
    }
  }
  //If we are unable to find a fraction, assume one does not 
  // exist (at least at this level of accuracy, anyway.
  return {
    n : '-',
    d : '-',
    w : '-'
  };
}

Hopefully that's self-evident enough. Incidentally, we're extending the Math object because it seems like the place to store a function like this. Obviously this is not necessary; I just happen to like it.

Wait, it's not working!


Okay, let's address a couple of issues.

Javascript numbers are defined according to IEEE-754, which is the standard for defining floating-point numbers in computing. It has been in general use since the eighties, and is generally acceptable for most common uses, but it is not without faults.

For storing integers it is pretty reliable, except for really big or really small values (we're talking more than fifteen digits, so for all practical purposes they are reliable enough).

Decimals, however, are not guaranteed to be reliable at all. This is because of the fairly complex way that number storage works in an environment with limited memory. For a really succinct and simple explanation, see this Stack Overflow answer. If you love Greek letters and have several hours, not to mention more brainpower than I do, this paper is also worth a read.

Bearing all that in mind, then, the caveat is that whatever we do is only going to be accurate up to a point.

A... *stifles giggles* ... decimal point.



* This barely scratches the surface, by the way. Wait till I finally upload all the stuff I have in my development lab....

No comments:

Post a Comment