PHP конвертировать десятичную дробь и обратно? - PullRequest
21 голосов
/ 23 декабря 2009

Я хочу, чтобы пользователь мог набирать дробь, например:

 1/2
 2 1/4
 3

И преобразовать его в соответствующее ему десятичное число, чтобы сохранить в MySQL, чтобы я мог заказать его и сделать другие сравнения с ним.

Но мне нужно иметь возможность преобразовывать десятичную дробь обратно в дробь при показе пользователю

так что в основном мне нужна функция, которая преобразует дробную строку в десятичную:

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

и функция, которая может преобразовывать десятичную дробь в строку фракции:

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

Как я могу это сделать?

Ответы [ 14 ]

19 голосов
/ 23 декабря 2009

Я думаю, я бы тоже сохранил строковое представление, так как, как только вы запустите математические вычисления, вы не получите его обратно!

А вот функция быстрого вычисления, без гарантий:

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

О, для полноты добавьте проверку, чтобы убедиться, $fraction['denominator'] != 0.

19 голосов
/ 04 февраля 2012

Иногда вам нужно найти способ сделать это, и округление приемлемо. Поэтому, если вы решите, какой диапазон округления подходит вам, вы можете создать такую ​​функцию. Чтобы преобразовать десятичную дробь в дробь, которая наиболее близко соответствует. Вы можете увеличить точность, добавив больше знаменателей для тестирования.

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;
}
8 голосов
/ 23 декабря 2009

Кому можно использовать класс PEAR Math_Fraction для некоторых ваших потребностей

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

?>
3 голосов
/ 15 июня 2013

Вот решение, которое сначала определяет действительную дробь (хотя не обязательно самую простую дробь). Так 0,05 -> 5/100. Затем он определяет наибольший общий делитель числителя и знаменателя, чтобы уменьшить его до простейшей дроби 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;
}

Это включает в себя реализацию gcd на чистом PHP, хотя, если вы уверены, что модуль gmp установлен, вы можете использовать тот, который поставляется с gcd .

Как отмечали многие другие, вам нужно использовать рациональные числа. Так что если вы конвертируете 1/7 в десятичную дробь, а затем попытаетесь преобразовать ее обратно в десятичную, вам не повезет, потому что потерянная точность не позволит вернуться к 1/7. Для моих целей это приемлемо, поскольку все числа, с которыми я имею дело (стандартные измерения), в любом случае являются рациональными числами.

2 голосов
/ 19 февраля 2013

Небольшое улучшение по сравнению с предыдущим, но не усложняйте.

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;
}
1 голос
/ 15 февраля 2013

Друзья, это может помочь?

[] s


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

        return "1/{$denominator}";
    }
    else {
        return $number;
    }
}
0 голосов
/ 14 августа 2015
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;
}
0 голосов
/ 19 июня 2015

Просто добавьте немного логики к принятому ответу Дерека - проверьте «деление на ноль» и проверку ввода целого числа.

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 голосов
/ 26 марта 2014

Я сделал сообщение в блоге с парой решений для этого, самый последний подход, который я выбрал: 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 голосов
/ 11 января 2013
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);
}

Тогда $f == $n / $d

Например:

print_r(dec2frac(3.1415926));

Выходы:

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