Efficient way of removing extraneous zeros from prices? - php

I have prices stored to five decimal places of precision, such as:
1.95000
2.25000
0.01150
2.10000
2.00000
When displaying prices, I'd like to show the standard $X.XX format if the rest of the digits are just zero, but if there are significant digits then I don't want to cut them out (so I can't simply use number_format()).
As an example, the above prices should display as:
1.95
2.25
0.0115
2.10
2.00
This process has to be done on hundreds of prices per page. What's an efficient way of formatting numbers in this manner?

This uses a regex to match everything before the trailing 0s
$i = "$1.00";
$pattern = '/\$(\d+)\.(\d\d)(0*)/';
$replacement = '\$$1.$2';
print preg_replace($pattern,$replacement,$i);
Another way using rtrim on everything after the first 2 digits right of the decimal
$pos = strpos($i, '.') + 3;
print substr($i, 0, $pos) . rtrim(substr($i, $pos), '0');

This is kind of ugly but it does the job:
function formatPrice($price) {
$out = (float)$price; // Trim off right-hand 0's
$out = "$out"; // Typecast back to string
if ((strlen($out) - strpos($out, '.')) <= 2) { // Test for string length after decimal point
$out = number_format($out, 2); // Format back with 0's
}
return $out;
}
Testing it now... Works!
Here's a one-liner function from my other comment thanks to #FuzzyTree's answer:
function formatPrice($price) {
return substr($price, 0, strpos($price, '.') + 3) . rtrim(substr($price, strpos($price, '.') + 3), '0');
}

Related

Convert a full string (represented in exponential format) back in number in PHP

I've been searching for converting my exponential number string into an exact number.
So, I've an exponential number stored as string in MySQL. I want to convert back this string in the exact number.
But It seems the number is quite big and crosses the boundary and provide me wrong data.
While I tried following code in various format but result is not in proper format.
policy_number = "2.9992020830803E+18";
number_format($policy_number, 0,'.','');
// Output
2999202083080300032
(float) $policy_number;
// Output
2.9992020830803E+18
sscanf($policy_number, "%f")[0];
// Output
2.9992020830803E+18
floatval($policy_number);
// Output
2.9992020830803E+18
gmp_init("2.9992020830803E+18");
gmp_strval($policy_number);
// Output
0
"2.9992020830803E+18" + 0;
// Output
2.9992020830803E+18
Please show me the right way to convert it.
Updated
I agree with Ed Cottrell that your problem is how you are storing your data in the db. However, if this is a project where you are already looking at a large set of data that is already stored as an exponent then this function I wrote should work for you. It should work for positive and negative bases and exponents. It basically mimics the way you would do the operation by hand.
I was not able to figure out a way to do it using math functions. If someone knows how to do it better please post. In the meantime, I had fun writing this.
Hope it helps you out!
function getRealNumber($number){
//Parse the base and exponent.
preg_match('/^(.*?)E[\-|\+](.*?)$/', $number, $data);
$base = $data[1];
$exp = $data[2];
//Test to see if the base is negative.
if(preg_match('/\-/', $base)){
$base = str_replace('-', '', $base);
$isNegative = TRUE;
}
//Capture the offset of the decimal point.
preg_match('/\./', $base, $position, PREG_OFFSET_CAPTURE);
$offset = $position[0][1]; //This is the offset of the decimal point.
$string = str_replace('.', '', $base); //Get numbers without decimal.
$length = strlen($string); //Get the length of string.
//Test to see if we are adding zeros to the end or beginning of string.
if(preg_match('/E\+/', $number)){
//Let's move the decimal.
if($length > ($exp + $offset)){
$string = substr_replace($string, '.', ($exp + $offset), 0);
} else {
$string = $string . str_repeat('0', $exp - ($length - $offset));
}
}elseif(preg_match('/E\-/', $number)){
//Calculate the number of zeros needed to add and append them.
if($offset > $exp){
$string = substr_replace($string, '.', $offset, 0);
} else {
$string = '0.' . str_repeat('0', $exp - $offset) . $string;
}
}
//Add the negative sign if we need to.
if(!$isNegative){
return $string;
} else {
return '-' . $string;
}
}
$policy_number = "2.9992020830803E+18";
echo getRealNumber($policy_number);
//Will output 2999202083080300000

Number format with a variable decimal place

Is there a built-in/neat way to format a number (just like number_format does), but without any rounding ups/downs?
For instance, number 1234.234 should be formatted as 1,234.234 and another number 1234 should be formatted as 1,234 (i.e. without any trailing .000)
You can define simple custom function for that:
<?php
function custom_number_format($number, $decimal = '.')
{
$broken_number = explode($decimal, $number);
if (isset($broken_number[1]))
return number_format($broken_number[0]) . $decimal . $broken_number[1];
else
return number_format($broken_number[0]);
}
$n1 = '1234.234';
$n2 = '1234';
echo custom_number_format($n1);
echo '<br>';
echo custom_number_format($n2);
?>
Output is:
1,234.234
1,234
Based on the arhey's answer
TLDR ;)
You can use number_format to format the number to a fixed-width format, then use rtrim twice to remove trailing zeroes, and dot.
rtrim(rtrim(number_format($number, 3, '.', ','), '0'), '.')
Starting from the last character, rtrim removes it while it is one of those given. In our case, we remove trailing dots, then we remove an eventual trailing zero.
rtrim(rtrim(number_format(1234.123, 3, '.', ','), '0'), '.')
// returns 1,234.123
rtrim(rtrim(number_format(1234.12, 3, '.', ','), '0'), '.')
// returns 1,234.12 (1,234.120, trimmed to 1234.12)
rtrim(rtrim(number_format(1234, 3, '.', ','), '0'), '.')
// returns 1,234 (1,234.000, trimmed to 1234)
rtrim(rtrim(number_format(1200, 3, '.', ','), '0'),'.')
// returns 1,200 (1,200.000, trimmed to 1200., trimmed to 1200)
Formal form, and discussion about the parameters (notably the decimals count)
rtrim(rtrim(number_format($number, <N>, '<D>', ''), '0'), '<D>')
Where :
D is the decimal separator. To avoid locale-formatting problems, explicitly specify it
N is the maximum digits you number can have.
If you know all your numbers will have less than 3 digits, go and take N=3.
What if you don't know how many decimals are at most ? Well, things are getting more complex.
It may worth recalling (as stated in the PHP documentation) that floats are stored :
with a precision (a number of digits, without distinction whether they are before or after the decimal separator), not a number of decimals
and in their binary form, not their decimal one, and that can lead to rounding errors when reaching precision limit.
For example, floor((0.1+0.7)*10) will usually return 7 instead of the
expected 8, since the internal representation will be something like
7.9999999999999991118....
So there is no universal good value, you'll have to choose it depending on the usual scale of your data.
And that explains why there is no built-in function for that : PHP can't choose for you.
You can use function:
<?php
function getNumberFormat($number) {
$numberAr = explode('.', (string)$number);
$count = 0;
if (2 === count($numberAr)) {
$count = strlen($numberAr[1]);
}
return number_format($number, $count, ',', '.');
}
$test1 = 1234.234;
$test2 = 1234;
echo getNumberFormat($test1); //1,234.234
echo getNumberFormat($test2); //1,234
I really liked arhey's answer, but later realized it has a major flaw. A number like 2100 will get converted to 2,1 instead of 2,100.
Below is how I ended up modifying it.
public function formatDecimal($number)
{
$stringVal = strval($number); //convert number to string
$decPosition = strpos($stringVal, ".");
if ($decPosition !== false) //there is a decimal
{
$decPart = substr($stringVal, $decPosition); //grab only the decimal portion
$result = number_format($stringVal) . rtrim($decPart, ".0");
}
else //no decimal to worry about
{
$result = number_format($stringVal);
}
return $result;
}
It's not as succinct a solution as I was hoping, but in my case I put it into a view helper (I'm using ZF2) and so it's just one simple function call in my view.
Hope this is helpful for someone!
rtrim(number_format(1234.234, 3),'.0');
rtrim(number_format(1234, 3),'.0');
Let's begin with that there's no decimal type in PHP. There's float only.
And if you know how float works, then you know that it's usually not possible to store exact decimal value that you think you have, but it's an approximation. That's because you can't express most of decimal numbers in binary system.
Therefore if you say:
$number = 1234.234;
Then you have a float that is close to this value. The real value is:
1234.23399999999992360244505107402801513671875
Therefore PHP can't just guess how do you want to round it. It needs to be specified explicitly.

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

Advanced Php Number Formatting

I want to have a PHP number formatted with a minimum of 2 decimal places and a maximum of 8 decimal places. How can you do that properly.
Update: I'm sorry, my question is say I have number "4". I wish for it to display as "4.00" and if I have "2.000000001" then it displays as "2.00" or if I have "3.2102" it will display as such. There is a NSNumber formatter on iPhone, what is the equivalent in PHP.
This formats the $n number for 8 decimals, then removes the trailing zero, max 6 times.
$s = number_format($n, 8);
for($i=0; $i<8-2; $i++) {
if (substr($s, -1) == '0')
$s = substr($s, 0, -1);
}
print "Number = $s";
Use sprintf() to format a number to a certain number of decimal places:
$decimal_places = 4;
$format = "%.${decimal_places}f";
$formatted = sprintf($format,$number);
I don't understand why you would want to display numbers to an inconsistent degree of accuracy. I don't understand what pattern you're trying to describe in your comment, either.
But let us suppose that you want the following behaviour: you want to express the number to 8 decimal places, and if there are more than 2 trailing zeroes in the result, you want to remove the excess zeroes. This is not much more difficult to code than it is to express in English. In pseudocode:
$mystring = string representation of number rounded to 8 decimal places;
while (last character of $mystring is a 0) {
chop off last character of $mystring;
}
Check the number format function:
<?php
$num = 43.43343;
$formatted = number_format(round((float) $num, 2), 2);
?>
http://php.net/manual/en/function.number-format.php
Using preg_match just get the zero ending with and then rtim it
<?php
$nn = number_format(10.10100011411100000,13);
preg_match('/[0]+$/',$nn,$number);
if(count($number)>0){
echo rtrim($nn,$number[0]);
}
Hope it will help you.

Add a period to the middle string PHP

It will actually be a decimal but that is not the main point. I will have a set of numbers like:
8976
8765
3454
3453
10198
What I am wanting to do is add a decimal 2 places from the right. So the first would be 89.76 and so forth.
Can't you just multiply each by 0.01?
$formatted = number_format($unformatted_number / 100, 2, '.', '');
2 - decimal places
'.' - decimal separator
'' - thousands separator
docs for the function are here.
try this
$number = 8976;
$number = (float)$number/100;
results:
89.76
You may have to do some checking to see how many digits the number is, i.e 89768 would be devided by 1000 and so on.
Comments are available,
//the string you need to split
$string = "123456";
// read from right 2 character
$rightNums = substr($string, -2, 2);
// maximum 100 character to the left defined now
$otherNums = substr($string, -4, 100);
// pront them just with . between
echo $otherNums.".".$rightNums; ?>
hope it help much.
Try with this
$tmpString = substr("8976", 0, -2);
$finalString = str_replace($tmpString, "." . $tmpString, "8976");
echo $finalString;

Categories