PHP конвертирует десятичную дробь в дробь и обратно?
Я хочу, чтобы пользователь мог вводить дробь, например:
1/2
2 1/4
3
и преобразовать его в соответствующий десятичный, чтобы быть сохраненным в MySQL, таким образом, я могу заказать его и сделать другие сравнения с ним.
но мне нужно иметь возможность конвертировать десятичную дробь обратно в дробь при показе пользователю
поэтому в основном мне нужна функция, которая преобразует строку дроби в десятичную:
fraction_to_decimal("2 1/4");// return 2.25
и функция, которая может преобразовать десятичное число в строка фракции:
decimal_to_fraction(.5); // return "1/2"
как я могу это сделать?
14 ответов
Я думаю, что я бы сохранил строковое представление тоже, как только вы запустите математику, вы не получите его обратно!
и, вот функция quick-n-dirty compute, никаких гарантий:
$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 груши для некоторые потребности
<?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.
если вы хотите узнать больше, я предлагаю вам копаться в проблемах с плавающей запятой... Начиная с Википедия.
вариация подхода Jir может действительно работать, если используется только ограниченное количество знаменателей: умножьте все на наименьшие общие знаменатели (и округлите результат, чтобы отбросить любые оставшиеся десятичные дроби из-за аппроксимации).
т. е.: если вам приходится иметь дело только с половинками, трид и четвертями, просто умножьте все на 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;
}