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;
}
Related
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);
}
How can I write this equation.
Pretty much here is what I have
(float) $total = (float) $product_price * (float) $percentage;
return (float) $total;
Although now I need to add a country percentage. Therefore if the country percentage is 10% I need to add 10% onto the current total and return it.
I use the variable $country_percentage
If I understand correctly, you want to add the percentage (tax?) to the total. If the $country_percentage is the integer '10', that would equate to '0.10'
($total * (1 + ($country_percentage / 100))
Which would be ($total * 1.10)
You use math, my friend. If your percentage is a string, remove the % symbol with str_replace() and then parse that value using intval($int) or floatval($float). That'll give you a number to work with, 10 for example.
Then you divide your country percentage by 100
10/100 = 0.1
Finally, you multiply your product price by the above value +1.
So:
//if $percentage is a string with '%' in it
$percentage = (floatval( str_replace("%", $percentage) );
$percentageNum = ($percentage/100) + 1;
$total = $product_price * $percentageNum;
Im trying to make a calculation with the following values:
Product cost (without VAT) = 12,40 ($product)
The VAT percentage = 21%, what I will store in the database as 0,21 ($vat_perc)
The VAT is 2,604 ($vat)
edit: The VAT is per product
When I try to get the total then I get 15,00 ($total)
What I did is the following:
$total = $product + $vat
This will echo 15.004
Then I use the number_format:
echo(number_format($total,2,',','.'));
This will print 15.00
But then I want to multiply the product with 2
So that will give the following calculation:
$total = $product * 2 + $vat
Then again I use the format:
echo(number_format($total,2,',','.'));
Then the total = 30,01
I tried serveral things like ROUND en INT, but with no succes.
What am I doing wrong in this? In know that the VAT is the evil one here but I have no idea how to fix this.
$tax = round( ($price / 100) * 3.8, 2);
tax is rounded price divided by the 100 to make a clear percentage. Multiplied by the wanted stack
then you do the addition to or from your price table.
Well good to have you on the phone - maybe we can solve this faster by phone. Thank god for phones!
Cheers mate!
Here are some examples of how the numbers are rounded with PHP functions.
$product = 12.40;
$vat = 2.644;
$total = ( $product + $vat ) * 2;
var_dump( $total ); // float(30.088)
var_dump( number_format($total,2,',','.') ); // string(5) "30,09", rounded
var_dump( round( $total, 2 ) ); // float(30.09), rounded
var_dump( sprintf('%0.2f', $total ) ); // string(5) "30.09", rounded
var_dump( floor( $total * 100 ) / 100 ); // float(30.08), not rounded
All three founctions ( number_format, round, sprintf ) will round the result, to avoid the rounding and discard the decimal part after two decimal points you can use the last example.
Your initial total is 15.004 so when you call number_format that gets rounded down to 15.00. Now when you multiply by 2 your total is 15.008 which number_format will round up to 15.01. The issue isn't with the addition it is with the multiplication by 2. number_format rounds to the nearest place which for your case would be 30.01.
If you want the number to be rounded down all the time use floor, like so:
$total = floor(($product * 200)) / 100 + $vat;
echo(number_format($total,2,',','.'));
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);
A have price data stored like this: 10.25
And percentage data like this 1.1100 (1.00%)
I need a function that accurately multiplies the price by the percentage. (in php)
Thanks
You should never store currency as a decimal. Always use intergers.
What is wrong with $money * ($percentage / 100)
It would probably be best if you split the storage of your percent data into two different fields if possible. But this will do what you're looking for, I believe. I split it into two functions - "convertPercentageDataToDecimal" extracts the percent from your data and converts it to a decimal, then "convertPercentageDataToDecimal" simply multiplies the price by the percent.
<?php
$price = 10.25;
$percentageData = "1.1100 (1.00%)";
function multiplyPriceByPercentageData($price, $percentageData) {
$percent = convertPercentageDataToDecimal($percentageData);
return $price * $percent;
}
function convertPercentageDataToDecimal($percentageData) {
$start = strpos($percentageData, '(') + 1;
$length = strpos($percentageData, '%') - $start;
$percent = substr($percentageData, $start, $length);
return $percent / 100;
}
var_dump(convertPercentageDataToDecimal($percentageData));
var_dump(multiplyPriceByPercentageData($price, $percentageData));
// output:
// float(0.01)
// float(0.1025)