PHP Float Subtract [duplicate] - php

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 5 years ago.
I have this simple substraction code:
<?php
$n1 = 257931.076;
$n2 = 257930;
echo $n1 - $n2;
?>
Why i got 1.0760000000009 instead of 1.076
Where did the 0000000009 came from? i need precise result and i don't want to use round() or number_format() because sometime i have more than 3 decimal, for example: 12345.678912, anyone know?
I have tried to use round() or number_format() but it only for fixed decimal point, not dynamic

As #GordonM perfectly explained it, you cannot expect exact results when using floating point values.
You can use a library such as brick/math to perform exact calculations on decimal numbers of any size:
use Brick\Math\BigDecimal;
$n1 = BigDecimal::of('257931.076'); // pass the number as a string to retain precision!
$n2 = 257930;
echo $n1->minus($n2); // 1.076
The library uses GMP or BCMath internally when available, but can work without these extensions as well (with a performance penalty).

For technical reasons that every programmer should be aware of, IEEE floating point numbers simply can't represent numbers precisely and will use the closest approximation they can when storing them (In fact the only fractions that can be stored perfectly have denominators that are powers of 2 (1/2, 1/4, 1/8, 1/16, etc. All other values are approximations). PHP has an ini value called "precision", which controls how many digits are considered significant WHEN OUTPUTTING floating point values. It defaults to 14, with any digits after that hidden.
However, the actual value stored may try to approximate the desired value with far more digits than that. If you change precision, you'll see what is really being stored.
php > $test = 0.1;
php > var_dump ($test);
php shell code:1:
double(0.1)
php > ini_set("precision", 100);
php > var_dump ($test);
php shell code:1:
double(0.1000000000000000055511151231257827021181583404541015625)
php > var_dump (0.25);
php shell code:1:
double(0.25)
php > var_dump (0.4);
php shell code:1:
double(0.40000000000000002220446049250313080847263336181640625)
What can you actually do about this? Not a great deal, this is just a consequence of how floating point works. You can try to avoid using floating point if you need exact values (for example when dealing with money amounts, store 3.99 as 399 pennies/cents instead of 3.99 pounds/dollars), or you can use the "bugnum" libraries that are available in PHP, GMP and BC_Math, but these are both tricky to use and have their own sets of gotchas. They can also be hard on storage and/or processor time. In most cases it's best to just live with it and be aware that when you're dealing with floating point you're not dealing with an exact representation.

Related

Reliable Margin of Error for Float -> String -> Float Conversion?

I have a float value that I need to store as a string in PHP and then compare later after casting back into a float.
Due to the conversion I know that relying on equality would be a mistake, as there's potential for a loss of precision, so I'm doing something like the following:
if (abs((float)$string_value - $float_value) < 0.001) { echo "Values are close enough\n"; }
Now, while a margin for error of 0.001 should be fine for my immediate purposes, it got me wondering; what is the smallest margin of error that I can reliably/safely use?
I realise that the safe margin of error will change with the size of the float (i.e- larger values have less or even no fractional precision), so an answer should probably account for this.
So to put it another way; given a float value that I want to store in base 10 and read back, how can I reliably decide what my margin of error should be such that I can reasonably confirm that the two values are the same?
Unfortunately the values I'm handling must be stored in plain decimal form, so my usual go-to of packing them as a network order 64-bit integer is not an option here ☹️
EDIT: To clarify; please assume that my question is about handling arbitrarily sized floats; the example code I've given is for a recent case where I'm handling floats within a limited range, so setting the margin of error manually is fine, but I'd like to be able to handle floats of any magnitude in future.
As mentioned in Mark Dickinson's comment, it is possible to convert a floating-point number to a string and back without losing precision. This only works if
you use enough significant decimal digits (17 for IEEE doubles)
the conversions are accurate (i.e. they're guaranteed to convert to the nearest number)
From a quick look, it seems that casting a double $f to a string in PHP, either implicitly or with (string) $f, only uses 14 significant digits, so this method isn't accurate enough. But you can use sprintf with a %.16e conversion specifier to get 17 significant digits. So after the following roundtrip
$s = sprintf("%.16e", $f);
$f2 = (double) $s;
$f2 should equal $f exactly unless PHP uses suboptimal algorithms internally.
Note that the %e conversion specifier uses scientific (exponential) notation. If you need plain decimal strings, you can use the %f specifier and calculate the required number of digits after the decimal point using log10:
if ($f != 0) {
$prec = 16 - floor(log10(abs($f)));
if ($prec < 0) $prec = 0;
}
else {
$prec = 0;
}
$s = sprintf("%.${prec}f", $f);
This can produce extremely long strings for very small or large numbers, though.
It would probably require a huge amount of research to tell the whether these methods are completely reliable, and if not what the maximum error is. It all depends on several implementation details like PHP version, underlying C library, etc.
Another idea is to compare the string representations instead of floating-point values:
# Assuming $string_value was also converted with float_to_string
if ($string_value == float_to_string($float_value)) {
echo "Values are close enough\n";
}
This should be reliable as long as you stick to the same PHP version.
If you must compare floating-point numbers, it often makes more sense to compare the relative error. See Bruce Dawson's excellent blog for more details.

PHP bcmath needed for whole cent values?

In PHP, I am writing an application which requires precision to 2 digits right of the decimal point for currency (eg: I care about 1.23 === 1.23 but no more right-side digits).
I am aware that floats are generally considered bad practice because they are imprecise with values based on the nature of converting from base 2 to base 10 right of the decimal point. However, in my research for a best practice for working with currency values, I saw some arguments that float is not good if you need precision greater than whole cent values. I clearly do not need greater precision that whole cent values.
So my questions, then, are:
Is it worth going through the extra effort of storing the values as strings to be used with the bcmath library?
If using the bcmath lib, should I store the values in the MySQL db as strings or decimal that MySQL supports?
Thanks!
After further digging, I found the solution at Should I use BCMath for values with about 1,2 or 3 decimals?
According the the accepted answer on the given post, floats can not be guaranteed for any precision right of the decimal point.
As far as storage in the DB, it seems that storing it as a string would be the easiest option since the bcmath lib works with strings.
Use this to trim to two decimal places without rounding.
<?php
$a = 12.37675;
$a = floor($a * 100) / 100; // 12.37
echo $a;
or
<?php
function dollar($value) {
return floor($value * 100) / 100;
}
$a = 12.37675;
echo dollar($a);

Is this floating point behavior or a bug in PHP?

CLARIFYING: This isn't asking why I'm getting rounding errors. I understand this is a mistake or an oversight. The question asks why it prints as whole in the first var_dump, but casting acts as if it were 57916.9repeating and truncates said .9repeating.
The following occurs:
You take a string (or float -- does not matter) that contains the value 579.17 and multiply it 100. It var_dumps the expected 57917. Not 57916.99999999999999999999999 or similar. var_dump should not be rounding anything as a debugging function in my opinion. It may have to truncate, but rounding is unexpected in a debugging function.
However, if one then casts that to an integer, you get an unexpected 57916 from var_dump.
I'm aware of issues with floating point numbers, but the act of casting a floating point number that prints as exactly 57917 in PHP apparently effectively subtracts 1. This is a very small number.
This only appears to happen for some numbers, such as 579.17. It does not occur for others I've tested. All we're doing is multiplying a number by 100 to send to an API that expects cents. The API library understandably casts to integer since the API doesn't accept fractional cents.
Test case:
php -r '$n = ("579.17" * 100); var_dump($n, (int)$n);'
Output:
float(57917)
int(57916)
Environment:
x86-32,
x86-64 both.
var_dump uses precision from php.ini to display float value. You could raise it to see what happens.
php -r 'ini_set("precision", 20); $n = ("579.17" * 100); var_dump($n, (int)$n);'
// double(57916.999999999992724)
// int(57916)
Also. There is no matter x86 or x64. PHP uses 64 bits for floats.
http://php.net/manual/en/language.types.float.php
Use round() instead of int(). The actual value of 579.17 * 100 is something like 57916.99999. var_dump() shows this as 57917, but when you use int() it truncates the fraction. Using round() will go to the nearest integer, rather than always truncating down.
I believe this is because hardware cannot truly and accurately express floating point numbers. So what appears as 579.17 is actually more like 579.16999999. So when you multiply it and cast it as an int it truncates the decimal leaving you with 57916.

How to emulate single precision float operations in PHP?

I need to port a simple C program to PHP. Currently we have to start the process and parse it's output. The program is very trivial but it is important for the algorithm to use float as the errors will sum up and the result will be way off.
C example:
#include <stdio.h>
int main( void ) {
printf("%f\n", 123456 * (float)0.99524);
printf("%f\n", 123456 * (double)0.99524);
return 0;
}
PHP example:
<?php
printf("%f\n", 123456 * 0.99524);
?>
The C example will result in 122868.343750 and 122868.349440 while PHP will end up with 122868.349440.
How do I get the C float result in PHP?
There is no way you can do this using built in php functions.
The one using "double" gives you the real result, 100% precise. The float one is wrong.
In PHP float and double are the same type, which is double.
If you need high precision results, that always give the same results, try using BC Math module: http://php.net/bcmath
Example code using BC Math:
$result = bcmul("123456", "0.99524", 6); // gives 122868.34944
$result = number_format($result, 6, ".", ""); // 122868.349440 - appending zeros
echo $result;
Output:
122868.349440
If you really, really want the same result as in the C program, then you have 2 options:
Create your own c-like function by writing a php extension: http://www.google.com/search?q=writing+php+extensions
Talk to your C-program from PHP via function proc_open():
http://www.php.net/manual/en/function.proc-open.php (see also popen(), exec() or shell_exec())
You could always create a PHP module.
Here are a list of resources that I've compiled over time...
http://www.delicious.com/homer6/php+extension
Also, I'd highly recommend reading Sara Goleman's book:
http://blog.simonholywell.com/post/1156691738/15-excellent-resources-for-php-extension-development
Hope that helps...
Floating point numbers have limited precision. Although it depends on
the system, PHP typically uses the IEEE 754 double precision format,
which will give a maximum relative error due to rounding in the order
of 1.11e-16. Non elementary arithmetic operations may give larger
errors, and, of course, error progragation must be considered when
several operations are compounded.
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 never
compare floating point numbers for equality. If higher precision is
necessary, the arbitrary precision math functions and gmp functions
are available.
Quoted from : http://php.net/manual/en/language.types.float.php
To change the precision level of PHP , change the precision settings in php.ini

php - why does floor round down a integer?

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

Categories