PHP конвертирует десятичную дробь в дробь и обратно?

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

 1/2
 2 1/4
 3

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

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

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

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

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

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

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

14 ответов


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

и, вот функция quick-n-dirty compute, никаких гарантий:

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


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

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

можно использовать класс 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();

?>

вот решение, которое сначала определяет допустимую дробь (хотя и не обязательно простейшую дробь). Так 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;
}

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

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


небольшое улучшение выше, но держите его простым.

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

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

[]s


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

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

подход должен быть, чтобы получить десятичное значение и умножьте его на 2, 3, 4 и так далее, пока вы не получите целое число.

, Я бы палку с ответом Дерек. Угадайте, что происходит, когда пользователь вставляет n / (n+1) С N высоким. Такой алгоритм должен был бы сканировать все числа до n+1. Не говоря уже о том, что, скорее всего, вы столкнетесь с проблемами аппроксимации.

вам придется столкнуться с серьезной проблемой, потому что поплавки недостаточно точны.

когда вам придется иметь дело с 1.3333, PHP сделает оценку этого значения... Так что вы никогда не сможете преобразовать его в 1 1/3.

кажется, что это просто преодолеть, но если вы хотите, чтобы ваша программа отличалась 1/7901 (~ 1,2656625743576762435134793064169e-4) С 1/7907 (~ 1,2647021626406981155937776653598e-4) точно... это будет настоящий ад !!

ИМХО, Если вы хотите иметь дело с математикой, вы должны полагаться на внешняя библиотека... или попробуйте заставить PHP общаться с Matlab.

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


вариация подхода Jir может действительно работать, если используется только ограниченное количество знаменателей: умножьте все на наименьшие общие знаменатели (и округлите результат, чтобы отбросить любые оставшиеся десятичные дроби из-за аппроксимации).

т. е.: если вам приходится иметь дело только с половинками, трид и четвертями, просто умножьте все на 12.

а также, если вы знаете общий знаменатель, это должно значительно снизить скорость поиска, зная точно, какие цифры искать вместо поиска все n+1 возможно.

Если вам приходится иметь дело с большим количеством необычных дроби, как 1/7, 1/13 и т. д. ну, придерживайтесь решения Дерека и сохраните исходное значение.


дробь до десятичной довольно проста, и есть много решений. Я бы пошел с обрезкой строки, заменив пробелы на " + " и все, что угодно, кроме пространства,/,. или цифры С "затем запуск его через "eval".

десятичную дробь практически невозможно сделать правильно - не в последнюю очередь потому, что ваша десятичная дробь, вероятно, сначала должна быть преобразована в двоичную - в этот момент Вы теряете много точности. Как академическое упражнение.....Если сможешь. живите с разницей между 20976/41953 и 1/2, тогда вы можете попробовать нечеткое совпадение для предопределенного количества фракций:

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

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

но лично я бы просто сохранить исходное представление в отдельном поле в базе данных.


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
)

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

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

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

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