Basic Arithmetic on Fractions

teammatt3's picture

He has: 2,102 posts

Joined: Sep 2003

I've been trying to find some code that does basic arithmetic on fractions in PHP but never found any. I spent my Sunday afternoon writing a class that performs addition, subtraction, multiplication and division on two fractions (as strings).

Here's the code if you're interested (and if you have some time, please give it a look over and let me know what ya think), and here's an award winning interface where you can test it out.

Let me know what ya think of it Smiling

JeevesBond's picture

He has: 3,956 posts

Joined: Jun 2002

Nice one! I had brief look at your code, and played with the award winning interface. Looks good! Smiling

Abhishek Reddy's picture

He has: 3,348 posts

Joined: Jul 2001

I'd try to find some way of not repeating the parse() and format() work in every operation. Specifically I would try:

  • Initialize a couple of fields in an immutable Fraction class called $numerator and $denominator
  • Make a static Fractions class with all those functions (as static)
  • Have them take two (or more) pre-parsed Fraction object arguments and return one new Fraction object.

You could then do stuff like:

$x = new Fraction(1, 2);
$y = new Fraction(71, 44);
$prod = Fractions::multiply($x, $y);
$z = new Fraction(111, 3);
echo Fractions::add($prod, $z, $x); // 3327/8

And it won't have to pack and unpack strings in every operation, but instead just access pairs of integers.

teammatt3's picture

He has: 2,102 posts

Joined: Sep 2003

I was hoping you would chime in Abhishek Smiling Great suggestions. I'll try them out and update the post.

pr0gr4mm3r's picture

He has: 1,502 posts

Joined: Sep 2006

Pretty cool, well documented, and very efficient. It took me a minute to understand the lcm() and gcf() functions. Smiling

I'm wondering if it would be better to accept the different numbers instead of parsing a string. It would leave less room for user error if you accept the num, den, and whole number separately.

teammatt3's picture

He has: 2,102 posts

Joined: Sep 2003

I made some heavy revisions:

  • A fraction is now it's own data type. Its read only properties are numerator and denominator. It has two accessors for those properties.
  • gcf() has been revised. I got the new code from the wikipedia. I don't understand any of it, but I'm hoping it's more efficient than the old gcf() Smiling
  • The Fractions class now parses fraction strings (Fractions::parseString()) and arrays containing 2 or 3 integers (Fractions::parseArray()).
  • All fraction operations (add, subtract, multiply, divide) can calculate with 2 or more fraction objects. I believe there is a better way to calculate the gcf in addition and subtraction operations, but I haven't had the time to find a better solution.

I'm wondering if it would be better to accept the different numbers instead of parsing a string. It would leave less room for user error if you accept the num, den, and whole number separately.

I wrote this class specifically for parsing input from the user from a textbox so I started with the string as input. But with the latest revision there are two ways to parse non-fraction objects, parseArray() (from your suggestion) and parseString() which I need for my app.

Thanks for comments and input guys Smiling. The new code has been posted.

Abhishek Reddy's picture

He has: 3,348 posts

Joined: Jul 2001

These are good improvements.

I'm not entirely in agreement with the parse functions:

Parse functions are conventionally named after the result type, with the assumption that a string is the input. Hence intval() in PHP, or parseInt()/parseInteger()/parse-integer in other languages. So parseString() could be renamed parseFraction().

As for parseArray(), it might be better not to explicitly represent different forms of fractions as arrays. Why not just overload the new Fraction() call? parseArray(array(1)) could be written new Fraction(1). parseArray(array(1, 2)) could be written new Fraction(1, 2). parseArray(array(1, 2, 3)) could be written new Fraction(1, 2, 3). Just provide three different constructors in Fraction to handle those cases.

Abhishek Reddy's picture

He has: 3,348 posts

Joined: Jul 2001

Incidentally, Python 2.6 was marked stable today, and I noticed it has a fractions module you could compare with. http://docs.python.org/dev/library/fractions.html

Their fraction constructor can accept a string representation and parse it as a fraction, instead of separately exposing the parse function.

There are some other differences that you probably can't adopt due to limitations in PHP. e.g. they overload native arithmetic operators where you need a Fractions class.

teammatt3's picture

He has: 2,102 posts

Joined: Sep 2003

So should I have one constructor for Fraction that handles all the possible types: strings, integers, floats, kitchen sinks and arrays? So when ever a fraction is instantiated I will have to run some logic like:

if the parameters are integers

else if the parameter are strings

else if they are arrays

else blah?

The constructor would pretty much turn into the parser (is handler a better term since we're not just taking about strings anymore?) for all those data types (or pass control over to a specific parse function). I can see the benefit in that, because the programmer won't have to write his own logic to handle all those types. He can just send Fraction anything and it can handle it for him.

I downloaded the Python source and I'm at their fraction code right now...

Abhishek Reddy's picture

He has: 3,348 posts

Joined: Jul 2001

I've given it a bit more thought and I guess it's not as simple in PHP as I'd hoped.

The idea is twofold:

  • avoid representing fractions in arrays
  • avoid dispatching by an explicit, monolithic construct. That means no if/elseif or switch forms.

In PHP (5), you can't overload the constructor in a single class easily, so you'd have to specify a small tree of classes to model this.

But first, you can handle the case of the implicit denominator with an optional argument, like so:

function __construct ($numerator, $denominator = 1) {

This lets us say new Fraction(4) when we mean new Fraction(4, 1).

For strings, you could have one conditional upon the type of the first argument, to parse a single string input. That would look ugly. I'd suggest keeping to a parseFraction method in Fractions.

Here is as far as Python and other languages' rational support goes, usually. Mixed fractions are represented by adding a normal integer to a fraction, rather than passing the integer as an argument, always returning either a proper or improper fraction. This makes life considerably simpler.

To allow a 3-argument mixed fraction representation would require (at least in PHP), you could provide a convenience method in Fractions, perhaps called toImproperFraction(), taking 3 arguments but returning an improper fraction. Actually, since this is really about adding integers to fractions, a Fractions::addIntegers() method could be more useful.

If you wanted richer support for mixed fractions, you could define a MixedFractions class with its own constructor. It could be a subclass of Fraction, or merely have a $fraction field -- whichever you choose, just keep the semantics consistent. But this is probably more complicated than you'd want for an easy-to-use library.

Ultimately, you'll find it very hard to incorporate fractions into PHP as if it was native and first-class. PHP is not itself a programmable programming language, so the best you can do besides bolted-on libraries like this, is wait for the language implementers to add the feature (like the Python people did).

Want to join the discussion? Create an account or log in if you already have one. Joining is fast, free and painless! We’ll even whisk you back here when you’ve finished.