I am using the moneyphp/money class to store monetary values. However when calculating the tax owed I have an issue where the calculated tax is a decimal and the library is looking for an integerish value.
Example:
$invoiceTotal = new Money("155" new Currency("USD")); //$1.55
$taxRate= 0.065;
$invoiceTotalWithTax = $invoiceTotal->multiply($taxRate);
echo $invoiceTotalWithTax; //0.10 whereas actual value is 1.55*0.065 = 0.10075
$formatter = new DecimalMoneyFormatter();
$formatter->format($invoiceTotalWithTax); //will return $0.10
From the above example, some fractional cent value is being lost. Individually it's not a lot, however if we process several thousand invoice in a tax period, the total tax collected will eventually surpass 1 cent.
Is there a way to handle these situations with the Money package?
If not, then is there another package that can handle this?
Shameless plug: I don't know if there's a way to do it with the moneyphp/money library, but here's how you can handle this situation with the brick/money library (disclaimer: I authored it).
The option you choose will depend on what you're trying to achieve.
Option 1: use a Money with the default scale, round up or down
Use this method if you need the result in the default scale for the currency (2 decimal places for USD), and know which rounding to apply:
use Brick\Money\Money;
use Brick\Math\RoundingMode;
$invoiceTotal = Money::ofMinor('155', 'USD'); // USD 1.55
// or
$invoiceTotal = Money::of('1.55', 'USD');
$taxRate = '0.065'; // prefer strings over floats!
$totalWithTax = $invoiceTotal->multipliedBy($taxRate, RoundingMode::DOWN); // USD 0.10
$totalWithTax = $invoiceTotal->multipliedBy($taxRate, RoundingMode::UP); // USD 0.11
You have many more rounding modes to choose from. If you don't provide a rounding mode, and the result does not fit into 2 decimal places, you'll get an exception.
Option 2: use a Money with a custom scale
If you need to work with a given precision, say 5 decimal places, you can specify this when you create the Money:
use Brick\Money\Money;
use Brick\Money\Context\CustomContext;
use Brick\Math\RoundingMode;
$invoiceTotal = Money::of('1.55', 'USD', new CustomContext(5)); // USD 1.55000
$taxRate = '0.065';
$totalWithTax = $invoiceTotal->multipliedBy($taxRate); // USD 0.10075
If the result does not fit into 5 decimal places, you'll need to provide a RoundingMode, or you'll get an exception.
Option 3: use a Money with auto scale
Use this method to automatically adjust the scale of the result to the correct number of decimal places:
use Brick\Money\Money;
use Brick\Money\Context\AutoContext;
use Brick\Math\RoundingMode;
$invoiceTotal = Money::of('1.55', 'USD', new AutoContext()); // USD 1.55
$taxRate = '0.065';
$totalWithTax = $invoiceTotal->multipliedBy($taxRate); // USD 0.10075
No rounding mode is involved, but if a division yields a decimal number with an infinite number of digits, you'll get an exception.
Option 4: use a RationalMoney
A RationalMoney is a money object that represents its amount as a rational number (a fraction). It's particularly useful when you need to chain several operations with no rounding whatsoever:
use Brick\Money\Money;
use Brick\Math\RoundingMode;
$amount = Money::of('1.55', 'USD'); // USD 1.55
$amount = $amount->toRational(); // USD 155/100
$amount = $amount->dividedBy(3); // USD 155/300
$amount = $amount->dividedBy(7); // USD 155/2100
Once you have performed all your operations, you can convert your final number to a decimal Money, using a rounding mode if necessary:
use Brick\Money\Context\DefaultContext;
use Brick\Money\Context\CustomContext;
$amount->to(new DefaultContext(), RoundingMode::DOWN); // USD 0.07
$amount->to(new CustomContext(6), RoundingMode::DOWN); // USD 0.073809
Final considerations
The brick/money package offers formatting, cash roundings, money allocation, currency conversion, and more. It is based on the brick/math package, that performs calculations on numbers of any scale. Give it a try!
Related
I have a PHP code that will compute the balance of the quantity but it gives me a negative value as a balance quantity as shown in the image below.
I tried to check the quantities if what's causing the problem and try to var_dump the quantity. after checking using var_dump, it shows that the data type of my quantity is string while my balance quantity is float.
so far, I have my code below:
$query_po_quantity = mysqli_query($new_conn, "SELECT quantity, po_number FROM purchase_order WHERE supplier_name = '$supplier_name' AND category_name = '$category_name' AND activity = '$activity' AND description = '$description'");
$row = mysqli_fetch_assoc($query_po_quantity);
$po_quantity = $row['quantity'];
$po_number = $row['po_number'];
$query_rr_quantity = mysqli_query($new_conn, "SELECT SUM(total_received) AS quantity FROM receiving_reports WHERE po_number = '$po_number' AND category_name = '$category_name' AND activity = '$activity' AND description = '$description'");
$row = mysqli_fetch_assoc($query_rr_quantity);
$rr_quantity = $row['quantity'];
$balance = $po_quantity - $rr_quantity;
$supplier_name = preg_replace('/\\\\/', '', $supplier_name);
echo $po_quantity.' - '.$rr_quantity.' = '.$balance.'<br />';
This is the output:
how can I get the actual balance?
The reason you're getting an incorrect result when calculating 0.42 - 0.420000000000000000004 is due to errors with floating point precision. This is due to the way floating point numbers are stored, and both MySQL and PHP are susceptible to floating point errors if done incorrectly, but they also both have ways to prevent them when you do need highly precise calculations. With floating point types only the approximate value is stored and attempts to treat them as exact values in comparisons may lead to problems.
For PHP, this means you need to use either the arbitrary precision math functions or gmp functions. For MySQL, you need to be storing the numbers using the DECIMAL format with the desired precision you require.
First thing's first, you need to change the data type of your column in MySQL to DECIMAL, not a string. Strings are inappropriate to store numbers. Even if you were using a FLOAT or DOUBLE to store your values
your code may have actually worked, because these values likely would have been rounded.
Next, seeing as the value 0.420000000000000000004 came from a string stored in your database, I'm assuming the error stems from whatever calculations you did using PHP beforehand when you were calculating the value to be inserted. You will need to update this code to use precise math.
Use number_format:
$rr_quantity = number_format($row['quantity'], 2);
Float variable range 1.7E-308 and 1.7E+308 so it's give 15 digits of accuracy. Use number format
I am trying to solve this problem:
When I purchase items I receive a receipt which lists the name of all
the items and their price (including tax), finishing with the total
cost of the items, and the total amounts of sales taxes paid. The
rounding rules for sales tax are that for a tax rate of n%, a shelf
price of p contains (np/100 rounded up to the nearest 0.05) amount of
sales tax.
So, ... I have a price in php:
$value = 11.25;
and I don't understand why
var_export(ceil($value * 0.05 * 10));
returns 6 and dividing per 10, the result is
var_export(ceil($value * 0.05 * 10) / 10);
0.59999999999999998
some nice experiments:
php > echo bcmul(11.25, 0.05, 3);
0.562
php > echo bcmul(ceil(11.25), 0.05, 3);
0.60
You could read what bishop point out and also you could read this in order to understand what is the problem.
Now talking about a solution, you could use PHP BCMath extension which should be used when you want to work with precision mathematical numbers
For arbitrary precision mathematics PHP offers the Binary Calculator
which supports numbers of any size and precision, represented as
strings.
One solution (a ugly one) could be this
$value = 11.25;
var_export(bcdiv(ceil($value * 0.05 * 10), 10, 1)); // Output '0.6'
Here I am using the bcdiv from the mentioned extension.
I'm using the "number_format" function to denote money" attribute in PHP/MySQL.
The attribute itself is stored in my database.
Select account_balance from my_table where login = 'xxxxxx';
$correct_account_balance = number_format($account_balance,
['my_balance'],2); }
In other words : the denotation "2" will add two extra numbers after the decimal point, as follows : 10.00 (for example)
This code works fine............except for one small problem : if the amount after the decimal point has a zero at the end, it does not display!
For example : if the amount is, say, 10 dollars and 35 cents, it displays correctly : 10.35
However, if the amount is 10 dollars, and 30 cents, it displays as : 10.3 (instead of : 10.30 )
The reason is : my program also performs arithmetical operations on the account_balance AFTER I have converted it using the "number_format" function.
For example :
$correct_account_balance -= 0.25 (this will subtract 0.25 each time the program is executed)
This is why, anytime there is a "zero" at the end of the actual amount (like : 10.30), it displays as : 10.3
Is there anyway to get around this? Google doesn't seem to know;
The reason is : my program also performs arithmetical operations on the account_balance AFTER I have converted it using the "number_format" function.
You'll need to re-run number_format after doing the operations on it.
You really shouldn't run it at all until it's ready for display, either, the commas it adds to larger numbers will hugely mess up your calculations. As an example, the following:
<?php
$number = 100000.30;
$number = number_format($number, 2);
$number -= 0.25;
echo number_format($number, 2);
results in the output:
99.75
Which means you've just stolen $99,900.55 from your customers with a type conversion error.
Hey guy's I am working on a project of mine which involves the use of money. I am trying to not use round anymore because, it's rounding things to nearest tenth and I need exact numbers. One reason I was using it, was because it was giving a whole number.
The way I am working my number system is that 100 = $1, 2000 = $20, etc.. I was currently using the round function because it would get rid of the decimal point for me and give me a whole number, lets say: 223 which in turn would = $2.23.
Here is what I am using:
$amount += round(($amount / 29) + 30);
Here are the numbers:
Lets say we have a charge of 100 and we add 125 which equal 225 (USD $2.25). Now we add taxes and processing: + 2.9% + $.30. After multiplying 2.25 by 2.9% and adding .30 the amount would be: 0.36525 - this is the amount that should be added than to the $2.25 which than would be 261 = $2.61
The issue is because of the rounding, when I look in my Stripe panel (I am using Stripe API for payments) I see a charge of $2.63. So my question is, how would I go about making it exact without having any rounding and decimal places.
UPDATE:
Here is the above example more explained:
Lets say we have a charge of 100 and we add 125 which equal 225 (USD $2.25). Now we add taxes and processing: + 2.9% + $.30. After multiplying 2.25 by 2.9% and adding .30 the amount would be: 0.36525 - this is the amount that should be added than to the $2.25 which than would be 261 = $2.61
So now with that the actual value of amount that should be charged is $2.61 but instead when using the round it gives me 263 which also means $2.63. The above example is the simple math that is correct.
In order to avoid calculation hiccups like that, only round the final result. Keep all other calculations as accurate as possible:
$original = 100;
$original += 125;
$tax = $original * 2.9 / 100; //+2.9%
$tax += 30; //+$.30
$original += $tax; //Add tax.
echo $original; //Result is 261.525. Round as you please.
You can specify precision and rounding method to keep it consistent (PHP round()), then you can deal with the actual values. Doing math tricks like multiplication by a multiple of 10 will only make it more confusing in the long run.
$amount += round(($amount / 29) + 30, 2, PHP_ROUND_HALF_UP);
Will this solve your problem?
I would like to insert this type of number format in to my database.
$value = "20.000,00";
I tried with FLOAT and DOUBLE, but they can't handle this. Only option left that I know which works is VARCHAR?
Although if I do this when I am working with the numbers later and tries to subtract number from each other:
$value2 = "15.933,50";
$calc = $value - $value2;
$calc is now 4.067, it should be 4.066,50 - how can this be correct?
For financial numbers, you should use the DECIMAL type. It has a fixed precision and isn't subject to the rounding problems of floating point numbers.
Never store a money number in a VARCHAR or a FLOAT.
If you want to introduce in the database your $value, you must parse it so that you can introduce the number :
$fmt = new NumberFormatter( 'da_DK', NumberFormatter::DECIMAL );
$num = $fmt->parse($value, NumberFormatter::TYPE_INT32)
To display a number you got from your DB as "20.000,00", you may use
$display_value = $fmt->format($num);
EDIT :
If you can't use a NumberFormatter, you may use this to build a string that you can introduce in a DECIMAL field (in a float one too but don't) :
$v = str_replace(',', '.', str_replace('.', '', $value));
This changes "20.000,00" to "20000.00" which the database can understand.
But it's always dangerous to ask a database to parse the strings as numbers (you may change the locale later). I'd recommend you to parse the number in PHP and to explicitly specify the locale.