General class for Fractions

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

Post Reply
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

General class for Fractions

Post by Ambush Commander »

Actually, I didn't want to code this, but I ended up coding it anyway (it's for the polynomial class).

Code: Select all

<?php

class Fraction
{
    
    var $_numerator;
    var $_denominator;
    
    function Fraction($numerator, $denominator) {
        if ($denominator == 0) {
            trigger_error('Denominator cannot be zero', E_USER_WARNING);
            return;
        }
        $this->_numerator   = (int) $numerator;
        $this->_denominator = (int) $denominator;
    }
    
    function &add(&$fraction) {
        $num1 = $this->getNumerator();
        $num2 = $fraction->getNumerator();
        $den1 = $this->getDenominator();
        $den2 = $fraction->getDenominator();
        
        $den = $den1 * $den2;
        $num = $num1 * $den2 + $num2 * $den1;
        
        $fraction =& new Fraction($num, $den);
        $fraction->reduce();
        
        return $fraction;
    }
    
    function &subtract(&$fraction) {
        return $this->add($fraction->getNegative());
    }
    
    function &multiply(&$fraction) {
        $num1 = $this->getNumerator();
        $num2 = $fraction->getNumerator();
        $den1 = $this->getDenominator();
        $den2 = $fraction->getDenominator();
        
        $den = $den1 * $den2;
        $num = $num1 * $num2;
        
        $fraction =& new Fraction($num, $den);
        $fraction->reduce();
        
        return $fraction;
    }
    function &divide(&$fraction) {
        return $this->multiply($fraction->getReciprocal());
    }
    
    function reduce() {
        if (!$this->isReducible()) {
            return;
        }
        $num = $this->getNumerator();
        $den = $this->getDenominator();
        
        if ($num === 0) {
            $this->setDenominator(1);
            return;
        }
        
        if (($num < 0 && $den < 0) || $den < 0) {
            $num = -$num;
            $den = -$den;
        }
        
        $gcf = $this->gcd(abs($num),abs($den));
        if ($gcf != 1) {
            $num = floor($num / $gcf);
            $den = floor($den / $gcf);
        }
        
        $this->setNumerator($num);
        $this->setDenominator($den);
    }
    
    function isReducible() {
        $num = $this->getNumerator();
        $den = $this->getDenominator();
        
        return ($num == 0 && $den !== 1) // for zero, 0/1 is best
          || (($num < 0 && $den < 0) || $den < 0) //test if both are negative
          || !(($num % $den) && ($den % $num)) //test if divisible
          || ($this->gcd(abs($num),abs($den)) != 1); //gcd
    }
    
    function getNumerator() {
        return $this->_numerator;
    }
    function getDenominator() {
        return $this->_denominator;
    }
    
    function setNumerator($int) {
        $this->_numerator = (int) $int;
    }
    function setDenominator($int) {
        if ($int == 0) {
            trigger_error('Denominator cannot be zero', E_USER_WARNING);
            return;
        }
        $this->_denominator = (int) $int;
    }
    
    function &getReciprocal() {
        if ($this->getNumerator() == 0) {
            trigger_error('Zero has no reciprocal', E_USER_NOTICE);
            return false;
        }
        return new Fraction($this->getDenominator(), $this->getNumerator());
    }
    
    function &getNegative() {
        return new Fraction(-($this->getNumerator()), $this->getDenominator());
        
    }
    
    function toPrimitive() {
        return $this->getNumerator() / $this->getDenominator();
    }
    
    function toString() {
        return $this->getNumerator() . '/' . $this->getDenominator();
    }
    
    function gcd($a, $b) {
        return Fraction::_euclidianAlgorithm($a,$b);
    }
    
    function _euclidianAlgorithm($a, $b) {
        while ($b != 0) {
            $t = $b;
            $b = $a % $b;
            $a = $t;
        }
        return $a;
    }
    
    //not working...
    function _binaryAlgorithm($u, $v) {
        $k = 0;
        if ($u == 0)
            return $v;
        if ($v == 0)
            return $u;
        while (($u & 1) == 0  &&  ($v & 1) == 0) { // while both u and v are even
            $u >> 1;   // shift u right, dividing it by 2
            $v >> 1;   // shift v right, dividing it by 2
            $k++;      // add a power of 2 to the final result
        }
        // At this point either u or v (or both) is odd 
        do {
            if (($u & 1) == 0)     //if u is even 
                $u >> 1;           //divide u by 2
            elseif (($v & 1) == 0) //else if v is even
                $v >> 1;           //divide v by 2 */
            elseif ($u >= $v)      //u and v are both odd
                $u = ($u-$v) >> 1;
            else                   //u and v both odd, v > u
                $v = ($v-$u) >> 1;
        } while ($u > 0);
        return $v << $k;  //returns v * 2^k
    }
    
}

?>
And, of course, the test cases...

Code: Select all

<?php

include_once(dirname(__FILE__) . '/Fraction.php');
include_once('simpletest/unit_tester.php');

class SimpleTest_Fraction extends UnitTestCase{
    
    function test_Fraction() {
        $fraction =& new Fraction(1,2);
        $this->assertEqual($fraction->getNumerator(), 1);
        $this->assertEqual($fraction->getDenominator(), 2);
        $fraction =& new Fraction(-1, -2);
        $this->assertEqual($fraction->getNumerator(), -1);
        $this->assertEqual($fraction->getDenominator(), -2);
        $fraction =& new Fraction(0, 0);
        $this->assertError('Denominator cannot be zero');
    }
    
    function test_isReducible() {
        $fraction =& new Fraction(2,4);
        $this->assertTrue($fraction->isReducible());
        
        $fraction =& new Fraction(-2,-4);
        $this->assertTrue($fraction->isReducible());
        
        $fraction =& new Fraction(-1,-3);
        $this->assertTrue($fraction->isReducible());
        
        $fraction =& new Fraction(6,10);
        $this->assertTrue($fraction->isReducible());
        
        $fraction =& new Fraction(29,13);
        $this->assertFalse($fraction->isReducible());
        
        $fraction =& new Fraction(-29,13);
        $this->assertFalse($fraction->isReducible());
        
        $fraction =& new Fraction(0,13);
        $this->asserttrue($fraction->isReducible());
        
        $fraction =& new Fraction(3,-1);
        $this->asserttrue($fraction->isReducible());
    }
    
    function test_reduce() {
        $fraction =& new Fraction(2,4);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(1,2));
        
        $fraction =& new Fraction(-2,-4);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(1,2));
        
        $fraction =& new Fraction(-1,-3);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(1,3));
        
        $fraction =& new Fraction(29,13);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(29,13));
        
        $fraction =& new Fraction(0,13);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(0,1));
        
        $fraction =& new Fraction(3,1);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(3,1));
        
        $fraction =& new Fraction(3,-1);
        $fraction->reduce();
        $this->assertEqual($fraction, new Fraction(-3,1));
    }
    
    function test_gcd() {
        $this->assertEqual(Fraction::gcd(10,5), 5);
        $this->assertEqual(Fraction::gcd(3,5), 1);
        $this->assertEqual(Fraction::gcd(27,6), 3);
    }
    
    function test_getNegative() {
        $frac =& new Fraction(1,2);
        $this->assertEqual($frac->getNegative(),new Fraction(-1,2));
    }
    
    function test_getReciprocal() {
        $frac =& new Fraction(1,2);
        $this->assertEqual($frac->getReciprocal(),new Fraction(2,1));
    }
    
    function test_add() {
        $frac1 =& new Fraction(1, 2);
        $frac2 =& new Fraction(3, ;
        $this->assertEqual($frac1->add($frac2), new Fraction(7, );
        
        $frac1 =& new Fraction(24, 36);
        $frac2 =& new Fraction(1, 3);
        $this->assertEqual($frac1->add($frac2), new Fraction(1, 1));
    }
    
    function test_subtract() {
        $frac1 =& new Fraction(1, 2);
        $frac2 =& new Fraction(3, ;
        $this->assertEqual($frac1->subtract($frac2), new Fraction(1, );
        
        $frac1 =& new Fraction(24, 36);
        $frac2 =& new Fraction(1, 3);
        $this->assertEqual($frac1->subtract($frac2), new Fraction(1, 3));
    }
    
    function test_multiply() {
        $frac1 =& new Fraction(1, 2);
        $frac2 =& new Fraction(3, ;
        $this->assertEqual($frac1->multiply($frac2), new Fraction(3, 16));
        
        $frac1 =& new Fraction(24, 36);
        $frac2 =& new Fraction(1, 3);
        $this->assertEqual($frac1->multiply($frac2), new Fraction(2, 9));
    }
    
    function test_divide() {
        $frac1 =& new Fraction(1, 2);
        $frac2 =& new Fraction(3, ;
        $this->assertEqual($frac1->divide($frac2), new Fraction(4, 3));
        
        $frac1 =& new Fraction(3, 4);
        $frac2 =& new Fraction(3, 1);
        $this->assertEqual($frac1->divide($frac2), new Fraction(1, 4));
    }
    
    function test_toPrimitive() {
        $fraction = new Fraction(1,2);
        $this->assertEqual($fraction->toPrimitive(), 0.5);
        $fraction = new Fraction(1,3);
        $this->assertEqual($fraction->toPrimitive(), 1 / 3);
        $fraction = new Fraction(1,10);
        $this->assertEqual($fraction->toPrimitive(), 0.1);
        $fraction = new Fraction(2,1);
        $this->assertIdentical($fraction->toPrimitive(), 2);
    }
    
    function test_toString() {
        $fraction = new Fraction(1,2);
        $this->assertEqual($fraction->toString(), '1/2');
    }
    
}



?>
I probably could factor out the GCD algorithms to their own class, but I was too lazy too... Maybe later.
Charles256
DevNet Resident
Posts: 1375
Joined: Fri Sep 16, 2005 9:06 pm

Post by Charles256 »

holy hell you went off on a tangent all frmo that denominator thing didn't ya?:-D
User avatar
sweatje
Forum Contributor
Posts: 277
Joined: Wed Jun 29, 2005 10:04 pm
Location: Iowa, USA

Post by sweatje »

Looks pretty good to me.

Since Fraction is essentially a value object, you might want to drop many of the return by references in the factory methods as they are unnescesary (you don't pass $this anywhere in the constructor and the identity of the object is not very relevant).
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Since Fraction is essentially a value object, you might want to drop many of the return by references in the factory methods as they are unnescesary (you don't pass $this anywhere in the constructor and the identity of the object is not very relevant).
Hmm... that's a good point, as references can be a little slow in PHP. I'll do that.

The _binaryAlgorithm isn't working though,, T_T. So I have to use the _euclidianAlgorithm.
Post Reply