I am trying to convert calculations keyed in by users with decimal results into fractions. For e.g.; 66.6666666667 into 66 2/3. Any pointers?
Thanx in advance
Continued fractions can be used to find rational approximations to real numbers that are "best" in a strict sense. Here's a PHP function that finds a rational approximation to a given (positive) floating point number with a relative error less than $tolerance:
<?php
function float2rat($n, $tolerance = 1.e-6) {
$h1=1; $h2=0;
$k1=0; $k2=1;
$b = 1/$n;
do {
$b = 1/$b;
$a = floor($b);
$aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
$aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
$b = $b-$a;
} while (abs($n-$h1/$k1) > $n*$tolerance);
return "$h1/$k1";
}
printf("%s\n", float2rat(66.66667)); # 200/3
printf("%s\n", float2rat(sqrt(2))); # 1393/985
printf("%s\n", float2rat(0.43212)); # 748/1731
I have written more about this algorithm and why it works, and even a JavaScript demo here: https://web.archive.org/web/20180731235708/http://jonisalonen.com/2012/converting-decimal-numbers-to-ratios/
Farey fractions can be quite useful in this case.
They can be used to convert any decimal into a fraction with the lowest possible denominator.
Sorry - I don't have a prototype in PHP, so here's one in Python:
def farey(v, lim):
"""No error checking on args. lim = maximum denominator.
Results are (numerator, denominator); (1, 0) is 'infinity'."""
if v < 0:
n, d = farey(-v, lim)
return (-n, d)
z = lim - lim # Get a "zero of the right type" for the denominator
lower, upper = (z, z+1), (z+1, z)
while True:
mediant = (lower[0] + upper[0]), (lower[1] + upper[1])
if v * mediant[1] > mediant[0]:
if lim < mediant[1]:
return upper
lower = mediant
elif v * mediant[1] == mediant[0]:
if lim >= mediant[1]:
return mediant
if lower[1] < upper[1]:
return lower
return upper
else:
if lim < mediant[1]:
return lower
upper = mediant
Converted Python code in answer from #APerson241 to PHP
<?php
function farey($v, $lim) {
// No error checking on args. lim = maximum denominator.
// Results are array(numerator, denominator); array(1, 0) is 'infinity'.
if($v < 0) {
list($n, $d) = farey(-$v, $lim);
return array(-$n, $d);
}
$z = $lim - $lim; // Get a "zero of the right type" for the denominator
list($lower, $upper) = array(array($z, $z+1), array($z+1, $z));
while(true) {
$mediant = array(($lower[0] + $upper[0]), ($lower[1] + $upper[1]));
if($v * $mediant[1] > $mediant[0]) {
if($lim < $mediant[1])
return $upper;
$lower = $mediant;
}
else if($v * $mediant[1] == $mediant[0]) {
if($lim >= $mediant[1])
return $mediant;
if($lower[1] < $upper[1])
return $lower;
return $upper;
}
else {
if($lim < $mediant[1])
return $lower;
$upper = $mediant;
}
}
}
// Example use:
$f = farey(66.66667, 10);
echo $f[0], '/', $f[1], "\n"; # 200/3
$f = farey(sqrt(2), 1000);
echo $f[0], '/', $f[1], "\n"; # 1393/985
$f = farey(0.43212, 2000);
echo $f[0], '/', $f[1], "\n"; # 748/1731
Based upon #Joni's answer, here is what I used to pull out the whole number.
function convert_decimal_to_fraction($decimal){
$big_fraction = float2rat($decimal);
$num_array = explode('/', $big_fraction);
$numerator = $num_array[0];
$denominator = $num_array[1];
$whole_number = floor( $numerator / $denominator );
$numerator = $numerator % $denominator;
if($numerator == 0){
return $whole_number;
}else if ($whole_number == 0){
return $numerator . '/' . $denominator;
}else{
return $whole_number . ' ' . $numerator . '/' . $denominator;
}
}
function float2rat($n, $tolerance = 1.e-6) {
$h1=1; $h2=0;
$k1=0; $k2=1;
$b = 1/$n;
do {
$b = 1/$b;
$a = floor($b);
$aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
$aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
$b = $b-$a;
} while (abs($n-$h1/$k1) > $n*$tolerance);
return "$h1/$k1";
}
Based on #APerson's and #Jeff Monteiro's answers I've created PHP version of Farey fractions that will be simplified to whole values with fractions with lowest possible denominator:
<?php
class QuantityTransform
{
/**
* #see https://stackoverflow.com/questions/14330713/converting-float-decimal-to-fraction
*/
public static function decimalToFraction(float $decimal, $glue = ' ', int $limes = 10): string
{
if (null === $decimal || $decimal < 0.001) {
return '';
}
$wholeNumber = (int) floor($decimal);
$remainingDecimal = $decimal - $wholeNumber;
[$numerator, $denominator] = self::fareyFraction($remainingDecimal, $limes);
// Values rounded to 1 should be added to base value and returned without fraction part
if (is_int($simplifiedFraction = $numerator / $denominator)) {
$wholeNumber += $simplifiedFraction;
$numerator = 0;
}
return (0 === $wholeNumber && 0 === $numerator)
// Too small values will be returned in original format
? (string) $decimal
// Otherwise let's format value - only non-0 whole value / fractions will be returned
: trim(sprintf(
'%s%s%s',
(string) $wholeNumber ?: '',
$wholeNumber > 0 ? $glue : '',
0 === $numerator ? '' : ($numerator . '/' . $denominator)
));
}
/**
* #see https://stackoverflow.com/a/14330799/842480
*
* #return int[] Numerator and Denominator values
*/
private static function fareyFraction(float $value, int $limes): array
{
if ($value < 0) {
[$numerator, $denominator] = self::fareyFraction(-$value, $limes);
return [-$numerator, $denominator];
}
$zero = $limes - $limes;
$lower = [$zero, $zero + 1];
$upper = [$zero + 1, $zero];
while (true) {
$mediant = [$lower[0] + $upper[0], $lower[1] + $upper[1]];
if ($value * $mediant[1] > $mediant[0]) {
if ($limes < $mediant[1]) {
return $upper;
}
$lower = $mediant;
} elseif ($value * $mediant[1] === $mediant[0]) {
if ($limes >= $mediant[1]) {
return $mediant;
}
if ($lower[1] < $upper[1]) {
return $lower;
}
return $upper;
} else {
if ($limes < $mediant[1]) {
return $lower;
}
$upper = $mediant;
}
}
}
}
Then you san use it like:
QuantityTransform::decimalToFraction(0.06); // 0.06
QuantityTransform::decimalToFraction(0.75); // 3/4
QuantityTransform::decimalToFraction(1.75, ' and '); // 1 and 3/4
QuantityTransform::decimalToFraction(2.33, ' and '); // 2 and 1/3
QuantityTransform::decimalToFraction(2.58, ' ', 5); // 2 3/5
QuantityTransform::decimalToFraction(2.58, ' & ', 10); // 2 & 4/7
QuantityTransform::decimalToFraction(1.97); // 2
Here is my approach to this problem. Works fine with rational numbers.
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;
}
Sometimes it is necessary to treat only the decimals of a float. So I created a code that uses the function created by #Joni to present a format that is quite common in culinary recipes, at least in Brazil.
So instead of using 3/2 which is the result for 1.5, using the function I created it is possible to present the value 1 1/2, and if you want, you can also add a string to concatenate the values, creating something like "1 and 1/2 ".
function float2rat($n, $tolerance = 1.e-6) {
$h1=1; $h2=0;
$k1=0; $k2=1;
$b = 1/$n;
do {
$b = 1/$b;
$a = floor($b);
$aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
$aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
$b = $b-$a;
} while (abs($n-$h1/$k1) > $n*$tolerance);
return "$h1/$k1";
}
function float2fraction($float, $concat = ' '){
// ensures that the number is float,
// even when the parameter is a string
$float = (float)$float;
if($float == 0 ){
return $float;
}
// when float between -1 and 1
if( $float > -1 && $float < 0 || $float < 1 && $float > 0 ){
$fraction = float2rat($float);
return $fraction;
}
else{
// get the minor integer
if( $float < 0 ){
$integer = ceil($float);
}
else{
$integer = floor($float);
}
// get the decimal
$decimal = $float - $integer;
if( $decimal != 0 ){
$fraction = float2rat(abs($decimal));
$fraction = $integer . $concat . $fraction;
return $fraction;
}
else{
return $float;
}
}
}
Usage e.g:
echo float2fraction(1.5);
will return "1 1/2"
I need to round up any integer between 1 and infinity in php to the next significant figure (though in practice I'm unlikely to need to round up infinity, so will be happy to settle on reasonable internal limits) eg:
$x <= 10 ? $x = 10
10 < $x <= 100 ? $x = 100
100 < $x <= 1000 ? $x = 1000
etc.
Round / ceil etc don't seem to do the job quite as planned. A pointer towards the correct algorhythm (or function?) would be much appreciated
i think this method will fix your problem:
function n($nr, $p = 10) {
if($nr <= $p) {
return $p;
}
return n($nr, $p*10);
}
heres the result:
echo n(1);
//output 10
echo n(232);
//output 1000
echo n(89289382);
//output 100000000
$x = pow(10,floor(log10($x)) + (floor(log10($x)) == log10($x) && $x!=1 ? 0:1) );
function my_ceil($in) {
if($in == 1) return $in;
if($in == pow(10, strlen($in)-1)) return $in;
return pow(10, strlen($in));
}
echo my_ceil(11); //100
echo my_ceil(10); //10
I think this is what you're looking for:
echo ceil($x / pow(10, strlen($x))) * pow(10, strlen($x));
Only works when $x is an integer, but you say in your question that that is indeed the case, so there's no issue (unless you try to later use it with numbers containing decimals).
This should do the trick:
<?php
function nextSignificantFeature($number){
$upper = pow(10, strlen($number));
return $number == $upper/10 ? $number : $upper;
}
?>
Actually there is the infinity number in PHP, so the implementation should deal with it as you wrote any number from 1 up to infinity Demo:
<?php
function n($number) {
if ($number < 1) {
throw new InvalidArgumentException('Number must be greater or equal 1.');
}
if ($number === INF) {
return INF;
}
$p = 10;
while($number > ($p*=10));
return $p;
}
echo n(1), "\n";
//output 10
echo n(232), "\n";
//output 1000
echo n(89289382), "\n";
//output 100000000
echo n(INF), "\n";
// output INF
echo n(-INF), "\n";
// throws exception 'InvalidArgumentException' with message 'Number must be greater or equal 1.'
This example does the iterative calculation in PHP userland code. There are some math functions in PHP that can do it inline like pow.
so I am new at the idea of recursion and i wrote this simple code to factor a number ($n) this is the code:
$n = 120;
$y = 1;
function factor($n, $y) {
if($y > $n) {
return 1;
} else {
$x = $n / $y;
list($whole, $dec) = array_pad(explode('.', $x), 2, Null);
if($dec == '') {
echo 'x:' . $x . ' y:' . $y . '</br>';
return factor($n, ($y + 1));
}
}
}
this is what the code outputs:
x:120 y:1
x:60 y:2
x:40 y:3
x:30 y:4
x:24 y:5
x:20 y:6
so my question is why does this stop before it completes?
Your next step would be 120 / 7 which equals 17.142857....
So this check fails and as such the recursion does not happen:
if($dec=='') // $dec would equal to 142857.....
{
echo'x:'.$x.' y:'.$y.'</br>';
return factor($n,($y+1));
}
else
{
$x=$n/$y;
list($whole,$dec)=array_pad(explode('.', number_format($x)), 2, Null);
if($dec=='')
{
echo'x:'.$x.' y:'.$y.'</br>';
return factor($n,($y+1));
}
}
PART OF DECIMAL LIMITATION
There are two things I see wrong with your example:
Your recursion stops the first time it encounters a fractional value. The tail recursion (return factor($n, $y + 1);) only occurs when $dec == ''. Otherwise, the function simply exits. That's why it stops when $y is 7.
Your condition for ending recursion ($y > $n) is incorrect. You want to break the recursion when the divisor is greater than the quotient — i.e., when $y > $x — because that means you've found all the integer factors.
I think this is what you want:
$n = 120;
$y = 1;
function factor($n, $y) {
$x = $n / $y;
if($y > $x) {
return 1;
} else {
list($whole, $dec) = array_pad(explode('.', $x), 2, Null);
if($dec == '') {
echo 'x:' . $x . ' y:' . $y . "</br>\n";
}
return factor($n, ($y + 1));
}
}
echo factor($n, $y);
Ok so I am trying to turn my hit counter to round thousands to a single digit too display 3 thousand hits as 3K for example, like the Facebook Share and Twitter Tweet Buttons do. Here is my code. Any idea what I am doing wrong?
$postresultscount = (($resultscount) ? $resultscount->sumCount : 1);
$k = 1000;
$L = '';
if ($postresultscount > $k) {
$echoxcount = round($postresultscount/$k);
$L = 'K';
} else if ($postresultscount == $k) {
$echoxcount = 1;
$L = 'K';
} else {
$echoxcount = $postresultscount;
}
echo 'document.write("'.$echoxcount.' '.$L.'")';
Here comes a PHP function to format numbers to nearest thousands such as Kilos, Millions, Billions, and Trillions with comma
Function
function thousandsCurrencyFormat($num) {
if($num>1000) {
$x = round($num);
$x_number_format = number_format($x);
$x_array = explode(',', $x_number_format);
$x_parts = array('k', 'm', 'b', 't');
$x_count_parts = count($x_array) - 1;
$x_display = $x;
$x_display = $x_array[0] . ((int) $x_array[1][0] !== 0 ? '.' . $x_array[1][0] : '');
$x_display .= $x_parts[$x_count_parts - 1];
return $x_display;
}
return $num;
}
Output
thousandsCurrencyFormat(3000) - 3k
thousandsCurrencyFormat(35500) - 35.5k
thousandsCurrencyFormat(905000) - 905k
thousandsCurrencyFormat(5500000) - 5.5m
thousandsCurrencyFormat(88800000) - 88.8m
thousandsCurrencyFormat(745000000) - 745m
thousandsCurrencyFormat(2000000000) - 2b
thousandsCurrencyFormat(22200000000) - 22.2b
thousandsCurrencyFormat(1000000000000) - 1t (1 trillion)
Resources
https://code.recuweb.com/2018/php-format-numbers-to-nearest-thousands/
function shortNumber($num)
{
$units = ['', 'K', 'M', 'B', 'T'];
for ($i = 0; $num >= 1000; $i++) {
$num /= 1000;
}
return round($num, 1) . $units[$i];
}
I adapted this one from a function created to display bytes in human readable form by bashy here:
https://laracasts.com/discuss/channels/laravel/human-readable-file-size-and-time
a bit better than the post of Yuki
if ($value > 999 && $value <= 999999) {
$result = floor($value / 1000) . ' K';
} elseif ($value > 999999) {
$result = floor($value / 1000000) . ' M';
} else {
$result = $value;
}
Question is 8 years old but each time I see an answer that contains an else statement, I think it can be done in a better (cleaner) way.
<?php
if (!function_exists('format_number_in_k_notation')) {
function format_number_in_k_notation(int $number): string
{
$suffixByNumber = function () use ($number) {
if ($number < 1000) {
return sprintf('%d', $number);
}
if ($number < 1000000) {
return sprintf('%d%s', floor($number / 1000), 'K+');
}
if ($number >= 1000000 && $number < 1000000000) {
return sprintf('%d%s', floor($number / 1000000), 'M+');
}
if ($number >= 1000000000 && $number < 1000000000000) {
return sprintf('%d%s', floor($number / 1000000000), 'B+');
}
return sprintf('%d%s', floor($number / 1000000000000), 'T+');
};
return $suffixByNumber();
}
}
dump(format_number_in_k_notation(123)); // "123"
dump(format_number_in_k_notation(73000)); // "73K+"
dump(format_number_in_k_notation(216000)); // "216K+"
dump(format_number_in_k_notation(50400123)); // "50M+"
dump(format_number_in_k_notation(12213500100600)); // "12T+"
die;
function print_number_count($number) {
$units = array( '', 'K', 'M', 'B');
$power = $number > 0 ? floor(log($number, 1000)) : 0;
if($power > 0)
return #number_format($number / pow(1000, $power), 2, ',', ' ').' '.$units[$power];
else
return #number_format($number / pow(1000, $power), 0, '', '');
}
My func
function numsize($size,$round=2){
$unit=['', 'K', 'M', 'G', 'T'];
return round($size/pow(1000,($i=floor(log($size,1000)))),$round).$unit[$i];
}
Use floor instead of round if you want 3500 to round down to 3 K.
Otherwise, your code works, albeit problematically. Try this:
if ($postresultscount > 1000) {
$result = floor($postresultscount / 1000) . 'K';
} else {
$result = $postresultscount;
}
echo 'document.write("' . $result . '")";
It also appears you're writing JavaScript using PHP—take care.
This is a modified version with k and m lowercase and show one decimal place for milllions.
<?php
if ($value > 999 && $value <= 999999) {
$result = floor($value / 1000) . 'k';
} elseif ($value > 999999) {
$result = number_format((float)$value , 1, '.', '')/1000000 . 'm';
} else {
$result = $value;
}
?>
Several good answers have already been given to this particularly old question, however, most are too simple for my taste or not easy to extend for more units, so here's what I use:
# The function that returns a number formatted as a string in thousands, millions etc.
public static function getNumberAbbreviation (Int $number, Int $decimals = 1) : String {
# Define the unit size and supported units.
$unitSize = 1000;
$units = ["", "K", "M", "B", "T"];
# Calculate the number of units as the logarithm of the absolute value with the
# unit size as base.
$unitsCount = ($number === 0) ? 0 : floor(log(abs($number), $unitSize));
# Decide the unit to be used based on the counter.
$unit = $units[min($unitsCount, count($units) - 1)];
# Divide the value by unit size in the power of the counter and round it to keep
# at most the given number of decimal digits.
$value = round($number / pow($unitSize, $unitsCount), $decimals);
# Assemble and return the string.
return $value . $unit;
}
I created my own method inspired by Twitter.
Function:
function legibleNumb($numb, $lang = 'en') {
if ($lang == 'tr') { // Usage with commas in Turkish
if ($numb >= 1000000) { // Million
if (strstr(round(number_format($numb,0,',','.'),1),'.')) {
$legibleNumb = number_format(round(number_format($numb,0,',','.'),1),1,',','.') . ' Mn';
} else {
$legibleNumb = round(number_format($numb,0,',','.'),1) . ' Mn';
}
} elseif ($numb >= 100000 && $numb < 1000000) { // One hundred thousand
$legibleNumb = round(number_format($numb,0,',','.'),0) . ' B';
} elseif ($numb >= 10000 && $numb < 100000) { // Ten thousand
if (strstr(round(number_format($numb,0,',','.'),1),'.')) {
$legibleNumb = number_format(round(number_format($numb,0,',','.'),1),1,',','.') . ' B';
} else {
$legibleNumb = round(number_format($numb,0,',','.'),1) . ' B';
}
} else {
$legibleNumb = number_format($numb,0,',','.');
}
} else { // Dotted usage in English
if ($numb >= 1000000) { // Million
$legibleNumb = round(number_format($numb,0,',','.'),1) . ' M';
} elseif ($numb >= 100000 && $numb < 1000000) { // One hundred thousand
$legibleNumb = round(number_format($numb,0,',','.'),0) . ' K';
} elseif ($numb >= 10000 && $numb < 100000) { // Ten thousand
$legibleNumb = round(number_format($numb,0,',','.'),1) . ' K';
} else {
$legibleNumb = number_format($numb,0,',','.');
}
}
return $legibleNumb;
}
Usage:
echo legibleNumb(9999999,'en');
echo legibleNumb(9999999,'tr');
echo legibleNumb(54669,'en');
echo legibleNumb(54669,'tr');
echo legibleNumb(5466,'en');
echo legibleNumb(5466,'tr');
Results:
10 M
10 Mn
54.7 K
54,7 B
5.466
5.466
You can try it here and check out sample usages: https://glot.io/snippets/eljyd9ssjx
if ($postresultscount > 999999) {
$postresultscount = floor($postresultscount / 1000000) . ' M';
}
elseif ($postresultscount > 999) {
$postresultscount = floor($postresultscount / 1000) . ' K';
}
echo $postresultscount;
This questuion have the same goal as this question in here Shorten long numbers to K/M/B?
Reference:
https://gist.github.com/RadGH/84edff0cc81e6326029c
Try this code:
function number_format_short( $n, $precision = 1 ) {
if ($n < 900) {
// 0 - 900
$n_format = number_format($n, $precision);
$suffix = '';
} else if ($n < 900000) {
// 0.9k-850k
$n_format = number_format($n / 1000, $precision);
$suffix = 'K';
} else if ($n < 900000000) {
// 0.9m-850m
$n_format = number_format($n / 1000000, $precision);
$suffix = 'M';
} else if ($n < 900000000000) {
// 0.9b-850b
$n_format = number_format($n / 1000000000, $precision);
$suffix = 'B';
} else {
// 0.9t+
$n_format = number_format($n / 1000000000000, $precision);
$suffix = 'T';
}
// Remove unecessary zeroes after decimal. "1.0" -> "1"; "1.00" -> "1"
// Intentionally does not affect partials, eg "1.50" -> "1.50"
if ( $precision > 0 ) {
$dotzero = '.' . str_repeat( '0', $precision );
$n_format = str_replace( $dotzero, '', $n_format );
}
return $n_format . $suffix;
}
The code above create a function to convert the numbers. To use this function later just call it like in the code below:
// Example Usage:
number_format_short(7201); // Output: 7.2k
Rounding up, not accounting for any abbreviations above 'k' or thousands, showing one decimal place.
function numToKs($number) {
if ($number >= 1000) {
return number_format(($number / 1000), 1) . 'k';
} else {
return $number;
}
}
numToKs(1) = 1
numToKs(111) = 111
numToKs(999) = 999
numToKs(1000) = "1.0k"
numToKs(1499) = "1.5k"
numToKs(1500) = "1.5k"
numToKs(1501) = "1.5k"
numToKs(1550) = "1.6k"
numToKs(11501) = "11.5k"
numToKs(1000000000) = "1,000,000.0k"
numToKs(1234567890) = "1,234,567.9k"