How to check if an integer is within a range of numbers in PHP? - php

How can I check if a given number is within a range of numbers?

The expression:
($min <= $value) && ($value <= $max)
will be true if $value is between $min and $max, inclusively
See the PHP docs for more on comparison operators

You can use filter_var
filter_var(
$yourInteger,
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => $min,
'max_range' => $max
)
)
);
This will also allow you to specify whether you want to allow octal and hex notation of integers. Note that the function is type-safe. 5.5 is not an integer but a float and will not validate.
Detailed tutorial about filtering data with PHP:
https://phpro.org/tutorials/Filtering-Data-with-PHP.html

Might help:
if ( in_array(2, range(1,7)) ) {
echo 'Number 2 is in range 1-7';
}
http://php.net/manual/en/function.range.php

You could whip up a little helper function to do this:
/**
* Determines if $number is between $min and $max
*
* #param integer $number The number to test
* #param integer $min The minimum value in the range
* #param integer $max The maximum value in the range
* #param boolean $inclusive Whether the range should be inclusive or not
* #return boolean Whether the number was in the range
*/
function in_range($number, $min, $max, $inclusive = FALSE)
{
if (is_int($number) && is_int($min) && is_int($max))
{
return $inclusive
? ($number >= $min && $number <= $max)
: ($number > $min && $number < $max) ;
}
return FALSE;
}
And you would use it like so:
var_dump(in_range(5, 0, 10)); // TRUE
var_dump(in_range(1, 0, 1)); // FALSE
var_dump(in_range(1, 0, 1, TRUE)); // TRUE
var_dump(in_range(11, 0, 10, TRUE)); // FALSE
// etc...

if (($num >= $lower_boundary) && ($num <= $upper_boundary)) {
You may want to adjust the comparison operators if you want the boundary values not to be valid.

You can try the following one-statement:
if (($x-$min)*($x-$max) < 0)
or:
if (max(min($x, $max), $min) == $x)

Some other possibilities:
if (in_array($value, range($min, $max), true)) {
echo "You can be sure that $min <= $value <= $max";
}
Or:
if ($value === min(max($value, $min), $max)) {
echo "You can be sure that $min <= $value <= $max";
}
Actually this is what is use to cast a value which is out of the range to the closest end of it.
$value = min(max($value, $min), $max);
Example
/**
* This is un-sanitized user input.
*/
$posts_per_page = 999;
/**
* Sanitize $posts_per_page.
*/
$posts_per_page = min(max($posts_per_page, 5), 30);
/**
* Use.
*/
var_dump($posts_per_page); // Output: int(30)

using a switch case
switch ($num){
case ($num>= $value1 && $num<= $value2):
echo "within range 1";
break;
case ($num>= $value3 && $num<= $value4):
echo "within range 2";
break;
.
.
.
.
.
default: //default
echo "within no range";
break;
}

I've created a simple helper function.
if ( !function_exists('number_between') )
{
/**
* number_between
*
* #param {integer} $number
* #param {array} $range [min, max]
* #return {boolean}
*/
function number_between(
int $number,
array $range
){
if(
count($range) !== 2 ||
is_numeric($range[0]) === FALSE ||
is_numeric($range[1]) === FALSE
){
throw new \Exception("number_between second parameter must contain two numbers.", E_WARNING);
}
if(
in_array($number, range($range[0], $range[1]))
){
return TRUE;
}else{
return FALSE;
}
}
}

Another way to do this with simple if/else range. For ex:
$watermarkSize = 0;
if (($originalImageWidth >= 0) && ($originalImageWidth <= 640)) {
$watermarkSize = 10;
} else if (($originalImageWidth >= 641) && ($originalImageWidth <= 1024)) {
$watermarkSize = 25;
} else if (($originalImageWidth >= 1025) && ($originalImageWidth <= 2048)) {
$watermarkSize = 50;
} else if (($originalImageWidth >= 2049) && ($originalImageWidth <= 4096)) {
$watermarkSize = 100;
} else {
$watermarkSize = 200;
}

I created a function to check if times in an array overlap somehow:
/**
* Function to check if there are overlapping times in an array of \DateTime objects.
*
* #param $ranges
*
* #return \DateTime[]|bool
*/
public function timesOverlap($ranges) {
foreach ($ranges as $k1 => $t1) {
foreach ($ranges as $k2 => $t2) {
if ($k1 != $k2) {
/* #var \DateTime[] $t1 */
/* #var \DateTime[] $t2 */
$a = $t1[0]->getTimestamp();
$b = $t1[1]->getTimestamp();
$c = $t2[0]->getTimestamp();
$d = $t2[1]->getTimestamp();
if (($c >= $a && $c <= $b) || $d >= $a && $d <= $b) {
return true;
}
}
}
}
return false;
}

Here is my little contribution:
function inRange($number) {
$ranges = [0, 13, 17, 24, 34, 44, 54, 65, 200];
$n = count($ranges);
while($n--){
if( $number > $ranges[$n] )
return $ranges[$n]+1 .'-'. $ranges[$n + 1];
}

I have function for my case
Use:
echo checkRangeNumber(0);
echo checkRangeNumber(1);
echo checkRangeNumber(499);
echo checkRangeNumber(500);
echo checkRangeNumber(501);
echo checkRangeNumber(3001);
echo checkRangeNumber(999);
//return
0
1-500
1-500
1-500
501-1000
3000-3500
501-1000
function checkRangeNumber($number, $per_page = 500)
{
//$per_page = 500; // it's fixed number, but...
if ($number == 0) {
return "0";
}
$num_page = ceil($number / $per_page); // returns 65
$low_limit = ($num_page - 1) * $per_page + 1; // returns 32000
$up_limit = $num_page * $per_page; // returns 40
return "$low_limit-$up_limit";
}

function limit_range($num, $min, $max)
{
// Now limit it
return $num>$max?$max:$num<$min?$min:$num;
}
$min = 0; // Minimum number can be
$max = 4; // Maximum number can be
$num = 10; // Your number
// Number returned is limited to be minimum 0 and maximum 4
echo limit_range($num, $min, $max); // return 4
$num = 2;
echo limit_range($num, $min, $max); // return 2
$num = -1;
echo limit_range($num, $min, $max); // return 0

$ranges = [
1 => [
'min_range' => 0.01,
'max_range' => 199.99
],
2 => [
'min_range' => 200.00,
],
];
foreach($ranges as $value => $range){
if(filter_var($cartTotal, FILTER_VALIDATE_FLOAT, ['options' => $range])){
return $value;
}
}

Thank you so much and I got my answer by adding a break in the foreach loop and now it is working fine.
Here are the updated answer:
foreach ($this->crud->getDataAll('shipping_charges') as $ship) {
if ($weight >= $ship->low && $weight <= $ship->high) {
$val = $ship->amount;
break;
}
else
{
$val = 900;
}
}
echo $val ;

Related

How to find backward primes within a range of integers?

I'm trying to solve a backward prime question.
Following is the question:
Find all Backwards Read Primes between two positive given numbers (both inclusive), the second one being greater than the first one. The resulting array or the resulting string will be ordered following the natural order of the prime numbers.
Example
backwardsPrime(2, 100) => [13, 17, 31, 37, 71, 73, 79, 97]
backwardsPrime(9900, 10000) => [9923, 9931, 9941, 9967]
I tried doing something like this:
public function backwardPrime()
{
$start = 7000;
$stop = 7100;
$ans = [];
while($start <= $stop)
{
if($start > 10)
{
if($start !== $this->reverse($start))
{
if($this->isPrime($start) && $this->isPrime($this->reverse($start)))
{
array_push($ans, $start);
}
}
}
$start++;
}
return $ans;
}
public function reverse($num)
{
$reverse = 0;
while($num > 0)
{
$reverse = $reverse * 10;
$reverse = $reverse + $num%10;
$num = (int)($num/10);
}
return $reverse;
}
public function isPrime($num)
{
if($num == 1 || $num == 2 || $num == 3)
return true;
elseif ($num%2 == 0 || $num%3 == 0)
return false;
else
{
$i=5;
while($i<=$num/2)
{
if($num%$i===0)
{
return false;
}
$i++;
}
}
return true;
}
I'm able to get the appropriate answer but while doing the same in single function I'm not able to get it:
public function backwardPrimes()
{
$start = 7000;
$stop = 7100;
$ans = [];
while($start <= $stop)
{
$isStartPrime = true;
$isReversePrime = true;
if($start > 10)
{
$reverse = 0;
$num = $start;
while($num > 0)
{
$reverse = $reverse * 10;
$reverse = $reverse + $num%10;
$num = (int)($num/10);
}
if($start !== $reverse)
{
if($start%2 != 0 && $start%3 != 0)
{
$i =5;
while($i<=$start/2)
{
if($start%$i === 0)
{
$isStartPrime = false;
break;
}
$i++;
}
}
if($reverse%2 != 0 && $reverse%3 != 0)
{
$i =5;
while($i<=$reverse/2)
{
if($reverse%$i === 0)
{
$isReversePrime = false;
break;
}
$i++;
}
}
if($isStartPrime && $isReversePrime)
{
array_push($ans, $start);
}
}
}
$start++;
}
return $ans;
}
I don't know where I'm having mistake, guide me.
Thanks.
An emirp ("prime" spelled backwards) is a prime whose (base 10) reversal is also prime, but which is not a palindromic prime. in other words
Backwards Read Primes are primes that when read backwards in base 10 (from right to left) are a different prime. (This rules out primes which are palindromes.)
try this short solution in which I use two helper function reverse and isPrime :
isPrime: Thanks to #Jeff Clayton for his method to test prime numbers, for more information click the link below https://stackoverflow.com/a/24769490/4369087
reverse: that use the php function [strrev()][1], this method take a string a reverse it, we'll use this trick to reverse a number by converting it to a string reverse it and converting back to an integer.
backwardsPrime: this last function's job is itterating over a range of numbers from $min value to $max value and test if the number if a prime number and it's reverse is a prime number as well and it's not a palindrome number if all of those conditions are true then we addit to the result array.
implementation
function isPrime($number)
{
return !preg_match('/^1?$|^(11+?)\1+$/x', str_repeat('1', $number));
}
function reverse($n)
{
return (int) strrev((string) $n);
}
function backwardsPrime($min, $max)
{
$result = [];
foreach(range($min, $max) as $number) {
$reverse = reverse($number);
if($reverse !== $number && isPrime($number) && isPrime($reverse)) {
$result[] = $number;
}
}
return $result;
}
echo "<pre>";
print_r(backwardsPrime(2, 100));
print_r(backwardsPrime(9900, 10000));
output :
Array
(
[0] => 13
[1] => 17
[2] => 31
[3] => 37
[4] => 71
[5] => 73
[6] => 79
[7] => 97
)
Array
(
[0] => 9923
[1] => 9931
[2] => 9941
[3] => 9967
)
you can even optimize the backwardsPrime function like this :
function backwardsPrime($min, $max)
{
$result = [];
foreach(range($min, $max) as $number) {
$reverse = reverse($number);
if($reverse !== $number && !in_array($number, $result) && isPrime($number) && isPrime($reverse)) {
$result[] = $number;
}
}
return $result;
}

How can I improve this method. Is there any php native method available to match the range?

I want to refactor this method
public function getRange($currentWeight, $maxWeight)
{
$shades = array(
'0-10' => 'Range0-10',
'10-20' => 'Range10-20',
'20-30' => 'Range20-30',
'30-40' => 'Range30-40',
'40-50' => 'Range40-50',
'50-60' => 'Range50-60',
'60-70' => 'Range60-70',
'70-80' => 'Range70-80',
'80-90' => 'Range80-90',
'90-100' => 'Range90-100'
);
$weightPercent = ($currentWeight * 100) / $maxWeight;
foreach ($shades as $colorRange => $colorClass) {
$range = explode('-', $colorRange);
if ($weightPercent >= $range[0] && $weightPercent <= $range[1]) {
$selectedColor = $colorClass;
}
}
return $selectedColor;
}
Is there any native php function available which can give me the range of a number or is there any other way of doing this better?
Any help would be greatly appreciated.
my version:
public function getRange($currentWeight, $maxWeight)
{
$weight = intval(($currentWeight * 100) / $maxWeight / 10) * 10;
return 'Range' . $weight .'-' . ($weight +10);
}
You need something to keep your values in range:
public function getRange($currentWeight, $maxWeight){
$range_base = intval(($currentWeight * 100) / $maxWeight / 10) * 10;
if($range_base >= 0 && $range_base <= 100 && $currentWeight<=$maxWeight){
if($range_base == 100) { $range_base = $range_base - 10; }
return 'Range' . $range_base .'-' . ($range_base +10);
}else{
return false; // Out of range
}
}
Another version using range, bcsub, array_search and array_slice functions:
function getRange($currentWeight, $maxWeight)
{
if ($currentWeight > $maxWeight) { // considering max threshold
throw new Exception("weight is out of range!");
}
$colorRange = range(0, 100, 10);
$weightPercent = ($currentWeight * 100) / $maxWeight;
$lower_boundary_key = array_search(intval(bcsub(floor($weightPercent), $weightPercent % 10, 0)), $colorRange);
$range = array_slice($colorRange, $lower_boundary_key, 2);
return 'Range' . implode("-", $range);
}
print_r(getRange(41.5, 200)); // outputs: "Range20-30"
print_r(getRange(201.5, 200)); // will throw "Exception: weight is out of range! ..."

Rewrite a PHP function with arrays instead

Is there any way I could rewrite this function with an array instead of all these if statements? Could i maybe use some for loop together with an array? How would that look like? Any suggestions of simpler code?
Here is my php function:
function to_next_level($point) {
/*
**********************************************************************
*
* This function check how much points user has achievents and how much procent it is until next level
*
**********************************************************************
*/
$firstlevel = "3000";
$secondlevel = "7000";
$thirdlevel = "15000";
$forthlevel = "28000";
$fifthlevel = "45000";
$sixthlevel = "80000";
if($point <= $firstlevel) {
$total = ($point/$firstlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
} elseif ($point <= $secondlevel) {
$total = ($point/$secondlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
} elseif ($point <= $thirdlevel) {
$total = ($point/$thirdlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
} elseif ($point <= $forthlevel) {
$total = ($point/$forthlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
} elseif ($point <= $fifthlevel) {
$total = ($point/$fifthlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
} elseif ($point <= $sixthlevel) {
$total = ($point/$sixthlevel) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
}
}
Try this:
function to_next_level($point) {
/*
**********************************************************************
*
* This function check how much points user has achievents and how much procent it is until next level
*
**********************************************************************
*/
$levelArray = array(3000, 7000, 15000, 28000, 45000, 80000);
foreach ($levelArray as $level)
{
if ($point <= $level) {
$total = ($point/$level) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
}
}
}
Start using OOP programming style. This is the perfect opportunity since it is a task without much complexity. Create a class that acts as central authority. That class can receive more methods over time. That way your code stays easy to maintain since all those functions are kept inside a class.
<?php
class levelAuthority
{
public static $thresholds = [ 3000, 7000, 15000, 28000, 45000, 80000 ];
public static function getDistanceToNextlevel($points)
{
foreach (self::$thresholds as $threshold) {
if ($points <= $threshold) {
$total = ($points/$threshold) * 100;
$remaining = round($total);
return $remaining;
}
}
}
}
// in the calling scope:
$points = 5000;
echo levelAuthority::getDistanceToNextlevel($points);
lots of answers to this!!
here is mine using a while loop - single exit point outside the loop:
function to_next_level($point) {
/*
**********************************************************************
*
* This function check how much points user has achievements and how much percent it is until next level
*
**********************************************************************
*/
$arr_level = array(3000,15000,28000,45000,80000);
$remaining = false;
while (!$remaining and list($key,$level) = each($arr_level)) {
if ($point <= $level) {
$total = ($point/$level) * 100;
$remaining = round($total);
}
}
// will return false if $point is greater than highest value in $arr_level
return $remaining;
}
You could write an additional function, that does the calculations and trigger it from the if/else if/else blocks.
function calculate_remaining($points, $level) {
$total = ($point/$level) * 100;
$remaining = round($total);
return $remaining;
}
You'd trigger this like:
if($point <= $firstlevel) {
return $calculate_remaining($point, $firstlevel);
} elseif ($point <= $secondlevel) {
return $calculate_remaining($point, $secondlevel);
} etc.
What about something like this?
function to_next_level($point)
{
$levels = array(
3000,
7000,
15000,
28000,
45000,
80000
);
foreach ($levels as $level)
{
if ($point <= $level)
{
$total = ($point / $level) * 100;
$remaining = round($total);
//echo number_format($remaining, 0, '.', ' ');
return $remaining;
}
}
}
The point levels are in order in the array, so [0] is $firstlevel, and so on. You simply iterate through the array and return whenever we reach the condition where $point is <= to the the $level.
Also, since $levels is static, it can be defined outside of the function.
simple:
<?php
$point = 100;
$remaining = 0;
$data = [
'firstlevel' => 3000,
'secondlevel' => 7000,
'thirdlevel' => 15000,
'forthlevel' => 28000,
'fifthlevel' => 45000,
'sixthlevel' => 80000
];
foreach($data as $item)
{
if($point <= $item)
{
$remaining = round(($point / $item ) * 100); //or return val
}
}
How about putting your variable into array and loop it?
function to_next_level($point) {
$data[0] = "3000";
$data[1] = "7000";
$data[2] = "15000";
$data[3] = "28000";
$data[4] = "45000";
$data[5] = "80000";
foreach ($data as $key => $value) {
if($point <= $value) {
$total = ($point/$value) * 100;
$remaining = round($total);
return $remaining;
}
}
}
I haven't try it. But it might work for you.

Converting float decimal to fraction

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"

Generating a range with irregular steps. Optimized

Does PHP have existing functionality for irregular step ranges, is there a common solution to provide this functionality, or how can the following function be optimized?
The first function is the function I am concerned about. The second function is a real world use case that generates an array to populate values for a function that outputs a select dropdown for HTML.
<?php
function range_multistep($min, $max, Array $steps, $jmp = 10) {
$steps = array_unique($steps);
sort($steps, SORT_NUMERIC);
$bigstep = ($jmp > 0) ? $jmp : $jmp * -1;
$e = ($min > 0) ? floor(log($min, $bigstep)) : 0;
for (; ; $e++) {
foreach ($steps as $step) {
$jump = pow($bigstep, $e);
$num = $step * $jump;
if ($num > $max) {
break 2;
} elseif ($num >= $min) {
$arr[] = $num;
}
}
}
$arr = array_unique($arr);
sort($arr, SORT_NUMERIC);
return $arr;
}
function prices() {
$price_steps = range_multistep(50, 100000, array(5, 10, 25));
$prev_step = 0;
foreach ($price_steps as $price) {
$price_str = '$' . $prev_step . ' - $' . ($price - 1);
$price_arr[] = $price_str;
$prev_step = $price;
}
$price_arr[] = '$' . end($price_steps) . "+";
return $price_arr;
}
print_r(prices());
The result of the previous:
Array
(
[0] => $0 - $49
[1] => $50 - $99
[2] => $100 - $249
[3] => $250 - $499
[4] => $500 - $999
[5] => $1000 - $2499
[6] => $2500 - $4999
[7] => $5000 - $9999
[8] => $10000 - $24999
[9] => $25000 - $49999
[10] => $50000 - $99999
[11] => $100000+
)
Repeated addition is best replaced by multiplication, and repeated multiplication is best replaced by raising to powers -- which you've done.
I see nothing here that requires improvement assuming you don't need "bulletproof" behavior in the face of $jmp = 1 or $min >= $max badly-behaved inputs.
The $e incrementor in the for loop is more of a while(1) endless loop.
So instead misusing the incrementor in pow(), do the pow on your own by just multiplying once per iteration. Calling pow() can be pretty expensive, so doing the pow calculation your own would better distribute the multiplication onto each iteration.
Edit: The following is a variant of your function that distributes the pow() calculation over the iteration. Additionally it does more proper variable initialisation (the return value was not set for example), gives notice if $min and $max are swapped and corrects that, uses abs instead of your ternary, throws an exception if an invalid value was given for log(), renamed some variables and add $num to the return value as key first to spare the array_unique operation at the end:
/**
* #param int $min
* #param int $max
* #param array $steps
* #param int $jmp
* #return array range
*/
function range_multistep($min, $max, Array $steps, $jmp = 10) {
$range = array();
if (!$steps) return $range;
if ($min < $max) {
trigger_error(__FUNCTION__.'(): Minima and Maxima mal-aligned.', E_USER_NOTICE);
list($max, $min) = array($min, $max);
}
$steps = array_unique($steps);
sort($steps, SORT_NUMERIC);
$bigstep = abs($jmp);
if ($bigstep === 0) {
throw new InvalidArgumentException(sprintf('Value %d is invalid for jmp', $jmp));
}
$initExponent = ($min > 0) ? floor(log($min, $bigstep)) : 0;
for ($multiplier = pow($bigstep, $initExponent); ; $multiplier *= $bigstep) {
foreach ($steps as $step) {
$num = $step * $multiplier;
if ($num > $max) {
break 2;
} elseif ($num >= $min) {
$range[$num] = 1;
}
}
}
$range = array_keys($range);
sort($range, SORT_NUMERIC);
return $range;
}
In case you feel experimental, it's also possible to turn the two loops (for+foreach) into one, but the readability of the code does not benefit from it:
for(
$multiplier = pow($bigstep, $initExponent),
$step = reset($steps)
;
$num = $step * $multiplier,
$num <= $max
;
# infinite array iterator:
($step=next($steps))?:
(
$step=reset($steps)
# with reset expression:
AND $multiplier *= $bigstep
)
){
if ($num >= $min)
$range[$num] = 1;
}
I think if you take care to not re-use variables (like the function parameter) and give them better to read names, improvement comes on it's own.

Categories