How to validate decimal stepping from a starting number in PHP - php

I need to validate that an inputted number is a valid number based on my stepping rules and round up to the nearest valid number if not. These numbers will change but one example would be:
$min = 0.25;
$step = 0.1
$qty = 0.75 // user input
so these would be valid inputs:
0.75
0.85
0.95
But these should round:
0.76 (to 0.85)
0.80 (to 0.85)
I thought I could use modulus somehow but not getting the calculation correct.
if (($qty % min) / $step == 0)) {
echo "good";
}
I've tried some variations of math that are likely very wrong
$step = 0.1;
$min = 0.25;
$qty = .85;
$h = ($qty / $min) / $step;
echo $h;
$j = mround($qty, $min-$step);
echo $j;
function mround($num, $parts) {
if ($parts <= 0) { $parts = 1; }
$res = $num * (1/$parts);
$res = round($res);
return $res /(1/$parts);
}

I think you can use fmod to do this.
$new = $original + ($step - fmod($original - $minimum, $step));
Example on 3v4l.org

Related

Round down very large or small float by scale

I have next situation, I have some qty it can be very big or very small float like:
3698541.2569854 or 0.569875255456.
Also, I have some scale
or min step, like
0.0001 or 1000.00.
My challenge is round my qty to nearest down number by step. Example:
$qty = 323.659;
$step = 0.1;
// result must be 323.6
I have my own solution, it work well, but i have problem with wery large nums.
private function roundDownByStep($qty, $stepSize) {
$stepSize = sprintf('%.16f', $stepSize);
$stepSize = preg_replace('/|\\..+?\\K0+$/', "", $stepSize);
$precision = strlen(substr(strrchr($stepSize, "."), 1));
if ($precision == 1 && $stepSize >= 1) {
$precision = 0;
}
if ($precision == 0) {
$subArg = 1/$stepSize;
return floor(($qty * $subArg)) / $subArg;
} else {
$subArg = bcdiv('1', $stepSize, $precision);
$result = floor(bcmul($qty, $subArg, $precision));
$result = sprintf('%.16f', $result);
$result = preg_replace('/|\\..+?\\K0+$/', "", $result);
return bcdiv($result, $subArg, $precision);
}
}
You can try it on
http://sandbox.onlinephpfunctions.com/code/333f58c1487e86e53dde64c26930b57e1f9e0fe8
You are over complicating a simple task:
function cutAtPrecision($number, $precision) {
return intval($number * (1 / $precision)) / (1 / $precision);
}
echo cutAtPrecision(323.659, 0.01); // 323.65

PHP slightly increment value based on number of decimals

I am trying to slightly increment a value based on the number of decimals it has.
For example if the value is 1.2 I would increase it by 0.1, 12.345 by 0.001, 12.345678 by 0.000001, etc.
I currently have a long implementation using a chain of if, else if. I know this is not the most efficient way and a loop can be used, but I was unsure of how to structure the loop. I tried using the PHP substr_replace function, but I could not get it to work for this.
Is there another way I can structure a loop to reduce my lines of code and be more efficient?
Here is my php code so far:
$valueOne = 12.345678;
// get amount of decimals
$decimal = strlen(strrchr($valueOne, '.')) -1;
/*
this also works for finding how many decimals
$test = floatval($valueOne);
for ( $decimal_count = 0; $test != round($test, $decimal_count); $decimal_count++ );
echo $decimal_count;
*/
// see value before change
echo $valueOne;
if ($decimal == "1") {
$valueOne = $valueOne + 0.1;
}
else if ($decimal == "2") {
$valueOne = $valueOne + 0.01;
}
else if ($decimal == "3") {
$valueOne = $valueOne + 0.001;
}
// etc ...
// see value after change
echo $valueOne;
/*
i tried messing around with using a loop, but did not have much luck
$start = 0.1;
$count = 0;
$position = 2;
while ($count != $decimal) {
echo substr_replace($start, 0, $position, 0) . "<br />\n";
$count++;
//$position++;
}
*/
Get the number of digits after the decimal. Then create a number with a decimal point, one less 0, followed by 1, to get the amount to add.
$valueOne = 12.345678;
// get amount of decimals
$decimal = strlen(strrchr($valueOne, '.')) -1;
// see value before change
echo $valueOne . "<br>\n";
// Get amount to add
$increment = '.' . str_repeat('0', $decimal-1) . '1';
$valueOne += $increment;
echo $valueOne;
Get the number of decimals
Multiply by the appropriate factor so the number is now an integer
Increment by 1
Divide by the same factor to get back to the original number (properly incremented)
function increment($number){
// get amount of decimals
$decimal = strlen(strrchr($valueOne, '.')) -1;
$factor = pow(10,$decimal);
$incremented = (($factor * $number) + 1) / $factor;
return $incremented;
}

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.

Generate alphanumeric unique numbers

I want to generate alphanumeric unique numbers but the format should be like this
that should be starts from AA001 to AA999 after that AB001 to AB999 .... BA001 to BA999 end with ZZ999. if i give the input is
1 = result AA001
999 = result AA999
1000 = result AB001
any one can help this ?
Complete solution (see it running):
function formatNum1000($num) {
$tail = $num % 1000;
$head = (int)($num / 1000);
$char1 = chr(ord('A') + (int)($head / 26));
$char2 = chr(ord('A') + ($head % 26));
return sprintf('%s%s%03d', $char1, $char2, $tail);
}
function formatNum999($num) {
$tail = (($num - 1 ) % 999) + 1;
$head = (int)(($num - $tail) / 999);
$char1 = chr(ord('A') + (int)($head / 26));
$char2 = chr(ord('A') + ($head % 26));
return sprintf('%s%s%03d', $char1, $char2, $tail);
}
$ns = array(1, 500, 999, 1000, 1998, 1999, 2000, 25974, 25975, 25999, 26000, 675324, 675999);
foreach($ns as $n) {
$formatted1000 = formatNum1000($n);
$formatted999 = formatNum999 ($n);
echo "Num: $n => $formatted1000 / $formatted999\n";
}
Note: you need to make sure that the input number is within the valid range (0...675999 when including 000-numbers, 1...675324 otherwise)
Note: answer revised, missed the point earlier that 000 is not allowed
How about:
$start = 'AA997';
for($i = 0; $i < 5; $i++) {
$start++;
if (substr($start, 2) == '000') continue;
echo $start,"\n";
}
output:
AA998
AA999
AB001
AB002

IMEI validation function

Does anybody know a PHP function for IMEI validation?
Short solution
You can use this (witchcraft!) solution, and simply check the string length:
function is_luhn($n) {
$str = '';
foreach (str_split(strrev((string) $n)) as $i => $d) {
$str .= $i %2 !== 0 ? $d * 2 : $d;
}
return array_sum(str_split($str)) % 10 === 0;
}
function is_imei($n){
return is_luhn($n) && strlen($n) == 15;
}
Detailed solution
Here's my original function that explains each step:
function is_imei($imei){
// Should be 15 digits
if(strlen($imei) != 15 || !ctype_digit($imei))
return false;
// Get digits
$digits = str_split($imei);
// Remove last digit, and store it
$imei_last = array_pop($digits);
// Create log
$log = array();
// Loop through digits
foreach($digits as $key => $n){
// If key is odd, then count is even
if($key & 1){
// Get double digits
$double = str_split($n * 2);
// Sum double digits
$n = array_sum($double);
}
// Append log
$log[] = $n;
}
// Sum log & multiply by 9
$sum = array_sum($log) * 9;
// Compare the last digit with $imei_last
return substr($sum, -1) == $imei_last;
}
Maybe can help you :
This IMEI number is something like this: ABCDEF-GH-IJKLMNO-X (without “-” characters)
For example: 350077523237513
In our example ABCDEF-GH-IJKLMNO-X:
AB is Reporting Body Identifier such as 35 = “British Approvals Board of Telecommunications (BABT)”
ABCDEF is Type Approval Code
GH is Final Assembly Code
IJKLMNO is Serial Number
X is Check Digit
Also this can help you : http://en.wikipedia.org/wiki/IMEI#Check_digit_computation
If i don't misunderstood, IMEI numbers using Luhn algorithm . So you can google this :) Or you can search IMEI algorithm
Maybe your good with the imei validator in the comments here:
http://www.php.net/manual/en/function.ctype-digit.php#77718
But I haven't tested it
Check this solution
<?php
function validate_imei($imei)
{
if (!preg_match('/^[0-9]{15}$/', $imei)) return false;
$sum = 0;
for ($i = 0; $i < 14; $i++)
{
$num = $imei[$i];
if (($i % 2) != 0)
{
$num = $imei[$i] * 2;
if ($num > 9)
{
$num = (string) $num;
$num = $num[0] + $num[1];
}
}
$sum += $num;
}
if ((($sum + $imei[14]) % 10) != 0) return false;
return true;
}
$imei = '868932036356090';
var_dump(validate_imei($imei));
?>
IMEI validation uses Luhn check algorithm. I found a link to a page where you can validate your IMEI. Furthermore, at the bottom of this page is a piece of code written in JavaScript to show how to calculate the 15th digit of IMEI and to valid IMEI. I might give you some ideas. You can check it out here http://imei.sms.eu.sk/index.html
Here is a jQuery solution which may be of use: https://github.com/madeinstefano/imei-validator
good fun from kasperhartwich
function validateImei($imei, $use_checksum = true) {
if (is_string($imei)) {
if (ereg('^[0-9]{15}$', $imei)) {
if (!$use_checksum) return true;
for ($i = 0, $sum = 0; $i < 14; $i++) {
$tmp = $imei[$i] * (($i%2) + 1 );
$sum += ($tmp%10) + intval($tmp/10);
}
return (((10 - ($sum%10)) %10) == $imei[14]);
}
}
return false;
}

Categories