2009-12-23 2 views
18

Я хочу, чтобы пользователь мог ввести фракцию, как:PHP преобразовать десятичную дробь и обратно?

1/2 
2 1/4 
3 

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

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

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

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

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

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

Как это сделать?

+0

Как хорошо, как для пользователя, вы просите много работы против определения трех полей - целое число, числитель и знаменатель. –

+0

Проблема заключается в том, что, учитывая внутреннее представление плавающих точек, вы часто оказываетесь в виде простой фракции, но не имеете простого апериодического двоичного представления с плавающей точкой. (Подумайте 1/7 в десятичной нотации, не имея возможности использовать нотацию периодичности). См. Здесь: http://en.wikipedia.org/wiki/Binary_numeral_system#Fractions_in_binary – DrYak

+0

, если вы хотите, чтобы точность float до больших чисел взглянула на этот https://gist.github.com/anonymous/8ec4a38db78701e7bbc6, я адаптировал его так он может поддерживать точность до самой большой стоимости int. Могли бы даже сделать это с большой математикой чисел для неограниченной точности. – Tschallacka

ответ

17

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

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

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

+0

ОК, поэтому мне нужно сохранить дробь и десятичную, но как начать с десятичной запятой? –

+0

Спасибо! Ваша функция возвращает пустую строку, если $ input = "1". – Stan

8

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

<?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

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

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

+0

Я был бы рад получить комментарий, объясняющий мотивацию, стоящую за «-1». – Jir

+0

Аппроксимация: действительно, этот метод не будет работать, если вы не используете более эффективную эвристику, чтобы определить конец как «целое число». Поскольку в дополнение к обычным задачам округления плавающих чисел только фракции, которые имеют мощность двух (1/2, 1/4, 1/8 и т. Д.), Имеют апериодическую двоичную нотацию. со всем остальным, вы, вероятно, все еще останетесь до 0,0001 ... даже после правильного определения знаменателя. (В двоичном поплавке 10 * 0,1 не точно 1). – DrYak

0

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

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

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

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

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

0

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

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

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

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

0

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

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

+0

Doh, должен быть ksort ($ fracs) в конце init_fracs() – symcbean

11

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

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

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

Например:

print_r(dec2frac(3.1415926)); 

Выходы:

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

Buddies, это может помочь?

[] s


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

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

небольшое улучшение по выше, но Keepin это просто.

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

Вот это решение, которое сначала определяет действительную часть (хотя и не обязательно самую простую фракцию). Итак, 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 реализацию НОД, хотя, если вы уверены, что модуль ГМП будет установлен, вы могли бы use the one that comes with gcd.

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

+0

красивый, любимый, что вы использовали gcd. – xxstevenxo

0

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

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

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