Я хочу, чтобы пользователь смог ввести в части как:
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;
О, для полноты, добавьте проверку, чтобы убедиться, что $ дробь ['знаменатель' ]! = 0
.
Чтобы можно было использовать класс 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();
?>
Подход может состоять в том, чтобы получить десятичное значение и умножьте его на 2, 3, 4 и так далее, пока не получите целое число.
Однако я бы придерживался ответа, данного Дереком. Угадайте, что происходит, когда пользователь вставляет n / (n + 1) с высоким n. Такой алгоритм должен сканировать все числа до n + 1. Не говоря уже о том, что скорее всего у вас возникнут проблемы с приближением.
Дробь до десятичной запятой достаточно проста и решений много. Я бы предложил обрезку строки, замену пробелов на '+', и все, что угодно, кроме пробела,/, или цифр на '', а затем прогонял бы ее через '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;
}
}
}
}
Но лично я бы просто сохранил оригинальное представление в отдельном поле в базе данных.
.Вам придется столкнуться с серьезной проблемой, потому что плавающие диски недостаточно точны.
Когда вам придется иметь дело с 1.3333
, PHP вычислит это значение.... Таким образом, вы никогда не сможете преобразовать его в 1 1/3
.
Это кажется простым, но если вы хотите, чтобы ваша программа различала 1/7901
(~ 1,2656625743576762435134793064169e-4
) с 1/7907
(~).
1,2647021626406981155937776653598e-4
) именно... это будет настоящий ад!!
IMHO, если вы хотите иметь дело с математикой, вы должны полагаться на внешнюю библиотеку... или попытаться заставить PHP общаться с Matlab.
Если вы хотите узнать больше, я предлагаю вам покопаться в проблемах с плавающей точкой... Начиная с википедии.
.Вариант подхода Джира мог бы действительно сработать, если бы использовался только ограниченный набор знаменателей: умножить все на наименьший общий знаменатель (и округлить результат, чтобы отбросить все оставшиеся десятичные числа за счет аппроксимации).
Т.е.: если вам нужно иметь дело только с половинами, четвертями и тридевятью, просто умножить все на двенадцать.
А также, если известен общий знаменатель, это должно значительно снизить скорость поиска, точно зная, какие числа искать, вместо того, чтобы искать все n+1.
Если Вам приходится иметь дело с большим количеством необычных дробей, например, 1/7, 1/13 и т.д., то придерживайтесь решения Дерека и сохраняйте исходное значение тоже.
.