views:

1269

answers:

6

I want the user to be able to type in a fraction like:

 1/2
 2 1/4
 3

And convert it into its corresponding decimal, to be saved in MySQL, that way I can order by it and do other comparisons to it.

But I need to be able to convert the decimal back to a fraction when showing to the user

so basically i need a function that will convert fraction string to decimal:

fraction_to_decimal("2 1/4");// return 2.25

and a function that can convert a decimal to a faction string:

decimal_to_fraction(.5); // return "1/2"

How can I do this?

Thanks!

+6  A: 

I think I'd store the string representation too, as, once you run the math, you're not getting it back!

And, here's a quick-n-dirty compute function, no guarantees:

$input = '1 1/2';
$fraction = array('whole' => 0);
preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction);
$result = $fraction['whole'] + $fraction['numerator']/$fraction['denominator'];
print_r($result);die;

Oh, for completeness, add a check to make sure $fraction['denominator'] != 0.

Derek Illchuk
OK so I need to store the fraction and the decimal, but how do I gt the decimal to begin with?
John Isaacks
+3  A: 

To can use PEAR's Math_Fraction class for some of your needs

<?php

include "Math/Fraction.php";

$fr = new Math_Fraction(1,2);


// print as a string
// output: 1/2
echo $fr->toString();

// print as float
// output: 0.5
echo $fr->toFloat();

?>
codaddict
A: 

An approach would be to retrieve the decimal value and multiply it by 2, 3, 4 and so on until you get an integer number.

However, I'd stick with the answer given by Derek. Guess what happens when a user inserts n/(n+1) with n high. Such an algorithm would have to scan all the numbers up to n+1. Not to mention it is likely you'll end up with approximation problems.

Jir
I'd be glad to have a comment explaining the motivations behind the "-1".
Jir
Approximation: Indeed, this method won't work unless you use a better heuristic to determine the end as "integer number". Because in addition to the usual rounding problems of floating numbers, only fractions which are power of two (1/2, 1/4, 1/8, etc.) have an aperiodic binary notation. with everything else, you'll probably still have a remainder of 0.0001... even after correctly figuring out the denominator. (In binary float 10*0.1 is not exactly 1).
DrYak
A: 

You'll have to face a serious problem, because floats are not precise enough.

When you'll have to deal with 1.3333, PHP will make an estimate of this value... So you will never be able to convert it to 1 1/3.

It seems to be simple to overcome, but if you want your program to differentiate 1/7901 (~ 1,2656625743576762435134793064169e-4) with 1/7907 (~ 1,2647021626406981155937776653598e-4) precisely... this will be a real hell !!

IMHO, if you want to deal with maths, you should rely on an external library... or try to make PHP communicate with Matlab.

If you want to know more, i suggest you dig in floating point problems... Starting with wikipedia.

Arno
A: 

A variation of Jir's approach could actually work if only a limited amount of denominators are used : multiply everything by the least common denominators (and round the result to discard any remaining decimals due to approximation).

I.e.: if you only have to deal with halfs, thrids and quarters, just multiply everything by 12.

And also if you know the common denominator, this should greatly reduce the search speed by knowing exactly which numbers to search instead of searching all n+1 possible.

If you have to deal with lots of unusual fractions, like 1/7, 1/13, etc. well, stick to Derek's solution and store the original value too.

DrYak
A: 

The fraction to decimal is quite straightforward and there are lots of solutions. I'd go with trimming the string, replacing spaces with '+', and anything other than space,/,. or digits with '' then running it through 'eval'.

The decimal to fraction is virtually impossible to do correctly - not least because your decimal fraction would probably have to be converted to binary first - at which point you loose a lot of precision. As an academic exercise.....If you can live with the difference between 20976/41953 and 1/2 then you could try a fuzzy match for a predefined number of fractions:

(there's probably a neater way of implementing the same algorithm - but I'll leave that as an exercise for the reader).

define('DECIMAL_DIGITS',5);

function decimal_2_frac($inp_decimal)
{
  static $fracs;
  if (!is_array($fracs)) {
    init_fracs($fracs);
  }
  $int_part=(integer)$inp_decimal;
  $inp_decimal=$inp_decimal-$int_part;
  $candidate='';
  $distance=10;
  foreach ($fracs as $decimal=>$frac) {
     if (abs($decimal-$inp_decimal)<$distance) {
       $candidate=$frac;
       $distance=abs($decimal-$inp_decimal);
     }
  if (abs($decimal-$inp_decimal)>$distance) {
     break;
  }
 }
 return $int_part . ' ' . $candidate;
}

function init_fracs(&$fracs)
{
   $fracs=array(); 
   for ($x=2;$x<(5*DECIMAL_DIGITS);$x++) {
       // there's probably a beter way to calculate the loop limit
      for ($y=1; $y<$x; $y++) {
         $decimal=round($y/$x,DECIMAL_DIGITS);
         $frac="$x/$y";
         if (!array_key_exists($decimal,$fracs)) {
         $fracs[$decimal]=$frac;
   }
  }    
 }
}

But personally, I'd just store the original representation in a seperate field in the database.

C.

symcbean
Doh, there should be a ksort($fracs) at the end of init_fracs()
symcbean