I'm having a problem with a floating point value conversion. I have a private property in a class with a value of 317.46. If I multiply this value by 100, I should have the value of 317.46, and that's what seems to be happening, but when i use json_encode it returns another value, slightly smaller. This is the result from the debug console:
$this->valor
317.46
$this->valor * 100
31746
json_encode($this->valor * 100)
"31745.99999999999996"
I known this is due to the way floating points are stored, but in this case I need it to be 'rendered' as a integer, otherwise it will generate an error in the API I'm calling. In other languages I would convert it to a int value, or use a decimal datatype, like in c#, but it's not available in PHP as far as I know.
Use round() to remove the round-off error from floating point representation.
echo json_encode(round($this->valor * 100))
If this is money, it's a good idea to use pennies as the representation in the first place, to avoid problems from converting to and from decimal fractions.
Related
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.
Given the following cod:
$number = 1050.55;
var_dump($number - floor($number));
Why does the above code returns the following result?
float(0.54999999999995)
I want a fixed value like 0.55 in this case. Can you help me please?
Floating point operations are not precise and the remainder errors are common.
If you know, what is your desired precission (eg. two digits after the dot), you can use round() function on the result.
In this case this will be:
$number = 1050.55;
var_dump(round($number - floor($number), 2));
For most floats, binary can only approximately represent the correct number. The rule is to perform floor(), ceil() or fmod() last in a series of calculations. At least only do integer math after you use them. If you cast an int to a float, as in your code, then floor() is not going to behave has you expect.
Use printf() when printing floats. Its conversion routines usually do a much better job and give you the answer you expect when truncating floats.
EDIT: Or, to be more exact, printf() works on the decimal character representation of the number when deciding where to truncate so you don't get any weird, unspecified, binary/decimal conversion artifacts.
See this question. While that is about java and you're asking about PHP the math is the same.
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
How come the result for
intval("19.90"*100)
is
1989
and not 1990 as one would expect (PHP 5.2.14)?
That's because 19.90 is not exactly representable in base 2 and the closest approximation is slightly lower than 19.90.
Namely, this closest approximation is exactly 2^-48 × 0x13E66666666666. You can see its exact value in decimal form here, if you're interested.
This rounding error is propagated when you multiply by 100. intval will force a cast of the float to an integer, and such casts always rounds towards 0, which is why you see 1989. Use round instead.
You can also use bc* function for working with float :
$var = bcmul("19.90", "100");
echo intval($var);
intval converts doubles to integers by truncating the fractional component of the number. When dealing with some values, this can give odd results. Consider the following:
print intval ((0.1 + 0.7) * 10);
This will most likely print out 7, instead of the expected value of 8.
For more information, see the section on floating point numbers in the PHP manual
Why are you using intval on a floating point number? I agree with you that the output is a little off but it has to do with the relative inprecision of floating point numbers.
Why not just use floatval("19.90"*100) which outputs 1990
I believe the php doc at http://de2.php.net/manual/en/function.intval.php is omitting the fact that intval will not deliver "the integer value" but the integer (that is non-fractional) part of the number. It does not round.
I have a small financial application with PHP as the front end and MySQL as the back end. I have ancient prejudices, and I store money values in MySQL as an integer of cents. My HTML forms allow input of dollar values, like "156.64" and I use PHP to convert that to cents and then I store the cents in the database.
I have a function that both cleans the dollar value from the form, and converts it to cents. I strip leading text, I strip trailing text, I multiply by 100 and convert to an integer. That final step is
$cents = (integer) ($dollars * 100);
This works fine for almost everything, except for a very few values like '156.64' which consistently converts to 15663 cents. Why does it do this?
If I do this:
$cents = (integer) ($dollars * 100 + 0.5);
then it consistently works. Why do I need to add that rounding value?
Also, my prejudices about storing money amounts as integers and not floating point values, is that no longer needed? Will modern float calculations produce nicely rounded and accurate money values adequate for keeping 100% accurate accounting?
If you want precision, you should store your money values using the DECIMAL data type in MySQL.
Your "prejudices" about floats will never be overcome - it's fundamental to the way they work. Without going into too much detail, they store a number based on powers of two and since not all decimal number can be presented this way, it doesn't always work. Your only reliable solution is to store the number as a sequence of digits and the location of the decimal point (as per DECIMAL type mentioned above).
I'm not 100% on the PHP, but is it possible the multiplication is converting the ints to floats and hence introducing exactly the problem you're trying to avoid?
Currency/money values should never be stored in a database (or used in a program) as floats.
Your integer method is fine, as is using a DECIMAL, NUMERIC or MONEY type where available.
Your problem is caused by $dollars being treated as a float and PHP doesn't have a better type to deal with money. Depending on when $dollars is being assigned, it could be being treated as a string or a float, but is certainly converted to a float if it's still a string for the * 100 operation if it looks like a float.
You might be better off parsing the string to an integer "money" value yourself (using a regex) instead of relying on the implicit conversions which PHP is doing.
The code you posted does the multiplication first, forcing a floating point calculation that introduces error, before converting the value to an integer. Instead, you should avoid floating point arithmetic entirely by reversing the order. Convert to integer values first, then perform the arithmetic.
Assuming previous code already validated and formatted the input, try this:
list($bills, $pennies) = explode('.', $dollars);
$cents = 100 * $bills + $pennies;
Your prejudice against floating point values to represent money is well founded because of truncation and because of values being converted from base-10 to base-2 and back again.
Casting does not round() as in round-to-nearest, it truncates at the decimal: (int)3.99 yields 3. (int)-3.99 yields -3.
Since float arithmetic often induces error (and possibly not in the direction you want), use round() if you want reliable rounding.
You should never ever store currency in floating point, because it always get results you don't expect.
Check out php BC Maths, it allow you to store your currency as string, then perform very high precision arithmetic on them.
Instead of using
$cents = (integer) ($dollars * 100);
you may want to try to use:
$cents = bcmul($dollars, 100, 2);
When converting from float to integer, the number will be rounded towards zero (src).
Read the Floating point precision warning.
There's no point in storing money as integer if you enter it through a floating point operation (no pun intended). If you want to convert from string to int and be consistent with your "prejudice" you can simply use string functions.
You can use an arbitrary precision library to divide by 10 (they handle numbers internally as strings), e.g. bcdiv() or gmp_div_q(), but of course, you could have also used it from the beginning for all the math.
Or you can use plain string functions:
<?php
// Quick ugly code not fully tested
$input = '156.64';
$output = NULL;
if( preg_match('/\d+(\.\d+)?/', $input) ){
$tmp = explode('.', $input);
switch( count($tmp) ){
case 1:
$output = $tmp[0];
break;
case 2:
$output = $tmp[0] . substr($tmp[1], 0, 2);
break;
default:
echo "Invalid decimal\n";
}
}else{
echo "Invalid number\n";
}
var_dump($output);
?>