Php comparison integer with double - php

I got a problem with this script
$total = 0;
$expected_total = 1111;
$i = [85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.48];
foreach ($i as $item) { $total += $item; }
if($total != $expected_total) {
echo json_encode([$total,$expected_total]);
}
The problem is that at the end of the sum the $total should be equal to the $expected_total.
I thought about different number types, I printed the type of the two vars and I was right, one was double and the other was integer, so I converted the integer to double
$expected_total = 1111.00;
but the result is still the same.
The only solution that I could find was comparing the rappresentation of the two numbers, casting them to a string.
if((string)$total != (string)$expected_total) {
echo json_encode([$total,$expected_total]);
}
But obviously this is kind of a hack.
Have you ever had a similar problem? How have you solved it?
PHP version : 5.5.9
Many Thanks

This is not only PHP problem. It is about representation of floating point numbers in memory. Floating numbers has limited precision. PHP uses IEEE 754. Read carefully the manual page and you will understand.
You can find in manual code snippet of how to do it.
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001; //very small number
if(abs($a-$b) < $epsilon) {
echo "true";
}

If you want to check them as integers you could round both values.
You should change the if-statement to the following:
if(round($total) != round($expected_total)) {
echo json_encode([$total,$expected_total]);
}
If you do it like this you will compare the rounded values, which will be the same.

There is a big red label in the PHP Manual, that says:
Floating point numbers have limited precision…
So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.
In this specific case, you could add the floating numbers using bcadd() and compare the totals using bccomp(). Both functions are provided by the BC Math extension:
foreach ($i as $item) {
$total = bcadd((string) $total, (string) $item, 2);
}
if (bccomp((string) $total, (string) $expected_total, 2) == 0) {
echo json_encode([$total,$expected_total]);
}

Related

PHP - How to validate float number has maximum 2 digits

I have to validate if a float number has maximum two digits.
I've tried a lot of methods but all fails in more or lase cases.
Last of them were:
//fails for 2638655.99
private function hasMoreThanTwoDecimals(string $number): bool
{
$number = abs($number);
$intPart = floor($number);
$floatPart = $number - $intPart;
return (strlen($floatPart) > 4);
}
OR
//fails for 36.62
private function hasMoreThanTwoDecimals(string $number): bool
{
return $number * 100 - floor($number * 100) > 0.00001;
}
What other methods do you use?
You can't determine the exact number of decimals with the float datatype, because the internal representation is binary. In binary, fx. 0.1 can not be represented exactly. That's why loops always should have integer increments.
for ($i = -1; $i < 1; $i += 0.1) {
if ($i == 0) {
echo "Zero is here!";
}
}
will never say "Zero is here!" because of binary rounding issues.
Using an Epsilon
You already tried to use an epsilon (a very small value) for thesholding (here a refactored version of your function):
private function hasMoreThanTwoDecimals(string $number): bool
{
$epsilon = 0.00001;
return fmod($number * 100, 1.0) > $epsilon;
}
but fails for some values. In that case, you need to increase your epsilon value.
String Arithmetic
The more precise way is to avoid float and use string representations instead. This is your best option, since - according to your function signature - your numbers are represented as strings already.
private function hasMoreThanTwoDecimals(string $number): bool
{
return bcmod(bcmul($number, '100'), '1.0') != 0;
}
This needs the BCMath module to be included in your PHP. A package supporting BCMath and other solutions is brick/math.
The Cheap Solution
However, if you really just need to probe the number and not are doing calculations, you can get the desired result with pattern matching using preg_match.
private function hasMoreThanTwoDecimals(string $number): bool
{
// Trailing 0 does not add to number of decimals
$number = rtrim($number, '0');
return preg_match('~\.\d\d\d~', $number);
}
You can explode the number using the . delimeter, then you return the length of the second part :
$num = 2638655.99;
echo strlen(explode('.',$num)[1]); // Echo 2
Taking the question literally, if a binary floating point number has a maximum of two decimal digits after the decimal point, the fractional part must be one of .0, .25, .5, or .75.
All other binary floating point numbers really have more decimal digits, although printout formatting may hide them. For example, the closest IEEE 754 64-bit binary number to 2638655.99 is 2638655.99000000022351741790771484375, which has more than two digits after the decimal point.
You could subtract the integer part and then test for the remainder being one of the four possibilities.
Alternatively, the real question may be how to determine whether displaying the number will show no more than two digits after the decimal point. If so, convert to string using the appropriate method, then locate the decimal point and count the digits after it, for example as suggested in this answer.
$res = preg_match("^[+-]?([0]{1}|[1-9]{1}[0-9]*)(\.?[0-9]{1,2})?$", $num) == true;
would be the best solution in my opinion. You can use signs (optional) and enforce that a number starts with only one zero.
Possible:
+0.10
+123.01
-1
123
Not possible:
00.0
0001.0
1.
123.123
Be aware that preg_match returns 0 if no match is found and false if an error occurred (preg_match)
you can use the number_format
number_format($number, 2, '.', '');

Floating point rounding error php ...how can I make sure it works correctly?

I have the following function that determines if I sale is fully paid for. I don't remember why I did it this way, but it has been working so far and I don't remember why I had to do it this way.
function _payments_cover_total()
{
//get_payments is a list of payment amounts such as:
//10.20, 10.21, or even 10.1010101101 (10 decimals max)
$total_payments = 0;
foreach($this->sale_lib->get_payments() as $payment)
{
$total_payments += $payment['payment_amount'];
}
//to_currency_no_money rounds total to 2 decimal places
if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
{
return false;
}
return true;
}
I am wondering if there is ever a case where due to a rounding error that this function would return false when it shouldn't.
The main part I have a question about is:
> 1e-6
I think before I had, but it was causing problems in some cases.
> 0
I think you are doing what is mentioned on php floating help page. To quote it directly :
To test floating point values for equality, an upper bound on the
relative error due to rounding is used. This value is known as the
machine epsilon, or unit roundoff, and is the smallest acceptable
difference in calculations.
$a and $b are equal to 5 digits of precision.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
So in your case:
(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6
relative error due to rounding should not be great than 1e-6 or 0.000001
if you are not sure about left operand being greater than right 100% time,then you should add abs() e.g for correctness.
$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
if(abs($relative_error) > 1e-6){
return false
}
return true;
$x = (1.333-1.233)-(1.334-1.234);
echo $x;
//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)
if($x === 0){
echo "|zero";
}
else {
echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero
//how to get to zero?
if($x > 1e-6){//1e-6 mathematical constant
echo "|non zero";
}
else {
echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero
if ($x > 1e-6 )
{
echo " false";
//echo "|non zero";
//return false;
}
else{
echo " true";//<== this resut
//echo "|zero";
//return true;
}
//screen -2.2204460492503E-16|non zero|zero true
printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503
I am sorry, but when dealing with currency, you shouldn't really be using PHP floats, as IMSoP stated. The reason is also from PHP float help pages:
Additionally, rational numbers that are exactly representable as
floating point numbers in base 10, like 0.1 or 0.7, do not have an
exact representation as floating point numbers in base 2, which is
used internally, no matter the size of the mantissa. Hence, they
cannot be converted into their internal binary counterparts without a
small loss of precision. This can lead to confusing results: 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 never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
Note that the help page specifically says you can't trust float results to the last digit, no matter how short (after comma) it is.
So while you do have very short floats (just 2 digits after comma), the float precision (1e-6) doesn't enter into it really, and you can't trust them 100%.
Since it is a question of money, in order to avoid angry customers and lawsuits accusing of penny shaving (https://en.wikipedia.org/wiki/Salami_slicing), the real solutions are:
1) either use PHP BC math, which works with string representation of numbers
http://php.net/manual/en/book.bc.php
2) as IMSoP suggested, use integers and store the amounts in smallest denomination (cents, eurocents or whatever you have) internally.
First solution might be a bit more resource intense: I haven't used BC math myself much, but storing strings and doing arbitrary precision math (which might be a bit of an overkill in this case) are by definition more RAM and CPU intense than working with integers.
It might, however, need less changes in other parts of the code.
The second solution requires changes to represenation, so that wherever user sees the amounts, they are normalized to dollars,cents (or whatever have you).
Note, however, that in this case also you run problems with rounding risks at some point, as when you do:
float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100;
you may reintroduce the rounding problems as you have floats again and possibilities for rounding errors, so tread carefully and be sure to make calculations and roundings only on real_amount, never on shown_amount

Strange subtraction result with PHP [duplicate]

This question already has answers here:
php float calculation 2 decimal point
(8 answers)
Closed 9 years ago.
I am working on a project for a client, its a quite simple. But there is one calculation I have to make which is also very simple, its like this example:
$a = (49.95 - 24.95);
if ($a == 25.00) {
echo "TRUE";
}
This equals 25.00 right! But no its returning false???
But if I do this, another example similar to a calculation I need:
$a = (99.95 - 24.95);
if ($a == 75.00) {
echo "TRUE";
}
Then I get true! Am I going mad, or is this a bug???
Like you can read in manual, don't compare float directly. Instead use epsilon.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
That's because of the way how PHP stores float internally. You can read about it in manual, eg. here http://pl1.php.net/float
It's not a bug. It's about float number precision. Since float numbers are stored with decimal precision, you can't rely on precise compare operations, like == (equality comparison).
Instead you shoult use precision delta and compare floats like:
$a = (49.95 - 24.95);
$b = 25;
$delta = 1E-13;
if(abs($a-$b)<$delta)
{
echo('TRUE');
}
In PHP, 1E-13 will be enough for using as precision delta. For very simple explanation, see this guide about float numbers and their representation.
$a = (49.95 - 24.95);
if ((int)$a == 25) {
echo "TRUE";
}
At the first example there is a float number which is compared to an int number and at the second there is a float number compared to a float number. The difference occurs because of the floating point precision and its not a bug.
To solve this you can define a d (delta) number which will be the precision of the calculation
and then you could check if the absolute result of x-y will be lower then the precision you defined. Something like this if(abs(x-y) < d)

PHP Simple Math Calculation

can help me to see this calculation? It suppose echo "equal"... but it give me "not equal"
<?php
$tl_pax = 1;
$ct_pax = 2;
$at_pax = 2;
$a = 0.5;
$b = 0.2;
$c = 0.2;
$d = 0.2;
$e = 0.2;
$f = 0.2;
$g = 0.2;
$h = 0.9;
$sum = $a + $b + $c + $d + ($e * $tl_pax) + ($f * $ct_pax) + ($g * $at_pax) + $h;
$total = 3;
if($total == $sum){
echo 'equal: ' . $sum . ' - ' . $total;
}
else{
echo 'not equal: ' . $sum . ' - ' . $total;
}
?>
This is the usual case of the rounding error associated with binary floating point numbers. There are numbers that can't be represented exactly in binary, and thus the result will be of by some margin. To read up on it, the wikipedia article about floating point numbers is great.
The usual pattern found in this case is to pick a delta and compare against it:
if(abs($total - $sum) < 0.01)
echo "equal";
You'll have to pick your delta appropiately according to the usecase.
Check if they have difference less than 0.00001
if(abs($total - $sum) < 0.00001){
http://sandbox.phpcode.eu/g/56905/6
This article shows you why is this happening
It's because your sum is really something like 2.9999999999999999999, due to floating pont arithmetic. PHP just hides that from you when you print it. See the example on floor((0.1+0.7)*10) here: http://php.net/manual/en/language.types.float.php
You should never compare a floating point number for equality. The proper way to compare floats is using a range like:
if($total-0.0000001 <= $sum && $sum <= $total+0.0000001){
You can see it in action here: http://codepad.org/kaVXM5g0
That line just means that $total must be within 0.0000001 of $sum to be considered equal. You can pick the number yourself, depending on the amount of precision you need.
Alternatively you can just round $sum in this case, but then you're basically doing the same thing just with a range from 2.5 - 3.499... instead of 2.9999999 - 3.0000001
The difference is due to the limits of floating point precision.
Values like 0.9 (9/10) can't be written exactly as binary floating point numbers, just like 0.3333... (1/3) can't be written exactly as a decimal fraction. This means that e.g. $h holds an inexact, rounded representation of 0.9. As a result, your calculation yields something very close to 3, but not exactly 3.
Floats are evil.
Quote from http://php.net/float
"So never trust floating number results to the last digit, and never compare floating point numbers for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available."

Odd behavior comparing doubles, two PHP double values aren't equivalent

I have two seemingly equal double values in PHP (at least when echoing them).
But when comparing them with double equals, for some reason, it evaluates to false. Are there any special considerations when performing this kind of comparison?
You shouldn't compare floating point numbers using the == operator.
See the big warning and explanation in the php manual
What will work is asserting that the two numbers are within a certain small distance of each other like this:
if(abs($a - $b) < 0.0001) {
print("a is mostly equal to b");
}
The reason is because of rounding errors due to floating point arithmetic performed after the decimals are converted to binary, then converted back to decimal. These back and forth conversions cause the phenomenon where 0.1 + 0.2 does not equal 0.3.
float and double should never be compared for equality: there are precision errors that will make two numbers different even if they seem equal (when they are printed out, they are usually rounded).
Proper way to compare is using some DELTA constant:
define(DELTA, 0.00001); // Or whatever precision you require
if (abs($a-$b) < DELTA) {
// ...
}
Also note that this is not PHP specific but also important in other languages (Java, C, ...)
Representation of floating point numbers in PHP (as well as in C and many other languages) is inexact. Due to this fact, seemingly equal numbers can in fact be different and comparison will fail. Instead, choose some small number and check that the difference is less than that, like:
if(abs($a-$b)<0.00001) {
echo "Equal!";
}
See also explanations in the PHP manual.
A small function i made, hope helps someone:
function are_doubles_equal($double_1, $double_2, $decimal_count) {
if (!$decimal_count || $decimal_count < 0) {
return intval($double_1) == intval($double_2);
}
else {
$num_1 = (string) number_format($double_1, $decimal_count);
$num_2 = (string) number_format($double_2, $decimal_count);
return $num_1 == $num_2;
}
}
Usage:
$a = 2.2;
$b = 0.3 + 1.9002;
are_doubles_equal($a, $b, 1); // true : 2.2 == 2.2
are_doubles_equal($a, $b, 1); // false : 2.2000 == 2.2002
Not the fastest way but convert to string before comparing:
if( strval($a) === strval($b) ){
// double values are exactly equal
}

Categories