2009-12-23 177 views
18

Quiero que el usuario sea capaz de escribir una fracción como:PHP convierte decimal a fracción y viceversa?

1/2 
2 1/4 
3 

y convertirlo en su correspondiente decimal, para ser salvo en MySQL, de esa manera puedo pedir por ella y hacer otras comparaciones a ella .

pero tengo que ser capaz de convertir el número decimal de nuevo a una fracción al mostrar al usuario

así que básicamente necesito una función que convierte una cadena fracción a decimal:

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

y una función que puede convertir un decimal a una cadena de facción:

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

¿Cómo puedo hacer esto?

+0

Tan agradable como lo es para un usuario, que está pidiendo un montón de trabajo frente a la definición de tres campos - número entero, numerador y denominador. –

+0

El problema es que, dada la representación interna de los puntos flotantes, a menudo terminará con una fracción simple, pero no tiene una simple representación de flotación binaria aperiódica. (Piense 1/7 en notación decimal, sin poder usar una notación de periodicidad). Consulte aquí: http://en.wikipedia.org/wiki/Binary_numeral_system#Fractions_in_binary – DrYak

+0

si desea una precisión de flotación de hasta grandes números, eche un vistazo a este https://gist.github.com/anonymous/8ec4a38db78701e7bbc6. Lo adapté para que puede admitir precisión hasta el mayor valor int. Incluso podría hacer eso con la matemática de los números grandes para una precisión ilimitada. – Tschallacka

Respuesta

17

Creo que también almacenaría la representación de cadenas, ya que, una vez que ejecutas las operaciones matemáticas, ¡no las recuperarás!

Y, aquí es una función de cómputo sucia rápida-n-, no hay garantías:

$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, por completo, añadir una comprobación para asegurarse de $fraction['denominator'] != 0.

+0

OK, así que necesito almacenar la fracción y el decimal, pero ¿cómo puedo calcular el decimal para empezar? –

+0

¡Gracias! Sin embargo, su función devuelve una cadena vacía si $ input = "1". – Stan

8

Para puede utilizar la clase Math_Fraction de la pera por algunos de sus necesidades

<?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(); 

?> 
0

Un enfoque consistiría en recuperar el valor decimal y se multiplica por 2, 3, 4 y así sucesivamente hasta obtener un número entero número.

Sin embargo, me quedaría con la respuesta dada por Derek. Adivina qué sucede cuando un usuario inserta n/(n + 1) con n alto. Tal algoritmo debería escanear todos los números hasta n + 1. Sin mencionar que es probable que termines con problemas de aproximación.

+0

Estaría encantado de tener un comentario explicando las motivaciones detrás del "-1". – Jir

+0

Aproximación: De hecho, este método no funcionará a menos que use una mejor heurística para determinar el final como "número entero". Porque además de los problemas de redondeo habituales de los números flotantes, solo las fracciones con una potencia de dos (1/2, 1/4, 1/8, etc.) tienen una notación binaria aperiódica. con todo lo demás, probablemente aún tengas un resto de 0.0001 ... incluso después de calcular correctamente el denominador. (En float binario 10 * 0.1 no es exactamente 1). – DrYak

0

Tendrá que enfrentar un problema grave, ya que los flotadores no son lo suficientemente precisos.

Cuando tenga que tratar con 1.3333, PHP hará una estimación de este valor ... Por lo tanto, nunca podrá convertirlo a 1 1/3.

parece ser fácil de superar, pero si usted quiere que su programa para diferenciar 1/7901 (~ 1,2656625743576762435134793064169e-4) con 1/7907 (~ 1,2647021626406981155937776653598e-4), precisamente ... esto va a ser un verdadero infierno !!

En mi humilde opinión, si desea tratar con las matemáticas, debe confiar en una biblioteca externa ... o tratar de que PHP se comunique con Matlab.

Si quiere saber más, le sugiero que profundice en los problemas de coma flotante ... Comenzando con wikipedia.

0

Una variación del enfoque de Jir en realidad podría funcionar si solo se usa una cantidad limitada de denominadores: multiplique todo por los menos comunes denominadores (y redondee el resultado para descartar los decimales restantes debido a la aproximación).

I.e.: si solo tiene que lidiar con halfs, thrids y quarters, solo multiplique todo por 12.

Y también si conoce el denominador común, esto debería reducir en gran medida la velocidad de búsqueda al saber exactamente qué números buscar en lugar de buscar todo n + 1 posible.

Si tiene que lidiar con muchas fracciones inusuales, como 1/7, 1/13, etc., adhiérase a la solución de Derek y almacene también el valor original.

0

La fracción a decimal es bastante sencilla y hay muchas soluciones. Me gustaría recortar la cadena, reemplazar espacios con '+', y cualquier cosa que no sea espacio,/,. o dígitos con '' luego ejecutarlo a través de 'eval'.

El decimal a la fracción es virtualmente imposible de hacer correctamente, entre otras cosas porque su fracción decimal probablemente tendría que convertirse primero en binario, en cuyo punto pierde mucha precisión. Como ejercicio académico ..... Si puede vivir con la diferencia entre 20976/41953 y 1/2, podría intentar una coincidencia difusa para un número predefinido de fracciones:

(probablemente haya una forma más ordenada de implementar el mismo algoritmo, pero lo dejo como un ejercicio para el lector).

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; 
    } 
    }  
} 
} 

Pero personalmente, me gustaría almacenar la representación original en un campo separado en la base de datos.

+0

Doh, debería haber un ksort ($ fracs) al final de init_fracs() – symcbean

11

A veces tiene que encontrar la forma de hacerlo y el redondeo es aceptable. Entonces, si decides qué rango de redondeo te funciona, puedes construir una función como esta. Para convertir un decimal en la fracción que más se aproxima. Puede ampliar la precisión agregando más denominadores para probar.

function decToFraction($float) { 
    // 1/2, 1/4, 1/8, 1/16, 1/3 ,2/3, 3/4, 3/8, 5/8, 7/8, 3/16, 5/16, 7/16, 
    // 9/16, 11/16, 13/16, 15/16 
    $whole = floor ($float); 
    $decimal = $float - $whole; 
    $leastCommonDenom = 48; // 16 * 3; 
    $denominators = array (2, 3, 4, 8, 16, 24, 48); 
    $roundedDecimal = round ($decimal * $leastCommonDenom)/$leastCommonDenom; 
    if ($roundedDecimal == 0) 
     return $whole; 
    if ($roundedDecimal == 1) 
     return $whole + 1; 
    foreach ($denominators as $d) { 
     if ($roundedDecimal * $d == floor ($roundedDecimal * $d)) { 
      $denom = $d; 
      break; 
     } 
    } 
    return ($whole == 0 ? '' : $whole) . " " . ($roundedDecimal * $denom) . "/" . $denom; 
} 
0
function dec2frac($f) 
{ 
    $d = 1 

    while (fmod($f, 1) != 0.0) { 
     $f *= 2; 
     $d *= 2; 
    } 

    $n = sprintf('%.0f', $f); 
    $d = sprintf('%.0f', $d); 

    return array($n, $d); 
} 

Entonces $f == $n/$d

Por ejemplo:

print_r(dec2frac(3.1415926)); 

Salidas:

Array 
(
    [0] => 3537118815677477 // $n 
    [1] => 1125899906842624 // $d 
) 
1

Buddies, puede esta ayuda?


function toFraction($number) { 
    if (!is_int($number)) { 
     $number = floatval($number); 
     $denominator = round(1/$number); 

     return "1/{$denominator}"; 
    } 
    else { 
     return $number; 
    } 
} 
2

Poca mejoría [] s en el anterior, pero manteniéndolo simple.

function dec2frac($f) { 
    $base = floor($f); 
    if ($base) { 
    $out = $base . ' '; 
    $f = $f - $base; 
    } 
    if ($f != 0) { 
    $d = 1; 
    while (fmod($f, 1) != 0.0) { 
     $f *= 2; 
     $d *= 2; 
    } 
    $n = sprintf('%.0f', $f); 
    $d = sprintf('%.0f', $d); 
    $out .= $n . '/' . $d; 
    } 
    return $out; 
} 
3

Aquí es una solución que primero determina una fracción válida (aunque no necesariamente la fracción más simple). Entonces 0.05 -> 5/100. Luego determina el máximo común divisor del numerador y el denominador para reducirlo a la fracción más simple, 1/20.

function decimal_to_fraction($fraction) { 
    $base = floor($fraction); 
    $fraction -= $base; 
    if($fraction == 0) return $base; 
    list($ignore, $numerator) = preg_split('/\./', $fraction, 2); 
    $denominator = pow(10, strlen($numerator)); 
    $gcd = gcd($numerator, $denominator); 
    $fraction = ($numerator/$gcd) . '/' . ($denominator/$gcd); 
    if($base > 0) { 
    return $base . ' ' . $fraction; 
    } else { 
    return $fraction; 
    } 
} 

# Borrowed from: http://www.php.net/manual/en/function.gmp-gcd.php#69189 
function gcd($a,$b) { 
    return ($a % $b) ? gcd($b,$a % $b) : $b; 
} 

Esto incluye una aplicación PHP puro del mcd aunque si está seguro del módulo GMP se instala usted podría use the one that comes with gcd.

Como muchos otros han señalado, debe usar números racionales.Por lo tanto, si convierte 1/7 a decimal, intente convertirlo de nuevo en decimal, no tendrá suerte porque la precisión perdida evitará que regrese a 1/7. Para mis propósitos, esto es aceptable ya que todos los números con los que estoy tratando (mediciones estándar) son números racionales de todos modos.

+0

hermoso, me encantó que haya usado gcd. – xxstevenxo

0

hice un post con un par de soluciones para esto, el enfoque más reciente Tomé es: http://www.carlosabundis.com/2014/03/25/converting-decimals-to-fractions-with-php-v2/

function dec2fracso($dec){ 
    //Negative number flag. 
    $num=$dec; 
    if($num<0){ 
     $neg=true; 
    }else{ 
     $neg=false; 
    } 

    //Extracts 2 strings from input number 
    $decarr=explode('.',(string)$dec); 

    //Checks for divided by zero input. 
    if($decarr[1]==0){ 
     $decarr[1]=1; 
     $fraccion[0]=$decarr[0]; 
     $fraccion[1]=$decarr[1]; 
     return $fraccion; 
    } 

    //Calculates the divisor before simplification. 
    $long=strlen($decarr[1]); 
    $div="1"; 
    for($x=0;$x<$long;$x++){ 
     $div.="0"; 
    } 

    //Gets the greatest common divisor. 
    $x=(int)$decarr[1]; 
    $y=(int)$div; 
    $gcd=gmp_strval(gmp_gcd($x,$y)); 

    //Calculates the result and fills the array with the correct sign. 
    if($neg){ 
     $fraccion[0]=((abs($decarr[0])*($y/$gcd))+($x/$gcd))*(-1); 
    }else{ 
     $fraccion[0]=(abs($decarr[0])*($y/$gcd))+($x/$gcd); 
    } 
    $fraccion[1]=($y/$gcd); 
    return $fraccion; 
} 
0

Simplemente añadiendo un poco más lógica para respuesta aceptada de Derek - comprobar si hay "división por cero "y verificación de entrada de número entero.

function fractionToDec($input) { 
    if (strpos($input, '/') === FALSE) { 
     $result = $input; 
    } else { 
     $fraction = array('whole' => 0); 
     preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction); 
     $result = $fraction['whole']; 

     if ($fraction['denominator'] > 0) 
      $result += $fraction['numerator']/$fraction['denominator']; 
    } 

    return $result; 
} 
0
function frac2dec($fraction) { 
    list($whole, $fractional) = explode(' ', $fraction); 

    $type = empty($fractional) ? 'improper' : 'mixed'; 

    list($numerator, $denominator) = explode('/', $type == 'improper' ? $whole : $fractional); 

    $decimal = $numerator/(0 == $denominator ? 1 : $denominator); 

    return $type == 'improper' ? $decimal : $whole + $decimal; 
} 
Cuestiones relacionadas