Problem is bit critical. Output I need is 2.9558577807620168e-12.
1#working.php
<?php
$a = 465.90928248188;
$b = 15.651243716447;
$c = 450.25803876543;
echo $a - $b -$c // output 2.9558577807620168e-12 as expected
?>
2#notworking.php
<?php
lot of arithmetic calculation almost 200-250 LoC
$array1_28x1[3]; // 465.90928248188
$array2_28x1[3]; // 15.651243716447
$array3_28x1[3]; // 450.25803876543
echo $array1_28x1[3] - $array2_28x1[3] - $array3_28x1[3];
// output -4.5474735088646E-13
?>
I Don't understand what is the issue. Can it be memory leak? I have done step by step debugging also but could not find any solution. And this is very important calculation so can not even ignore.
Note: There is no changes in the variable's value under those 250 LoC. I have dumped variables before the subtraction.
You did use 2 times the array2, so i think it is a typo.
when i execute this code it works:
<?php
$array1_28x1[3] = 465.90928248188;
$array2_28x1[3] = 15.651243716447;
$array3_28x1[3] = 450.25803876543;
echo $array1_28x1[3] - $array2_28x1[3] - $array3_28x1[3];
// output 2.955857780762E-12
The problem arises since you display your intermediate variables with only 14 significant digits. This hides 2 additional digits that are present in the original computation but missing in the reconstruction.
The general solution is to recognize that within the bounds of floating point arithmetic, you have effectively computed zero.
To get a reconstructable result you could convert the intermediate results to strings which are displayed and then those back to numbers. This will trivially give you results that can be reproduced from the displayed intermediate results.
As to having a result that is essentially floating point noise and thus represents zero, your scale/magnitude of inputs is 1000, which gives a absolute error scale resp. scale of floating point noise of 1e4*1e-16=1e-12. Both indicated results fall within this scale, i.e., both have to be considered to be zero.
Related
I'm working on a system where I need to round down to the nearest penny financial payments. Naively I thought I would multiply up by 100, take the floor and then divide back down. However the following example is misbehaving:
echo 1298.34*100;
correctly shows:
129834
but
echo floor(1298.34*100);
unexpectedly shows:
129833
I get the same problem using intval for example.
I suspect the multiplication is falling foul of floating point rounding. But if I can't rely on multiplication, how can I do this? I always want to round down reliably, and I don't need to take negative amounts into consideration.
To be clear, I want any fractional penny amounts to be stripped off:
1298.345 should give 1298.34
1298.349 should give 1298.34
1298.342 should give 1298.34
Since you mention you only use this for displaying purposes, you could take the amount, turn it into a string and truncate anything past the second decimal. A regular expression could do the job:
preg_match('/\d+\.{0,1}\d{0,2}/', (string) $amount, $matches);
This expression works with any number of decimals (including zero). How it works in detail:
\d+ matches any number of digits
\.{0,1} matches 0 or 1 literal dot
\d{0,2} matches zero or two digits after the dot
You can run the following code to test it:
$amounts = [
1298,
1298.3,
1298.34,
1298.341,
1298.349279745,
];
foreach ($amounts as $amount) {
preg_match('/\d+\.{0,1}\d{0,2}/', (string) $amount, $matches);
var_dump($matches[0]);
}
Also available as a live test in this fiddle.
You can use round() to round to the required precision, and with the expected behavior when rounding the final 5 (which is another financial hurdle you might encounter).
$display = round(3895.0 / 3.0, 2);
Also, as a reminder, I have the habit of always writing floating point integers with a final dot or a ".0". This prevents some languages from inferring the wrong type and doing, say, integer division, so that 5 / 3 will yield 1.
If you need a "custom rounding" and want to be sure, well, the reason it didn't work is because not all floating point numbers exist in machine representation. 1298.34 does not exist; what does exist (I'm making the precise numbers up!) in its place might be 1298.33999999999999124.
So when you multiply it by 100 and get 129833.999999999999124, of course truncating it will yield 129833.
What you need to do then is to add a small quantity that must be enough to cover the machine error but not enough to matter in the financial calculation. There is an algorithm to determine this quantity, but you can probably get away with "one thousandth after upscaling".
So:
$display = floor((3895.0 / 3.0)*100.0 + 0.001);
Please be aware that this number, which you will "see" as 1234.56, might again not exist precisely. It might really be 1234.5600000000000123 or 1234.559999999999876. This might have consequences in complex, composite calculations.
Since You're working with financial, You should use some kind of Money library (https://github.com/moneyphp/money). Almost all other solutions are asking for trouble.
Other ways, which I don't recommend, are: a) use integers only, b) calculate with bcmath or c) use Number class from the Money library e.g.:
function getMoneyValue($value): string
{
if (!is_numeric($value)) {
throw new \RuntimeException(sprintf('Money value has to be a numeric value, "%s" given', is_object($value) ? get_class($value) : gettype($value)));
}
$number = \Money\Number::fromNumber($value)->base10(-2);
return $number->getIntegerPart();
}
he other function available is round(), which takes two parameters -
the number to round, and the number of decimal places to round to. If
a number is exactly half way between two integers, round() will always
round up.
use round :
echo round (1298.34*100);
result :
129834
I have a very strange issue. If I subtract 2 float vars where one is the result of a mathematical operation I get a wrong value.
Example:
var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];
This it the output:
float 5.4
float 1.4
5.3290705182008E-15
5.4-1.4 should be 4
If I add the two values the result is correct.
Where is my mistake?
It can not be a rounding issue.
If still somebody hits this page with similar problems where floating number subtraction causes error or strange values.
Below I will explain this problem with a bit more details.
It is not directly related to PHP and it is not a bug.
However, every programmer should be aware of this issue.
This problem even took many lives two decades ago.
On 25 February 1991 an incorrect floating-point arithmetic (called rounding error) in a MIM-104 Patriot missile battery prevented it from intercepting an incoming Scud missile in Dhahran, Saudi Arabia, killing 28 soldiers and injuring near 100 servicemen from the U.S. Army's 14th Quartermaster Detachment.
But why it happens?
The reason is that floating point values represent a limited precision. So, a value might
not have the same string representation after any processing (chopped off). It also
includes writing a floating point value in your script and directly
printing it without any mathematical operations.
Just a simple example:
$a = '36';
$b = '-35.99';
echo ($a + $b);
You would expect it to print 0.01, right?
But it will print a very strange answer like 0.009999999999998
Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.
Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....
Decimal numbers are not well represented in binary due to lack of enough space. So, you can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.
If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary, when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.
If you format the result using
echo number_format(0.009999999999998, 2);
it will print 0.01.
It is because in this case you instruct how it should be read and how precision you require.
Note number_format() is not the only function, a few other functions and ways can be used to tell the programming language about the precision expectation.
References:
https://sdqweb.ipd.kit.edu/publications/pdfs/saglam2016a.pdf
https://en.wikipedia.org/wiki/Round-off_error
This worked for me:
<?php
$a = 96.35;
$b = 96.01;
$c = ( ( floor($a * 100) - floor($b * 100) ) / 100 );
echo $c; // should see 0.34 exactly instead of 0.33999999999999
?>
Since the problem occurs with floating point subtraction operation I decided to eliminate that by transforming it into an integer operation, then backing up the result into a floating point again.
I much prefer that solution because basically it does prevent the error on calculation rather than rouding up the result with other functions.
In addition to using number_format(), there are three other ways to obtain the correct result. One involves doing a little math, as follows:
<?php
$a = '36';
$b = '-35.99';
$a *= 100;
$b *= 100;
echo (($a + $b)/100),"\n";
See demo
Or, you could simply use printf():
<?php
$a = '36';
$b = '-35.99';
printf("\n%.2f",($a+$b));
See demo
Note, without the precision specifier, the printf() result will contain trailing zero decimals, as follows: 0.010000
You also could also utilize the BC Math function bcadd(), as follows:
<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);
See demo
I wrote a simple function to deal with this.
It works similarly to the bcadd function from the bcmath extension of php.
You pass it 2 decimal numbers in string form, $a and $b, and specify how many decimals should be used which must match the number of decimals in both $a and $b.
As you can see it will use integers to do the math, then convert back to string without using floating point operations at any point.
function decimalAdd($a,$b,$numDecimals=2) {
$intSum=(int)str_replace(".","",$a)+(int)str_replace(".","",$b);
$paddedIntSum=str_pad(abs($intSum),$numDecimals,0,STR_PAD_LEFT);
$result=($intSum<0?"-":"").($intSum<100&&$intSum>-100?"0":"").substr_replace($paddedIntSum,".",-$numDecimals,0);
return $result;
}
Sample usage:
echo decimalAdd("36.00","-35.99");
0.01
I'm adding together two numerical strings $a and $b and then comparing the result against another numerical string $c. All three numbers are stored as strings, and being converted to floats by PHP at the comparison step.
For some reason, the test $a+$b == $c does not evaluate as true, even though it should.
You can recreate the problem with this script:
<?php
$a = "-111.11";
$b = "-22.22";
$c = "-133.33";
echo '$a is '.$a."\n";
echo '$b is '.$b."\n";
echo '$c is '.$c."\n";
echo '$a + $b is '.($a+$b). "\n";
if ($a + $b == $c) {
echo 'a + b equals c'."\n";
} else {
echo 'a + b does not equal c'."\n";
}
?>
Weirdly, if I change the values slightly so that $a=-111.11, $b=-22.23 and $c=-133.34 it works as expected.
Am I missing something obvious, or is this a bug with PHP?
From the large red box on this page: http://php.net/manual/en/language.types.float.php
never compare floating point numbers for equality.
Basically, you're not getting the correct numbers, because they are saved in a slightly different format, so when you compare, it gets screwed.
That link of #Corbin is really good, So I'm adding it just for the love :)
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
What Every Computer Scientist Should Know About Floating-Point Arithmetic
This paper presents a tutorial on those aspects of floating-point that
have a direct impact on designers of computer systems. It begins with
background on floating-point representation and rounding error,
continues with a discussion of the IEEE floating-point standard, and
concludes with numerous examples of how computer builders can better
support floating-point.
You're running into a limitation of floating point arithmetic. Just as there are certain numbers you can't represent exactly in decimal (1/3 for instance), so there are certain numbers you can't represent exactly in floating point binary.
You should never try and compare floating point numbers for equality, as the limitations of floating point make it unlikely that the variables you're comparing have an actual value that matches exactly the value you think they have. You need to add a "fudge factor", that is if the two numbers are similar to within a certain tolerance, then you should consider them to be equal.
You can do this by subtracting one number from another and seeing if the absolute result is below your threshold (in my example, 0.01):
if (abs ($someFloatingPointNumber - $someOtherFloatingPointNumber) <= 0.01)
{
// The values are close enough to be considered equal
}
Of course, this combined with rounding errors that can creep in with successive mathematical operations mean that floating point numbers are often not the best choice anyway, and should be avoided where possible. For example, if you're dealing with currency, store your values as integers in the minor unit (pennies for GBP, cents for USD, etc), and only convert to the major unit by dividing by 100 for display.
Do your number have always two decimal positions?
If so, you can try this:
$aDec = round($a * 100);
$bDec = round($b * 100);
$cDec = round($c * 100);
if ($aDec + $bDec == $cDec) {
...
}
In PHP I have a 64 bit number which represents tasks that must be completed. A second 64 bit number represents the tasks which have been completed:
$pack_code = 1001111100100000000000000011111101001111100100000000000000011111
$veri_code = 0000000000000000000000000001110000000000000000000000000000111110
I need to compare the two and provide a percentage of tasks completed figure. I could loop through both and find how many bits are set, but I don't know if this is the fastest way?
Assuming that these are actually strings, perhaps something like:
$pack_code = '1001111100100000000000000011111101001111100100000000000000011111';
$veri_code = '0000000000000000000000000001110000000000000000000000000000111110';
$matches = array_intersect_assoc(str_split($pack_code),str_split($veri_code));
$finished_matches = array_intersect($matches,array(1));
$percentage = (count($finished_matches) / 64) * 100
Because you're getting the numbers as hex strings instead of ones and zeros, you'll need to do a bit of extra work.
PHP does not reliably support numbers over 32 bits as integers. 64-bit support requires being compiled and running on a 64-bit machine. This means that attempts to represent a 64-bit integer may fail depending on your environment. For this reason, it will be important to ensure that PHP only ever deals with these numbers as strings. This won't be hard, as hex strings coming out of the database will be, well, strings, not ints.
There are a few options here. The first would be using the GMP extension's gmp_xor function, which performs a bitwise-XOR operation on two numbers. The resulting number will have bits turned on when the two numbers have opposing bits in that location, and off when the two numbers have identical bits in that location. Then it's just a matter of counting the bits to get the remaining task count.
Another option would be transforming the number-as-a-string into a string of ones and zeros, as you've represented in your question. If you have GMP, you can use gmp_init to read it as a base-16 number, and use gmp_strval to return it as a base-2 number.
If you don't have GMP, this function provided in another answer (scroll to "Step 2") can accurately transform a string-as-number into anything between base-2 and 36. It will be slower than using GMP.
In both of these cases, you'd end up with a string of ones and zeros and can use code like that posted by #Mark Baker to get the difference.
Optimization in this case is not worth of considering. I'm 100% sure that you don't really care whether your scrip will be generated 0.00000014 sec. faster, am I right?
Just loop through each bit of that number, compare it with another and you're done.
Remember words of Donald Knuth:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
This code utilizes the GNU Multi Precision library, which is supported by PHP, and since it is implemented in C, should be fast enough, and supports arbitrary precision.
$pack_code = gmp_init("1001111100100000000000000011111101001111100100000000000000011111", 2);
$veri_code = gmp_init("0000000000000000000000000001110000000000000000000000000000111110", 2);
$number_of_different_bits = gmp_popcount(gmp_xor($pack_code, $veri_code));
$a = 11111;
echo sprintf('%032b',$a)."\n";
$b = 12345;
echo sprintf('%032b',$b)."\n";
$c = $a & $b;
echo sprintf('%032b',$c)."\n";
$n=0;
while($c)
{
$n += $c & 1;
$c = $c >> 1;
}
echo $n."\n";
Output:
00000000000000000010101101100111
00000000000000000011000000111001
00000000000000000010000000100001
3
Given your PHP-setuo can handle 64bit, this can be easily extended.
If not you can sidestep this restriction using GNU Multiple Precision
You could also split up the HEx-Representation and then operate on those coresponding parts parts instead. As you need just the local fact of 1 or 0 and not which number actually is represented! I think that would solve your problem best.
For example:
0xF1A35C and 0xD546C1
you just compare the binary version of F and D, 1 and 5, A and 4, ...
I am confused as to why:
echo log10(238328) / log10(62);
results in 3
but
echo floor(log10(238328) / log10(62));
results in 2
I know floor rounds down but I thought it was only for decimal numbers.
How can I get an answer of 3 out of the latter statment whilst still normally rounding down?
PHP uses double-precision floating point numbers. Neither of the results of the two logarithms can be represented exactly, so the result of dividing them is not exact. The result you get is close to, but slightly less than 3. This gets rounded to 3 when being formatted by echo. floor, however returns 2.
You can avoid the inexact division by taking advantage of the fact that log(x, b) / log(y, b) is equivalent to log(x, y) (for any base b). This gives you the the expression log(238328, 62) instead, which has a floating point result of exactly 3 (the correct result since 238328 is pow(62, 3)).
It's due to the way floating point numbers are polished in PHP.
See the PHP Manual's Floating Point Numbers entry for more info
A workaround is to floor(round($value, 15));. Doing this will ensure that your number is polished quite accurately.
If you var_dump you'll see that the "3" is actually a float. Which means its probably close to 3 and rounded up. If you wanted 3, you would have to use the sister function, ceil.
You might get better results using the round() function and/or explicitly casting it to an int rather than relying on ceil(). Look here for more information: http://php.net/manual/en/language.types.integer.php
At the cost of a little performance, you could coerce it, reducing the precision to a more useful range by rounding or string formatting the number:
echo floor(round(log10(238328)/log10(62), 4));
echo floor(sprintf('%.4f', log10(238328)/log10(62)));
// output:
// 3
// 3
You should go with the minimum precision that you need. More precision is not what you want. Rounding without flooring might be more correct, the results are different depending on precision.
echo floor(round(log10(238328)/log10(62), 16));
echo round(log10(238328)/log10(62), 16);
// output:
// 2
// 3
there three functions for doing nearly the same:
ceil --> ceil(0.2)==1 && ceil(0.8)==1
floor --> floor(0.2)==0 && floor(0.8)==0
round --> round(0.2)==0 && round(0.8)==1