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

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

1/2 2 1/4 3 

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

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

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

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

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

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

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

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

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

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

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

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

Т.е.: если вам нужно иметь дело только с половиной, тридисом и четвертями, просто умножьте все на 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; }