fmod(floatval("314.6"), floatval("1.3"))
=> 1.1990408665952E-14
I understand more or less the underlying problem of representing the numbers in binary form and something with IEEE-754. But: How would I get a result that is usable in pratice?
Example what I want to achieve: A merchant can define the price P of his product, but the price has to be a multiple of x:
if (fmod(P, x) != 0) { echo "price must be a multiple of " . x; }
It would be so simple, but the approach fails whenever I get something like 1234E-18 as return value of fmod().
What to do in real life to check the price interval easily without using custom code?
This or similar questions have been around, but all I can find are explanations why fmod() behaves like it does. Never an answer how to solve this real-life problem...
The problem here is that 314.6 is exactly 1.3 * 242 so floating point remainder is zero but you get 0.00000000000001199041 due to the IEEE 754 inaccuracies you're well aware of.
But don't forget one of the rules of floating point maths: you can't compare floats for equality carelessly. If your arguments have one decimal position you don't need 14-position accuracy in your results. You have prices: how many decimals make sense in your currency? If you were using e.g. euros you're unlikely to use more than two (cents) and 1.1990408665952E-14 is zero to all effects:
var_dump(round(1.1990408665952E-14, 2));
double(0)
Some people recommend doing all price maths using integers (e.g. cents instead of euros) to avoid rounding errors but most real life issues come from very specific errors:
Displaying raw floats to user (rather than rounding and formatting them).
Doing raw comparisons (if ($foo == $bar)).
… and integers don't prevent all rounding errors anyway (e.g. tax calculations on individual items not matching calculations on invoice totals).
Related
For example, I know 0.1 is not accurate due to floating point rounding error, however, I have another question: does
$num=floatval('0.1');
exactly equals to
$num=1/10
? Do 2 $num above rounds to the same value finally?
or in general, does
$num=floatval('a.bc');
exactly equals to
$num=abc/100;
(which abc are digits, and abc are integers)?
To my knowledge, PHP does not have an official standard. Such documentation as there is does not fully specify the semantics of floating-point operations. It does not specify which floating-point representation or format implementations must use.
That said, good practice in floating-point operations is for an operation to produce a result as if the result were first computed with infinitely precise arithmetic (exact mathematics) and then rounded according to the rounding rule in effect. (Most commonly, the rule used is to round to the nearest representable value and to round ties to the nearer value with an even low digit.)
If that rule is used, then the operation of converting the decimal numeral a.bc to floating-point should yield exactly the same result as dividing the number abc by 100. This is because the exact mathematical result of interpreting a.bc as decimal equals the exact mathematical result of dividing abc by 100, so they both produce exactly the same result before rounding, and rounding should round them to the same representable value.
In some programming languages, such as C and C++, floating-point expressions are permitted to be evaluated with more precision than the nominal type, and floating-point expressions are permitted to be contracted, meaning that multiple operations can be combined into a single operation that would be equivalent mathematically.
An example of the former is that a programming language implementation might use double for doing arithmetic with values that are nominally of type float. If a PHP implementation did this, then a.bc might effectively have a different value from abc/100 because one is computed with float precision while the other is computed with double precision. If a particular PHP implementation is implemented using C or C++, this variation in precision could happen because the underlying C or C++ implementation does it, as permitted by the C and C++ standards.
An example of the latter is that, when computing a*b + c, some C or C++ implementations may choose to use an instruction that produces a result as if a*b + c were fully computed with exact mathematics and then rounded to the nearest representable value instead of first computing a*b and rounding it, then adding c to the rounded result and rounding a second time. I have not seen indication that PHP implementations do this. However, if they do, then it is possible that interpreting .abc as a decimal numeral and dividing abc by 100 may appear to have different results in some circumstances where extra precision is used or where they are combined with other operations.
Note that, if an implementation is using extra precision and/or contractions, the problem is not just that a.bc might have different value than abc/100. The expression abc/100 might appear to have different values from itself in different situations, as the implementation might use different precisions in different situations.
In my php script I do a calculation of entries from a MySQL db. The concerning fields in the db are defined as decimal(10,3). It's an accounting plattform where I have to check if in every entry debit = credit.
I do this with the following operation:
$sumupNet = 0;
$sumup = 0;
foreach($val['Record'] as $subkey => $subval)
{
$sumupNet = $sumupNet + $subval['lc_amount_net'];
$sumup = $sumup + $subval['lc_amount_debit'] - $subval['lc_amount_credit'];
}
Now we say every entry is correkt, then $sumupNet and $sumup results in 0. In most cases, this works. But in some cases the result is something like this: -1.4432899320127E-15 or this -8.8817841970013E-15. If I calculate this values manually, the result is 0. I guess (not sure) that the above results are numbers near 0 and are outputted in the form of exponential.
So I think I have to convert something or my calculation is wrong. But what? I tried floatval() at some points but didn't work. If anybody has a hint, I thank you very much.
You're getting this because you are doing math with floating-point values. Read some theory about it.
You really don't want to calculate money like that as you might get weird rounding problems that you can't really do anything to fix.
For PHP, there are plenty of libraries that help you evade the problem, such as BC Math, or GMP.
Other solution would be to calculate all of the values using the smallest monetary value that the currency has (like cents) so you are always using integers.
These are rounding problems. These are perfectly normal when we are talking about floats. To give you an everyday example,
1/3 = 0.3333333333333333...333333333...3333...
Reason: 10 is relative prime with 3. You might wonder where is 10 coming from. We are using 10-base for numbers, that is, whenever we speak about a number, its digits represent 10-base exponential values. The computer works with binary numbers, that is, 2-base numbers. This means that division with such numbers often result in endless sequences of digits. For instance, 1/3 as a binary number looks like this:
0.010101010101010101010101010101010101010101010101010101...
Decimal types are representing decimal numbers, that is, 10-base numbers. You use three digits for the part after the . Let's supose your number ends like this:
.xyz
this means:
xyz / 1000
However, 1000 can be divided with the following prime numbers:
2 and 5.
Since 5 is relative prime with 2, whenever you are representing the result of a division by 5 as a binary number, there is a potential that the result will be an endless cycle of digits. 1/5 as a binary number looks like this:
0.0011001100110011001100110011001100110011001100110011...
Since a computer cannot store endless digits, it has to round the number, that is, find a number close to its value which can be represented in an easier manner. If the number a is rounded to b and the two numbers are not equal, then a certain amount of precision is lost and this is the reason of the bug you have mentioned.
You can solve the problem as follows: when you select the values from the database, multiply them by 1000 (thus, converting them into integers) and then check the operations. At the end, divide by 1000.
I am working with dollar amounts. In MySQL database the following fields fee and rate(percentage) are DECIMAL type with 2 decimal precision.
SELECT ROUND(fee * (1- rate/100))),2 ) as profit
from products
Since query is just returning the values instead of saving them in variables, does the precision problem* still exist (that comes with PHP or JS)? If so is it best to round the floating point number in PHP or JS?
*Yes I mean precision issue that occurs when saving double, e.g., 1.5 may be saved as 1.49999999
Others may have alluded to this, but I wanted to let you know my system for handling money calculations in PHP.
I use integers. The thing is that I have each increment represent the highest precision I need. For most of my applications, this is hundredths of a dollar, or one cent. However, you can have it be millionths or whatever you need.
So in practice, with the precision being in hundredths, $.01 is represented as 1, $.10 is represented as 10, $1.00 is represented as 100, and so forth. This really gets rid of the rounding issue as you are going to be manipulating integers only, since the decimal part of any computation will be truncated. This is OK, though, since the integer represents the finest precision you need.
Admittedly, this takes a bit more developing to handle, but rounding should not be one of the issues that crop up.
In JavaScript this math operation returns:
1913397 / 13.054 = 146575.53240386088
but same operation in php returns:
1913397 / 13.054 = 146575.53240386
I guess this is due to rounding.
How can I extend php's precision?
From an article I wrote for Authorize.Net:
One plus one equals two, right? How about .2 plus 1.4 times 10? That equals 16, right? Not if you're doing the math with PHP (or most other programming languages):
echo floor((0.2 + 1.4) * 10); // Should be 16. But it's 15!
This is due to how floating point numbers are handled internally. They are represented with a fixed number of decimal places and can result in numbers that do not add up quite like you expect. Internally our .2 plus 1.4 times 10 example computes to roughly 15.9999999998 or so. This kind of math is fine when working with numbers that do not have to be precise like percentages. But when working with money precision matters as a penny or a dollar missing here or there adds up quickly and no one likes being on the short end of any missing money.
The BC Math Solution
Fortunately PHP offers the BC Math extension which is "for arbitrary precision mathematics PHP offers the Binary Calculator which supports numbers of any size and precision, represented as strings." In other words, you can do precise math with monetary values using this extension. The BC Math extension contains functions that allow you to perform the most common operations with precision including addition, subtraction, multiplication, and division.
A Better Example
Here's the same example as above but using the bcadd() function to do the math for us. It takes three parameters. The first two are the values we wish to add and the third is the number of decimal places we wish to be precise to. Since we're working with money we'll set the precision to be two decimal palces.
echo floor(bcadd('0.2', '1.4', 2) * 10); // It's 16 like we would expect it to be.
24151.40 - 31891.10 = -7739.699999999997
I grab these two numbers from a MySQL table with the type as decimal(14,2)
24151.40
31891.10
It is saved exactly as stated above and it echos exactly like that in PHP. But the minute I subtract the second value from the first value, I get a number -7739.699999999997 instead of -7,739.7. Why the extra precision? And where is it coming from?
From an article I wrote for Authorize.Net:
One plus one equals two, right? How about .2 plus 1.4 times 10? That equals 16, right? Not if you're doing the math with PHP (or most other programming languages):
echo floor((0.2 + 1.4) * 10); // Should be 16. But it's 15!
This is due to how floating point numbers are handled internally. They are represented with a fixed number of decimal places and can result in numbers that do not add up quite like you expect. Internally our .2 plus 1.4 times 10 example computes to roughly 15.9999999998 or so. This kind of math is fine when working with numbers that do not have to be precise like percentages. But when working with money precision matters as a penny or a dollar missing here or there adds up quickly and no one likes being on the short end of any missing money.
The BC Math Solution
Fortunately PHP offers the BC Math extension which is "for arbitrary precision mathematics PHP offers the Binary Calculator which supports numbers of any size and precision, represented as strings." In other words, you can do precise math with monetary values using this extension. The BC Math extension contains functions that allow you to perform the most common operations with precision including addition, subtraction, multiplication, and division.
A Better Example
Here's the same example as above but using the bcadd() function to do the math for us. It takes three parameters. The first two are the values we wish to add and the third is the number of decimal places we wish to be precise to. Since we're working with money we'll set the precision to be two decimal palces.
echo floor(bcadd('0.2', '1.4', 2) * 10); // It's 16 like we would expect it to be.
PHP doesn't have a decimal type like MySQL does, it uses floats; and floats are notorious for being inaccurate.
To cure this, look into number_format, e.g.:
echo number_format(24151.40 - 31891.10, 2, '.', '');
For more accurate number manipulation, you could also look at the math extensions of PHP:
http://www.php.net/manual/en/refs.math.php
This has to do with general float / double precision rates, which scientifically relates to 1.FRACTAL * 2^exponential power. Being that there's a prefix of 1, there's technically no such thing as zero, and the closest value you can obtain to 0 is 1.0 * 2 ^ -127 which is .000000[127 0s]00001
By rounding off your answer to a certain precision, the round factor will give you a more precise answer
http://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html#function_round