Additional digits after decimal points - php

Select Query giving additional digits after decimal point.
The datatype of the column is decimal(4,2).
The value stored is 1.39 but what i get is 1.3899999999999999.
I can perform round,number_format in php but Is there a way to get the exact value without extra digits after decimal ??
Database is MSSQL.

Most probably your database library is converting the DECIMAL(4, 2) datatype to a float behind the scenes. Now 1.39 cannot be expressed exactly as a floating point number, instead it is approximated to 1.3899999999999999023003738329862244427204132080078125 (use an online converter or do a <?php ini_set("precision", 99); var_dump(1.39);).
There are some workarounds, the simplest one is to convert the decimal number to string at the query level:
SELECT CAST(col AS VARCHAR(4)) AS col_as_string FROM ...
The other solution is to use the number_format function which seems to round the values.

Be aware the PHP has no precise data type for numbers with decimal places. When working with such numbers it will always use an approximite data type (floating point numbers).
So comparing 0.1 + 0.2 with 0.3 for instance may very likely result in false. That means: be careful with equality comparisions.
When displaying such values you should never rely on the default presentation, but use number_format instead. E.g. number_format($value, 2).
If you want to avoid floating point issues completely, then don't use numbers with decimal places. That can be a hassle though (e.g. retrieving the integer 139 for 1.39 and keeping in mind that this value means hundredths).

I suspect 1.39 can't be exactly represented as a float.
I don't know PHP, so here is some Java code to demonstrate:
class DoubleFloat {
public static void main(String[] args) {
float f = 1.39f;
double d = 1.39f;
System.out.println("f = " + f);
System.out.println("d = " + d);
}
}
The output:
f = 1.39
d = 1.3899999856948853
Depends what you are trying to do on the PHP side, but you could store the number as an integer, e.g. 1.39 is stored as 139 and convert it somehow on the PHP side.

Related

Floating number upto two decimal PHP

I know the question is very basic but it seems nothing working for me.
I have a number (either or float or integer) which I want to be formatted upto two decimal point. For this purpose I'm using PHP function number_format but it converts my number to string.
To convert it back to float I am using (float) or floatval(). But these functions just truncates the number after converting it to float from string.
Here is my code
$revenue_sum = array_sum(array_column($val2, 'weighted_revenue')); //23722
$test = number_format($revenue_sum, 2); //"23,722.00"
$test = (float)number_format($revenue_sum, 2); //23.0
$test = floatval(number_format($revenue_sum, 2)); //23.0
I want the $test to be 23722.00 for the $revenue_sum = 23722
If $revenue_sum = 2372.2 the $test should be 2372.20
number_format() function can be used as follows:
echo number_format($revenue_sum, 2,'.',''); // will return 23722.00 for the $revenue_sum = 23722
You are trying to type cast with ',' value, it is truncating the string.
you can try this
<?php echo sprintf("%2.2f", 8900.258); ?>
which will output as
8900.26
If you assign a floating point value to a variable, then it is converted to an internal binary format (usually using IEEE 754). Not all possible values has an internal representation. So while scanning a text, the float is rounded to the nearest possible value. So for example 1.23 is rounded to 1.22999999999999998.
Because of the internal representation, there is no difference between 100 or 1e2 or 100.0 or 100.0000.
And when printing a floating point value without any formatting instruction, PHP guess a good format and rounding some digits. So 1.22999999999999 is displayed as 1.23(may varies on different systems).
In general: As long you are calculating, formatting doesn't matter. It is mostly the best, to ignore the decimal fragments on debugging. But when printing (=converting to text), use functions like format_number() or any of the printf() functions.
To be more pragmatic:

PHP: Limits when Counting with float values

I need to count numbers but have to use a float datatype (don't as why - it's kind of an embedded system).
The simple counter goes like:
$num=1.0;
do {
$num = $num + 1;
// do some stuff
// ...
} while (...);
Question 1: What is the biggest number that can be counted correctly using a 32-bit and a 64-bit PHP system?
Question 2: When $num is read from a MySQL database using a standard FLOAT type (no precision specified) at the beginning of the loop and stored back at the end of the loop, is the answer to Question 1 still valid?
PHP uses double precision floating point, which has a 52-bit mantissa. This means that integers represented using floats start losing precision when they reach 253 (the extra bit of precision is because the leading bit of a normalized mantissa is always 1, so this doesn't need to be included explicitly in the representation). The following example demonstrates this:
echo number_format(pow(2.0, 53)-1) . "<br>" .
number_format(pow(2.0, 53)) . "<br>" .
number_format(pow(2.0, 53)+1);
outputs:
9,007,199,254,740,991
9,007,199,254,740,992
9,007,199,254,740,992
To get equivalent floating point precision in MySQL you should use the DOUBLE datatype, which is 64-bit floating point. If you just use FLOAT you'll get 32-bit single precision, which only has 23 bits of mantissa, and loses integer precision at 16,777,216.
See FLOAT and DOUBLE Data Type Representation for more details about how MySQL stores floating point internally.

Is casting to float destructive?

In PHP, I know we shouldn't do math on floats without things like bcmath, but is the mere act of casting a string to float destructive?
Will expressions like (float)'5.111' == '5.111', always be true? Or will the cast itself change that to something like 5.1110000000000199837 as the number is converted?
The main reason is, just as I use (int) to escape integer values going into a database, I would like to use (float) in the same way, without having to rely on quotes and my escape function.
NO, Casting to a float is almost always destructive.
In your example, 5.111 represented in binary is:
101.00011100011010100111111011111001110110110010001011010000111001...
A float would store 23 digits:
101.0001110001101010011
(5.1109981536865234375)
A double would store 52 digits:
101.0001110001101010011111101111100111011011001000101
(5.1109999999999988773424774990417063236236572265625)
In this case, there wouldn't be a difference. However, in larger numbers, it can affect what you display.
For example:
1025.4995
double:
10000000001.011111111101111100111011011001000101101
(1025.499499999999898136593401432037353515625)
float:
10000000001.011111111101
(1025.499267578125)
You can see the precision starts to drop off dramatically after around 8 digits.
The double would round to 1025.4995 whereas the float would be 1025.4993
You shouldn't use (int) to escape integer values. Use a parametrized query and set the type of your input to 'int'. A much better way!
for an example in mysql/php see:
http://us.php.net/manual/en/mysqli.prepare.php
It depends on whether or not the fractional part can be represented exactly in binary (see Fractions in binary). For example, 0.5 has an exact binary representation but 0.1 does not. If the number does not have an exact representation, you are likely to see a different result when printing it again.

php intval() and floor() return value that is too low?

Because the float data type in PHP is inaccurate, and a FLOAT in MySQL takes up more space than an INT (and is inaccurate), I always store prices as INTs, multipling by 100 before storing to ensure we have exactly 2 decimal places of precision. However I believe PHP is misbehaving. Example code:
echo "<pre>";
$price = "1.15";
echo "Price = ";
var_dump($price);
$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);
$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);
echo "</pre>";
Produced output:
Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)
I was surprised. When the final result was lower than expected by 1, I was expecting the output of my test to look more like:
Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)
which would demonstrate the inaccuracy of the float type. But why is floor(115) returning 114??
Try this as a quick fix:
$price_int = intval(floor($price_corrected + 0.5));
The problem you are experiencing is not PHP's fault, all programming languages using real numbers with floating point arithmetics have similar issues.
The general rule of thumb for monetary calculations is to never use floats (neither in the database nor in your script). You can avoid all kinds of problems by always storing the cents instead of dollars. The cents are integers, and you can freely add them together, and multiply by other integers. Whenever you display the number, make sure you insert a dot in front of the last two digits.
The reason why you are getting 114 instead of 115 is that floor rounds down, towards the nearest integer, thus floor(114.999999999) becomes 114. The more interesting question is why 1.15 * 100 is 114.999999999 instead of 115. The reason for that is that 1.15 is not exactly 115/100, but it is a very little less, so if you multiply by 100, you get a number a tiny bit smaller than 115.
Here is a more detailed explanation what echo 1.15 * 100; does:
It parses 1.15 to a binary floating point number. This involves rounding, it happens to round down a little bit to get the binary floating point number nearest to 1.15. The reason why you cannot get an exact number (without rounding error) is that 1.15 has infinite number of numerals in base 2.
It parses 100 to a binary floating point number. This involves rounding, but since 100 is a small integer, the rounding error is zero.
It computes the product of the previous two numbers. This also involves a little rounding, to find the nearest binary floating point number. The rounding error happens to be zero in this operation.
It converts the binary floating point number to a base 10 decimal number with a dot, and prints this representation. This also involves a little rounding.
The reason why PHP prints the surprising Corrected price = float(115) (instead of 114.999...) is that var_dump doesn't print the exact number (!), but it prints the number rounded to n - 2 (or n - 1) digits, where n digits is the precision of the calculation. You can easily verify this:
echo 1.15 * 100; # this prints 115
printf("%.30f", 1.15 * 100); # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different"; # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less"; # this prints `less'
If you are printing floats, remember: you don't always see all digits when you print the float.
See also the big warning near the beginning of the PHP float docs.
The other answers have covered the cause and a good workaround to the problem, I believe.
To aim at fixing the problem from a different angle:
For storing price values in MySQL, you should probably look at the DECIMAL type, which lets you store exact values with decimal places.
Maybe it's another possible solution for this "problem":
intval(number_format($problematic_float, 0, '', ''));
PHP is doing rounding based on significant digits. It's hiding the inaccuracy (on line 2). Of course, when floor comes along, it doesn't know any better and lops it all the way down.
As stated this is not a problem with PHP per se, It is more of an issue of handling fractions that can't be expressed as finite floating point values hence leading to loss of character when rounding up.
The solution is to ensure that when you are working on floating point values and you need to maintain accuracy - use the gmp functions or the BC maths functions - bcpow, bcmul et al. and the problem will be resolved easily.
E.g instead of
$price_corrected = $price*100;
use $price_corrected = bcmul($price,100);

PHP money string conversion to integer error

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);
?>

Categories