Я хочу, чтобы пользователь мог вводить такую фракцию, как:
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; }