I'm working on project where you can make payments with credit card.
I have booking system from what I get "price" and then it sends this price to merchant online payment page.
My problem is that booking system sends price in decimal. Like 123.45.
But merchant accepts only this format: 12345. And after receiving this format it ads decimal point automatically.
I need to convert this "price" without decimal point. In other words move it to right. What is the best function or solution to do that?
For now I'm using round() function and add two zeros (00). But this way is only if I really use rounded up prices.
$price = $_POST['price'];
$price = round($price) . '00';
I expect the output 123.45 to be 12345. Other example 123.00 to be 12300.
Careful... you may lose precision. I had to switch to using bcmul()
$version=22.0220;
$int1=(int)($version*10000); // $int1 is 220219 (oops)
$int2=(int)(bcmul($version,10000)); // $int2 is 220220 (correct)
<?php
$withoutDecimal = $withDecimal * 100;
?>
For those who need more flexible solution. For example, Bitcoin has 8 decimal places, US dollar - 2
function price_minor_units($dollars, $decimals = 2)
{
return $dollars * (10 ** $decimals);
}
function price_major_units($cents, $decimals = 2){
return $cents / (10 ** $decimals);
}
Related
I am trying to do a 2 digit precision in PHP Laravel project but it doesnt work. I have the value 1234666.6666667 that I want to make 1234666.66 but all the results I've seen in here or/and in other search pages.
This is my code:
$value = 1234666.6666667;
return round($value,2);
any other solution?
EDIT:
As I see, you actually want to floor number to 2 decimal points, not to round it, so this answer could help you:
$value = 1234666.6666667;
floor($value * 100) / 100; // returns 1234666.66
If you want 3 decimal points you need to multiple and divide with 1000, for 4 - with 10000 and etc.
You can use number_format, it convert value to string though, so you lose real float value:
$value = 1234666.6666667;
echo number_format($value, 2, '.', ''); // prints 1234666.67
Use this function.
function truncate($i) {
return floor($i*100) / 100.0;
}
Then you can do
$value = truncate(123.5666666); // 123.56
A pragmatic way is to use round($value - 0.05, 2), but even that gets you into hot water with some edge cases. Floating point numbers just don't round well. It's life I'm afraid. The closest double to 1234666.66 is
1234666.65999999991618096828460693359375
That's what $value will be after applying my formula! Really, if you want exact decimal precision, then you need to use a decimal type. Else use integer types and work in multiples of 100.
For the former choice, see http://de2.php.net/manual/en/ref.bc.php
$value = bcadd($value, 0, 2); // 1234666.6666667 -> 1234666.66
Another more exotic way to solve this issue is to use bcadd() with a dummy value for the $right_operand of 0,
This will give you 2 number after decimal.
I have a nasty bug with payments in Magento, Paybox and SOAP web services, the idea is the following:
Payment is made in cents $36.37 = 3637cents (Paybox - API)
What I am trying to do is to transform my order price in cents in the following way:
$cents = $order->getBaseGrandTotal() * 100;
Also I have a web service SOAP (strict types) that respond this $cents amount but it concerts it to (int), then the magic happens sometimes the converted amount is not the expected one, the converted result is less than an cent, in my case it could be 3736.
$prices = array(39.8699, 12.3299, 11.3211);
foreach ($prices as $price) {
$stuff = round($price, 2) * 100;
echo $stuff . PHP_EOL;
}
echo "After int conversion" . PHP_EOL;
foreach ($prices as $price) {
$stuff = (int) (round($price, 2) * 100);
echo $stuff . PHP_EOL;
}
The result is the following:
3987
1233
1132
After int conversion
3986
1233
1132
Question
Is there a way to fix this bug, it seems to be a php bug ?
Your algorithm summarises as this:
$price = 39.8699; // 39.869900000000001228
round($price, 2) * 100; // 3986.9999999999995453
(int)3986.9999999999995453; // 3986
You're rounding properly everywhere, except in the last step, where your (int) casting truncates. Rounding would be more appropriate:
round($price * 100)
Said that, the root problem is that computers use binary logic and normally store numbers as base 2, while us humans use fuzzy logic and prefer base 10. There isn't much problem with "small" integers because there's a 1 to 1 correspondence but storing arbitrary base 10 floating points numbers in a fixed-sized base 2 representation is normally just an approximation. A classical example is 1.1 which has two digits in base 10 but is periodic in base 2:
1.0001100110011001100110011001100110011001100110011001100110011001101...
That's why common advice includes using exact data types when available (DECIMAL in your relational database, integers in your client code).
Not sure if I'm being stupid here. Probably something obvious but when you've been staring at the same issue for hours on end it starts to drive you crazy.
I'm doing a few calcuations using PHP, all fairly straight forward.
I have a table called sales, say:
total, costs
424.53, 125
853.91, 125
To get the data I need...
gross = total - cost
vat = gross - ( gross / 1.2 )
profit = gross - vat
I need to generate a report, so for each row in the sales database I need to loop over and run the above calculations to get the data I need.
If I add the sum of total and the sum of costs, and then work out the gross, vat and profit above, and round vat and profit to 2 decimal plates the values are as expected.
The problem I'm having is where I'm looping over each row and calculating gross, vat and profit. If I don't round vat and profit on each row, but round the final totals, they match the values where I add sum(total) and sum(costs).
But then in the report I generate, if I don't round vat and profit then they don't show to two decimal places, which I need.
Actual code is below, pretty sure it's more of a logic issue than code.
$sum = 0; // Test variable
foreach( .. as ... )
{
// Assigning $total and $cost
$gross = $total - $cost;
$data['profit'] = $gross;
// If I round this VAT so vat shows to two decimal points, $sum becomes off by some pence.
// If I don't round it but then round $sum after the loop, it matches the echo statement value which is the correct amount
$vat = $this->vat( $gross );
$data['vat'] = $vat;
$profit = $gross - $vat;
$data['net_profit'] = $profit;
$sum += $profit;
$array[] = $data;
}
echo "131547.82<br><br>";
echo $sum;
die;
It's an accuracy problem caused by using floats.
When you do calculations with pure PHP you need to be careful.
You may run into glitches, when comparing two floats.
I would suggest to use some helper function or a currency / money object in order to work with them. It might be better to use a PHP Extension for math stuff, like the PHP Extensions BCMath, which has for instance the function bcadd(). Anyway, here are some helpers, which you might use in your calculation loop.
/**
* turn "string float" into "rounded float with precision 2"
* (string) 123,19124124 = (float) 123.19
*
* #param type $string
*/
function formatNumber($string, $precision = 2)
{
return round((float) str_replace(',', '.', $string), $precision);
}
There is also sprintf: echo sprintf("%.2f", $a);.
These two are based on PHP's NumberFormatter from the Intl Extension.
// 123,19 EUR = 123.19
function parseNumber($string_number)
{
$fmt = numfmt_create('de_DE', \NumberFormatter::DECIMAL);
return numfmt_parse($fmt, $string_number);
}
// 123.19 = 123,19 EUR
function numberFormat($value)
{
$f = \NumberFormatter::create("de_DE", \NumberFormatter::CURRENCY);
return $f->formatCurrency($value, 'EUR');
}
For comparing two floats:
/**
* Do not check, that the numbers are exactly the same,
* but check that their difference is very small!
* A really small rounding error margin (epsilon) is expected.
* It's an equals check within defined precision.
*/
function compareFloats($a, $b)
{
return (abs(($a - $b) / $b) < 0.000001) ? true : false;
}
I have a project that stores money as bigint column in a database (storing in cents). I'm planning to rewrite this thing to use BCMATH instead. I don't mind integers, but they give me some terrible rounding errors event stored in cents and I suspect I might have the same rounding errors in BCMATH. The problem arises in situations, like in this pseudocode:
$price = $some_price_in_cents * $store_price_increase; // second value is a float for final price calculation, so the result might have fractions of a cent
$price_total = $price * $qty;
$discount = // some discount in cents (might have fractions of a cent)
$discount *= $qty;
$discounted_price = $price_total - $discount;
When inserting into a database, I do round() on all values in cents. And now I have a record which says:
total price = 12134
discount = 460
discounted price = 11675
Now if I do 12134 - 460 ... I obviously get 11674 and not 11675.
I also suspect that if I changed the way things are calculated (eg. multiply everything by the QTY at the end), I'd get even different results.
Would I get this kind of behaviour using BCMATH? Would the result depend on the order of math operations? How would I properly calculate the above using BCMATH and store it in DB (assuming 2 decimal places are required)?
I believe this is what you need. Note that bcmath requires strings. The number 2 is to specify how many decimals you need.
$price = bcmul($some_price_in_cents, $store_price_increase, 2);
$price_total = bcmul($price, $qty, 2);
$discount = bcmul($qty, "discount amount", 2);
$discounted_price = bcsub($price_total, $discount, 2);
I have a e-commerce shop and on the shopping cart page it gives me a separate price for every product, but I need total price.
in order to do that, I need to calculate all these values together and that's fine.
But, what bugs me is that I should calculate the sum of variables that are given in this format:
$455.00
What is the best way to extract the value "455" so I could add it to another value afterwards?
I hope I made myself clear...
Don't use float, but instead use an integer in cent. Floats are not precise (see Floating Point Precision), so the calculation tend to fail if you use floats. That's especially a burden if it is related to payments.
$str = '$455.00';
$r = sscanf($str, '$%d.%d', $dollar, $cent);
if ($r <> 2 or $cent > 99 or $cent < 0 or $dollar > 9999 or $dollar < 0) throw new Exception(sprintf('Invalid string "%s"', $str));
$amountInDollarCents = $dollar * 100 + $cent;
echo $str, ' -> ', $amountInDollarCents;
Demo
If you need only the dollar sign removed, use str_replace. To convert that to int or float, typecast it. However, using float results in non-exact calculations so be careful with it!
$newval = (int)str_replace('$', '', '$455.00');
I think that your ECommerce site only has $ (USD)
$price= substr($string_price,1);
This will convert your string to a float:
$price = (float)substr("$455.00", 1);
echo($price);
For more information, you can see this answer, which has a couple of good links for you in it.
What about the following:
$amount = array();
$amount[0] = '$455.15';
$amount[2] = '$85.75';
$total = 0;
foreach ($amount AS $value) {
$value = str_replace('$', '', $value);
$total += $value;
}
echo $total . "\n";
The cleaning operation is:
$value = str_replace('$', '', $value);
You might want to extract it in a function, especially if you need to use it in more than one place.
Another thing to think about is, why do you have the value in such way? It's a display format and such conversion should be the last to be done, ideally by the template. Maybe, if possible, you should consider to fix the code before, instead of applying a patch like this one.
It really looks like your program is doing it wrong. You should really represent all prices as (double) instead of a string. Then only when you need to show the price to the user you would prepend the $ sign to it, converting it to a string. But your program should really treat prices as numbers and not strings.
If you storing your price in the database as a string "$5.99" then you are really doing it wrong.
It's been a long time since I worked with PHP, so I don't know what the best practice would be for working with currency. One quick method would be to remove "$" and ".", and just add together the resulting as integers.
use str_replace() for instance, and replace "$" and "." with an empty string: http://se2.php.net/manual/en/function.str-replace.php
This will give you the whole sum in cents (thus avoiding some potential rounding problems). You can then divide it by 100 and format it however you like to display the sum as dollars.