Подтвердить что ты не робот

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

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

 1/2
 2 1/4
 3

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

И здесь функция быстрого n-грязного вычисления не гарантирует:

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

Ответ 2

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

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

Ответ 3

Чтобы использовать класс 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();

?>

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

[] S


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

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

Ответ 7

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

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

Ответ 8

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

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

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

IMHO, если вы хотите иметь дело с математикой, вы должны полагаться на внешнюю библиотеку... или попытаться заставить PHP общаться с Matlab.

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

Ответ 9

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

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

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

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

Ответ 10

Дробь до десятичного числа довольно проста и существует множество решений. Я бы пошел с обрезкой строки, заменив пробелы на "+" и ничего, кроме пробела, /. или цифры с помощью '', а затем пропустите его через '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 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;
   }
  }    
 }
}

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

Ответ 11

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
)

Ответ 12

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

Ответ 13

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

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

Ответ 14

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