PHP Round decimals always up to the nearest highest decimal - php

I would like to create a rounding methode to round decimals always up to the closest highest decimal value.
for example: 51.13 => 51.20 or 22.33 => 23.40 but lets say 30.10 stays 30.10
Is this even possible?
Sorry if my explonation is bad but my math english is not the best.

maybe custom function for rounding ?
something like this
$inputNumber=51.13;
function round2deci($number){
$explode = explode(".", $number);
/// 51.13 == 51.ab
$a = substr($explode[1], 0, 1);
$b = substr($explode[1], 1, 2);
// fix for 51.91
if($a == 9){
$explode[0]++;
$a = 0;
$b = 0;
}
if($b > 0){
$a++;
}
echo $explode[0].".".$a."0";
}
round2deci($inputNumber);
and result is :
51.20

I found this answer on the php.net website under the ceil docs that does exactly what you want.
I've added an improved version below which returns a pure float where you'd be able to apply number_format to preserve the trailing zeros of the decimals:
function ceil_dec(float $number, int $precision = 2, string $separator = '.') : float
{
$numberpart = explode($separator, (string)$number);
$numberpart[1] = substr_replace($numberpart[1], $separator, $precision, 0);
if ($numberpart[0] >= 0) {
$numberpart[1] = ceil($numberpart[1]);
} else {
$numberpart[1] = floor($numberpart[1]);
}
$ceil_number = [$numberpart[0], $numberpart[1]];
return (float)implode($separator, $ceil_number);
}
var_dump(
ceil_dec(10.23,1)
);
The above code is good for PHP 7.0 and above, and with declare(strict_types = 1) enabled.

Related

How to remove zeroes at the end of decimal part of a float number?

I have in database product prices with 4 digits in decimal part, i.e;
4.5000
0.0050
5.0000
I want to show on the website these prices with minimum 2 decimal digits (without rounding), i.e;
4.50
0.005
5.00
I tried number_format, but it still leaves 4 digits in decimal part.
And also I need to use thousands separator on a base part of that number and own delimiter of decimal part.
How to do this?
function trimDecimalZero($num, $delim = ',', $tsep = ' ') {
#list($base, $decimals) = explode('.',
rtrim(number_format((float) $num, 4, '.', $tsep), '0'));
if (intval($decimals)) {
return sprintf('%s%s%s',
$base, $delim, strlen($decimals) < 2 ? $decimals .'0' : $decimals);
}
return sprintf('%s%s%02d', $base, $delim, $decimals);
}
$nums = [4.5000, 0.0050, 5.0000];
foreach ($nums as $num) {
var_dump(trimDecimalZero($num));
}
Result as expected;
string(4) "4,50"
string(5) "0,005"
string(4) "5,00"
Try with -
echo number_format('4.5000', 2);
Update
$v = (float)'0.0050';
$newV = explode('.', $v);
if(strlen($newV[1]) < 2) {
$v = number_format($v, 2);
}
echo $v;
For future reference, once again I modified my answer and yes, it outputs expected results:
function trimDecimalZero($number) {
$number += 0;
if(strlen(substr(strrchr($number, "."),1)) < 2) {
$number = sprintf("%0.2f", $number);
}
return $number;
}
echo trimDecimalZero(4.5000); //4.50
echo trimDecimalZero(0.00050); //0.00005
echo trimDecimalZero(5.0000); //5.00
Hope this would help future readers!
I guess that if it is for showing purposes, you could do something like this:
$number= 4.500000;
$number.= 'x';
$length = strlen($number);
$new_number = rtrim($number, '0x');
if (strlen($new_number) < $length) {
$new_number .= '0';
}
echo $new_number;

PHP check if is integer

I have the following calculation:
$this->count = float(44.28)
$multiple = float(0.36)
$calc = $this->count / $multiple;
$calc = 44.28 / 0.36 = 123
Now I want to check if my variable $calc is integer (has decimals) or not.
I tried doing if(is_int()) {} but that doesn't work because $calc = (float)123.
Also tried this-
if($calc == round($calc))
{
die('is integer');
}
else
{
die('is float);
}
but that also doesn't work because it returns in every case 'is float'. In the case above that should'n be true because 123 is the same as 123 after rounding.
Try-
if ((string)(int) $calc === (string)$calc) {
//it is an integer
}else{
//it is a float
}
Demo
As CodeBird pointed out in a comment to the question, floating points can exhibit unexpected behaviour due to precision "errors".
e.g.
<?php
$x = 1.4-0.5;
$z = 0.9;
echo $x, ' ', $z, ' ', $x==$z ? 'yes':'no';
prints on my machine (win8, x64 but 32bit build of php)
0.9 0.9 no
took a while to find a (hopefully correct) example that is a) relevant to this question and b) obvious (I think x / y * y is obvious enough).
again this was tested on a 32bit build on a 64bit windows 8
<?php
$y = 0.01; // some mambojambo here...
for($i=1; $i<31; $i++) { // ... because ...
$y += 0.01; // ... just writing ...
} // ... $y = 0.31000 didn't work
$x = 5.0 / $y;
$x *= $y;
echo 'x=', $x, "\r\n";
var_dump((int)$x==$x);
and the output is
x=5
bool(false)
Depending on what you're trying to achieve it might be necessary to check if the value is within a certain range of an integer (or it might be just a marginalia on the other side of the spectrum ;-) ), e.g.
function is_intval($x, $epsilon = 0.00001) {
$x = abs($x - round($x));
return $x < $epsilon;
};
and you might also take a look at some arbitrary precision library, e.g. the bcmath extension where you can set "the scale of precision".
You can do it using ((int) $var == $var)
$var = 9;
echo ((int) $var == $var) ? 'true' : 'false';
//Will print true;
$var = 9.6;
echo ((int) $var == $var) ? 'true' : 'false';
//Will print false;
Basically you check if the int value of $var equal to $var
round() will return a float. This is because you can set the number of decimals.
You could use a regex:
if(preg_match('~^[0-9]+$~', $calc))
PHP will convert $calc automatically into a string when passing it to preg_match().
You can use number_format() to convert number into correct format and then work like this
$count = (float)(44.28);
$multiple = (float)(0.36);
$calc = $count / $multiple;
//$calc = 44.28 / 0.36 = 123
$calc = number_format($calc, 2, '.', '');
if(($calc) == round($calc))
die("is integer");
else
die("is not integer");
Demo
Ok I guess I'am pretty late to the party but this is a alternative using fmod() which is a modulo operation. I simply store the fraction after the calculation of 2 variables and check if they are > 0 which would imply it is a float.
<?php
class booHoo{
public function __construct($numberUno, $numberDos) {
$this->numberUno= $numberUno;
$this->numberDos= $numberDos;
}
public function compare() {
$fraction = fmod($this->numberUno, $this->numberDos);
if($fraction > 0) {
echo 'is floating point';
} else {
echo 'is Integer';
}
}
}
$check= new booHoo(5, 0.26);
$check->compare();
Eval here
Edit: Reminder Fmod will use a division to compare numbers the whole documentation can be found here
if (empty($calc - (int)$calc))
{
return true; // is int
}else{
return false; // is no int
}
Try this:
//$calc = 123;
$calc = 123.110;
if(ceil($calc) == $calc)
{
die("is integer");
}
else
{
die("is float");
}
you may use the is_int() function at the place of round() function.
if(is_int($calc)) {
die('is integer');
} else {
die('is float);
}
I think it would help you
A more unorthodox way of checking if a float is also an integer:
// ctype returns bool from a string and that is why use strval
$result = ctype_digit(strval($float));

Validate IBAN PHP

As designing a new platform we tried to integrate the IBAN numbers. We have to make sure that the IBAN is validated and the IBAN stored to the database is always correct. So what would be a proper way to validate the number?
As the logic was explained in my other question, I've created a function myself. Based on the logic explained in the Wikipedia article find a proper function below. Country specific validation.
Algorithm and character lengths per country at https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN.
function checkIBAN($iban)
{
if(strlen($iban) < 5) return false;
$iban = strtolower(str_replace(' ','',$iban));
$Countries = array('al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24);
$Chars = array('a'=>10,'b'=>11,'c'=>12,'d'=>13,'e'=>14,'f'=>15,'g'=>16,'h'=>17,'i'=>18,'j'=>19,'k'=>20,'l'=>21,'m'=>22,'n'=>23,'o'=>24,'p'=>25,'q'=>26,'r'=>27,'s'=>28,'t'=>29,'u'=>30,'v'=>31,'w'=>32,'x'=>33,'y'=>34,'z'=>35);
if(array_key_exists(substr($iban,0,2), $Countries) && strlen($iban) == $Countries[substr($iban,0,2)]){
$MovedChar = substr($iban, 4).substr($iban,0,4);
$MovedCharArray = str_split($MovedChar);
$NewString = "";
foreach($MovedCharArray AS $key => $value){
if(!is_numeric($MovedCharArray[$key])){
if(!isset($Chars[$MovedCharArray[$key]])) return false;
$MovedCharArray[$key] = $Chars[$MovedCharArray[$key]];
}
$NewString .= $MovedCharArray[$key];
}
if(bcmod($NewString, '97') == 1)
{
return true;
}
}
return false;
}
Slight modification of #PeterFox answer including support for bcmod() when bcmath is not available,
<?php
function isValidIBAN ($iban) {
$iban = strtolower($iban);
$Countries = array(
'al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,
'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,
'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,
'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,
'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24
);
$Chars = array(
'a'=>10,'b'=>11,'c'=>12,'d'=>13,'e'=>14,'f'=>15,'g'=>16,'h'=>17,'i'=>18,'j'=>19,'k'=>20,'l'=>21,'m'=>22,
'n'=>23,'o'=>24,'p'=>25,'q'=>26,'r'=>27,'s'=>28,'t'=>29,'u'=>30,'v'=>31,'w'=>32,'x'=>33,'y'=>34,'z'=>35
);
if (strlen($iban) != $Countries[ substr($iban,0,2) ]) { return false; }
$MovedChar = substr($iban, 4) . substr($iban,0,4);
$MovedCharArray = str_split($MovedChar);
$NewString = "";
foreach ($MovedCharArray as $k => $v) {
if ( !is_numeric($MovedCharArray[$k]) ) {
$MovedCharArray[$k] = $Chars[$MovedCharArray[$k]];
}
$NewString .= $MovedCharArray[$k];
}
if (function_exists("bcmod")) { return bcmod($NewString, '97') == 1; }
// http://au2.php.net/manual/en/function.bcmod.php#38474
$x = $NewString; $y = "97";
$take = 5; $mod = "";
do {
$a = (int)$mod . substr($x, 0, $take);
$x = substr($x, $take);
$mod = $a % $y;
}
while (strlen($x));
return (int)$mod == 1;
}
The accepted answer is not the preferred way of validation. The specification dictates the following:
Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid
Replace the two check digits by 00 (e.g. GB00 for the UK)
Move the four initial characters to the end of the string
Replace the letters in the string with digits, expanding the string as necessary, such that A or a = 10, B or b = 11, and Z or z = 35. Each alphabetic character is therefore replaced by 2 digits
Convert the string to an integer (i.e. ignore leading zeroes)
Calculate mod-97 of the new number, which results in the remainder
Subtract the remainder from 98, and use the result for the two check digits. If the result is a single digit number, pad it with a leading 0 to make a two-digit number
I've written a class that validates, formats and parses strings according to the spec. Hope this helps some save the time required to roll their own.
The code can be found on GitHub here.
top rated function does NOT work.
Just try a string with '%' in it...
I'm using this one :
function checkIBAN($iban) {
// Normalize input (remove spaces and make upcase)
$iban = strtoupper(str_replace(' ', '', $iban));
if (preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $iban)) {
$country = substr($iban, 0, 2);
$check = intval(substr($iban, 2, 2));
$account = substr($iban, 4);
// To numeric representation
$search = range('A','Z');
foreach (range(10,35) as $tmp)
$replace[]=strval($tmp);
$numstr=str_replace($search, $replace, $account.$country.'00');
// Calculate checksum
$checksum = intval(substr($numstr, 0, 1));
for ($pos = 1; $pos < strlen($numstr); $pos++) {
$checksum *= 10;
$checksum += intval(substr($numstr, $pos,1));
$checksum %= 97;
}
return ((98-$checksum) == $check);
} else
return false;
}
I found this solution in cakephp 3.7 validation class. Plain beautiful php realization.
/**
* Check that the input value has a valid International Bank Account Number IBAN syntax
* Requirements are uppercase, no whitespaces, max length 34, country code and checksum exist at right spots,
* body matches against checksum via Mod97-10 algorithm
*
* #param string $check The value to check
*
* #return bool Success
*/
public static function iban($check)
{
if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $check)) {
return false;
}
$country = substr($check, 0, 2);
$checkInt = intval(substr($check, 2, 2));
$account = substr($check, 4);
$search = range('A', 'Z');
$replace = [];
foreach (range(10, 35) as $tmp) {
$replace[] = strval($tmp);
}
$numStr = str_replace($search, $replace, $account . $country . '00');
$checksum = intval(substr($numStr, 0, 1));
$numStrLength = strlen($numStr);
for ($pos = 1; $pos < $numStrLength; $pos++) {
$checksum *= 10;
$checksum += intval(substr($numStr, $pos, 1));
$checksum %= 97;
}
return ((98 - $checksum) === $checkInt);
}
This function check the IBAN and need GMP activate http://php.net/manual/en/book.gmp.php.
function checkIban($string){
$to_check = substr($string, 4).substr($string, 0,4);
$converted = '';
for ($i = 0; $i < strlen($to_check); $i++){
$char = strtoupper($to_check[$i]);
if(preg_match('/[0-9A-Z]/',$char)){
if(!preg_match('/\d/',$char)){
$char = ord($char)-55;
}
$converted .= $char;
}
}
// prevent: "gmp_mod() $num1 is not an integer string" error
$converted = ltrim($converted, '0');
return strlen($converted) && gmp_strval(gmp_mod($converted, '97')) == 1;
}
enjoy !

PHP implementation for an URL shortening algorithm

I found Marcel Jackwerth's response to How to code a URL shortener? to be a good answer for the problem, however my question is how it'll look in PHP? Here's Marcel's answer:
You need a Bijective Function f (there must be no x1 != x2, that will make f(x1) = f(x2); and for every y you will find a x so that f(x)=y). This is necessary so that you can find a inverse function g('abc') = 123 for your f(123)='abc' function.
I would continue your "convert number to string" approach (however you will realize that your proposed algorithm fails if your id is a prime and greater than 52).
How to convert the id to a shortened url:
Think of an alphabet you want to use. In your case that's [a-zA-Z0-9]. It contains 62 letters.
Take the auto-generated unique numerical key (auto-incremented id): for example 125 (a decimal number)
Now you have to convert the 125 (base 10) to X (base 62). This will then be {2}{1} (2×62+1=125).
Now map the symbols {2} and {1} to your alphabet. Say {0} = 'a', {25} = 'z' and so on. We will have {2} = 'c' and {1} = 'b'. So '/cb' will be your shortened url.
How to resolve a shortened url abc to the initial id:
If you want to do this in reverse, it's not quite diffcult. 'e9a' will be resolved to "4th,61st,0th letter in alphabet" = {4}{61}{0}, which is 4×62×62 + 61×62 + 0 = 19158. You will then just have to find your database-record with id 19158.
function convert($src, $srcAlphabet, $dstAlphabet) {
$srcBase = strlen($srcAlphabet);
$dstBase = strlen($dstAlphabet);
$wet = $src;
$val = 0;
$mlt = 1;
while ($l = strlen($wet)) {
$digit = $wet[$l - 1];
$val += $mlt * strpos($srcAlphabet, $digit);
$wet = substr($wet, 0, $l - 1);
$mlt *= $srcBase;
}
$wet = $val;
$dst = '';
while ($wet >= $dstBase) {
$digitVal = $wet % $dstBase;
$digit = $dstAlphabet[$digitVal];
$dst = $digit . $dst;
$wet /= $dstBase;
}
$digit = $dstAlphabet[$wet];
$dst = $digit . $dst;
return $dst;
}
// prints cb
print convert('125', '0123456789', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
// prints 19158
print convert('e9a', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '0123456789');
I like this PHP function which allows you to customise the alphabet (and remove confusing 0/O's etc.)
// From http://snipplr.com/view/22246/base62-encode--decode/
private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
$str = '';
do {
$i = fmod($val, $base);
$str = $chars[$i] . $str;
$val = ($val - $i) / $base;
} while($val > 0);
return $str;
}
Follow the URL to find the reverse 'decode' function too.
The main problem with Marcel's solution is that it uses a zero digit as a placeholder. By converting between bases, inevitably the numeral chosen to represent 0 can't appear at the front of the converted number.
For example, if you convert base 10 integers to base 4 using "ABCD" using the provided mechanism, there is no way to obtain output that starts with the letter "A", since that represents a zero in the new base and won't prefix the number. You might expect 5 to be "AA", but instead, it is "BA". There is no way to coerce that algorithm into producing "AA", because it would be like writing "00" in decimal, which has the same value as "0".
Here's an alternate solution in PHP that uses the entire gamut:
function encode($n, $alphabet = 'ABCD') {
$output = '';
if($n == 0) {
$output = $alphabet[0];
}
else {
$digits = floor(log($n, strlen($alphabet))) + 1;
for($z = 0; $z < $digits; $z++) {
$digit = $n % 4;
$output = $alphabet[$digit] . $output;
$n = floor($n / 4) - 1;
}
}
return $output;
}
function decode($code, $alphabet = 'ABCD') {
$n = 0;
$code = str_split($code);
$unit = 1;
while($letter = array_pop($code)) {
$n += (strpos($alphabet, $letter) + 1) * $unit;
$unit = $unit * strlen($alphabet);
}
return $n - 1;
}
echo encode(25); // should output "ABB"
echo decode('ABB'); // should output 25
Change/pass the second parameter to a list of characters to use instead of the short 4-character dictionary of "ABCD".
all you need to do is convert between different base systems base 10 to base 62
https://github.com/infinitas/infinitas/blob/dev/core/short_urls/models/short_url.php

PHP: Simple recursive function into iterative function

I was going to do it in C but was confused, so I turned to PHP and was able to copy a recursive function to do this. I am converting an integer into a string with math. Here it is:
function intToString($myDecimal){
if($myDecimal < 10) {
return $myDecimal;
}
return intToString(($myDecimal / 10)) . ($myDecimal % 10);
}
I was able to convert a recursive factorial function before.. but with this I just have no clue.. My attempt is as follows:
function intToStringIter($myDecimal){
$out = "";
while($myDecimal > 10) {
$myDecimal /= 10;
$out .= $myDecimal;
}
$out .= $myDecimal % 10;
return $out;
}
I think I am too tired to see the proper logic at the moment.. It returns 22 instead of 20, I cannot wrap my head around what is correct. Do you see what I am doing wrong?
If you're looking for a conversion to string for big unsigned integers, the code is actually:
function intToString($myDecimal)
{
return sprintf('%u', $myDecimal);
}
If you need to do it with iteration:
function intToString($myDecimal)
{
$result = '';
while ($myDecimal > 9) {
$result = ($myDecimal % 10) . $result;
$myDecimal /= 10;
}
return $myDecimal . $result;
}
UPDATE: My bad, digits were inserted in reversed order. Now it should work. Sorry, untested too.
PHP is not very strict with variables. An integer will become an float if the situation likes it. In your code, $myDecimal /= 10 could make a float of $myDecimal. The following forces $myDecimal to stay an integer. Note: you should pass only integers, if you're passing 9.99, the output would still be 9.99 because 9.99 < 10.
function intToStringIter($myDecimal){
$out = "";
while($myDecimal >= 10) {
$myDecimal = (int) ($myDecimal / 10);
$out .= $myDecimal;
}
$out .= $myDecimal % 10;
return $out;
}

Categories